aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/scalevalapokalypsi/Model/Action.scala65
-rw-r--r--src/scalevalapokalypsi/Model/Adventure.scala23
-rw-r--r--src/scalevalapokalypsi/Model/Entities/NPCs/NPC.scala89
-rw-r--r--src/scalevalapokalypsi/Model/Entities/Player.scala2
-rw-r--r--src/scalevalapokalypsi/Server/Server.scala3
5 files changed, 165 insertions, 17 deletions
diff --git a/src/scalevalapokalypsi/Model/Action.scala b/src/scalevalapokalypsi/Model/Action.scala
index fdfbf75..287b008 100644
--- a/src/scalevalapokalypsi/Model/Action.scala
+++ b/src/scalevalapokalypsi/Model/Action.scala
@@ -1,6 +1,7 @@
package scalevalapokalypsi.Model
import scalevalapokalypsi.Model.Entities.*
+import scalevalapokalypsi.Model.Entities.NPCs.*
/** The class `Action` represents actions that a player may take in a text
* adventure game. `Action` objects are constructed on the basis of textual
@@ -36,20 +37,60 @@ class Action(input: String):
result.map((true, _))
case "rest" => Some((true, actor.rest()))
case "get" => Some((false, actor.pickUp(this.modifiers)))
- case "say" =>
- val to = "to"
- val recipient = modifiers.reverse.takeWhile(_ != ' ').reverse
- val recipientEntity = actor.location.getEntity(recipient)
- val maybeTo = modifiers.slice(
- modifiers.length - recipient.length - s"$to ".length,
- modifiers.length - recipient.length - 1
+ case "sano" =>
+ val entityNames = actor.location.getEntityNames.map(_.toLowerCase)
+ val recipientNamePair = entityNames.map(name =>
+ val possibleNamesWithSuffix = (0 to "ille".length).map(i =>
+ modifiers.takeRight(name.length + i)
+ )
+ possibleNamesWithSuffix.find(s =>
+ s.take(name.length) == name
+ )
+ .map(_.splitAt(name.length))
+ ).flatten.headOption
+
+ val recipient = recipientNamePair.flatMap(p =>
+ actor.location.getEntity(p(0))
)
- val message =
- modifiers.take(modifiers.length - recipient.length - 4)
- if maybeTo == to then
- recipientEntity.map(e => (false, actor.sayTo(e, message)))
- else
+
+ val message = recipientNamePair
+ .map(p => modifiers.dropRight(p(0).length + p(1).length))
+ .filter(_.takeRight(1) == " ")
+ .map(_.dropRight(1))
+
+ message.map(m =>
+ recipient.map(e => (false, actor.sayTo(e, m)))
+ ).getOrElse(
Some((false, actor.say(modifiers)))
+ )
+ case "puhu" =>
+ val recipient = modifiers
+ .indices.take("ille".length + 1)
+ .map(i => modifiers.take(modifiers.length - i))
+ .find(name => actor.location.getEntity(name).isDefined)
+ .flatMap(name => actor.location.getEntity(name))
+ val dialog = recipient match
+ case Some(npc: NPC) =>
+ s"${npc.name}: ”${npc.getDialog}”"
+ case Some(player: Player) =>
+ "Et voi puhua pelaajille, vain sanoa asioita heille."
+ case Some(other) =>
+ "Et voi puhua tälle olennolle."
+ case None =>
+ "Kyseistä puhujaa ei löytynyt."
+
+ val fromThirdPerson = recipient
+ .filter(a => a.isInstanceOf[NPC])
+ .map(a => s"${actor.name} puhuu $modifiers")
+
+ Some(
+ (
+ false,
+ Event(Vector((
+ actor, dialog
+ )).toMap, fromThirdPerson.getOrElse(""))
+ )
+ )
case "drop" => Some((false, actor.drop(this.modifiers)))
case "laula" =>
val end = modifiers.takeRight("suohon".length)
diff --git a/src/scalevalapokalypsi/Model/Adventure.scala b/src/scalevalapokalypsi/Model/Adventure.scala
index 9347bfa..09eed54 100644
--- a/src/scalevalapokalypsi/Model/Adventure.scala
+++ b/src/scalevalapokalypsi/Model/Adventure.scala
@@ -2,6 +2,7 @@ package scalevalapokalypsi.Model
import scala.collection.mutable.Map
import scalevalapokalypsi.Model.Entities.*
+import scalevalapokalypsi.Model.Entities.NPCs.*
/** The class `Adventure` holds data of the game world and provides methods
* for implementing a user interface for it.
@@ -20,7 +21,6 @@ class Adventure(val playerNames: Vector[String]):
private val clearing = Area("Forest Clearing", "You are at a small clearing in the middle of forest.\nNearly invisible, twisted paths lead in many directions.")
private val tangle = Area("Tangle of Bushes", "You are in a dense tangle of bushes. It's hard to see exactly where you're going.")
private val home = Area("Home", "Home sweet home! Now the only thing you need is a working remote control.")
- private val destination = home
middle.setNeighbors(Vector("north" -> northForest, "east" -> tangle, "south" -> southForest, "west" -> clearing))
northForest.setNeighbors(Vector("east" -> tangle, "south" -> middle, "west" -> clearing))
@@ -37,6 +37,23 @@ class Adventure(val playerNames: Vector[String]):
))
val entities: Map[String, Entity] = Map()
+
+ val npcs: Map[String, NPC] = Map()
+
+ private val zombieAttrs = Vector(
+ ("Weary zombie", clearing, 20),
+ ("Smelly zombie", home, 20),
+ ("Rotten zombie", tangle, 10)
+ )
+ zombieAttrs.foreach(z =>
+ val zombie = Zombie(z(0), z(1), z(2))
+ npcs += z(0) -> zombie
+ z(1).addEntity(zombie)
+ )
+
+ def takeNpcTurns(): Unit =
+ npcs.values.foreach(_.act())
+
private val gruu = Entity("Gruu", northForest)
northForest.addEntity(gruu)
this.entities += gruu.name -> gruu
@@ -64,7 +81,9 @@ class Adventure(val playerNames: Vector[String]):
def getPlayer(name: String): Option[Player] = this.players.get(name)
def getEntity[A >: Entity](name: String) =
- this.players.getOrElse(name, this.entities.get(name))
+ this.players.get(name)
+ .orElse(this.npcs.get(name))
+ .getOrElse(this.entities.get(name))
end Adventure
diff --git a/src/scalevalapokalypsi/Model/Entities/NPCs/NPC.scala b/src/scalevalapokalypsi/Model/Entities/NPCs/NPC.scala
new file mode 100644
index 0000000..21709ba
--- /dev/null
+++ b/src/scalevalapokalypsi/Model/Entities/NPCs/NPC.scala
@@ -0,0 +1,89 @@
+
+package scalevalapokalypsi.Model.Entities.NPCs
+
+import scala.collection.mutable.Buffer
+import scalevalapokalypsi.Model.*
+import scalevalapokalypsi.Model.Entities.*
+import scala.util.Random
+
+/** A `NPC` object represents a non-playable in-game character controlled by
+ * the server using this objects `act` method. It can also be "talked to": it
+ * returns a dialog when asked for.
+ *
+ * A NPC object’s state is mutable: the NPC’s location and possessions can change,
+ * for instance.
+ *
+ * @param name the NPC's name
+ * @param initialLocation the NPC’s initial location
+ */
+abstract class NPC(
+ name: String,
+ initialLocation: Area,
+ initialHP: Int,
+ maxHp: Int
+) extends Entity(name, initialLocation, initialHP, maxHp):
+ def getDialog: String
+ def act(): Unit
+
+class Zombie(
+ identifier: String,
+ initialLocation: Area,
+ initialHP: Int = 20
+) extends NPC(identifier, initialLocation, initialHP, 20):
+
+ private val damage = 10
+ private val dialogs = Vector(
+ "örvlg",
+ "grr",
+ "äyyrrrgrlgb ww",
+ "aaak brzzzwff ååö",
+ "äkb glan abglum",
+ "öub gpa"
+ )
+
+ override def getDialog: String =
+ val dialogIndex = Random.between(0, this.dialogs.length)
+ this.dialogs(dialogIndex)
+
+ override def act(): Unit =
+ val possibleVictims = this.location
+ .getEntities
+ .filter(_ != this)
+ .toVector
+ val index: Int =
+ if possibleVictims.isEmpty then 0
+ else Random.between(0, possibleVictims.length)
+ if possibleVictims.isEmpty then
+ val possibleDirections = this.location.getNeighborNames.toVector
+ val directionIndex = Random.between(0, possibleDirections.length*2)
+ possibleDirections
+ .toVector
+ .lift(directionIndex)
+ .flatMap(this.go(_))
+ .map(this.location.observeEvent(_))
+ else
+ this.location.observeEvent(
+ this.attack(possibleVictims(index))
+ )
+
+
+ private def attack(entity: Entity): Event =
+ if Random.nextBoolean() then
+ entity.takeDamage(this.damage)
+ Event(
+ Map.from(Vector((
+ entity,
+ s"${this.name} puree sinua, hyi yäk!\n" +
+ s"${entity.condition(0)}"
+ ))),
+ s"${this.name} puree henkilöä ${entity.name}.\n" +
+ s"${entity.condition(1)}"
+ )
+ else
+ Event(
+ Map.from(Vector((
+ entity,
+ s"${this.name} yrittää purra sinua mutta kaatuu ohitsesi."
+ ))),
+ s"${this.name} yrittää purra henkilöä ${entity.name}, mutta epäonnistuu surkeasti."
+ )
diff --git a/src/scalevalapokalypsi/Model/Entities/Player.scala b/src/scalevalapokalypsi/Model/Entities/Player.scala
index a66f521..7d166b4 100644
--- a/src/scalevalapokalypsi/Model/Entities/Player.scala
+++ b/src/scalevalapokalypsi/Model/Entities/Player.scala
@@ -27,7 +27,7 @@ class Player(name: String, initialLocation: Area) extends Entity(name, initialLo
val res = (res1 ++ res2).toVector
observations.clear()
observedEvents.clear()
- res
+ res.filter(s => !(s.isEmpty))
/** Returns whether this player has a pending sing effect. */
def isSinging: Boolean = this.pendingSingEffect.isDefined
diff --git a/src/scalevalapokalypsi/Server/Server.scala b/src/scalevalapokalypsi/Server/Server.scala
index 89db302..2ea8bd4 100644
--- a/src/scalevalapokalypsi/Server/Server.scala
+++ b/src/scalevalapokalypsi/Server/Server.scala
@@ -56,8 +56,7 @@ class Server(
this.writeObservations()
if this.canExecuteTurns then
this.clients.foreach(_.giveTurn())
- //this.writeClientDataToClients()
- //this.writeObservations()
+ this.adventure.foreach(_.takeNpcTurns())
this.clients.foreach(c =>
this.writeToClient(this.turnStartInfo(c), c)
)