So I’m, in a rather desultory fashion, putting together a simple text adventure in the Clojure programming language. (You can find the code at GitHub, should you be interested.)
The first thing you need in a text adventure game is a world to move around in. The world usually consists of a collection of rooms. Each room can have any number of links to other rooms; each link is in a particular direction. Directions include the four cardinal directions (north, south, east, and west) and their quarters (northeast, northwest, southeast, southwest); up and down, and sometimes in and out. Mathematically speaking, what we have is a directed graph, with rooms for the nodes and links between rooms as the edges.
A room has quite a bit of data associated with it. A room will have at least the following:
- A name
- A detailed description (which might be partially or completed computed)
- The links to other rooms (ditto)
In addition, a room is usually a container for objects. It may contain some objects as the game starts, and the player can usually drop objects and leave them behind.
In Clojure, it’s common to represent objects like rooms as maps. A map is a table of keys and values: given the key, you can retrieve the value. (It’s called a “map” because it’s a mapping from keys to values.) The keys and values can be any kind of Clojure data value (including quite complicated ones), but when you use a map to represent an object the keys are usually Clojure keyword symbols.
Here’s an example of a room object:
{:name "Home Base" :description "Your apartment. Looks kind of scruffy." :links {:out :street :south :bedroom :east :kitchen}}
The words beginning a colon (e.g., :name
) are keyword symbols. I’ve specified the room’s name, description and links, all in one concise data value.
Note that the value associated with the key :links
is another map: a map from directions to room IDs.
Room IDs? Where did that come from? What is this room ID of which you speak?
In a language like C or Java or Tcl it’s common to keep object records in a collection of some kind, and look them up as needed given an ID. In C it would often be a pointer; in Tcl it would be a string. In Clojure, you might use a keyword; and that’s what I’m doing. I’m going to assign each room a unique ID, a keyword, and use that to refer to the room. When I want to pass a room to a function, I’ll pass the keyword and let the function look up the room’s data.
In short, I’ll quite literally have a map of rooms:
(def rooms { :home {...} :bedroom {...} :kitchen {...} :street {...}})
This is somewhat perverse in Clojure code; I understand that the done thing is to pass the room’s full map to functions, and even use it as a key in other maps. I’m not used to thinking that way, but I didn’t choose this
structure just because it’s familiar.
The problem with the map of rooms is that it’s a cyclic graph. That means that if you want to define the links between rooms as you define the rooms, you need to be able to link to rooms that haven’t been defined yet…and that means using some kind of indirection.
To put it another way: when I define the “Home Base” room, I haven’t yet defined the :street
room; but I know that its ID is going to be :street
so I’ll use that in the link. I’ll get around to defining the details of the :street
room later on in the code.