diff options
author | Aleksi Heikkila <aleksi.e.heikkila@aalto.fi> | 2024-11-23 22:33:53 +0200 |
---|---|---|
committer | Aleksi Heikkila <aleksi.e.heikkila@aalto.fi> | 2024-11-23 22:33:53 +0200 |
commit | 27dd937617cce1e43df1c16e12050f6e88763d54 (patch) | |
tree | 8247736f7c4d5b1a0c1541c25f477e73e5783944 /src/scalevalapokalypsi/Model | |
parent | b11c02e8f1f4dea09847da999c2acb2f96df8a58 (diff) | |
parent | 28b83db50f33cb704311ffe608dcd8c4412635cf (diff) | |
download | scalevalapokalypsi-27dd937617cce1e43df1c16e12050f6e88763d54.tar.gz scalevalapokalypsi-27dd937617cce1e43df1c16e12050f6e88763d54.zip |
Merge branch 'master' of ssh://cron4.fi/~/O1-game
Diffstat (limited to 'src/scalevalapokalypsi/Model')
-rw-r--r-- | src/scalevalapokalypsi/Model/Action.scala | 70 | ||||
-rw-r--r-- | src/scalevalapokalypsi/Model/Adventure.scala | 23 | ||||
-rw-r--r-- | src/scalevalapokalypsi/Model/Area.scala | 27 | ||||
-rw-r--r-- | src/scalevalapokalypsi/Model/Entities/Entity.scala | 6 | ||||
-rw-r--r-- | src/scalevalapokalypsi/Model/Entities/NPCs/NPC.scala | 89 | ||||
-rw-r--r-- | src/scalevalapokalypsi/Model/Entities/Player.scala | 5 |
6 files changed, 170 insertions, 50 deletions
diff --git a/src/scalevalapokalypsi/Model/Action.scala b/src/scalevalapokalypsi/Model/Action.scala index 52ba165..c7c8a65 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 @@ -29,25 +30,68 @@ class Action(input: String): val resOption: Option[(Boolean, Event)] = this.verb match case "go" => val result = actor.go(this.modifiers) - result.foreach(r => oldLocation.observeEvent(r)) + result.foreach(r => + if actor.location != oldLocation then + oldLocation.observeEvent(r) + ) result.map((true, _)) case "rest" => Some((true, actor.rest())) case "get" => Some((false, actor.pickUp(this.modifiers))) case "inventory" => Some((false, actor.inventory)) - 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 57f0dfd..ba45abe 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/Area.scala b/src/scalevalapokalypsi/Model/Area.scala index 96392ba..c07f2f9 100644 --- a/src/scalevalapokalypsi/Model/Area.scala +++ b/src/scalevalapokalypsi/Model/Area.scala @@ -103,33 +103,6 @@ class Area(val name: String, var description: String): def removeEntity(entityName: String): Option[Entity] = this.entities.remove(entityName.toLowerCase()) - /** Returns a multi-line description of the area as a player sees it. This - * includes a basic description of the area as well as information about - * exits and items. If there are no items present, the return value has the - * form "DESCRIPTION\n\nExits available: - * DIRECTIONS SEPARATED BY SPACES". If there are one or more items present, - * the return value has the form "DESCRIPTION\nYou see here: ITEMS - * SEPARATED BY SPACES\n\nExits available: DIRECTIONS SEPARATED BY SPACES". - * The items and directions are listed in an arbitrary order. - */ - def fullDescription: String = - val exitList = this.neighbors.keys.mkString(" ") - val itemList = this.items.keys.mkString(" ") - val entityList = this.getEntityNames.mkString(" ") - val itemDescription = - if this.items.nonEmpty then - s"\nYou see here: ${itemList}" - else "" - val entityDescription = - if this.entities.nonEmpty then - s"\nThere are entities: ${entityList}" - else "" - (this.description + - itemDescription + - entityDescription + - s"\n\nExits available: $exitList") - - /** Returns a single-line description of the area for debugging purposes. */ override def toString = this.name + ": " + this.description.replaceAll("\n", " ").take(150) diff --git a/src/scalevalapokalypsi/Model/Entities/Entity.scala b/src/scalevalapokalypsi/Model/Entities/Entity.scala index c7bc180..336a6b1 100644 --- a/src/scalevalapokalypsi/Model/Entities/Entity.scala +++ b/src/scalevalapokalypsi/Model/Entities/Entity.scala @@ -59,10 +59,7 @@ class Entity( ("Olet täysin kunnossa.", s"$name näyttää kuin vastasyntyneeltä.") /** Does nothing, except possibly in inherited classes. */ - def observeString(observation: String): Unit = - println(" [debug] entity got observation string & discarded it") - def observe(event: Event): Unit = - println(" [debug] entity got observation event & discarded it") + def observe(event: Event): Unit = () /** Returns the player’s current location. */ def location = this.currentLocation @@ -194,4 +191,5 @@ class Entity( + end Entity 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 1dd5187..d6b3529 100644 --- a/src/scalevalapokalypsi/Model/Entities/Player.scala +++ b/src/scalevalapokalypsi/Model/Entities/Player.scala @@ -18,9 +18,6 @@ class Player(name: String, initialLocation: Area) extends Entity(name, initialLo private val observedEvents: Buffer[Event] = Buffer.empty private var pendingSingEffect: Option[SingEffect] = None - - override def observeString(observation: String): Unit = - this.observations.append(observation) override def observe(event: Event): Unit = this.observedEvents.append(event) @@ -30,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 |