An Adventure!

In my last post, I talked some about the need to look across projects and find common elements that could be factored out. I'd like to start a series of posts in which I talk about some of the work I'm doing at MITH in developing some foundational libraries that we are using to build digital humanities projects. Along the way, I'll discuss some of the philosophy behind those libraries and our approach to the projects.

Today, I want to walk through the design of an example application I'm working on that implements the classic Adventure game as a JavaScript web application. I'm not finished with it yet, but the framework is there. I'm just adding content and tweaking some behavior now, such as handling darkness. You can go into the hut, pick up the key, and then go down and open the grate to get into the cave.

If you load up the game (it's been tested in Safari, Firefox, and Chrome), you'll notice that there are several areas displaying information about the state of the game. There's a large text area describing your location and giving you a light narrative based on your actions. There's an area for your score, though we're not tracking scores yet. There's a spot for your inventory, and there's a crude compass showing which directions you can go. Below the narrative is a text input for your commands. The page isn't beautiful, but it works for our purposes at the moment.

Let's take a look at how we might implement this game using a naive approach. We'll assume that the user interface is the primary driver for the programming, so we'll focus on making sure that all the pieces get updated at the right times with the right information.

At the heart of the game is the command parser. Since nothing happens without the player typing something, we can make everything else happen as a result of parsing the command. When we move the player to a new environment, we will need to add some code that describes where the player is. When the player picks up something, we'll need to add code to update the inventory list. Likewise when the player drops something. If we update the player's score, we'll need to add code to update the score display.

A reasonable pseudo-code loop might look like the following:

Of course, for this type of process to work, we have to have some "local globals" that track things like player inventory, score, environment, and what else is around. We could wrap those in another function to provide scoping in JavaScript and share them with all the functions implementing the game.

One step more would be to have a state object that holds all the game state. The functions could pass this object around. This gets rid of the globals, but it doesn't change the global nature of the data. There's only one adventure game on the page. There's only one game object. But that's not that big of a problem.

The problem is that it's difficult to add to the game interface. If someone wanted to make another box that displayed the brief descriptions of the rooms next to the player, they'd have to muck around in the commandCycle() function to add their own handler. They'd have to make sure the game object had a way of discovering information about rooms other than the one the player was in.

Another approach is to focus on the data and have the user interface react to changes in the data. This leads naturally to an event-oriented design instead of a predetermined looped sequence. The results are much the same, but the program is more flexible.

Here, we have a data store that manages all the information about the game. This doesn't have to be different from the game object above. Instead of having interfaces that allow us to query the data, we can have interfaces that allow us to register handlers that get called when data changes. More specifically, we can tell it what data we're watching for different handlers. Now, if the score changes, the updateScore() handler gets called and it updates the display. If the player's environment changes, the displayNewEnvironment() handler gets called. And so forth.

This might not seem like much of an improvement, but the result is that we don't need anything in the commandCycle() function other than the parseCommand() call. Everything else is a consequence of the data manipulations that happen as commands run, and none of the commands need to know how their data manipulations affect the display.

Now we can easily add a new UI element that displays the rooms around the player. We just hook in a listener for changes in the player's environment. (This is left as an exercise for the reader.)

We can go one step more with the data store. Instead of being a game object, we can make it a general purpose data store to which we connect data views configured to alert us when certain slices of the game data change. A general purpose data store also allows us to add new data that wasn't considered when we first built the game. We can add more than just new views of existing data. We can add new views of new data and expand the game without touching anything in the original game, or even without knowledge of what the original game does, though we'd want to know what data was there so we didn't stomp on it.

If we make these other pieces configurable as to what properties they expect in the data store, then we can build plugins that we can share across applications.

The Adventure game is still under construction. I need to add many more rooms and specific actions. What's there now, though, illustrates what is possible with this data-centric, event-driven approach. As soon as we get licensing squared away and I finish the documentation, I'll post links to the source code and dive into it a bit. In a future post, I'll talk about how I break the program down further beyond encapsulating the data.