aboutsummaryrefslogtreecommitdiff
path: root/src/scalevalapokalypsi/UI
diff options
context:
space:
mode:
Diffstat (limited to 'src/scalevalapokalypsi/UI')
-rw-r--r--src/scalevalapokalypsi/UI/.bsp/scala.json45
-rw-r--r--src/scalevalapokalypsi/UI/Printer.scala78
-rw-r--r--src/scalevalapokalypsi/UI/StdinLineReader.scala33
-rw-r--r--src/scalevalapokalypsi/UI/main.scala132
4 files changed, 288 insertions, 0 deletions
diff --git a/src/scalevalapokalypsi/UI/.bsp/scala.json b/src/scalevalapokalypsi/UI/.bsp/scala.json
new file mode 100644
index 0000000..fe879ce
--- /dev/null
+++ b/src/scalevalapokalypsi/UI/.bsp/scala.json
@@ -0,0 +1,45 @@
+{
+ "name": "scala",
+ "argv": [
+ "/home/cron4/.cache/coursier/arc/https/github.com/scala/scala3/releases/download/3.5.0/scala3-3.5.0-x86_64-pc-linux.tar.gz/scala3-3.5.0-x86_64-pc-linux/bin/scala-cli",
+ "--cli-default-scala-version",
+ "3.5.0",
+ "--repository",
+ "file:///home/cron4/.cache/coursier/arc/https/github.com/scala/scala3/releases/download/3.5.0/scala3-3.5.0-x86_64-pc-linux.tar.gz/scala3-3.5.0-x86_64-pc-linux/maven2",
+ "--prog-name",
+ "scala",
+ "bsp",
+ "--json-options",
+ "/home/cron4/Dev/scalevalapokalypsi/src/scalevalapokalypsi/UI/.scala-build/ide-options-v2.json",
+ "--json-launcher-options",
+ "/home/cron4/Dev/scalevalapokalypsi/src/scalevalapokalypsi/UI/.scala-build/ide-launcher-options.json",
+ "--envs-file",
+ "/home/cron4/Dev/scalevalapokalypsi/src/scalevalapokalypsi/UI/.scala-build/ide-envs.json",
+ "/home/cron4/Dev/scalevalapokalypsi/src/scalevalapokalypsi/UI/main.scala",
+ "/home/cron4/Dev/scalevalapokalypsi/src/scalevalapokalypsi/UI/Printer.scala",
+ "/home/cron4/Dev/scalevalapokalypsi/src/scalevalapokalypsi/Client/Client.scala",
+ "/home/cron4/Dev/scalevalapokalypsi/src/scalevalapokalypsi/Client/GameEvent.scala",
+ "/home/cron4/Dev/scalevalapokalypsi/src/scalevalapokalypsi/Client/ReceivedLineParser.scala",
+ "/home/cron4/Dev/scalevalapokalypsi/src/scalevalapokalypsi/Client/RoomState.scala",
+ "/home/cron4/Dev/scalevalapokalypsi/src/scalevalapokalypsi/Client/StdinLineReader.scala",
+ "/home/cron4/Dev/scalevalapokalypsi/src/scalevalapokalypsi/Model/Action.scala",
+ "/home/cron4/Dev/scalevalapokalypsi/src/scalevalapokalypsi/Model/Adventure.scala",
+ "/home/cron4/Dev/scalevalapokalypsi/src/scalevalapokalypsi/Model/Area.scala",
+ "/home/cron4/Dev/scalevalapokalypsi/src/scalevalapokalypsi/Model/Entities",
+ "/home/cron4/Dev/scalevalapokalypsi/src/scalevalapokalypsi/Model/Event.scala",
+ "/home/cron4/Dev/scalevalapokalypsi/src/scalevalapokalypsi/Model/Item.scala",
+ "/home/cron4/Dev/scalevalapokalypsi/src/scalevalapokalypsi/Model/SingEffects.scala",
+ "/home/cron4/Dev/scalevalapokalypsi/src/scalevalapokalypsi/Server/Client.scala",
+ "/home/cron4/Dev/scalevalapokalypsi/src/scalevalapokalypsi/Server/Clients.scala",
+ "/home/cron4/Dev/scalevalapokalypsi/src/scalevalapokalypsi/Server/ConnectionGetter.scala",
+ "/home/cron4/Dev/scalevalapokalypsi/src/scalevalapokalypsi/Server/Server.scala",
+ "/home/cron4/Dev/scalevalapokalypsi/src/scalevalapokalypsi/constants/constants.scala",
+ "/home/cron4/Dev/scalevalapokalypsi/src/scalevalapokalypsi/utils/utils.scala"
+ ],
+ "version": "1.4.0",
+ "bspVersion": "2.1.1",
+ "languages": [
+ "scala",
+ "java"
+ ]
+} \ No newline at end of file
diff --git a/src/scalevalapokalypsi/UI/Printer.scala b/src/scalevalapokalypsi/UI/Printer.scala
new file mode 100644
index 0000000..a33864f
--- /dev/null
+++ b/src/scalevalapokalypsi/UI/Printer.scala
@@ -0,0 +1,78 @@
+package scalevalapokalypsi.UI
+import scalevalapokalypsi.Client.GameEvent
+import java.lang.System.currentTimeMillis
+import scalevalapokalypsi.utils.isPrintable
+
+/** A singleton for printing. Keeps track about the "action query" at the start
+ * of lines and has a helper function "pryntGameEvent".
+ */
+object Printer:
+ var inputIndicatorAtStartOfLine = false
+ var queriedLineToSing = false
+ var singStartTime: Option[Long] = None
+
+ /** Prints the given game event.
+ *
+ * @param gameEvent the event to print
+ */
+ 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 match
+ case Some(l) =>
+ if this.singStartTime.isEmpty then
+ this.singStartTime = Some(currentTimeMillis() / 1000)
+ print(s"Laula: “$l”\n ")
+ val timeSpent = this.singStartTime.map((t: Long) =>
+ (currentTimeMillis / 1000 - t).toString
+ ).getOrElse("?")
+ print(this.timeIndicatorUpdater(timeSpent))
+ case None =>
+ this.singStartTime = None
+
+ val timeLeft = s"${gameEvent.timeToNextTurn.getOrElse("∞")}"
+
+ if
+ gameEvent.playerCanAct &&
+ lineToSing.isEmpty &&
+ !inputIndicatorAtStartOfLine
+ then
+ this.inputIndicatorAtStartOfLine = true
+ print(s"[$timeLeft s]> ")
+
+ if gameEvent.playerCanAct && lineToSing.isEmpty then
+ print(this.timeIndicatorUpdater(timeLeft))
+
+ end printGameEvent
+
+ /** Prints the given string with a trailing newline added. Should be used
+ * instead of ordinary println because printing outside of the Printer
+ * might cause weird-looking output.
+ *
+ * @param s the line to print
+ */
+ def printLn(s: String): Unit =
+ if isPrintable(s) then
+ println(s)
+ else
+ println("Virhe: epätavallinen merkki havaittu tulosteessa.")
+ this.inputIndicatorAtStartOfLine = false
+
+ private def timeIndicatorUpdater(t: String): String =
+ s"\u001b7\u001b[0E[$t s]> \u001b8"
+
diff --git a/src/scalevalapokalypsi/UI/StdinLineReader.scala b/src/scalevalapokalypsi/UI/StdinLineReader.scala
new file mode 100644
index 0000000..4d0f778
--- /dev/null
+++ b/src/scalevalapokalypsi/UI/StdinLineReader.scala
@@ -0,0 +1,33 @@
+package scalevalapokalypsi.UI
+
+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)) =>
+ if s.contains("\u0000") then
+ println("End of stream!")
+ 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/UI/main.scala b/src/scalevalapokalypsi/UI/main.scala
new file mode 100644
index 0000000..44ca0e4
--- /dev/null
+++ b/src/scalevalapokalypsi/UI/main.scala
@@ -0,0 +1,132 @@
+package scalevalapokalypsi.UI
+
+import scalevalapokalypsi.Client.{newClient, Client, GameEvent}
+import scalevalapokalypsi.Server.Server
+import scalevalapokalypsi.constants.*
+import scalevalapokalypsi.utils.*
+import java.lang.Thread.sleep
+import scala.util.{Try,Success,Failure}
+import scala.collection.immutable.LazyList
+
+import java.lang.Thread
+import scala.io.StdIn.readLine
+
+
+@main def main(): Unit =
+
+ val option = getValidInput[Int](
+ "Miten tahdot pelata?\n" +
+ s"1) Liity viralliselle palvelimelle ($DEFAULT_SERVER)\n" +
+ "2) Käynnistä palvelin laitteellasi ja liity sille\n" +
+ "3) Liity mielivaltaiselle palvelimelle",
+ s => s.toIntOption match
+ case None =>
+ Left("Syötä kokonaisluku.")
+ case Some(i) =>
+ if i < 1 || i > 3 then
+ Left("Syötä kokonaisluku väliltä [1,3].")
+ else
+ Right(i)
+ )
+
+
+ val serverName =
+ if option == 3 then
+ readLine("Syötä palvelimen verkkotunnus.\n> ")
+ else if option == 2 then
+ "127.0.0.1"
+ else
+ DEFAULT_SERVER
+
+ val serverPort =
+ if option == 1 then
+ DEFAULT_PORT
+ else
+ getValidInput[Int](
+ s"Valitse portti palvelimelle. (Jätä tyhjäksi oletusta $DEFAULT_PORT varten)",
+ s =>
+ if s == "" then
+ Right(DEFAULT_PORT)
+ else
+ s.toIntOption match
+ case None =>
+ Left("Syötä kokonaisluku.")
+ case Some(i) =>
+ if i > 0 && i <= 65535 then
+ Right(i)
+ else
+ Left("Syötä portti lailliselta väliltä.")
+ )
+
+ val maxClients = if option == 2 then
+ getValidInput[Int](
+ "Kuinka monta pelaajaa pelissä saa olla samanaikaisesti?",
+ s => s.toIntOption match
+ case None =>
+ Left("Syötä kokonaisluku.")
+ case Some(i) =>
+ if i > 1000 then
+ println(
+ "Aika ison määrän valitsit, mutta olkoon menneeksi."
+ )
+ if i > 0 then
+ Right(i)
+ else
+ Left("Syötä positiivinen kokonaisluku.")
+ )
+ else
+ 0
+
+ val timeLimit = if option == 2 then
+ getValidInput[Int](
+ s"Syötä vuorojen aikaraja yksiköttömänä sekunneissa odottelun vähentämiseksi. Syötä 0 aikarajan poistamiseksi. (Jätä tyhjäksi oletusta $DEFAULT_TURN_TIME_LIMIT varten)",
+ s => if s == "" then
+ Right(DEFAULT_TURN_TIME_LIMIT)
+ else
+ s.toIntOption
+ .toRight("Syötä kokonaisluku")
+ .filterOrElse(
+ _ >= 0,
+ "Syötä epänegatiivinen kokonaisluku."
+ )
+ )
+ else
+ 0
+
+ if option == 2 then
+ Thread(() => new Server(serverPort, maxClients, timeLimit, true).startServer()).start()
+ println("Palvelin käynnistetty taustalla.")
+
+ val name = readLine("Valitse itsellesi pelin sisäinen alter ego.\n> ")
+
+ Try(newClient(name, serverName, serverPort))
+ .toOption
+ .flatten match
+ case Some(client) =>
+ startClient(client)
+ case None =>
+ println(
+ "Serverille liittyminen epäonnistui. Tarkista internet-yhteytesi. Jos yhteytesi on kunnossa ja liittyminen ei pian onnistu, ota yhteyttä Joel Kronqvistiin <joel.kronqvist@iki.fi> olettaen, ettei vuodesta 2024 ole kulunut kohtuuttomasti aikaa."
+ )
+
+
+
+/** Client game loop. Handles output to and from the client in the eyes of the
+ * terminal.
+ */
+def startClient(client: Client): Unit =
+ var hasQuit = false
+ val stdinReader = StdinLineReader()
+ stdinReader.startReading()
+ while !hasQuit do
+ sleep(POLL_INTERVAL)
+ val line = stdinReader.newLine()
+ if line.map(_.length).getOrElse(0) > 1024 then
+ Printer.printLn("Virhe: Syötteesi oli liian pitkä.")
+ else if line == Some("quit") then
+ hasQuit = true
+ else
+ val gameEvent = client.clientStep(line)
+ Printer.printGameEvent(gameEvent)
+
+