From 607b43a84d3bc8edffa05c722c7b8c3e6f72e964 Mon Sep 17 00:00:00 2001 From: Joel Kronqvist Date: Thu, 21 Nov 2024 21:09:52 +0200 Subject: Wooooohooo time limits and correcter printing --- src/scalevalapokalypsi/Client/Client.scala | 85 +++++++++++++-------------- src/scalevalapokalypsi/Client/GameEvent.scala | 11 ++++ src/scalevalapokalypsi/Client/Turn.scala | 4 +- 3 files changed, 55 insertions(+), 45 deletions(-) create mode 100644 src/scalevalapokalypsi/Client/GameEvent.scala (limited to 'src/scalevalapokalypsi/Client') diff --git a/src/scalevalapokalypsi/Client/Client.scala b/src/scalevalapokalypsi/Client/Client.scala index aab6bc3..75ce2e7 100644 --- a/src/scalevalapokalypsi/Client/Client.scala +++ b/src/scalevalapokalypsi/Client/Client.scala @@ -1,12 +1,11 @@ package scalevalapokalypsi.Client -import java.lang.Thread.sleep -import java.net.Socket +import java.net.{Socket,InetSocketAddress} import scala.io.Source import scala.sys.process.stdout import scalevalapokalypsi.constants.* import scalevalapokalypsi.utils.{stringToByteArray,getNCharsFromSocket} -import scalevalapokalypsi.Client.{ReceivedLineParser,StdinLineReader,Turn} +import scalevalapokalypsi.Client.{ReceivedLineParser,StdinLineReader,RoomState} import scala.concurrent.Future import scala.concurrent.ExecutionContext.Implicits.global import scala.util.{Try, Success, Failure} @@ -34,7 +33,8 @@ enum ServerLineState: * @return the client created, if all was successful */ def newClient(name: String, ip: String, port: Int): Option[Client] = - val socket = Socket(ip, port) + val socket = Socket() + socket.connect(new InetSocketAddress(ip, port), INITIAL_CONN_TIMEOUT) val output = socket.getOutputStream val input = socket.getInputStream val initMsg = s"$GAME_VERSION\r\n$name\r\n" @@ -61,7 +61,6 @@ class Client(socket: Socket): 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 @@ -70,41 +69,50 @@ class Client(socket: Socket): private var timeLimit: Long = 0 private var lastTurnStart: Long = 0 private var lastExecutedTurn: Long = 0 - private var isSinging: Boolean = false + private var lineToSing: Option[String] = None private val bufferedActions: Buffer[String] = Buffer.empty assert( lastTurnStart <= lastExecutedTurn, "don't initialize with unexecuted turn" ) - private val turnInfo = Turn() + private val turnInfo = RoomState() - /** Starts the client. This shouldn't terminate. */ - def startClient(): Unit = - - stdinReader.startReading() - - while true do - - sleep(POLL_INTERVAL) + /** Takes a client step and optionally returns an in-game event for UI + * + * @param clientInput one line of client input if any + * @return an event describing new changes in the game state + */ + def clientStep(clientInput: Option[String]): GameEvent = this.readAndParseDataFromServer() - this.displayActions() + val actions = this.getNewActions() - if + val roomState = if this.lastExecutedTurn < this.lastTurnStart && - !this.isSinging + this.lineToSing.isEmpty then - print(this.giveTurn()) - - // TODO: we probably want to quit at EOF - stdinReader.newLine().foreach((s: String) => - this.isSinging = false - output.write(stringToByteArray(s+"\r\n")) + this.giveTurn() + Some(this.turnInfo) + else + None + + for line <- clientInput do + this.lineToSing = None + output.write(stringToByteArray(s"$line\r\n")) + + val timeOfTurnEnd = this.lastTurnStart + this.timeLimit + val timeToTurnEnd = -currentTimeMillis()/1000 + timeOfTurnEnd + GameEvent( + actions, + roomState, + this.lineToSing, + this.canAct, + Some(timeToTurnEnd).filter(p => this.lastTurnStart != 0) ) - end startClient + end clientStep private def readAndParseDataFromServer(): Unit = @@ -116,33 +124,24 @@ class Client(socket: Socket): parseDataFromServer(buffer.take(bytesRead)) availableBytes = input.available() - private def giveTurn(): String = + private def giveTurn(): Unit = this.canAct = true this.lastExecutedTurn = currentTimeMillis / 1000 - s"\n\n${this.turnInfo}\n${this.actionGetterIndicator}" private def bufferAction(action: String): Unit = this.bufferedActions += action - private def displayActions(): Unit = + private def getNewActions(): Option[Vector[String]] = val somethingToShow = this.bufferedActions.nonEmpty - if somethingToShow then - if !this.isSinging then - this.bufferedActions.foreach(println(_)) - this.bufferedActions.clear() - if !this.isSinging && this.canAct && somethingToShow then - print(this.actionGetterIndicator) + if somethingToShow && this.lineToSing.isEmpty then + val res = this.bufferedActions.toVector + this.bufferedActions.clear() + Some(res) + else + None private def startSong(verse: String): Unit = - this.isSinging = true - print(s"\nLaula: “$verse”\n> ") - - // TODO: this is sometimes in front of actions, use an indicator to test if newline before actions? - private def actionGetterIndicator = - val timeOfTurnEnd = this.lastTurnStart + this.timeLimit - val timeToTurnEnd = -currentTimeMillis()/1000 + timeOfTurnEnd - s"[$timeToTurnEnd]> " - + this.lineToSing = Some(verse) private def parseDataFromServer(data: Array[Byte]): Unit = diff --git a/src/scalevalapokalypsi/Client/GameEvent.scala b/src/scalevalapokalypsi/Client/GameEvent.scala new file mode 100644 index 0000000..8aa1e1c --- /dev/null +++ b/src/scalevalapokalypsi/Client/GameEvent.scala @@ -0,0 +1,11 @@ +package scalevalapokalypsi.Client + +class GameEvent( + val actions: Option[Vector[String]], + val roomState: Option[RoomState], + val lineToSing: Option[String], + val playerCanAct: Boolean, + val timeToNextTurn: Option[Long] +) + + diff --git a/src/scalevalapokalypsi/Client/Turn.scala b/src/scalevalapokalypsi/Client/Turn.scala index 30101c5..02fe11b 100644 --- a/src/scalevalapokalypsi/Client/Turn.scala +++ b/src/scalevalapokalypsi/Client/Turn.scala @@ -4,7 +4,7 @@ package scalevalapokalypsi.Client * This class exists essentially so that the client has somewhere * to store data about turns and something to format that data with. */ -class Turn: +class RoomState: /** Description of the area the player controlled by the client is in * at the end of the turn. */ @@ -29,4 +29,4 @@ class Turn: (s"$areaDescription\n$directionDesc\n" + s"\n$itemDesc\n$entityDesc") -end Turn +end RoomState -- cgit v1.2.3 From 49985d1d11c426968fc298469671326aace96d00 Mon Sep 17 00:00:00 2001 From: Joel Kronqvist Date: Thu, 21 Nov 2024 23:19:00 +0200 Subject: Fixed singing from last commit --- src/scalevalapokalypsi/Client/Client.scala | 1 - 1 file changed, 1 deletion(-) (limited to 'src/scalevalapokalypsi/Client') diff --git a/src/scalevalapokalypsi/Client/Client.scala b/src/scalevalapokalypsi/Client/Client.scala index 75ce2e7..0532038 100644 --- a/src/scalevalapokalypsi/Client/Client.scala +++ b/src/scalevalapokalypsi/Client/Client.scala @@ -77,7 +77,6 @@ class Client(socket: Socket): ) private val turnInfo = RoomState() - /** Takes a client step and optionally returns an in-game event for UI * * @param clientInput one line of client input if any -- cgit v1.2.3 From db5612ed9734d51e6fcd0d7b5a7635e49b773581 Mon Sep 17 00:00:00 2001 From: Joel Kronqvist Date: Fri, 22 Nov 2024 22:42:22 +0200 Subject: Character safety checking, supported terminals updated --- src/scalevalapokalypsi/Client/Client.scala | 3 +- .../Client/ReceivedLineParser.scala | 7 +++-- src/scalevalapokalypsi/Client/RoomState.scala | 32 ++++++++++++++++++++++ .../Client/StdinLineReader.scala | 31 --------------------- src/scalevalapokalypsi/Client/Turn.scala | 32 ---------------------- 5 files changed, 37 insertions(+), 68 deletions(-) create mode 100644 src/scalevalapokalypsi/Client/RoomState.scala delete mode 100644 src/scalevalapokalypsi/Client/StdinLineReader.scala delete mode 100644 src/scalevalapokalypsi/Client/Turn.scala (limited to 'src/scalevalapokalypsi/Client') diff --git a/src/scalevalapokalypsi/Client/Client.scala b/src/scalevalapokalypsi/Client/Client.scala index 0532038..e94fdb0 100644 --- a/src/scalevalapokalypsi/Client/Client.scala +++ b/src/scalevalapokalypsi/Client/Client.scala @@ -5,7 +5,7 @@ import scala.io.Source import scala.sys.process.stdout import scalevalapokalypsi.constants.* import scalevalapokalypsi.utils.{stringToByteArray,getNCharsFromSocket} -import scalevalapokalypsi.Client.{ReceivedLineParser,StdinLineReader,RoomState} +import scalevalapokalypsi.Client.{ReceivedLineParser,RoomState} import scala.concurrent.Future import scala.concurrent.ExecutionContext.Implicits.global import scala.util.{Try, Success, Failure} @@ -119,7 +119,6 @@ class Client(socket: Socket): 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() diff --git a/src/scalevalapokalypsi/Client/ReceivedLineParser.scala b/src/scalevalapokalypsi/Client/ReceivedLineParser.scala index 9337ce1..bccba59 100644 --- a/src/scalevalapokalypsi/Client/ReceivedLineParser.scala +++ b/src/scalevalapokalypsi/Client/ReceivedLineParser.scala @@ -2,6 +2,7 @@ package scalevalapokalypsi.Client import scala.collection.mutable.Buffer import scalevalapokalypsi.constants.* +import scalevalapokalypsi.utils.* /** A class for checking asynchronously for received lines */ class ReceivedLineParser: @@ -10,11 +11,11 @@ class ReceivedLineParser: private var bufferedData: Buffer[Byte] = Buffer.empty // TODO: suboptimal DS - /** Add received data */ + /** Add received data */ def in(data: Array[Byte]): Unit = this.bufferedData ++= data - /** Read a line from the received data */ + /** Read a line from the received data */ def nextLine(): Option[String] = val indexOfCRLF = this.bufferedData.indexOfSlice(CRLF) if indexOfCRLF == -1 then @@ -22,6 +23,6 @@ class ReceivedLineParser: else val splitData = this.bufferedData.splitAt(indexOfCRLF) this.bufferedData = Buffer.from(splitData(1).drop(CRLF.length)) - Some(String(splitData(0).toArray)) + byteArrayToString(splitData(0).toArray) end ReceivedLineParser diff --git a/src/scalevalapokalypsi/Client/RoomState.scala b/src/scalevalapokalypsi/Client/RoomState.scala new file mode 100644 index 0000000..02fe11b --- /dev/null +++ b/src/scalevalapokalypsi/Client/RoomState.scala @@ -0,0 +1,32 @@ +package scalevalapokalypsi.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 RoomState: + + /** 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 RoomState diff --git a/src/scalevalapokalypsi/Client/StdinLineReader.scala b/src/scalevalapokalypsi/Client/StdinLineReader.scala deleted file mode 100644 index 6ba8761..0000000 --- a/src/scalevalapokalypsi/Client/StdinLineReader.scala +++ /dev/null @@ -1,31 +0,0 @@ -package scalevalapokalypsi.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/scalevalapokalypsi/Client/Turn.scala b/src/scalevalapokalypsi/Client/Turn.scala deleted file mode 100644 index 02fe11b..0000000 --- a/src/scalevalapokalypsi/Client/Turn.scala +++ /dev/null @@ -1,32 +0,0 @@ -package scalevalapokalypsi.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 RoomState: - - /** 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 RoomState -- cgit v1.2.3