diff options
Diffstat (limited to 'src/main/scala/Client')
-rw-r--r-- | src/main/scala/Client/Client.scala | 178 | ||||
-rw-r--r-- | src/main/scala/Client/ReceivedLineParser.scala | 27 | ||||
-rw-r--r-- | src/main/scala/Client/StdinLineReader.scala | 31 | ||||
-rw-r--r-- | src/main/scala/Client/Turn.scala | 32 |
4 files changed, 0 insertions, 268 deletions
diff --git a/src/main/scala/Client/Client.scala b/src/main/scala/Client/Client.scala deleted file mode 100644 index fc3e6b8..0000000 --- a/src/main/scala/Client/Client.scala +++ /dev/null @@ -1,178 +0,0 @@ -package o1game.Client - -import java.lang.Thread.sleep -import java.net.Socket -import scala.io.Source -import scala.sys.process.stdout -import o1game.constants.* -import o1game.utils.{stringToByteArray,getNCharsFromSocket} -import o1game.Client.{ReceivedLineParser,StdinLineReader,Turn} -import scala.concurrent.Future -import scala.concurrent.ExecutionContext.Implicits.global -import scala.util.{Try, Success, Failure} -import scala.collection.mutable.Buffer -import java.lang.System.currentTimeMillis - - -/** A helper enum for `Client` to keep track of communications with the server - */ -enum ServerLineState: - case WaitingForTimeLimit, - ActionDescription, - TurnIndicator, - AreaDescription, - Directions, - Items, - Entities - - -/** Creates a new client. - * - * @param name the name the client and its player should have - * @ip the ip of the server to connect to - * @port the port of the server to connect to - * @return the client created, if all was successful - */ -def newClient(name: String, ip: String, port: Int): Option[Client] = - val socket = Socket(ip, port) - val output = socket.getOutputStream - val input = socket.getInputStream - val initMsg = s"$GAME_VERSION\r\n$name\r\n" - output.write(stringToByteArray(initMsg)) - val msgLen = (PROTOCOL_VERSION_GOOD + "\r\n").length - val versionResponse = getNCharsFromSocket(input, msgLen) - if versionResponse == Some(s"$PROTOCOL_VERSION_GOOD\r\n") then - Some(Client(socket)) - else - None - - - -/** Main class for the client: handles communication with the server - * and the player. Should be initialized with `newClient`. - * - * @param socket the socket the client uses - */ -class Client(socket: Socket): - - /** Essential IO variables */ - private val input = socket.getInputStream - private val output = socket.getOutputStream - private val buffer: Array[Byte] = Array.ofDim(MAX_MSG_SIZE) - private var bufferIndex = 0 - private val serverLineParser = ReceivedLineParser() - private val stdinReader = StdinLineReader() - - private var serverLineState = ServerLineState.WaitingForTimeLimit - - /** Variables about the status of the current turn for the client */ - private var canAct = false - private var timeLimit: Long = 0 - private var lastTurnStart: Long = 0 - private var lastExecutedTurn: Long = 0 - assert( - lastTurnStart <= lastExecutedTurn, - "don't initialize with unexecuted turn" - ) - private val turnInfo = Turn() - - - /** Starts the client. This shouldn't terminate. */ - def startClient(): Unit = - - stdinReader.startReading() - - while true do - sleep(POLL_INTERVAL) - - this.readAndParseDataFromServer() - - if this.lastExecutedTurn < this.lastTurnStart then - print(this.giveTurn()) - - stdinReader.newLine().foreach((s: String) => - output.write(stringToByteArray(s+"\r\n")) - ) - - end startClient - - - private def readAndParseDataFromServer(): Unit = - var availableBytes = input.available() - while availableBytes != 0 do - val bytesRead = input.read(buffer, 0, availableBytes) - if bytesRead != -1 then - // TODO: unsafe conversion - parseDataFromServer(buffer.take(bytesRead)) - availableBytes = input.available() - - private def giveTurn(): String = - this.canAct = true - this.lastExecutedTurn = currentTimeMillis / 1000 - s"\n\n${this.turnInfo}\n${this.actionGetterIndicator}" - - private def displayAction(action: String): Unit = - println(s"$action") - if this.canAct then - print(this.actionGetterIndicator) - - private def actionGetterIndicator = - val timeOfTurnEnd = this.lastTurnStart + this.timeLimit - val timeToTurnEnd = -currentTimeMillis()/1000 + timeOfTurnEnd - s"[$timeToTurnEnd]> " - - - - private def parseDataFromServer(data: Array[Byte]): Unit = - this.serverLineParser.in(data) - var nextLine: Option[String] = Some("") - while nextLine.isDefined do - nextLine = this.serverLineParser - .nextLine() - nextLine - .foreach(this.parseLineFromServer(_)) - - - private def parseLineFromServer(line: String) = - - if line == TURN_INDICATOR then - this.serverLineState = ServerLineState.TurnIndicator - - serverLineState match - - case ServerLineState.WaitingForTimeLimit => - val time = line.toLongOption - time match - case Some(t) => this.timeLimit = t - case None => print("Invalid time limit, oh no!!!") - this.serverLineState = ServerLineState.TurnIndicator - this.lastTurnStart = currentTimeMillis / 1000 - - case ServerLineState.ActionDescription => - if line.nonEmpty && line.head == ACTION_BLOCKING_INDICATOR then - this.canAct = false - this.displayAction(line.tail) - - case ServerLineState.TurnIndicator => - this.serverLineState = ServerLineState.AreaDescription - - case ServerLineState.AreaDescription => - this.turnInfo.areaDescription = line - this.serverLineState = ServerLineState.Directions - - case ServerLineState.Directions => - this.turnInfo.possibleDirections = line.split(LIST_SEPARATOR) - this.serverLineState = ServerLineState.Items // TODO: maybe use a list instead? - - case ServerLineState.Items => - this.turnInfo.visibleItems = line.split(LIST_SEPARATOR) - this.serverLineState = ServerLineState.Entities - - case ServerLineState.Entities => - this.turnInfo.visibleEntities = line.split(LIST_SEPARATOR) - this.serverLineState = ServerLineState.ActionDescription - this.lastTurnStart = currentTimeMillis() / 1000 - - end parseLineFromServer - -end Client diff --git a/src/main/scala/Client/ReceivedLineParser.scala b/src/main/scala/Client/ReceivedLineParser.scala deleted file mode 100644 index 7cbf935..0000000 --- a/src/main/scala/Client/ReceivedLineParser.scala +++ /dev/null @@ -1,27 +0,0 @@ -package o1game.Client - -import scala.collection.mutable.Buffer -import o1game.constants.* - -/** A class for checking asynchronously for received lines */ -class ReceivedLineParser: - - private var serverLineState = ServerLineState.ActionDescription - - private var bufferedData: Buffer[Byte] = Buffer.empty // TODO: suboptimal DS - - /** Add received data */ - def in(data: Array[Byte]): Unit = - this.bufferedData ++= data - - /** Read a line from the received data */ - def nextLine(): Option[String] = - val indexOfCRLF = this.bufferedData.indexOfSlice(CRLF) - if indexOfCRLF == -1 then - None - else - val splitData = this.bufferedData.splitAt(indexOfCRLF) - this.bufferedData = Buffer.from(splitData(1).drop(CRLF.length)) - Some(String(splitData(0).toArray)) - -end ReceivedLineParser diff --git a/src/main/scala/Client/StdinLineReader.scala b/src/main/scala/Client/StdinLineReader.scala deleted file mode 100644 index 42a1f40..0000000 --- a/src/main/scala/Client/StdinLineReader.scala +++ /dev/null @@ -1,31 +0,0 @@ -package o1game.Client - -import scala.concurrent.Future -import scala.concurrent.ExecutionContext.Implicits.global -import scala.io.StdIn.readLine -import scala.util.{Try, Success, Failure} - -/** This class is for taking new lines from stdin when they are available. - * reading starts when either newLine or clear or startReading are called. - */ -class StdinLineReader: - - private var nextLine: Future[String] = Future.failed(Exception()) - - /** Returns a new line of input if there are any. */ - def newLine(): Option[String] = - this.nextLine.value match - case Some(Success(s)) => - this.startReading() - Some(s) - case Some(Failure(e)) => - this.startReading() - None - case None => None - - /** Discards the line that is currently being read and restarts reading */ - def startReading(): Unit = - this.nextLine = Future(readLine()) - - -end StdinLineReader diff --git a/src/main/scala/Client/Turn.scala b/src/main/scala/Client/Turn.scala deleted file mode 100644 index 6b78811..0000000 --- a/src/main/scala/Client/Turn.scala +++ /dev/null @@ -1,32 +0,0 @@ -package o1game.Client - -/** `Turn`s represent information the client has got about a turn. - * This class exists essentially so that the client has somewhere - * to store data about turns and something to format that data with. - */ -class Turn: - - /** Description of the area the player controlled by the client is in - * at the end of the turn. */ - var areaDescription: String = "" - - /** Directions the player controlled by the client can go to. */ - var possibleDirections: Array[String] = Array.empty - - /** Items the player controlled by the client can see. */ - var visibleItems: Array[String] = Array.empty - - /** Entities the player controlled by the client can see. */ - var visibleEntities: Array[String] = Array.empty - - override def toString: String = - val itemDesc = "You can see the following items: " + - this.visibleItems.mkString(", ") - val entityDesc = "The following entities reside in the room: " + - this.visibleEntities.mkString(", ") - val directionDesc = "There are exits to " + - this.possibleDirections.mkString(", ") - (s"$areaDescription\n$directionDesc\n" + - s"\n$itemDesc\n$entityDesc") - -end Turn |