UPDATE: there’s now a beautifully-illustrated cheat-sheet for our text-adventure!
Text Adventures are excellent mental exercise to write! They are useful for learning to
program; they are equally useful for seasoned programmers to refresh and recuperate on! They can fit
anywhere on a sliding scale between straightforward and high-end CS-research, and the programmer can
slide their game anywhere up and down that scale at whim. And they are fun :)
Text
Adventures are out of fashion though. This Ludum
Dare we decided to resurrect the Text Adventure and this is a description of the approach we
took.
So after tiring of this blog post please do play our illustrated Text
Adventure The
Small World of Professor Strange - we hope you enjoy playing it as
much as we enjoyed making it!
Before the competition started we
set our hearts on doing a Text Adventure. It was just to work out how to bend a story to fit whatever
the Ludum Dare theme was. But we hadn’t made - or played - Text Adventures in 20 years.
Couldn’t even remember how to make them. So we started from first principles.
The
proper way to make a Text Adventure is to use the INFORM
7 engine. You just describe your world in a DSL shockingly like natural language and the
clever engine does the rest:
A thing has some text called scent. The scent of a thing is usually "nothing".
The block smelling rule is not listed in any rulebook.
Carry out smelling something:
say "From [the noun] you smell [scent of the noun]."
Instead of smelling a room:
if a scented thing can be touched by the player, say "You smell [the list of scented things which can be touched by the player].";
otherwise say "The place is blissfully odorless." [via]
Where is the fun in that?! I’m going to explore the do-it-yourself approach:
We
wanted to use Javascript so that the game could run in your browser.
Most Ludum Dare entries are Windows binaries, and that’s such a turn-off and disincentive to trying to actually play those games. There are other ways to run in the browser; we’d done a Chrome “native” app for Ludum Dare previously, and there’s other plug-in -based alternatives like Flash, Java applets and Unity’s browser plug-in. But these plug-ins are still a massive barrier, and there’s always a lot of rough edges. We felt that Javascript was the absolute lowest hurdle for players, and in the case of a Text Adventure its no barrier to development. So go make your Text Adventures in Javascript and let everyone play them!
A great free hosting solution is to use github
pages; we
did.
A direct technical benefit of using Javascript is that this means
that the entire game data can be something like JSON but neater. Javascript object declarations are
super neat; unlike JSON the keys do not need to be quoted string literals and the values can be complex
objects such as anonymous functions.
I’m inexperienced in Javascript -
this Text Adventure being my first Javascript app - but Javascript’s object declarations are
something as a Python fan I’m jealous of! Lets compare:
In Javascript,
you can do this:
var locations = {
kitchen:{
name:"The Kitchen",
description:"Your kitchen is untidy and cramped",
visits: 0,
on_enter: function() {
if(!locations.kitchen.visits++) {
.... // first time in this room
};
},
commands: [...],
objects:[...],
},
bedroom:{
},
...
};
Now in Python you can’t put that kitchen’s on_enter function in a simple
dictionary like that; you’d have to declare it with some name, and then reference it by name.
But
to put the methods in-line in Python, you can’t turn to anonymous functions because Python
doesn’t have them. You can’t use dot notation to dereference the fields inside the
structure because Python uses [“key”] notation. Its just clunkier; much clunkier,
sadly. Coming from a Python fan, this is a bitter pill. Javascript is actually more concise and clear
for the Text Adventure data definition problem.
(You can use lambdas but only in the most limited way and you can make a class hierarchy and avoid instansiation via @classmethods, but that’s hardly much consolation!)
The whole way you can declare instances rather than types you instantiate and the duality of
dicts and classes in Javascript is super cool and concise and useful; this is an example of where it
shines.
After waxing lyrical about Javascript’s object declaration syntax
and anonymous functions, I will slap it for letting empty dictionaries and lists evaluate to true. I
lost hours learning that the hard way in the debugger.
Back to the Text
Adventure problem!
A Text Adventure is made from two key types of things
- locations and objects. Your text adventure
may have more; ours has a third, characters (“non-playing
characters”, NPCs).
A very straightforward way as we saw is to just have
a variable called locations, one called objects and perhaps a third called npcs or whatever.
In
a location you may have a list of objects, or in an object you may have its location. You might get a
cart-before-horse situation where you can’t use dot notation because the thing being
referenced hasn’t been declared yet. You can trivially fall back to quoting their names and
resolving them at runtime with array access. In our locations we have an objects array that is a list
of strings that are the names of the objects in that location.
Locations have
commands which are typically typically navigation-related. Objects have commands which are typically
take, drop and use -related.
Its also useful to have triggers - we found we could
all the overarching plot scripting just using a single function called each time a room is entered - the
“on_enter” function in the kitchen above, for example. You might want richer
trigger points.
In the game loop itself you have a variable that tracks the current
location. The commands available to the player at any point are then the union of those commands
attached the location, those attached to each object in that location and those commands attached to the
objects in your inventory.
It is super-useful to have a function that checks a condition optionally on a command; for
example, you can only have a ‘slide panel’ command if the panel has been examined,
so you’d add a boolean to track that from the panel’s view command, and have a
condition on the slide panel command something like function() { return
objects.dining_room_panel.examined; }
.
(Here, Python’s any
and all
functions would trump Javascript, so its swings and roundabouts on the conciseness front.)
Here’s our game data file: http://raw.github.com/williame/ludum_dare_23_tiny_world/gh-pages/game_data.js
Parsing
commands is really where the Text Adventure complexity slider lies. Before the contest I
asked for links on this; ideal off-line reading. The INFORM engine
understands enough natural language processing to make sense of long, complex commands that combine
multiple objects with multiple outcomes. We opted for the other end of the scale: literally listing the
legal commands such as “go east”, “east” and “exit
east” all being linked to the function go_to(“bedroom”).
The
intermediate step is to allow richer variations efficiently; I imagined a system of denoting optional
words in brackets e.g. “[go|exit] east” and “[take|pick up] [the]
[cooking] pot”. This is still defined command strings created explicitly by the game maker,
but efficiently describes the multitude of legal aliases.
I hope this gives you the
encouragement to go make your own Text Adventure!
Presentation is where our own
game gets interesting. It has several modes, notably safe-for-work and illustrated.
The
programmer pretending to work UI just draws an Eclipse frame around the Text Adventure pane. We had
imagined some neat adaptations but we run out of time; they were: command auto-completion drop-down like
in an IDE, indenting location descriptions and such so from a distant squint it looks more code-shaped,
and injecting faint punctuation and camelCase to make it interesting.
Our
illustrated UI is gorgeous. Absolutely fabulously awesome. But its also
innovative - rather than stills for each location, locations are plotted on a very prettily-drawn map.
This gives you excellent spatial awareness and raises the game into a whole new
plane.
We didn’t tackle saving, but these days browser local storage
seems viable; as would simply using the new Javascript calls to trigger the user to receive their
game-state as a JSON blog the browser prompts them to save; they can then restore by drag-dropping that
file onto the game or using a file picker dialog.
So what are you waiting for? You’ve got a game to make; but first why not my mine?