diff options
Diffstat (limited to 'src/main/scala/Server')
-rw-r--r-- | src/main/scala/Server/Client.scala | 19 | ||||
-rw-r--r-- | src/main/scala/Server/Server.scala | 56 | ||||
-rw-r--r-- | src/main/scala/Server/constants.scala | 10 |
3 files changed, 51 insertions, 34 deletions
diff --git a/src/main/scala/Server/Client.scala b/src/main/scala/Server/Client.scala index e557c22..cd557c6 100644 --- a/src/main/scala/Server/Client.scala +++ b/src/main/scala/Server/Client.scala @@ -53,7 +53,6 @@ class Client(val socket: Socket): * @param entity the entity this client is to control */ def giveEntity(entity: Entity): Unit = - println(entity) this.character = Some(entity) /** Gets the name of this client, which should match the name of the entity @@ -90,18 +89,19 @@ class Client(val socket: Socket): * @param data data to buffer for sending */ private def addDataToSend(data: String): Unit = - this.outData += s"$data\n" + this.outData += s"$data\r\n" /** Returns one line of data if there are any line breaks. * Removes the parsed data from the message buffering area. */ private def nextLine(): Option[String] = - val nextLF = this.incompleteMessage.indexOf(LF) - if nextLF != -1 then - val message = this.incompleteMessage.take(nextLF) - val rest = this.incompleteMessage.drop(nextLF + 1) - this.incompleteMessage = rest ++ Array.fill(nextLF + 1)(0.toByte) + var nextCRLF = this.incompleteMessage.indexOf(CRLF(0)) + if this.incompleteMessage(nextCRLF + 1) != CRLF(1) then nextCRLF = -1 + if nextCRLF != -1 then + val message = this.incompleteMessage.take(nextCRLF) + val rest = this.incompleteMessage.drop(nextCRLF + 1) + this.incompleteMessage = rest ++ Array.fill(nextCRLF + 1)(0.toByte) // TODO: the conversion may probably be exploited to crash the server Some(String(message)) else @@ -157,17 +157,14 @@ class Client(val socket: Socket): this.entity.exists(action.takesATurnFor(_)) ) then this.nextAction = Some(action) - this.addDataToSend("Waiting for everyone to end their turns...") else if this.nextAction.isEmpty then executeAction(action) - /** Executes the specified action */ + /** Executes the specified action and buffers its description for sending */ private def executeAction(action: Action) = this.character.flatMap(action.execute(_)) match case Some(s) => this.addDataToSend((s)) case None => this.addDataToSend("You can't do that") - this.character.map(_.location.fullDescription) - .foreach(this.addDataToSend(_)) end Client diff --git a/src/main/scala/Server/Server.scala b/src/main/scala/Server/Server.scala index faf82e1..a03bc53 100644 --- a/src/main/scala/Server/Server.scala +++ b/src/main/scala/Server/Server.scala @@ -4,21 +4,16 @@ 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 import o1game.Model.Entity +import o1game.utils.stringToByteArray import java.lang.System.currentTimeMillis import scala.util.Try -/** Converts this string to an array of bytes (probably for transmission). - * - * @param str the string to convert - * @return an array of bytes representing the string in UTF8. - */ -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`. @@ -52,17 +47,19 @@ class Server( sleep(POLL_INTERVAL) private def serverStep(): Unit = + this.clients.removeNonCompliant() if this.adventure.isEmpty || this.joinAfterStart then this.receiveNewClient() this.readFromAll() - this.writeClientDataToClients() - this.clients.removeNonCompliant() this.clients.foreach(_.interpretData()) + this.writeClientDataToClients() if this.canExecuteTurns then - println("taking turns") - this.clients.inRandomOrder(_.act()) - this.writeToAll("next turn!") - this.previousTurn = currentTimeMillis() / 1000 + this.clients.inRandomOrder(_.act()) + this.writeClientDataToClients() + 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 => @@ -91,7 +88,7 @@ class Server( case None => None case None => None entity.foreach(c.giveEntity(_)) - this.writeToClient(s"$timeLimit", c) + this.writeToClient(s"$timeLimit\r\n", c) /** Helper function to determine if the next turn can be taken */ @@ -119,6 +116,25 @@ class Server( case None => false + private def turnStartInfo(client: Client): String = + val clientArea = client.entity.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.entity.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 @@ -131,13 +147,13 @@ class Server( ) private def writeToClient(message: String, client: Client): Unit = - val success = Try( () => + try { val output = client.socket.getOutputStream - output.write(stringToByteArray(message)) + output.write(message.toVector.map(_.toByte).toArray) output.flush() - ) - if success.isFailure then - client.failedProtocol() + } catch { + case e: IOException => client.failedProtocol() + } /** Sends every client's `dataToThisClient` to the client */ private def writeClientDataToClients(): Unit = @@ -158,4 +174,4 @@ class Server( c.receiveData(buffer.take(bytesRead).toVector) ) -end Server
\ No newline at end of file +end Server diff --git a/src/main/scala/Server/constants.scala b/src/main/scala/Server/constants.scala index ddaab00..a9e4502 100644 --- a/src/main/scala/Server/constants.scala +++ b/src/main/scala/Server/constants.scala @@ -2,12 +2,16 @@ package o1game.constants val MAX_MSG_SIZE = 1024 // bytes -val LF: Byte = 10 +val CRLF: Vector[Byte] = Vector(13.toByte, 10.toByte) val POLL_INTERVAL = 100 // millisec. val GAME_VERSION = "0.1.0" +val TURN_INDICATOR = ">" -val PROTOCOL_VERSION_GOOD = "version ok" -val PROTOCOL_VERSION_BAD = "version bad" +val LIST_SEPARATOR=";" + +val PROTOCOL_VERSION_GOOD = "1" +val PROTOCOL_VERSION_BAD = "0" +//assert(PROTOCOL_VERSION_BAD.length <= PROTOCOL_VERSION_GOOD.length) enum ServerProtocolState: case WaitingForVersion, WaitingForClientName, WaitingForGameStart, InGame |