diff options
Diffstat (limited to 'src/scalevalapokalypsi/Model')
-rw-r--r-- | src/scalevalapokalypsi/Model/Action.scala | 75 | ||||
-rw-r--r-- | src/scalevalapokalypsi/Model/Adventure.scala | 11 | ||||
-rw-r--r-- | src/scalevalapokalypsi/Model/Entities/Entity.scala | 94 | ||||
-rw-r--r-- | src/scalevalapokalypsi/Model/Entities/Player.scala | 5 | ||||
-rw-r--r-- | src/scalevalapokalypsi/Model/Event.scala | 36 | ||||
-rw-r--r-- | src/scalevalapokalypsi/Model/SingEffects.scala | 8 |
6 files changed, 126 insertions, 103 deletions
diff --git a/src/scalevalapokalypsi/Model/Action.scala b/src/scalevalapokalypsi/Model/Action.scala index 30fbf46..a781ee8 100644 --- a/src/scalevalapokalypsi/Model/Action.scala +++ b/src/scalevalapokalypsi/Model/Action.scala @@ -15,33 +15,25 @@ class Action(input: String): private val verb = commandText.takeWhile( _ != ' ' ) private val modifiers = commandText.drop(verb.length).trim - def takesATurnFor(actor: Player): Boolean = - this.verb match - case "rest" => true - case "go" => actor.location.hasNeighbor(modifiers) - case "get" => actor.location.hasItem(this.modifiers) - case "drop" => actor.canDrop(this.modifiers) - case "say" => false - case "laula" => false - case other => false - /** Causes the given player to take the action represented by this object, - * assuming that the command was understood. Returns a description of what - * happened as a result of the action (such as “You go west.”). The - * description is returned in an `Option` wrapper; if the command was not - * recognized, `None` is returned. + * assuming that the command was understood. Informs the player and the + * entities surrounding it about the result. Returns true if the command + * was understood and possible, false otherwise. * * @param actor the acting player - * @return A textual description of the action, or `None` if the action - * was not recognized. + * @return Boolean indicating whether the action possibly taken takes a + * turn or not. */ - def execute(actor: Player): Option[String] = + def execute(actor: Player): Boolean = val oldLocation = actor.location - val resOption: Option[(String, String)] = this.verb match - case "go" => Some(actor.go(this.modifiers)) - case "rest" => Some(actor.rest()) - case "get" => Some(actor.pickUp(this.modifiers)) - case "say" => + val resOption: Option[(Boolean, Event)] = this.verb match + case "go" => + val result = actor.go(this.modifiers) + result.foreach(r => oldLocation.observeEvent(r)) + 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) @@ -52,10 +44,10 @@ class Action(input: String): val message = modifiers.take(modifiers.length - recipient.length - 4) if maybeTo == to then - recipientEntity.map(actor.sayTo(_, message)) + recipientEntity.map(e => (false, actor.sayTo(e, message))) else - Some(actor.say(modifiers)) - case "drop" => Some(actor.drop(this.modifiers)) + Some((false, actor.say(modifiers))) + case "drop" => Some((false, actor.drop(this.modifiers))) case "laula" => val end = modifiers.takeRight("suohon".length) val start = @@ -64,28 +56,27 @@ class Action(input: String): val targetEntity = actor.location.getEntity(start) targetEntity .foreach(e => actor.setSingEffect(DefaultSingAttack(e))) - targetEntity.foreach(_.observeString(s"${actor.name} laulaa sinua suohon!")) - targetEntity.map(e => ( - "Aloitat suohonlaulun.", - s"${actor.name} aloittaa suohonlaulun." - )) + targetEntity.map(t => + (false, Event( + Map.from(Vector((t, s"${actor.name} laulaa sinua suohon!"))), + s"${actor.name} laulaa henkilöä ${t.name} suohon." + )) + ) else None - case "xyzzy" => Some(( - "The grue tastes yummy.", + case "xyzzy" => Some((false, Event( + Map.from(Vector((actor, "The grue tastes yummy."))), s"${actor.name} tastes some grue.") - ) + )) case other => None - resOption.map(_(1)).filter(_.length > 0) - .foreach(s => - actor.location.getEntities.filter(_ != actor).foreach(_.observeString(s)) - if oldLocation != actor.location then - oldLocation.getEntities.foreach(_.observeString(s)) - ) - - resOption.map(_(0)) - + val res: (Boolean, Event) = resOption + .getOrElse((false, Event( + Map.from(Vector((actor, "Tuo ei ole asia, jonka voit tehdä."))), + "" + ))) + actor.location.observeEvent(res(1)) + res(0) /** Returns a textual description of the action object, for debugging purposes. */ override def toString = s"$verb (modifiers: $modifiers)" diff --git a/src/scalevalapokalypsi/Model/Adventure.scala b/src/scalevalapokalypsi/Model/Adventure.scala index 0fbf6cd..9347bfa 100644 --- a/src/scalevalapokalypsi/Model/Adventure.scala +++ b/src/scalevalapokalypsi/Model/Adventure.scala @@ -66,16 +66,5 @@ class Adventure(val playerNames: Vector[String]): def getEntity[A >: Entity](name: String) = this.players.getOrElse(name, this.entities.get(name)) - /** Returns a message that is to be displayed to the player at the beginning of the game. */ - def welcomeMessage = "Generic welcome message" - - /** Plays a turn by executing the given in-game command, such as “go west”. Returns a textual - * report of what happened, or an error message if the command was unknown. In the latter - * case, no turns elapse. */ - def playTurnOfPlayer(playerName: String, command: String): Option[String] = - val action = Action(command) - val actor = this.players.get(playerName) - actor.flatMap(action.execute(_)) - end Adventure diff --git a/src/scalevalapokalypsi/Model/Entities/Entity.scala b/src/scalevalapokalypsi/Model/Entities/Entity.scala index 26dd7dc..e7cd45c 100644 --- a/src/scalevalapokalypsi/Model/Entities/Entity.scala +++ b/src/scalevalapokalypsi/Model/Entities/Entity.scala @@ -1,8 +1,10 @@ package scalevalapokalypsi.Model.Entities -import scala.collection.mutable.{Buffer,Map} +import scala.collection.mutable.{Buffer, Map} import scalevalapokalypsi.Model.* +import scala.collection.immutable + /** An in-game entity. * @@ -69,46 +71,91 @@ class Entity( * direction name. Returns a description of the result: "You go DIRECTION." * or "You can't go DIRECTION." */ - def go(direction: String): (String, String) = + def go(direction: String): Option[Event] = val destination = this.location.neighbor(direction) + val oldEntities = this.location.getEntities.filter(_ != this) + val newEntities = destination.map(_.getEntities) if destination.isDefined then val removeSuccess = this.currentLocation.removeEntity(this.name) assert(removeSuccess.isDefined) // Production - assertions off this.currentLocation = destination.getOrElse(this.currentLocation) destination.foreach(_.addEntity(this)) - (s"You go $direction.", s"$name goes $direction") - else - ( - s"You can't go $direction.", - s"$name tries to go $direction and stumbles in their feet." - ) - def pickUp(itemName: String): (String, String) = + val leaving = oldEntities.zip( + Vector.fill + (oldEntities.size) + (s"${this.name} leaves this location.") + ) + //val arriving = newEntities.map(n => n.zip( + // Vector.fill + // (n.size) + // (s"${this.name} arrives here.") + //)).getOrElse(Vector()) + val self = Vector((this, s"You go $direction.")) + Some(Event( + (leaving ++ self).toMap, + s"$name arrives here." + )) + else None + + def pickUp(itemName: String): Event = this.currentLocation.removeItem(itemName) match case Some(i) => this.inventory += i.name -> i - (s"You pick up the ${i.name}", s"$name picks up the ${i.name}") - case None => ( - s"There is no $itemName here to pick up.", + Event( + immutable.Map.from(Vector((this, s"You pick up the ${i.name}"))), + s"$name picks up the ${i.name}" + ) + case None => Event( + immutable.Map.from(Vector((this, s"There is no $itemName here to pick up."))), s"${this.name} tries to pick up something but gets just dirt in their hands." ) - def drop(itemName: String): (String, String) = + def drop(itemName: String): Event = this.inventory.remove(itemName) match case Some(item) => this.currentLocation.addItem(item) - (s"You drop the $itemName", s"$name drops the $itemName") - case None => ( - "You don't have that!", + Event( + immutable.Map.from(Vector((this, s"You drop the $itemName"))), + s"$name drops the $itemName" + ) + case None => Event( + immutable.Map.from(Vector((this, "You don't have that!"))), s"$name reaches their backpack to drop $itemName but miserably fails to find it there." ) - def sayTo(entity: Entity, message: String): (String, String) = - entity.observeString(s"${this.name}: \"$message\"") - (s"You say so to ${entity.name}.", "") + def sayTo(entity: Entity, message: String): Event = + if entity == this then this.ponder(message: String) + else + Event( + immutable.Map.from(Vector( + (this, s"Sanot niin henkilölle ${entity.name}."), + (entity, s"${this.name}: “${message}”") + )), + s"Kuulet henkilön ${this.name} sanovan jotain henkilölle ${entity.name}" + ) - def say(message: String): (String, String) = - ("You say that aloud.", s"$name: \"$message\"") + def ponder(message: String): Event = + Event( + immutable.Map.from(Vector( + (this, s"Mietit itseksesi: “$message”") + )), + s"${this.name} näyttää pohtivan jotain itsekseen." + ) + + def say(message: String): Event = + Event( + immutable.Map.from(Vector((this, "Sanot niin ääneen."))), + s"$name: “$message”" + ) + + /** Causes the player to rest for a turn. + * Returns a description of what happened. */ + def rest(): Event = + Event( + immutable.Map.from(Vector((this, "Lepäät hetken."))), + s"${this.name} levähtää." + ) /** Tells whether this entity can drop the specified item * (if an action were to specify so). @@ -118,11 +165,6 @@ class Entity( */ def canDrop(itemName: String): Boolean = this.inventory.contains(itemName) - /** Causes the player to rest for a turn. - * Returns a description of what happened. */ - def rest(): (String, String) = - ("You rest for a while. Better get a move on, though.", "") - /** Returns a brief description of the player’s state, for debugging purposes. */ override def toString = s"${this.name} at ${this.location.name}" diff --git a/src/scalevalapokalypsi/Model/Entities/Player.scala b/src/scalevalapokalypsi/Model/Entities/Player.scala index f231c28..cac5bf1 100644 --- a/src/scalevalapokalypsi/Model/Entities/Player.scala +++ b/src/scalevalapokalypsi/Model/Entities/Player.scala @@ -41,7 +41,7 @@ class Player(name: String, initialLocation: Area) extends Entity(name, initialLo */ def setSingEffect(effect: SingEffect): Unit = this.pendingSingEffect = Some(effect) - + def getSingEffectTarget: Option[Entity] = this.pendingSingEffect.map(_.target) @@ -66,8 +66,7 @@ class Player(name: String, initialLocation: Area) extends Entity(name, initialLo val quality = s"Laulu on ${qualityDescriptions(0)} ja sen vaikutus on ${qualityDescriptions(1)}." val event = res.map(ev => Event( - ev.target, - s"$quality\n${ev.inFirstPerson}", + ev.inFirstPersons.map((k, v) => (k, s"$quality\n$v")), s"$quality\n${ev.inThirdPerson}" )) event.foreach(this.location.observeEvent(_)) diff --git a/src/scalevalapokalypsi/Model/Event.scala b/src/scalevalapokalypsi/Model/Event.scala index cba611d..9055fd8 100644 --- a/src/scalevalapokalypsi/Model/Event.scala +++ b/src/scalevalapokalypsi/Model/Event.scala @@ -1,28 +1,34 @@ package scalevalapokalypsi.Model import scalevalapokalypsi.Model.Entities.Entity +import scala.collection.immutable.Map /** A description of an action. - * - * @param target the entity that was as a target in this event - * @param inFirstPerson textual description of the event in first person - * @param inThirdPerson textual description of the event in third person - */ + * + * @param inFirstPersons a Map of descriptions in first person for entities + * given as keys + * @param inThirdPerson textual description of the event in third person + */ class Event( - val target: Entity, - val inFirstPerson: String, + val inFirstPersons: Map[Entity, String], val inThirdPerson: String ): + // And why are we not just using a map with a default value? + // Wrapping this in an Event creates a more specific abstraction. + // It indicates, that instances of this class are precisely descriptions + // of events, and it allows changing the private implementation without + // touching the public interface. + private val values = inFirstPersons.withDefaultValue(inThirdPerson) + /** Gets the description of this event as seen by the given - * entity. Note that this method does no checks whether the given entity - * could even see the event, only what it would have looked like to them. - * - * @param entity the entity whose perspective to use - * @return a textual description of the event - */ + * entity. Note that this method does no checks whether the given entity + * could even see the event, only what it would have looked like to them. + * + * @param entity the entity whose perspective to use + * @return a textual description of the event + */ def descriptionFor(entity: Entity): String = - if entity == target then inFirstPerson - else inThirdPerson + this.values.apply(entity) end Event
\ No newline at end of file diff --git a/src/scalevalapokalypsi/Model/SingEffects.scala b/src/scalevalapokalypsi/Model/SingEffects.scala index 6702df5..42f5188 100644 --- a/src/scalevalapokalypsi/Model/SingEffects.scala +++ b/src/scalevalapokalypsi/Model/SingEffects.scala @@ -1,11 +1,7 @@ package scalevalapokalypsi.Model import scalevalapokalypsi.Model.Entities.Entity - -def defaultSingAttack(targetEntity: Entity)(singQuality: Float): Event = - targetEntity.takeDamage((singQuality * 30).toInt) - val condition = targetEntity.condition - Event(targetEntity, condition(0), condition(1)) +import scala.collection.immutable.Map trait SingEffect(val target: Entity): def apply(singQuality: Float): Event @@ -14,4 +10,4 @@ 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(target, condition(0), condition(1)) + Event(Map.from(Vector((target, condition(0)))), condition(1)) |