It looks like you're new here. If you want to get involved, click one of these buttons!
Software: Hitchhiker's Guide to the Galaxy (Game)
Authors: Steve Meretzky and Douglas Adams
Language: ZIL: Zork Implementation Language
Year: 1984
Source file: https://github.com/historicalsource/hitchhikersguide/blob/master/heart.zil
Code repository: https://github.com/historicalsource/hitchhikersguide
Zil Code Manual: https://archive.org/details/Learning_ZIL_Steven_Eric_Meretzky_1995
The Hitchhiker's Guide to the Galaxy was a text adventure game based on the book/radio/television series that was known in the universe for serving puzzles that made you want to smash your brain with a brick wrapped in a lemon. See this discussion for some common reactions. In this puzzle, and this is a spoiler, you need to have previously removed your common sense so that you can at once have "tea" and "no tea" in your inventory.
I wanted to start a thread to invite you all on a sidequest to explore the code of this game that I encountered at a very formative time of my life. Pretty much influenced everything I do as an e-lit artist. But I do recognize (especially having tried to teach it) that this game goes beyond the usual level of frustration-inducing puzzles that @jeremydouglass has written about. Some argue that the difficulty of the puzzles was tied to a profit motive -- selling more hint books, increasing gameplay time. A later game from Adams, Bureaucracy, would make frustrating puzzles its main theme. I'm not sure this is the best passage to start with, but I'd like to use this to open a code exploration of this game and perhaps also The Restaurant at the End of the Universe code, which I am just learning exists!
<ROUTINE SCREENING-DOOR-F ()
<COND (<EQUAL? ,SCREENING-DOOR ,WINNER>
<COND (<AND <VERB? TELL-ABOUT>
<PRSO? ,ME>>
<SETG WINNER ,PROTAGONIST>
<PERFORM ,V?ASK-ABOUT ,SCREENING-DOOR ,PRSI>
<SETG WINNER ,SCREENING-DOOR>
<RTRUE>)
(<VERB? HELLO>
<SETG WINNER ,PROTAGONIST>
<PERFORM ,V?HELLO ,SCREENING-DOOR>
<SETG WINNER ,SCREENING-DOOR>
<RTRUE>)
(<AND <VERB? WHAT>
<PRSO? ,OBJECT-OF-GAME>>
<SETG WINNER ,PROTAGONIST>
<PERFORM ,V?ASK-ABOUT ,SCREENING-DOOR ,OBJECT-OF-GAME>
<SETG WINNER ,SCREENING-DOOR>
<RTRUE>)
(T
<TELL
"\"Unless you're here to show me some clear sign of your intelligence, please
leave me alone. I'm a very busy door.\"" CR>
<FUCKING-CLEAR>)>)
(<AND <FSET? ,SCREENING-DOOR ,OPENBIT>
<VERB? SHOW GIVE KNOCK OPEN>>
<TELL "You already induced the door to open." CR>)
(<AND <FSET? ,SCREENING-DOOR ,OPENBIT>
<VERB? CLOSE>>
<TELL
"The door snaps, \"Hey! I'm resting. I've had a very busy day.\"" CR>)
(<VERB? KICK>
<FSET ,SCREENING-DOOR ,MUNGEDBIT>
<TELL
"\"I suppose you think that since you have legs and I have not, you can get
away with that sort of thing. Well,\" the door continues stiffly, \"maybe you
can and maybe you can't.\"" CR>)
(<VERB? SHOW GIVE>
<COND (<AND <PRSO? ,TEA ,NO-TEA>
,TEA-SHOWN
<HELD? ,TEA>
,HOLDING-NO-TEA
<NOT <PRSO? ,TEA-SHOWN>>>
<PERFORM ,V?KNOCK ,SCREENING-DOOR>
<RTRUE>)
(T
<COND (<PRSO? ,TEA ,NO-TEA>
<SETG TEA-SHOWN ,PRSO>)>
<COND (<PROB 50>
<TELL
"The door says \"Big deal. Anyone can have">
<ARTICLE ,PRSO>
<TELL ".\"" CR>)
(T
<TELL "The door yawns." CR>)>)>)
(<VERB? OPEN KNOCK>
<COND (<AND <HELD? ,TEA>
,HOLDING-NO-TEA>
<FSET ,SCREENING-DOOR ,OPENBIT>
<TELL
"The door is almost speechless with admiration. \"Wow. Simultaneous tea
and no tea. My apologies. You are clearly a heavy-duty philosopher.\" It
opens respectfully." CR>)
(T
<TELL
"The door explains, in a haughty tone, that the room is occupied by a
super-intelligent robot and that lesser beings (by which it means you)
are not to be admitted. \"Show me some tiny example of your intelligence,\"
it says, \"and maybe, just maybe, I might reconsider.\"" CR>)>)
(<AND <VERB? ASK-ABOUT>
<PRSI? ,OBJECT-OF-GAME>>
<TELL "\"To keep out sub-intelligent beings.\"" CR>)
(<VERB? THROUGH>
<COND (<EQUAL? ,HERE ,PANTRY>
<DO-WALK ,P?EAST>)
(T
<DO-WALK ,P?WEST>)>)
(<VERB? EXAMINE>
<FCLEAR ,SCREENING-DOOR ,ACTORBIT>
<V-LOOK-INSIDE>
<FSET ,SCREENING-DOOR ,ACTORBIT>)>>
A few questions:
How does this code speak to the frustration-inducing nature of the game?
What does this code reveal about ZIL as a language?
Or does this code show that the game is just terribly misunderstood?
Is this a representative passage from the game?
How does this compare with puzzles of other Infocom games of the time?
What other passages of the HGTTG code should we explore?
Comments
I'm not sure what on line 24 does, but I like how it reflects a programmer's or user's actual language in use; after all, who hasn't periodically sworn at their machine?
I did peek into the source code for ZIL verbs, and happened to find HO-HUM. While it "doesn't do anything", it does make me laugh and reflect on 'ho-hum-ness' and ineptness on a planetary scale:
link to source code: https://github.com/historicalsource/hitchhikersguide/blob/d7fddb5ebf706e6b7ce2398169a4a987a8f8aeb2/verbs.zil#L2862
Yeah, my first thought is that there was a CLEAR command that didn't quite work and someone finally added FUCKING-CLEAR out of frustration. I bet this sort of thing was way more common before the expectation that everyone's code would eventually end up in public on github!
I have never seen this -cool! At first I thought - LISP (but not lisp). Then I thought is screening door only set to the winner within the conditional (so you can never access this block of code).
As I started reading this I realized that I don't want to read this. I want to see this represented as a graph. I started reading the ZIL manual, which I found more interesting than this code. Why? The ideas and metaphysical assumptions that go into ZIL are perhaps richer than the implementation.
The main conceit behind the hhg code seems more about playing with variable names, and playing up the fact that code is written in human readable language. There is a DSL aspect to to this program, enabled by ZIL, which provides x-ray vision into the world of hhg, and that is one of the things literature does, ie. create its own world.
My main gripe with this work is that it has a punch line, the play with language makes it appear like a piece of work by Gertrude Stein or something, is open to a multiplicity of interpretations. Where as with this program, the puzzle nature of it, implies a right and wrong way of reading it. To read it in another way is to 'queer' the game (recommended reading https://www.goodreads.com/book/show/42129126-video-games-have-always-been-queer).
I did have an inexplicable flashback to uni and writing a wumpus ai. https://en.wikipedia.org/wiki/Hunt_the_Wumpus
Hi! Mark invited me to drop in on this conversation.
I have the advantage of having written a lot of text games in this format, although I didn't use ZIL. So I have a pretty good idea of what the code is doing.
It's not exactly written in human-readable language. But given its genesis in the MIT community, maybe the developers didn't consider "human-readable" and "programming language" to be separate concepts!
I agree that HO-HUM is a fine example of a well-named data structure. (It's an array of strings, not a verb per se.) Even better is the function that makes use of it: HACK-HACK, which is used to print a sentence of the pattern "XXX doesn't do anything" or "YYY accomplishes nothing." (See verbs.zil:2887.) The word "hack" has a dense cultural history in the MIT world; I really don't have a sense of what "HACK-HACK" would have meant to an MIT alum circa 1984.
An even better example is the variable WINNER. In Infocom's parser code, this always refers to the character who is taking action at that point. It goes back to the earliest Infocom games -- you can see the same variable in Zork 1, where it essentially always referred to the player. In HHGG, WINNER switches around quite a bit. It can refer to the playable characters (Arthur, Trillian, Zaphod) or the active NPCs (Prosser, the Beast, etc).
"Winner" was common MIT slang. The Jargon File (http://catb.org/jargon/html/W/winner.html) says "often sarcastic" -- i.e., a euphemism for "loser". But not always. I sense that the ambiguity was part of the fun. Referring to the player as "WINNER" was some kind of joke, but cruel or kind?
HACK-HACK looks like a common routine (although implementations vary across games).
There's a description in one of the infocom memoires (which I can't currently find, alas) of the way that a new game would be started: the source for something recent copied across to a new directory, the imp hacking out the game-specific stuff, and then building on that. So there are many common idioms that would grow and become the established way of expressing ideas, I think (even if the implementations behind those ideas became more sophisticated over time). This would be a rich source for analysis.
Just out of curiosity, I've tabulated routine definitions across a bunch of infocom games for which source is available. You can see the result here. It's a pretty low-fidelity start [assembled using
awk
, so it's not even aware of the syntax of ZIL] to that kind of analysis, but it does indicate some things that shared common ancestry.(I was wondering if FUCKING-CLEAR was one of those inherited features - but it looks like it wasn't. I wonder if that was a contribution directly from Adams?)
So, in perhaps my most juvenile bit of research and no doubt a shining moment in CCS, I just checked the repository for the other Meretzky games and did not find it in any of them.
However, it does appear in the unreleased code for the Restaurant at the End of the Universe game in the files people.zap and people.zil, code attributed to Stu Galley and others. Seems to point to that coming from Adams.
Jimmy Maher's writeup (https://www.filfre.net/2013/11/the-computerized-hitchhikers/) is pretty clear that Adams was not involved in the coding of the game. Meretzky did all the implementation.
The definition of FUCKING-CLEAR in Restaurant is identical to that in HHGG, except that one line is commented out. (The routine clears two parser flags, P-CONT and QUOTE-FLAG. But all use of QUOTE-FLAG in the Restaurant code is commented out.) So I figure that the Restaurant project was started by cloning the HHGG code, and then rearranging, tidying up, and removing features that wouldn't be used in the new game.
The function name could still have been Adams' voice coming through, but it could equally well have been Meretzky. One can easily imagine the sort of day (or late night) where you spend two hours tracking down a bug, say "I forgot to fucking clear that fucking quote flag?!", and then write a function to fix it.
One of the things I was thinking about when posting this snippet was the way the frustration of the way this game is implemented is epitomized by a puzzle that requires the player/winner to possess tea and no tea at the same time. You need to possess not possessing tea, while not possessing your own common sense.
Can you all help me out. Is "no tea" a flag? a variable state?
How does that (whatever it is) change the way we read the scene with the puzzle or this code?
"No tea" is defined as an object just like any other object in the code. It's an 'object' in both the programming sense (as seen in object-oriented programming) and also, literally, an object manifest in the game- in this case an inventory item.
Having the absence of something as an inventory item (a felt absence) is a good joke in itself, and the joke is really committed to by giving the standard dropping and taking messages for the "no tea":
Moreover, there is a text adventure convention that player will soon check their inventory on starting a game, and so placing a goal in the inventory is a fine way to begin motivating the player, especially as the goal can then be checked later at the player's convenience.
But note the existence of a global variable HOLDING-NO-TEA, which is set true or false in sync with the NO-TEA object.
Well, let's verify that... it's set true when you TAKE the NO-TEA. It's set false when you TAKE the TEA. (This is in TEA-F and NO-TEA-F, the action handlers for those two objects.) Things get messier when you DROP the TEA, though! That's the end of the function quoted just above... it looks like it distinguishes the case of DROP TEA when you're holding the tea ("no tea: Taken") from the case when you're holding both ("Dropped.")
Makes sense once you squint at it and count on your fingers.
There are a few other places where the flag is set true. Turning on the Drive when it's running off the tea, and -- curiously -- when taking inventory. Perhaps they were worried about the flag getting set wrong, so they fix it up at INVENTORY time?
The interesting question is why have a flag at all? It should be sufficient to test <HELD? ,TEA> at any time.
Greetings—Not sure if the group is already aware of these links, but in case there’s an interest in playing the game alongside the code, here are some running implementations.
This one is a no-frills, pure-text implementation:
http://iplayif.com/?story=http://www.douglasadams.com/creations/hhgg.z3
And this one, commissioned by BBC, has a more elaborate graphical wrapper around the core game but I presume it's still the same game within:
https://www.bbc.co.uk/programmes/articles/1g84m0sXpnNCv84GpN2PLZG/the-game-30th-anniversary-edition
I wonder if there's an easy-to-start Z-machine implementation where one can load the code, play it, mess with it a bit, then play it again, just to see what happens (or if one can dodge/change certain puzzles in the game).
Perhaps flags are checked with a higher priority than objects and this was to ensure the correct line triggered somewhere. Or possibly they had it as a flag first, wrote some code for it, and then introduced it as a physical object in the inventory later and then never cleaned everything up again. I know from my own experience in authoring, it's very easy to end up with variables that have been made obsolete but may continue to be used. For instance, I might originally define a flag with a true/false boolean, and then later a third possibility is written and so a new numerical variable is created: if I'm lazy and it's relatively minor, both those variable might continue to persist in the code.
Douglas Adams also comments on the "no tea" puzzle from Infocom's Hitchhiker's Guide to the Galaxy in this production note shared by Jess Morrissette:
To provide some context for this code, here is a walkthrough narrating the gameplay by
Lisa Fitzsimmons,[1] who describes the conclusion of the screening door puzzle in this way:
There is also a possibility of encountering minor bugs elsewhere related to the way that TEA / NO TEA work as objects. From Nathan Simpson's More bugs in Infocom games list:
Fitzsimmons is clearer on this point than the HHGTG Walkthrough by Jason Coxon on IGN. ↩︎
@jeremydouglass What a find!
So it looks like this Tea/No Tea was of interest to Adams.
I wonder if then, it is worth meditating on the ways this seeming absurdity speaks to the very heart of the game (and in keeping with the theme is equally irrelevant with the game) as Adams and Meretzky en route to Bureaucracy are attempting to encode the impossible, taking on the inventory system as a proxy of the very binary nature of computing itself, asking the machine to be at 1 and 0 at the same time, with the perfect British icon (perhaps a bit of Postcolonial play here as well) tea!
In my caffeinated morning state, this notion strikes me as epicly deep (and perhaps consequently sophistrically shallow)! Perfection!
Thanks so much for sharing this! I finally had a moment to come back around to it and give the data a look. I'd love to see the script and sources at some point if you would care to share. I haven't looked closely at ZIL decompilers in a few years, but there are many options -- in particular, I can imagine approaches that would harmonize well with data mining other languages like Inform and TADS for cross-comparison. At any rate, rather than tackle the parsing question, I took a quick pass with the data keywords as-is. I noticed:
LIGHT-INT zork1-gold zork2 zork2 zork3 zork3 zork3 zork3
-- this broke my assumptions about representing presence / absence and counting the number of works in which keywords occurred, so I deduplicated this.> BUFFER-PRINT BUT-MERGE CANT-USE CCOUNT CLAUSE CLAUSE-ADD CLAUSE-COPY CLOCKER DO-SL GET-OBJECT GLOBAL-CHECK GLOBAL-IN? GO GOTO GWIM HACK-HACK IDROP ITAKE ITAKE-CHECK MAIN-LOOP MANY-CHECK NUMBER? OBJ-FOUND ORPHAN ORPHAN-MERGE PARSER PERFORM PICK-ONE PRE-GIVE PRE-PUT PRE-TAKE PREP-PRINT QUEUE SEARCH-LIST SEE-INSIDE? SNARF-OBJECTS SNARFEM SYNTAX-CHECK SYNTAX-FOUND THIS-IS-IT THIS-IT? UNKNOWN-WORD V-CLIMB-DOWN V-CLIMB-ON V-CLIMB-UP V-CLOSE V-DROP V-EAT V-EXAMINE V-FIND V-FOLLOW V-GIVE V-HELLO V-INVENTORY V-KICK V-KISS V-KNOCK V-LEAVE V-LISTEN V-LOOK V-LOOK-BEHIND V-LOOK-INSIDE V-LOOK-UNDER V-MOVE V-MUNG V-OPEN V-PUSH V-PUT V-PUT-UNDER V-QUIT V-READ V-RESTART V-RESTORE V-SAVE V-SCRIPT V-SEARCH V-SGIVE V-SMELL V-STAND V-TAKE V-THROW V-UNSCRIPT V-VERSION V-WAIT V-WALK V-WALK-AROUND WEIGHT WHICH-PRINT WORD-PRINT WT? YES?
Once I had a handle on the data I wrote a python script to transform it into CSV data for tabular visualization in the format
function, title, year
. I chose a simple d3 heatmap (among several options) and rendered several Infocom CSV data files, filtered in different ways, including:I have a temporary draft of the full-size interactive visualizations up here:
Infocom shared keyword visualizations
Whoa, @jeremydouglass, you never cease to amaze me. Can you talk a little more about what we're looking at here and what might do with these results in an interpretation?
In each of four images we see the Infocom works arranged along the x-axis roughly by publication date, 1980-1989. Along the y-axis we see code keywords, filtered differently in each image, but grouped vertically by first appearance. The keywords that appear first in Zork I are in a vertical band on the lower left, the ones that first appear in Restaurant at the End of the Universe are in a band on the upper right.
In the left-hand image we see everything. Zork acts as infrastructure, with most of its keywords appearing in most of the subsequent works. Most other words have shadow bands that echo some of their new keywords in later works, and these may be occurring across series and authors. If you look up
Deadline
and check to its right, you see that a group of its keywords, likeDO-FINGERPRINT
, reoccur inWitness
. If you look up theWitness
column to see keywords that appear in it first, then look to the right of those keywords, you find a group of keywords likeV-HANDCUFF
that all reoccur inSuspect
The first image shows everything; the second image focuses only on unique pairs that are repeated in just one other work.
Reading the second image, most notably:
Reading the fourth, the focus is on commonly recurring keywords so you can see which works did not implement something (or altered / renamed it, or intentionally removed it). For one example, every single work in the data set includes
TAKE_CHECK
except one: Trinity. For a more specific keyword history, the Zork series introduces anV-INFLATE
for a raft, and it is then selectively included in the Enchanter series, Infidel, and Leather Goddesses / Nord and Bert / Plundered Hearts, but no others.