Previously, I recounted day 1 of my adventure game project. Let’s continue.
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.
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
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
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.
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.
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.
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
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.
I am determined to solve the previous day’s problem and I have a plan. Instead of only allowing
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!