aboutsummaryrefslogtreecommitdiff
path: root/src/main/scala/Server/Server.scala
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/scala/Server/Server.scala')
-rw-r--r--src/main/scala/Server/Server.scala195
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