aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoel Kronqvist <joel.kronqvist@iki.fi>2024-11-21 21:09:52 +0200
committerJoel Kronqvist <joel.kronqvist@iki.fi>2024-11-21 21:09:52 +0200
commit607b43a84d3bc8edffa05c722c7b8c3e6f72e964 (patch)
tree8503eeb4822f11ca1f0f86ef17283aacb9a7a6e2
parentfe2543627bcec1ea0f7a429bede20ca293458ba9 (diff)
downloadscalevalapokalypsi-607b43a84d3bc8edffa05c722c7b8c3e6f72e964.tar.gz
scalevalapokalypsi-607b43a84d3bc8edffa05c722c7b8c3e6f72e964.zip
Wooooohooo time limits and correcter printing
-rw-r--r--src/scalevalapokalypsi/Client/Client.scala85
-rw-r--r--src/scalevalapokalypsi/Client/GameEvent.scala11
-rw-r--r--src/scalevalapokalypsi/Client/Turn.scala4
-rw-r--r--src/scalevalapokalypsi/constants/constants.scala3
-rw-r--r--src/scalevalapokalypsi/main.scala59
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