I have written before about my early experiences with text adventure games and about how I built a GW-BASIC translator to convert one such example game to C#. It was a fun challenge but was admittedly more of a compiler project than a game project. This time, I want to build a .NET Core adventure game library from the ground up.
I have no specific reason for doing this other than “personal enrichment.” What I hope in the end is to have a relatively maintainable codebase using object-oriented principles. I feel that the text adventure format provides a reasonable playground for exploration of design ideas with relatively few yak shaving detours.
These blog posts will document the process, based on notes that I’m taking on each day that I make changes. I do not have a strict schedule or deadline. On some days I might spend an hour building features; on other days, I might spend five minutes. Typically I’ll start with an observation about the current state of the program which spurs a set of improvements or additions.
Enough exposition — let’s move on to the content.
I’m starting from a blank slate. I know I’ll need at least four things to start with:
- a library project (“Core”)
- a test project for the library (“Core.Test”)
- a console app using the library (“App”)
- a test project for the app (“App.Test”)
To get started on the right foot, I enable StyleCop Analyzers for each project via an MSBuild Directory.Build.props file. In that props file I also customize the output path so that all projects use a common path — I like a single
bin folder! I verify everything works by adding one simple xUnit.net test to each project. It’s all green. Commit.
So that was all just my “tracer bullet” skeleton if you will. I need to start making this fit the mold that I’ll eventually have with a text-oriented adventure game. There is a concept of an adventure game walkthrough which tells you the commands to use to solve a game (example for King’s Quest III). I’ll borrow this concept and incorporate it into a golden master-style test. I need to change the basic App.Test to read an expected input file
walkthrough.in and produce an actual output file
walkthrough.actual.out and finally compare it to the expected output file
walkthrough.out. The app is so simple at this early stage that this is not hard to do. For testability, I need to design my
Game class to work with
TextWriter rather than depending directly on
Console streams. This enables the walkthrough approach to work as a unit test (you could call it “headless testing”). Commit.
Just a little while back I wrote about a message bus. I figure this will be a great core abstraction for building my adventure game. Thinking ahead a bit, I know I’m going to need components that will have some degree of interaction (e.g. taking an
Item from a
Room into the player’s
Inventory) but I still want loose coupling. The message bus will allow me to use an event-driven approach based on shared messages between classes, as opposed to having the classes all know about each other directly which could very easily become messy. At least, that is my current thinking — I suppose we’ll see later if I’m right. I build my
MessageBus in true TDD style, one test at a time. The first commit brings it into existence with its first test. I continue until the last commit for now which cleans up the subscriber
Dispose pattern (unlike in my blog post, unsubscribe is supported by this message bus via
The final order of the day is to extract a
TextConsole object from the game and into the library. I start with the first commit to form the basic shape and end with the final commit which deletes the custom console logic in the game in favor of the new library abstraction. Note the use of the
OutputMessage via the
MessageBus to which the
TextConsole subscribes. This is an example of the loose coupling I am after; any component, no matter where it is in the system, can display text output by sending a message. (This message-passing approach happens to be similar in spirit to what Sierra did with SCI, their adventure game engine.)
Overall, it was a rather productive day. The game does very little but it has a solid foundation for extension. We’ll resume with day 2 and beyond next time.