This will be a video let’s play with two players. One of us has played Zork but not Colossal Cave; the other has played Colossal Cave but not Zork. We’re going to take turns (alternating between “Z” episodes and “A” episodes) and guide each other through our respective games. After finishing both games, we’ll take a tour through their source code.
The versions we’ll be playing are revision 119 of Zork I and Donald Knuth’s literate programming port of 350-point Adventure (a.k.a. KNUT0350). If we had known better, we might have started with the more common revision 88 of Zork, but revision 119 leads to some funny moments, as you’ll see. The KNUT0350 version of Adventure is personally significant, for a reason that will be revealed.
We’ll spoiler-tag the written recap of each episode, for those who don’t want to know what’s going to happen in advance. But otherwise, you may consider this thread a place where people either know the games or are willing to have them revealed. Feel free to post comments and speculation without worrying too much about spoiler-tagging things.
In this episode, we started by exploring the forest, finding a grating and a tall tree with a nest and a jeweled egg. (By some mischance, the egg got broken, revealing a clockwork canary inside.) We got into the house and discovered a trophy case and a hidden trap door. We found—to the player character’s peril—that “throw” has an assumed indirect object of “you” (in this revision 119 of Zork I, at least). Died three times to errant throws, and had to start over. Went up into the attic, and got eaten by a grue (as an experiment). Every time we die, our belongings get scattered over the overworld. Went to the canyon, found a rainbow but nothing to interact with, and came back. At last, descended the trap door…
Personal best: 35/350 points, rank of Amateur Adventurer.
At about 0:02:57 in episode Z1 there is the question “am I a TAKE gamer or am I a GET gamer?” We didn’t know it at the time, but there is a poll on the forum for exactly that question:
Yeah, those final unreleased versions do have some oddities, some of them probably caused by trying to update and unify the standard parser and verbs. The “throw” command is defined like this:
So if you don’t specify what you want to throw an object at, it will look for anything with ACTORBIT. That’s apparently handled by the GWIM (“Get What I Mean”) routine. In the r88 source code, it never uses the ME object:
I don’t remember off-hand if r119 has any game-breaking bugs, but you can probably expect a few surprises at least. (The one with the most noticeable changes is probably Zork III, where some of the text has been changed a bit.)
This was fun. I’m looking forward to the “A” episodes. I’m glad you’re using the Knuth port, as it’s more authentic to the original experience than many of the others.
But make sure you’ve got a recent-ish build to ensure you have all the bug fixes. The advent.w source that your links lead to appears to be up to date, but the nicely typeset code on Knuth’s site still has some of the bugs that have since been fixed. (And even the errata for the latest printing of the book doesn’t mention one of the fixes that was made to the sources.)
Anyway, Knuth’s is one of the more authentic ports. I say this begrudgingly. The other widely distributed ports made most of the same changes that Knuth did (dropping cave hours, using lowercase text, adding an input prompt), but they also tend to break or omit navigation features.
Then again, Knuth didn’t port the save feature (which actually wasn’t much of a feature of the program but of the operating environment), so good luck completing the game.
We explored the forest in the overworld and entered the cave via the grate. Tempting fate by exploring without a light, we fell in a pit and died. Luckily, there is a reincarnation feature involving orange smoke; unluckily, it took us back outside the grate, which we had already re-locked…
On the next attempt, we discovered the magic word “XYZZY”, which at this point is understood as a form of extradimensional travel. We tested whether the fissure is jumpable (it’s not). We found a cheerful little bird, but for some reason couldn’t catch it in the cage. We got attacked a few times by dwarves, and managed to defeat them by throwing an axe. We encountered a huge green fierce snake in the Hall of the Mountain King and couldn’t find a way to get by it. Found a black rod whose purpose is unknown.
Personal best: 45/350 points, novice class adventurer.
We started to explore the underground which we had just entered at the end of Z1. We found a chasm—it’s no more jumpable than the fissure in Adventure. We discovered some capacity limits, both in what the player can carry and in what containers (like the sack) can hold. Going south from the Cellar, we passed through the Gallery and the Studio, discovering (and destroying) a valuable painting on the way. The chimney in the studio is a way out of the underground, possibly the way we’re meant to take treasure out to put it in the case, but it allows you to take up only a few items at a time.
North of the cellar is the Troll Room. After exploring non-violent options, we eventually killed the troll with the sword. The experience of combat shows that the player character can get injured, and that injury reduces carrying capacity. After being killed once by the troll, we made the informal decision not to use the save/restore feature, and instead to play in a permadeath style, like Adventure.
Going east from the Troll Room took us to the Round Room. Choosing the SE exit at random, we toured through the Engravings Cave, Dome Room, Torch Room, Temple, and Egyptian Room, where there is a coffin and a sceptre. “XYZZY” is not a magic word in this game… but “hello sailor” might be! Leaving the temple, we found a mysterious mirror but were unable to do anything useful with it. We finished the episode at the Entrance to Hades!
New personal best: 57/350 points, rank of Novice Adventurer.
If you’re curious why “take rock” moved the player south, read on.
The parser splits the input into words, W1 and W2.
The type of W1 determines what happens next. The relevant possibilities are motion, object, or action.
“TAKE” is an action word, so it sets the verb.
W1 is set to W2, and W2 is cleared, and it loops around.
Now W1 is “ROCK”, which you’d probably expect to be an object, but it’s actually a motion word.
A motion word, by itself, is a complete command, so the “TAKE” is ignored, and it attempts to execute the “ROCK” motion.
Executing the motion means looking for an entry that matches the current location (the 2-inch slit) and the motion word (“ROCK”). Sure enough, there is such an entry and it simply causes the player to move to the depression (where the grate is).
It’s very simplistic, but that simplicity let’s it handle a lot of common idioms at the cost of a sometimes surprising interpretation of some less common idioms.
But why is “ROCK” a motion rather than an object?
Adventure supports several types of navigation including, in many parts of the map, navigating by landmark. The description for the 2-inch slit location mentions that there’s bare rock downstream. By treating “ROCK” as a motion word, the player could quite naturally type “GO ROCK” or “GO DOWNSTREAM”. Since there’s no manipulatable rock object in the game, there’s no real point to treat it as anything other than a motion command.
Many players never realize that navigating by nearby landmarks is possible not only above ground but also in many places even in the cave. Some of the ports reinforce that by not implementing only the compass points (and up/down) for travel within the cave. Fortunately, the Knuth port is more faithful to the original in that regard.
In the CWEB version, this part of the parsing logic is in Sections 76–78. (As @aidtopiasaid, the PDF on literateprogramming.com is out of date with respect to the current version of advent.w, but for these sections it’s close enough. Beware of major spoilers if you start reading other sections—we’ll be going through this source code in detail in a future episode.)
Our main task in the simulation loop is to parse your input. Depending on the kind of command you give, the following section of the program will exit in one of four ways:
gototry_move with mot set to a desired motion.
gototransitive with verb set to a desired action and obj set to the object of that motion.
gotointransitive with verb set to a desired action and obj = NOTHING; no object has been specified.
gotospeakit with hash_table[k].meaning the index of a message for a vocabulary word of message_type.
Sometimes we have to ask you to complete an ambiguous command before we know both a verb and its object. In most cases the words can be in either order; for example, takerod is equivalent to rodtake. A motion word overrides a previously given action or object.
So we parse one word, and based on that word set certain context variables (mot, verb, obj). Then we move word2 into word1 (the shift label in Section 76) and parse the second word by itself. Usually, the parsing of the second word combines with the already set mot/verb/obj to form a complete command. But when the second word forms a complete command by itself, the mot/verb/obj context is ignored. The program calls out a particular case of this: “A motion word overrides a previously given action or object.”
An example in HELP relies on this mechanism:
Some objects also imply verbs; in particular, “inventory” implies “take inventory”, which causes me to give you a list of what you’re carrying.
TAKE is a transitive action word (needs an object) and INVENTORY is an intransitive action word (doesn’t have an object). The program parses TAKE and sets verb = TAKE, but then the second word INVENTORY is treated as a complete command in itself, ignoring the already set verb. So the situation is actually the opposite of what HELP says: it’s not that TAKE is automatically supplied when only INVENTORY is given, but rather that the TAKE in TAKE INVENTORY is ignored.
The same thing happens with action words other than TAKE:
* inventory
You're not carrying anything.
* take inventory
You're not carrying anything.
* read inventory
You're not carrying anything.
* throw inventory
You're not carrying anything.
* eat inventory
You're not carrying anything.
But not JUMP INVENTORY, because JUMP is a motion word like SOUTH, not an action word. And while TAKE ROD and ROD TAKE are equivalent, the same is not true for INVENTORY TAKE. In this case INVENTORY is ignored, as if you had typed only TAKE.
* jump inventory
I don't know how to apply that word here.
You're at end of road again.
* south inventory
You are in a valley in the forest beside a stream tumbling along a
rocky bed.
* inventory take
Take what?
That’s right. I did not make much use of the named-location motion words when I first played through the game, even though HELP does tell you about them: “To speed the game you can sometimes move long distances with a single word. For example, ‘building’ usually gets you to the building from anywhere above ground except when lost in the forest.” Part of the problem is that these words don’t always work the way it seems they should. For example, from cobbles or debris you can type PIT to go directly to the spit room (brink of small pit), but from spit only DEBRIS works, not COBBLES, even though COBBLES works from other rooms (namely debris and inside). And in one case the connection seems bugged: from emist (east end of Hall of Mists), Y2 takes you to not to y2, but to the adjacent room jumble.
We tried out two important magic words: INFO and HELP. HELP gave a helpful hint about the black rod having a side effect on the bird. Having caught the little bird, we took it to the Hall of the Mountain King, with the shameful intention of feeding it to the snake. Instead, the little bird conquered the snake in combat. Beyond the Hall of the Mountain King, we found a room with a rock marked “Y2”. (Y2 doesn’t seem to be a magic word like XYZZY, though.) Found a window overlooking a pit, opposite which there was a mysterious shadowy figure. Nothing we could think of worked to cross the pit and encounter the figure. Trying to jump earned us another broken neck. Finding a way around to the opposite window was our motivator for the rest of the episode.
Exploring onward, we found a room with a giant clam in it, too large to carry out. We proceeded next to Bedquilt, then an anteroom where there is an issue of Spelunker Today magazine. Then we were killed by a dwarf—so they can get you with their sharp nasty knives.
From the anteroom, we stumbled into Witt’s End, a room from which escape is difficult. Continuing west from Bedquilt, we passed through the Swiss Cheese Room and the Soft Room, where there is a velvet pillow. Then we were unexpectedly attacked by a pirate, who stole all our treasure (some silver bars) and hid it in a maze somewhere. We entered the Twopit Room—could one of these be the pit under the window? One of the pits has oil in it. By first pouring out the water, we can fill the bottle with oil. In the other pit, there is a tiny little plant, which demands (what else?) water.