Thursday, April 26, 2012

How to make a Text Adventure

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?

Notes

  1. easyongo reblogged this from williamedwardscoder
  2. internetmarketingimris reblogged this from williamedwardscoder
  3. psdtohtmlshop reblogged this from williamedwardscoder
  4. scych reblogged this from williamedwardscoder
  5. c0d3 said: Back in the late 70’s when I was learning to code, one of the very first things I ever wrote was a minimal parser (verb/subject,object or rather “Look Room” style) and a small 10 room adventure game. It was that experience that got me hooked.
  6. williamedwardscoder posted this

 ↓ click the "share" button below!