diff options
author | Joel Kronqvist <joel.kronqvist@iki.fi> | 2024-11-21 21:09:52 +0200 |
---|---|---|
committer | Joel Kronqvist <joel.kronqvist@iki.fi> | 2024-11-21 21:09:52 +0200 |
commit | 607b43a84d3bc8edffa05c722c7b8c3e6f72e964 (patch) | |
tree | 8503eeb4822f11ca1f0f86ef17283aacb9a7a6e2 | |
parent | fe2543627bcec1ea0f7a429bede20ca293458ba9 (diff) | |
download | scalevalapokalypsi-607b43a84d3bc8edffa05c722c7b8c3e6f72e964.tar.gz scalevalapokalypsi-607b43a84d3bc8edffa05c722c7b8c3e6f72e964.zip |
Wooooohooo time limits and correcter printing
-rw-r--r-- | src/scalevalapokalypsi/Client/Client.scala | 85 | ||||
-rw-r--r-- | src/scalevalapokalypsi/Client/GameEvent.scala | 11 | ||||
-rw-r--r-- | src/scalevalapokalypsi/Client/Turn.scala | 4 | ||||
-rw-r--r-- | src/scalevalapokalypsi/constants/constants.scala | 3 | ||||
-rw-r--r-- | src/scalevalapokalypsi/main.scala | 59 |
5 files changed, 113 insertions, 49 deletions
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 diff --git a/src/scalevalapokalypsi/constants/constants.scala b/src/scalevalapokalypsi/constants/constants.scala index cb08962..7d4e1a6 100644 --- a/src/scalevalapokalypsi/constants/constants.scala +++ b/src/scalevalapokalypsi/constants/constants.scala @@ -9,12 +9,13 @@ val TURN_INDICATOR = ">" val SING_INDICATOR = "~" val ACTION_BLOCKING_INDICATOR='.' val ACTION_NONBLOCKING_INDICATOR='+' +val INITIAL_CONN_TIMEOUT = 5000 // millisec. val LIST_SEPARATOR=";" val PROTOCOL_VERSION_GOOD = "1" val PROTOCOL_VERSION_BAD = "0" -//assert(PROTOCOL_VERSION_BAD.length <= PROTOCOL_VERSION_GOOD.length) +// assert(PROTOCOL_VERSION_BAD.length <= PROTOCOL_VERSION_GOOD.length) enum ServerProtocolState: case WaitingForVersion, WaitingForClientName, WaitingForGameStart, InGame diff --git a/src/scalevalapokalypsi/main.scala b/src/scalevalapokalypsi/main.scala index 50e89e5..f953751 100644 --- a/src/scalevalapokalypsi/main.scala +++ b/src/scalevalapokalypsi/main.scala @@ -1,7 +1,10 @@ package scalevalapokalypsi -import scalevalapokalypsi.Client.newClient +import scalevalapokalypsi.Client.{newClient, Client, StdinLineReader, GameEvent} import scalevalapokalypsi.Server.Server +import scalevalapokalypsi.constants.* +import java.lang.Thread.sleep +import scala.util.Try import java.lang.Thread import scala.io.StdIn.readLine @@ -15,11 +18,61 @@ import scala.io.StdIn.readLine println("Server started in background.") print("Choose a name:\n> ") val name = readLine() - newClient(name, "127.0.0.1", 2267).foreach(_.startClient()) + Try(newClient(name, "127.0.0.1", 2267)) + .toOption + .flatten match + case Some(client) => + startClient(client) + case None => + println("Starting the client failed.") + case Some(2) => print("Choose a name:\n> ") val name = readLine() - newClient(name, "127.0.0.1", 2267).foreach(_.startClient()) + Try(newClient(name, "127.0.0.1", 2267)) + .toOption + .flatten match + case Some(client) => + startClient(client) + case None => + println("Starting the client failed.") case _ => println("Invalid input") +def startClient(client: Client): Unit = + var hasQuit = false + val stdinReader = StdinLineReader() + stdinReader.startReading() + val printer = Printer() + while !hasQuit do + sleep(POLL_INTERVAL) + val gameEvent = client.clientStep(stdinReader.newLine()) + printer.printGameEvent(gameEvent) + + +class Printer: + var inputIndicatorAtStartOfLine = false + def printGameEvent(gameEvent: GameEvent): Unit = + val actions = gameEvent.actions.map(_.mkString("\n")) + val roomState = gameEvent.roomState.map(_.toString) + val lineToSing = gameEvent.lineToSing + if + inputIndicatorAtStartOfLine && + (actions.isDefined || + roomState.isDefined || + lineToSing.isDefined) + then + this.printLn("") + actions.foreach(this.printLn(_)) + roomState.foreach(this.printLn(_)) + lineToSing.foreach(this.printLn(_)) + val timeLeft = s"${gameEvent.timeToNextTurn.getOrElse("∞")}" + if gameEvent.playerCanAct && !inputIndicatorAtStartOfLine then + this.inputIndicatorAtStartOfLine = true + print(s"[$timeLeft s]> ") + if gameEvent.playerCanAct then + print(s"\u001b[s\u001b[0E[$timeLeft s]\u001b[u") + + private def printLn(s: String): Unit = + println(s) + this.inputIndicatorAtStartOfLine = false |