aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--protocol.txt2
-rw-r--r--src/scalevalapokalypsi/Client/Client.scala9
-rw-r--r--src/scalevalapokalypsi/Client/GameEvent.scala3
-rw-r--r--src/scalevalapokalypsi/Model/Action.scala11
-rw-r--r--src/scalevalapokalypsi/Model/Adventure.scala25
-rw-r--r--src/scalevalapokalypsi/Model/Entities/Entity.scala19
-rw-r--r--src/scalevalapokalypsi/Model/Entities/NPCs/NPC.scala8
-rw-r--r--src/scalevalapokalypsi/Model/Entities/Player.scala17
-rw-r--r--src/scalevalapokalypsi/Model/SingEffects.scala4
-rw-r--r--src/scalevalapokalypsi/Server/Server.scala13
-rw-r--r--src/scalevalapokalypsi/UI/main.scala5
-rw-r--r--src/scalevalapokalypsi/constants/constants.scala3
12 files changed, 90 insertions, 29 deletions
diff --git a/protocol.txt b/protocol.txt
index f38e778..b0166ec 100644
--- a/protocol.txt
+++ b/protocol.txt
@@ -16,3 +16,5 @@ Server: [turn indicator]CRLF
[Entities separated with semicolon]CRLF
When running turn: [CRLF-separated list of things happening in the players room]
+
+At end of game: [GAME_END_INDICATOR] \ No newline at end of file
diff --git a/src/scalevalapokalypsi/Client/Client.scala b/src/scalevalapokalypsi/Client/Client.scala
index e94fdb0..5364405 100644
--- a/src/scalevalapokalypsi/Client/Client.scala
+++ b/src/scalevalapokalypsi/Client/Client.scala
@@ -76,6 +76,7 @@ class Client(socket: Socket):
"don't initialize with unexecuted turn"
)
private val turnInfo = RoomState()
+ private var gameOver = false
/** Takes a client step and optionally returns an in-game event for UI
*
@@ -108,7 +109,8 @@ class Client(socket: Socket):
roomState,
this.lineToSing,
this.canAct,
- Some(timeToTurnEnd).filter(p => this.lastTurnStart != 0)
+ Some(timeToTurnEnd).filter(p => this.lastTurnStart != 0),
+ !this.gameOver
)
end clientStep
@@ -157,6 +159,9 @@ class Client(socket: Socket):
if line == TURN_INDICATOR then
this.serverLineState = ServerLineState.TurnIndicator
+ if line == GAME_END_INDICATOR then
+ this.gameOver = true
+
serverLineState match
case ServerLineState.WaitingForTimeLimit =>
@@ -185,7 +190,7 @@ class Client(socket: Socket):
case ServerLineState.Directions =>
this.turnInfo.possibleDirections = line.split(LIST_SEPARATOR)
- this.serverLineState = ServerLineState.Items // TODO: maybe use a list instead?
+ this.serverLineState = ServerLineState.Items
case ServerLineState.Items =>
this.turnInfo.visibleItems = line.split(LIST_SEPARATOR)
diff --git a/src/scalevalapokalypsi/Client/GameEvent.scala b/src/scalevalapokalypsi/Client/GameEvent.scala
index 8aa1e1c..0397b48 100644
--- a/src/scalevalapokalypsi/Client/GameEvent.scala
+++ b/src/scalevalapokalypsi/Client/GameEvent.scala
@@ -5,7 +5,8 @@ class GameEvent(
val roomState: Option[RoomState],
val lineToSing: Option[String],
val playerCanAct: Boolean,
- val timeToNextTurn: Option[Long]
+ val timeToNextTurn: Option[Long],
+ val gameIsOn: Boolean
)
diff --git a/src/scalevalapokalypsi/Model/Action.scala b/src/scalevalapokalypsi/Model/Action.scala
index c7c8a65..21e1286 100644
--- a/src/scalevalapokalypsi/Model/Action.scala
+++ b/src/scalevalapokalypsi/Model/Action.scala
@@ -40,15 +40,14 @@ class Action(input: String):
case "inventory" => Some((false, actor.inventory))
case "sano" =>
val entityNames = actor.location.getEntityNames.map(_.toLowerCase)
- val recipientNamePair = entityNames.map(name =>
+ val recipientNamePair = entityNames.flatMap(name =>
val possibleNamesWithSuffix = (0 to "ille".length).map(i =>
- modifiers.takeRight(name.length + i)
+ modifiers.takeRight(name.length + i)
)
possibleNamesWithSuffix.find(s =>
- s.take(name.length) == name
- )
- .map(_.splitAt(name.length))
- ).flatten.headOption
+ s.take(name.length) == name
+ )
+ .map(_.splitAt(name.length))).headOption
val recipient = recipientNamePair.flatMap(p =>
actor.location.getEntity(p(0))
diff --git a/src/scalevalapokalypsi/Model/Adventure.scala b/src/scalevalapokalypsi/Model/Adventure.scala
index ba45abe..b10f7d9 100644
--- a/src/scalevalapokalypsi/Model/Adventure.scala
+++ b/src/scalevalapokalypsi/Model/Adventure.scala
@@ -46,7 +46,7 @@ class Adventure(val playerNames: Vector[String]):
("Rotten zombie", tangle, 10)
)
zombieAttrs.foreach(z =>
- val zombie = Zombie(z(0), z(1), z(2))
+ val zombie = Zombie(this, z(0), z(1), z(2))
npcs += z(0) -> zombie
z(1).addEntity(zombie)
)
@@ -54,7 +54,7 @@ class Adventure(val playerNames: Vector[String]):
def takeNpcTurns(): Unit =
npcs.values.foreach(_.act())
- private val gruu = Entity("Gruu", northForest)
+ private val gruu = Entity(this, "Gruu", northForest)
northForest.addEntity(gruu)
this.entities += gruu.name -> gruu
@@ -67,12 +67,29 @@ class Adventure(val playerNames: Vector[String]):
* @return the created player entity
*/
def addPlayer(name: String): Player =
- val newPlayer = Player(name, middle)
+ val newPlayer = Player(this, name, middle)
middle.addEntity(newPlayer)
this.entities += name -> newPlayer
players += name -> newPlayer
newPlayer
-
+
+ /** Removes the given entity without further observations. Makes sense in the
+ * game mostly if the entity's HP is nonpositive.
+ *
+ * Removes the entity both from the adventure and the game world
+ * (i.e. the entitys area).
+ *
+ * @param name the name of the entity to remove
+ * @return whether there was an entity to remove with the given name
+ */
+ def removeEntity(name: String): Boolean =
+ this.players.remove(name)
+ this.entities.remove(name).orElse(this.npcs.remove(name)) match
+ case Some(e) =>
+ e.location.removeEntity(name)
+ true
+ case None => false
+
/** Gets the player entity with the specified name.
*
* @param name name of the player to find
diff --git a/src/scalevalapokalypsi/Model/Entities/Entity.scala b/src/scalevalapokalypsi/Model/Entities/Entity.scala
index 336a6b1..aa2a2e2 100644
--- a/src/scalevalapokalypsi/Model/Entities/Entity.scala
+++ b/src/scalevalapokalypsi/Model/Entities/Entity.scala
@@ -12,6 +12,7 @@ import scala.collection.immutable
* @param initialLocation the Area where the entity is instantiated
*/
class Entity(
+ val adventure: Adventure,
val name: String,
initialLocation: Area,
initialHP: Int = 100,
@@ -31,10 +32,24 @@ class Entity(
*/
def getVerseAgainst: String = "Esimerkkirivi laulettavaksi"
+ def isAlive = this.hp > 0
+
def takeDamage(amount: Int): Unit =
hp -= amount
- if hp < 0 then
- println("Voi ei, kuolin!")
+ val event = if this.isAlive then
+ Event(
+ Vector(this -> this.condition(0)).toMap,
+ this.condition(1)
+ )
+ else
+ println(s"Could remove myself: ${this.adventure.removeEntity(this.name)}")
+ Event(
+ Vector(this ->
+ "Olet täysin menettänyt toimintakykysi. Kaadut elottomana maahan."
+ ).toMap,
+ s"${this.name} kaatuu elottomana maahan."
+ )
+ this.location.observeEvent(event)
/** Returns a description of the physical condition of this entity,
* i.e. the damage it has taken.
diff --git a/src/scalevalapokalypsi/Model/Entities/NPCs/NPC.scala b/src/scalevalapokalypsi/Model/Entities/NPCs/NPC.scala
index 21709ba..944f2e6 100644
--- a/src/scalevalapokalypsi/Model/Entities/NPCs/NPC.scala
+++ b/src/scalevalapokalypsi/Model/Entities/NPCs/NPC.scala
@@ -17,19 +17,21 @@ import scala.util.Random
* @param initialLocation the NPC’s initial location
*/
abstract class NPC(
+ adventure: Adventure,
name: String,
initialLocation: Area,
initialHP: Int,
maxHp: Int
-) extends Entity(name, initialLocation, initialHP, maxHp):
+) extends Entity(adventure, name, initialLocation, initialHP, maxHp):
def getDialog: String
def act(): Unit
class Zombie(
+ adventure: Adventure,
identifier: String,
initialLocation: Area,
initialHP: Int = 20
-) extends NPC(identifier, initialLocation, initialHP, 20):
+) extends NPC(adventure, identifier, initialLocation, initialHP, 20):
private val damage = 10
private val dialogs = Vector(
@@ -60,7 +62,7 @@ class Zombie(
.toVector
.lift(directionIndex)
.flatMap(this.go(_))
- .map(this.location.observeEvent(_))
+ .foreach(this.location.observeEvent(_))
else
this.location.observeEvent(
this.attack(possibleVictims(index))
diff --git a/src/scalevalapokalypsi/Model/Entities/Player.scala b/src/scalevalapokalypsi/Model/Entities/Player.scala
index d6b3529..9fc929d 100644
--- a/src/scalevalapokalypsi/Model/Entities/Player.scala
+++ b/src/scalevalapokalypsi/Model/Entities/Player.scala
@@ -12,7 +12,11 @@ import scalevalapokalypsi.Model.*
* @param name the player's name
* @param initialLocation the player’s initial location
*/
-class Player(name: String, initialLocation: Area) extends Entity(name, initialLocation):
+class Player(
+ adventure: Adventure,
+ name: String,
+ initialLocation: Area
+) extends Entity(adventure, name, initialLocation):
private val observations: Buffer[String] = Buffer.empty
private val observedEvents: Buffer[Event] = Buffer.empty
@@ -49,8 +53,6 @@ class Player(name: String, initialLocation: Area) extends Entity(name, initialLo
* @param singQuality the quality of the song
*/
def applySingEffect(singQuality: Float): Unit =
- val res = this.pendingSingEffect.map(ef => ef(singQuality))
- this.pendingSingEffect = None
val qualityDescriptions =
if singQuality < .10 then
("säälittävää", "epsilonin suuruinen")
@@ -63,11 +65,10 @@ class Player(name: String, initialLocation: Area) extends Entity(name, initialLo
else ("erinomaista", "merkittävä")
val quality =
s"Laulu on ${qualityDescriptions(0)} ja sen vaikutus on ${qualityDescriptions(1)}."
- val event = res.map(ev => Event(
- ev.inFirstPersons.map((k, v) => (k, s"$quality\n$v")),
- s"$quality\n${ev.inThirdPerson}"
- ))
- event.foreach(this.location.observeEvent(_))
+ val event = Event(Map.empty, s"$quality")
+ this.location.observeEvent(event)
+ this.pendingSingEffect.map(ef => ef(singQuality))
+ this.pendingSingEffect = None
diff --git a/src/scalevalapokalypsi/Model/SingEffects.scala b/src/scalevalapokalypsi/Model/SingEffects.scala
index 42f5188..23b7d37 100644
--- a/src/scalevalapokalypsi/Model/SingEffects.scala
+++ b/src/scalevalapokalypsi/Model/SingEffects.scala
@@ -10,4 +10,6 @@ class DefaultSingAttack(target: Entity) extends SingEffect(target):
def apply(singQuality: Float): Event =
this.target.takeDamage((singQuality * 50).toInt) // TODO: remove magic value
val condition = this.target.condition
- Event(Map.from(Vector((target, condition(0)))), condition(1))
+ Event(Map.empty, "") // The conditions are automatically shown to
+ // clients through takeDamage, but other effects
+ // should explain the changes they have.
diff --git a/src/scalevalapokalypsi/Server/Server.scala b/src/scalevalapokalypsi/Server/Server.scala
index 2ea8bd4..16a2128 100644
--- a/src/scalevalapokalypsi/Server/Server.scala
+++ b/src/scalevalapokalypsi/Server/Server.scala
@@ -54,6 +54,7 @@ class Server(
this.writeClientDataToClients()
this.makeClientsSing()
this.writeObservations()
+ this.endGameForDeadClients()
if this.canExecuteTurns then
this.clients.foreach(_.giveTurn())
this.adventure.foreach(_.takeNpcTurns())
@@ -183,6 +184,18 @@ class Server(
output.flush()
)
+ private def endGameForDeadClients(): Unit =
+ this.clients.removeNonSatisfying(c =>
+ c.player.forall((p: Player) =>
+ if !p.isAlive then
+ this.writeToClient(s"$GAME_END_INDICATOR\r\n", c)
+ false
+ else
+ true
+ )
+ )
+
+
private def writeToClient(message: String, client: Client): Unit =
try {
val output = client.socket.getOutputStream
diff --git a/src/scalevalapokalypsi/UI/main.scala b/src/scalevalapokalypsi/UI/main.scala
index 44ca0e4..7368803 100644
--- a/src/scalevalapokalypsi/UI/main.scala
+++ b/src/scalevalapokalypsi/UI/main.scala
@@ -106,7 +106,7 @@ import scala.io.StdIn.readLine
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."
+ "Palvelimelle 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."
)
@@ -128,5 +128,8 @@ def startClient(client: Client): Unit =
else
val gameEvent = client.clientStep(line)
Printer.printGameEvent(gameEvent)
+ if !gameEvent.gameIsOn then
+ hasQuit = true
+ println("Peli on pelattu.")
diff --git a/src/scalevalapokalypsi/constants/constants.scala b/src/scalevalapokalypsi/constants/constants.scala
index 970d6f7..159a153 100644
--- a/src/scalevalapokalypsi/constants/constants.scala
+++ b/src/scalevalapokalypsi/constants/constants.scala
@@ -4,9 +4,10 @@ package scalevalapokalypsi.constants
val MAX_MSG_SIZE = 1024 // bytes
val CRLF: Vector[Byte] = Vector(13.toByte, 10.toByte)
val POLL_INTERVAL = 100 // millisec.
-val GAME_VERSION = "0.1.0"
+val GAME_VERSION = "0.2.0"
val TURN_INDICATOR = ">"
val SING_INDICATOR = "~"
+val GAME_END_INDICATOR = "!"
val ACTION_BLOCKING_INDICATOR='.'
val ACTION_NONBLOCKING_INDICATOR='+'
val INITIAL_CONN_TIMEOUT = 5000 // millisec.