Previously, I recounted day 1 of my adventure game project. Let’s continue.
Day 2
This was a short day.
First I observe that it isn’t really necessary for the caller of Game
to pass the MessageBus
, so I change that. Commit. Then I determine that the “App” project is really better described as a “Sample” use case and change the names accordingly. Commit.
Day 3
Every text adventure game needs to interpret user input so I get to work building the SentenceParser
. I start with a simple test based around the existing InputMessage
protocol already established by the TextConsole
. I quickly decide that the output SentenceMessage
should split the input into a single verb and noun. Since the parser subscribes to input messages on construction, I make sure it is a good citizen by implementing the Dispose pattern to unsubscribe. Finally, I incorporate the code I have so far into the sample Game
.
Day 4
So far the Game
can accept a very small set of commands. Unfortunately, “quit” isn’t one of them, so there is no way to exit. I change this by first accepting a CancellationToken
in TextConsole
. Then I make it actually respond to cancellation requests. After a bit of refactoring, I add some Game
code to handle unknown verbs. As a last step, I add the “quit” command, thus completing the day’s work.
Day 5
Today is all about Words
. So far the game only knows how to handle exact matches for verbs and ignores nouns. Let’s start implementing the concept of a synonym using a Word
class to track the “actual” word entered by the user as well as the “primary” word that we match it against. The next test shows this in action by accepting “see,” “inspect,” or “examine” as synonyms for the primary verb “look.” Several test and implementation cycles later, I am ready to incorporate a few words into the game — “greet” (“hello,” “hi”) and “quit” (“exit”). Since I’m on a roll, I add “take” (“get”) to the game as well. I end the day by extracting the primary words into a Verb class that I later promote to the top level.
Day 6
An adventure game with no rooms to explore is hardly a game at all. Thus I begin building an abstract Room
class with methods to Enter and Register verbs. My plan with this is to activate the behavior of the room (i.e. allow it to respond to commands, etc.) in Enter()
and unregister/unsubscribe everything in Leave()
which I implement next. Note that I am explicitly not using the Dispose pattern here because Dispose implies destruction; rather, I want rooms to live on even after the player has left since reentry is always possible, hence the Enter/Leave semantics. With enough of the behavior fleshed out, I add a single MainRoom
to the sample game where I move all the verb handling logic so far.
Day 7
Looking at the code today, I don’t like how the TextConsole
does double duty as a console processor and an input loop. I begin to extract the loop behavior into InputLoop
. To avoid major rework of the code I add an intermediary NewLoop()
factory method to TextConsole
.
Next, I look at MainRoom
and decide that it doesn’t make a lot of sense for a specific room instance to handle a global message like “quit.” As I start to extract this behavior, I realize that there is a flaw. My initial approach involves subscribing to the SentenceMessage
in the Game’s Run()
method to respond directly to the “quit” verb. Unfortunately, MainRoom
is also subscribed so I end up with two responses — the real quit behavior, followed by the unknown verb behavior of Room (“I don’t know what ‘quit’ means”). I don’t commit anything because the code doesn’t yet work.
Day 8
I am determined to solve the previous day’s problem and I have a plan. Instead of only allowing void
(Action
) methods for MessageBus
subscribers, I add support for bool
return values. In the spirit of not breaking things, this support does not replace the existing code but rather allows a second type of subscriber. Eventually I make it so that returning ‘true’ stops subsequent subscribers from processing. Now I can actually implement the “quit” feature from yesterday by having the quit handler return true
if it detects the “quit” verb and false
otherwise, allowing the room to process everything else. Finally, I extract this pattern into QuitHandler
and use it in the Game.
At this point I have a game with a single room that understands a few commands and allows you to quit. It’s not much but it’s something!
Pingback: Building an adventure game: part 3 – WriteAsync .NET