aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoel Kronqvist <joel.kronqvist@iki.fi>2024-11-17 02:38:55 +0200
committerJoel Kronqvist <joel.kronqvist@iki.fi>2024-11-17 02:38:55 +0200
commit8595e892abc0e0554f589ed2eb88c351a347fbd4 (patch)
treed86a85243be6719f30646094e7a86e5d3d87aa68
parente0e720c1b78506f1f9c00e2d275caa170becc927 (diff)
downloadscalevalapokalypsi-8595e892abc0e0554f589ed2eb88c351a347fbd4.tar.gz
scalevalapokalypsi-8595e892abc0e0554f589ed2eb88c351a347fbd4.zip
Implemented talking
-rw-r--r--src/main/scala/Model/Action.scala19
-rw-r--r--src/main/scala/Model/Adventure.scala7
-rw-r--r--src/main/scala/Model/Area.scala10
-rw-r--r--src/main/scala/Model/Entity.scala10
-rw-r--r--src/main/scala/Server/Client.scala5
-rw-r--r--src/main/scala/Server/Server.scala6
6 files changed, 42 insertions, 15 deletions
diff --git a/src/main/scala/Model/Action.scala b/src/main/scala/Model/Action.scala
index 9f81256..55f7f27 100644
--- a/src/main/scala/Model/Action.scala
+++ b/src/main/scala/Model/Action.scala
@@ -10,24 +10,39 @@ class Action(input: String):
private val verb = commandText.takeWhile( _ != ' ' )
private val modifiers = commandText.drop(verb.length).trim
- def takesATurnFor(actor: Entity): Boolean =
+ 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 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. */
- def execute(actor: Entity): Option[String] =
+ def execute(actor: Player): Option[String] =
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 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
+ )
+ val message =
+ modifiers.take(modifiers.length - recipient.length - 4)
+ if maybeTo == to then
+ recipientEntity.map(actor.sayTo(_, message))
+ else
+ Some(actor.say(modifiers))
case "drop" => Some(actor.drop(this.modifiers))
case "xyzzy" => Some((
"The grue tastes yummy.",
diff --git a/src/main/scala/Model/Adventure.scala b/src/main/scala/Model/Adventure.scala
index 7d5a061..dfcb100 100644
--- a/src/main/scala/Model/Adventure.scala
+++ b/src/main/scala/Model/Adventure.scala
@@ -1,6 +1,7 @@
package o1game.Model
import scala.collection.mutable.Map
+
/** The class `Adventure` represents text adventure games. An adventure consists of a player and
* a number of areas that make up the game world. It provides methods for playing the game one
* turn at a time and for checking the state of the game.
@@ -38,6 +39,9 @@ class Adventure(val playerNames: Vector[String]):
playerNames.foreach(this.addPlayer(_))
val entities: Map[String, Entity] = Map()
+ private val gruu = Entity("Gruu", northForest)
+ northForest.addEntity(gruu)
+ this.entities += gruu.name -> gruu
/** Adds a player entity with the specified name to the game.
*
@@ -57,6 +61,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))
+
/** Returns a message that is to be displayed to the player at the beginning of the game. */
def welcomeMessage = "Generic welcome message"
diff --git a/src/main/scala/Model/Area.scala b/src/main/scala/Model/Area.scala
index 5a8de2a..6721957 100644
--- a/src/main/scala/Model/Area.scala
+++ b/src/main/scala/Model/Area.scala
@@ -22,7 +22,8 @@ class Area(val name: String, var description: String):
def getNeighborNames: Iterable[String] = this.neighbors.keys
def getItemNames: Iterable[String] = this.items.keys
- def getEntityNames: Iterable[String] = this.entities.keys
+ def getEntityNames: Iterable[String] = this.entities.values.map(_.name)
+ def getEntity(name: String): Option[Entity] = this.entities.get(name)
def getEntities: Iterable[Entity] = this.entities.values
/** Tells whether this area has a neighbor in the given direction.
@@ -73,7 +74,8 @@ class Area(val name: String, var description: String):
*
* @param entity the entity to add.
*/
- def addEntity(entity: Entity): Unit = this.entities += entity.name -> entity
+ def addEntity(entity: Entity): Unit =
+ this.entities += entity.name.toLowerCase -> entity
/** Removes the entity with the name `entityName`.
*
@@ -81,7 +83,7 @@ class Area(val name: String, var description: String):
* @return an option containing the removed entity if it was in the area
*/
def removeEntity(entityName: String): Option[Entity] =
- this.entities.remove(entityName)
+ 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
@@ -92,7 +94,7 @@ class Area(val name: String, var description: String):
def fullDescription: String =
val exitList = this.neighbors.keys.mkString(" ")
val itemList = this.items.keys.mkString(" ")
- val entityList = this.entities.keys.mkString(" ")
+ val entityList = this.getEntityNames.mkString(" ")
val itemDescription =
if this.items.nonEmpty then
s"\nYou see here: ${itemList}"
diff --git a/src/main/scala/Model/Entity.scala b/src/main/scala/Model/Entity.scala
index 37fdbc2..d8e8559 100644
--- a/src/main/scala/Model/Entity.scala
+++ b/src/main/scala/Model/Entity.scala
@@ -52,7 +52,8 @@ class Entity(val name: String, initialLocation: Area):
def go(direction: String): (String, String) =
val destination = this.location.neighbor(direction)
if destination.isDefined then
- this.currentLocation.removeEntity(this.name)
+ 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")
@@ -76,6 +77,13 @@ class Entity(val name: String, initialLocation: Area):
(s"You drop the $itemName", s"$name drops the $itemName")
case None => ("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.observe(s"Alice: \"$message\"")
+ (s"You say so to ${entity.name}.", "")
+
+ def say(message: String): (String, String) =
+ ("You say that aloud.", s"$name: \"$message\"")
+
/** Tells whether this entity can drop the specified item
* (if an action were to specify so).
*
diff --git a/src/main/scala/Server/Client.scala b/src/main/scala/Server/Client.scala
index 7c7a786..3cd2b36 100644
--- a/src/main/scala/Server/Client.scala
+++ b/src/main/scala/Server/Client.scala
@@ -100,11 +100,6 @@ class Client(val socket: Socket):
if nextCRLF != -1 then
val message = this.incompleteMessage.take(nextCRLF)
val rest = this.incompleteMessage.drop(nextCRLF + 2)
- assert(rest.headOption != Some(CRLF(1))) // I will compile this with
- // assertions off... I'd
- // like to know how to make
- // tests work with this
- // config...
this.incompleteMessage = rest ++ Array.fill(nextCRLF + 1)(0.toByte)
// TODO: the conversion may probably be exploited to crash the server
Some(String(message))
diff --git a/src/main/scala/Server/Server.scala b/src/main/scala/Server/Server.scala
index 4a984a5..7864c49 100644
--- a/src/main/scala/Server/Server.scala
+++ b/src/main/scala/Server/Server.scala
@@ -160,14 +160,14 @@ class Server(
private def writeToAll(message: String): Unit =
this.clients.mapAndRemove(c =>
val output = c.socket.getOutputStream
- output.write(message.toVector.map(_.toByte).toArray)
+ output.write(stringToByteArray(message))
output.flush()
)
private def writeToClient(message: String, client: Client): Unit =
try {
val output = client.socket.getOutputStream
- output.write(message.toVector.map(_.toByte).toArray)
+ output.write(stringToByteArray(message))
output.flush()
} catch {
case e: IOException => client.failedProtocol()
@@ -178,7 +178,7 @@ class Server(
this.clients.mapAndRemove(c =>
val output = c.socket.getOutputStream
val data = c.dataToThisClient()
- output.write(data.toVector.map(_.toByte).toArray)
+ output.write(stringToByteArray(data))
output.flush()
)