diff options
Diffstat (limited to 'src/main/scala/Server/Server.scala')
-rw-r--r-- | src/main/scala/Server/Server.scala | 195 |
1 files changed, 0 insertions, 195 deletions
diff --git a/src/main/scala/Server/Server.scala b/src/main/scala/Server/Server.scala deleted file mode 100644 index 7864c49..0000000 --- a/src/main/scala/Server/Server.scala +++ /dev/null @@ -1,195 +0,0 @@ -package o1game.Server - - -// TODO: TLS/SSL / import javax.net.ssl.SSLServerSocketFactory - -import java.lang.Thread.{currentThread, sleep} -import java.io.IOException -import java.net.{ServerSocket, Socket} -import o1game.constants.* -import o1game.Model.{Adventure,Entity,Player} -import o1game.utils.stringToByteArray - -import java.lang.System.currentTimeMillis -import scala.util.Try - - -/** `Server` exists to initialize a server for the game - * and run it with its method `startServer`. - * - * @param port the TCP port the server should listen on - * @param maxClients the maximum number of clients that may be in the game - * simultaneously. - * @param timeLimit the time limit clients should have to execute their turns. - * @param joinAfterStart whether new clients are accepted after the game has - * been started - */ -class Server( - port: Int, - maxClients: Int, - val timeLimit: Int, - val joinAfterStart: Boolean -): - - private val socket = ServerSocket(port) - private val clientGetter = ConnectionGetter(socket) - private val clients: Clients = Clients(maxClients) - private val buffer: Array[Byte] = Array.ofDim(1024) - private var bufferIndex = 0 - private var adventure: Option[Adventure] = None - private var previousTurn = 0.0 - - /** Starts the server. Won't terminate under normal circumstances. */ - def startServer(): Unit = - while true do - this.serverStep() - sleep(POLL_INTERVAL) - - private def serverStep(): Unit = - this.clients.removeNonCompliant() - if this.adventure.isEmpty || this.joinAfterStart then - this.receiveNewClient() - this.readFromAll() - this.clients.foreach(_.interpretData()) - this.writeClientDataToClients() - this.writeObservations() - if this.canExecuteTurns then - this.clients.inRandomOrder(_.act()) - this.writeClientDataToClients() - this.writeObservations() - this.clients.foreach(c => - this.writeToClient(this.turnStartInfo(c), c) - ) - this.previousTurn = currentTimeMillis() / 1000 - if this.adventure.isDefined && this.joinAfterStart then - this.clients.foreach( c => if c.isReadyForGameStart then - this.adventure.foreach(a => - c.getName.foreach(n => a.addPlayer(n)) - ) - startGameForClient(c) - ) - else if this.adventure.isEmpty && !this.clients.isEmpty && this.clients.forall(_.isReadyForGameStart) then - this.adventure = Some(Adventure(this.clients.names)) - this.clients.foreach(startGameForClient(_)) - this.previousTurn = currentTimeMillis() / 1000 - - /** Helper function to start the game for the specified client c. - * MAY ONLY BE USED IF `this.adventure` is Some! - * Apparently guard clauses are bad because they use return or something, - * but assertions should be fine, as long as they enforce the function - * contract? - */ - private def startGameForClient(c: Client): Unit = - assert(this.adventure.isDefined) - c.gameStart() - val name = c.getName - - val playerEntity: Option[Player] = name match - case Some(n) => this.adventure match - case Some(a) => a.getPlayer(n) - case None => None - case None => None - playerEntity.foreach(c.givePlayer(_)) - - this.writeToClient( - s"$timeLimit\r\n${this.turnStartInfo(c)}", c - ) - - this.clients.foreach(c => - if c.player != playerEntity then - c.player.foreach(_.observe(s"${name.getOrElse("Unknown player")} joins the game.")) - ) - - - private def writeObservations(): Unit = - this.clients.foreach(c => - val observations = c.player.map(_.readAndClearObservations()) - observations.foreach(_.foreach((s: String) => - this.writeToClient(s"$ACTION_NONBLOCKING_INDICATOR$s\r\n", c)) - ) - ) - - /** Helper function to determine if the next turn can be taken */ - private def canExecuteTurns: Boolean = - val requirement1 = this.adventure.isDefined - val requirement2 = !this.clients.isEmpty // nice! you can just return - // to the game after everyone - // left and everything is just - // as before! - val allPlayersReady = this.clients.forall(_.isReadyToAct) - val requirement3 = (allPlayersReady - || currentTimeMillis() / 1000 >= previousTurn + timeLimit) - requirement1 && requirement2 && requirement3 - - - /** Receives a new client and stores it in `clients`. - * - * @return describes if a client was added - */ - private def receiveNewClient(): Boolean = - this.clientGetter.newClient() match - case Some(c) => - clients.addClient(Client(c)) - true - case None => - false - - private def turnStartInfo(client: Client): String = - val clientArea = client.player.map(_.location) - val areaDesc = clientArea - .map(_.description) - .getOrElse("You are floating in the middle of a soothing void.") - val directions = clientArea - .map(_.getNeighborNames.mkString(LIST_SEPARATOR)) - .getOrElse("") - val items = clientArea - .map(_.getItemNames.mkString(LIST_SEPARATOR)) - .getOrElse("") - val entities = client.player.map(c => - c.location - .getEntityNames - .filter(c.name != _) - .mkString(LIST_SEPARATOR) - ).getOrElse("") - s"$TURN_INDICATOR\r\n$areaDesc\r\n$directions\r\n$items\r\n$entities\r\n" - - /** Sends `message` to all clients - * - * @param message the message to send - */ - private def writeToAll(message: String): Unit = - this.clients.mapAndRemove(c => - val output = c.socket.getOutputStream - output.write(stringToByteArray(message)) - output.flush() - ) - - private def writeToClient(message: String, client: Client): Unit = - try { - val output = client.socket.getOutputStream - output.write(stringToByteArray(message)) - output.flush() - } catch { - case e: IOException => client.failedProtocol() - } - - /** Sends every client's `dataToThisClient` to the client */ - private def writeClientDataToClients(): Unit = - this.clients.mapAndRemove(c => - val output = c.socket.getOutputStream - val data = c.dataToThisClient() - output.write(stringToByteArray(data)) - output.flush() - ) - - /** Reads data sent by clients and stores it in the `Client`s of `clients` */ - private def readFromAll(): Unit = - clients.mapAndRemove(c => - val input = c.socket.getInputStream - while input.available() != 0 do - val bytesRead = input.read(buffer) - if bytesRead != -1 then - c.receiveData(buffer.take(bytesRead).toVector) - ) - -end Server |