diff options
Diffstat (limited to 'src/main/scala/Server')
-rw-r--r-- | src/main/scala/Server/Character.scala | 7 | ||||
-rw-r--r-- | src/main/scala/Server/Client.scala | 5 | ||||
-rw-r--r-- | src/main/scala/Server/Server.scala | 83 |
3 files changed, 68 insertions, 27 deletions
diff --git a/src/main/scala/Server/Character.scala b/src/main/scala/Server/Character.scala deleted file mode 100644 index 174b7b0..0000000 --- a/src/main/scala/Server/Character.scala +++ /dev/null @@ -1,7 +0,0 @@ -package o1game.Server - -import scala.collection.mutable.Buffer - -class GameCharacter(val name: String): - private val items: Buffer[String] = Buffer.empty // TODO: Item class - // TODO: A lot of other things too diff --git a/src/main/scala/Server/Client.scala b/src/main/scala/Server/Client.scala index d9bb529..d9fa684 100644 --- a/src/main/scala/Server/Client.scala +++ b/src/main/scala/Server/Client.scala @@ -24,7 +24,10 @@ class Client(val socket: Socket): * * @return false if there has been a protocol violation, true otherwise */ - def isIntactProtocolWise: Boolean = protocolIsIntact + def isIntactProtocolWise: Boolean = this.protocolIsIntact + + /** Marks that this client misbehaved in eyes of the protocol */ + def failedProtocol(): Unit = this.protocolIsIntact = false /** Tests whether this client is initialized and ready to start the game * diff --git a/src/main/scala/Server/Server.scala b/src/main/scala/Server/Server.scala index 6fa284c..2f78e6b 100644 --- a/src/main/scala/Server/Server.scala +++ b/src/main/scala/Server/Server.scala @@ -8,6 +8,9 @@ import java.net.{ServerSocket, Socket} import o1game.constants.* import o1game.Model.Adventure import o1game.Model.Entity +import scala.util.Try + +import scala.util.Try /** Converts this string to an array of bytes (probably for transmission). * @@ -18,9 +21,22 @@ def stringToByteArray(str: String): Array[Byte] = str.toVector.map(_.toByte).toArray /** `Server` exists to initialize a server for the game - * and run it with its method `startServer`. - */ -class Server(port: Int, maxClients: Int, val timeLimit: Int): + * 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) @@ -31,25 +47,45 @@ class Server(port: Int, maxClients: Int, val timeLimit: Int): /** Starts the server. Won't terminate under normal circumstances. */ def startServer(): Unit = while true do + this.serverStep() sleep(POLL_INTERVAL) + + private def serverStep(): Unit = + if this.adventure.isEmpty || this.joinAfterStart then this.receiveNewClient() - this.readFromAll() - this.writeClientDataToClients() - this.clients.removeNonCompliant() - this.clients.foreach(_.interpretData()) - if this.adventure.isEmpty && !this.clients.isEmpty && this.clients.forall(_.isReadyForGameStart) then - this.adventure = Some(Adventure(this.clients.names)) - this.clients.foreach( c => - c.gameStart() - val name = c.getName - val entity: Option[Entity] = name match - case Some(n) => this.adventure match - case Some(a) => a.getPlayer(n) - case None => None - case None => None - entity.map(c.giveEntity(_)) + this.readFromAll() + this.writeClientDataToClients() + this.clients.removeNonCompliant() + this.clients.foreach(_.interpretData()) + 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)) ) - this.writeToAll(s"$timeLimit") + 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(_)) + + /** 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 entity: Option[Entity] = name match + case Some(n) => this.adventure match + case Some(a) => a.getPlayer(n) + case None => None + case None => None + entity.map(c.giveEntity(_)) + this.writeToClient(s"$timeLimit", c) + /** Receives a new client and stores it in `clients`. @@ -75,6 +111,15 @@ class Server(port: Int, maxClients: Int, val timeLimit: Int): output.flush() ) + private def writeToClient(message: String, client: Client): Unit = + val success = Try( () => + val output = client.socket.getOutputStream + output.write(stringToByteArray(message)) + output.flush() + ) + if success.isFailure then + client.failedProtocol() + /** Sends every client's `dataToThisClient` to the client */ private def writeClientDataToClients(): Unit = this.clients.mapAndRemove(c => |