diff options
Diffstat (limited to 'src/scalevalapokalypsi/Client')
-rw-r--r-- | src/scalevalapokalypsi/Client/Client.scala | 87 | ||||
-rw-r--r-- | src/scalevalapokalypsi/Client/GameEvent.scala | 11 | ||||
-rw-r--r-- | src/scalevalapokalypsi/Client/ReceivedLineParser.scala | 7 | ||||
-rw-r--r-- | src/scalevalapokalypsi/Client/RoomState.scala (renamed from src/scalevalapokalypsi/Client/Turn.scala) | 4 | ||||
-rw-r--r-- | src/scalevalapokalypsi/Client/StdinLineReader.scala | 31 |
5 files changed, 59 insertions, 81 deletions
diff --git a/src/scalevalapokalypsi/Client/Client.scala b/src/scalevalapokalypsi/Client/Client.scala index aab6bc3..e94fdb0 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,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,49 @@ 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 = @@ -112,37 +119,27 @@ 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() - 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/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/Turn.scala b/src/scalevalapokalypsi/Client/RoomState.scala index 30101c5..02fe11b 100644 --- a/src/scalevalapokalypsi/Client/Turn.scala +++ b/src/scalevalapokalypsi/Client/RoomState.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/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 |