diff options
Diffstat (limited to 'src/scalevalapokalypsi/UI')
-rw-r--r-- | src/scalevalapokalypsi/UI/.bsp/scala.json | 45 | ||||
-rw-r--r-- | src/scalevalapokalypsi/UI/Printer.scala | 78 | ||||
-rw-r--r-- | src/scalevalapokalypsi/UI/StdinLineReader.scala | 33 | ||||
-rw-r--r-- | src/scalevalapokalypsi/UI/main.scala | 132 |
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) + + |