Recreating Zork 285 in ZIL

Recreating Zork 285 in ZIL - Part 1 - Introduction and the sources

A while back I recreated one of the first known versions of Zork to ZIL. This is a write-up of the notes I did during the process and it will be an ongoing series that is updated here.

Zork is maybe one of the most famous computer games ever. It was originally developed at MIT and it was written in MDL, a Lisp like language. This is a documentation of the process of an attempt to recreate one of the earliest, preserved incarnations of Zork in ZIL. During the following months and years it progressively got a better and better parser and a widening map. Known versions are a 387 point from July 1977, a 500 point from December 1977, a version with an added 100 point end-game from April 1978 and the final 616+100 point version from December 1979 (The version from July 1981 only include minor bug fixes). I might eventually make ZIL versions of these too.

What is Zork 285?

Zork 285 is the earliest known preserved version of the mainframe version of Zork. This version is aproximately three weeks into the development in 1977 or as Tim Anderson retold the history in The New Zork Times in 1985:

"By late May, Adventure had been solved, and various DM’ers were looking for ways to have fun. Marc Blank was enjoying a respite from medical school; I had just finished
my master’s degree; Bruce Daniels was getting bored with his Ph.D. topic; and Dave Lebling was heartily sick of Morse code. Dave wrote (in MUDDLE) a command parser that
was almost as smart as Adventure’s; Marc and I, who were both in the habit of hacking all night, took advantage of this to write a prototype
four-room game. It has long since vanished. There was a band, a bandbox, a peanut room (the band was outside the door, playing “Hail to the Chief”), and a “chamber filled
with deadlines.” Dave played and tested the game, saw that it was pretty awful, and left to spend two weeks basking in the sun.

Marc, Bruce, and I sat down to write a real game. We began by drawing some maps, inventing some problems, and arguing a lot about how to make things work. Bruce still
had some thoughts of graduating, thus preferring design to implementation, so Marc and I spent the rest of Dave’s vacation in the terminal room
implementing the first version of Zork."

The History of Zork by Tim Anderson, The New Zork Times, Winter 1985 (Vol. 4, No. 1)

If the above account is correct, I guess it was a very busy three weeks.
There is two known binaries from this period preserved; one from June 12th and one from June 14th. There are very small differences between these and I have chosen to use the later one for this conversion. The only differences I have found are two bugs that are fixed in the later version and an added synonym to PUSH:

  • Bug: The game will crash if you try to DROP a treasure in the trophy case that’s not in your possession.
  • Bug: GET or DROP without specifying an object will crash the game.
  • PRESS is added as a synonym to PUSH.

About the PDP-10 ITS

PDP-10 is the hardware and ITS (“Incompatible Timesharing System”) is the operating system.
PDP-10 is a 36-bit computer. 36 bits seems odd in today’s world with 32-bit or 64-bit computers but actually have advantages.

  • It can address more memory (than 32 bit), up to 2^18 (256K) memory locations each containing a 36-bit WORD. This amount of memory was nicknamed “Moby”, after the whale Moby Dick, since it was considered an enormous amount of memory at the time.
  • A 36-bit WORD can, for example, store five 7-bit ASCII characters or six six-bit characters inside a single WORD.
  • 36 are dividable with 2, 3, 4, 6, 9, 12 and 18. Because of this numbers are often expressed in the octal numeral system.
  • 36 bits allow bigger integer numbers and floating point numbers with bigger precision stored inside a single WORD.

The filesystem in ITS is a bit peculiar compared with today’s tree-structures. Each file
consists of four parts each part stored in a single WORD; the first WORD indicate the device (DSK: is the local device), the second the directory in up to six six-bit characters, the third is the six characters of the first part of the filename and the fourth is the six characters of the second part of the filename. In ITS the files are usually referred to like: DIR;FNAME1 FNAME2.

The standard text editor inside the ITS is Emacs and the FNAME2is often used to indicate the version of the file. So, for example DEFS 1 is the first version of the file and DEFS 2is the second, and so on.

Lastly, keep in mind that there is no security in ITS. Every user has full admin rights and login doesn’t use passwords. It all works on an honor system I guess.

Sources for the conversion

The GitHub repositories at MITDDC contains extracted material from backup tapes. The repository zork-1977-07 is of special interest for this project. The file \tree.txt contains the filedates for all files in the repository.
From the file \9005183\taa\frob.glue [Jun 8 1977] we can see at line 8, <SET FOO (!.DEFS !.ROOMS !.TELL !.MAZER !.AACT)>, see that the game is glued together and compiled from the source-files; DEFS, ROOMS, TELL, MAZER and AACT.

<USE "GLUE"> 
<SNAME "MARC">
<GROUP-LOAD "DEFS NBIN">
<GROUP-LOAD "ROOMS NBIN">
<GROUP-LOAD "TAA;TELL NBIN">
<GROUP-LOAD "MAZER NBIN">
<GROUP-LOAD "AACT NBIN">
<SET FOO (!.DEFS !.ROOMS !.TELL !.MAZER !.AACT)>
<GROUP-GLUE FOO <> <>>
<GROUP-DUMP "TAA;FOO GBIN" FOO ,PRINT>

We don’t have all the files from the correct dates but these are the ones we have to work with:

DEFS 81 [Jun 30 1977]: This file contains a lot of type definitions and some functions to manipulate these newly defined types and the lists and vectors they contain. Much of this is built into ZIL and won’t need to be converted.

ROOMS 154 [Jun 14 1977]: Contrary to what one might believe, this is not where the rooms are defined. Instead this is where all basic functions for input, parsing and verb handling are defined. Luckily there is a version from the correct date available.

TELL 35 [Jun 28 1977]: This file is not written in MDL at all, instead it is originally written in assembler. The TELL function is built into ZIL and won’t need to be converted.

MAZER 78 [Jun 30 1977]: This file contains definitions and types for directions and exits. In ZIL this is mostly built into to language and much in here won’t need to be converted.

AACT: This is by far the biggest file and should contain all definitions of rooms and objects and actions associated whit these. Unfortunately there is no early version of this file preserved. In later versions it got split into DUNG and ACT and the earliest versions of these are DUNG 56 [Dec 12 1977] and ÀCT 37 [Dec 10 1977].

In addition to the above source files there are:

MADADV HELP [Jun 6 1977]: A text-file that is read and printed during run-time when the user issues the command HELP.

MADADV INFO [Jun 14 1977]: A text-file that is read and printed during run-time when the user issues the command INFO.

MADADV SAVE [Jun 14 1977]: This is the compiled game from this date. Luckily the text is readable ASCII in compiled MDL and the functions appear in the same order as they are defined in the source files. This makes it possible to extract text with, for example, notepad++ from this file and also to see that all cases inside the functions are converted and/or the function maybe is identical to the later one from December.
This file can be run on a PDP-10 ITS and if you don’t have one of those it is possible to run the file on an PDP-10 ITS emulator.

Running the game on a PDP-10 ITS emulator

There there is a ongoing project to restoring and emulating the PDP-10 ITS and you can set up your own emulation locally. Something I won’t go into more here, I refer to the project on how to do that. Fortunately there is also an instance of emulator that you can log into over telnet.

Starting the game on the emulator

  1. Log into to server at IP-address its.pdp10.se with port 10003. Use the telnet protocol. I myself use Kitty as terminal software.
  2. When you see "Connected to the KA-10 simulator MTY device, line 0", press Ctrl-Z.
  3. Now it is time to login. Type :login <username> and Enter. The username can be up to six characters long.
  4. Set up your terminal, :TCTYP AAA SCROLL NO MORE WIDTH 100.
  5. Start version 54 of MDL, :MUD54.
  6. Load and start the game, <RESTORE "MADMAN; MADADV JUN14">$. The $ means that you press ÈSC to execute the commands inside MDL, Enter won’t work.
TT ITS.1650. DDT.1547.
TTY 21
2. Lusers, Fair Share = 99%
THIS IS TT ITS, A HIGHTY VOLATILE SYSTEM FOR TESTING

For brief information, type ?
For a list of colon commands, type :? and press Enter.
For the full info system, type :INFO and Enter.

You may have to set the correct terminal type.
Try :TCTYP AAA or :TCTYP DATAPOINT

PLEASE LOG IN!  Type ":login", your name, and Enter.
Please log out when your are done.
:login heasm

To see system messages, do ":MSGS<CR>"
:tctyp aaa no more scroll width 100
:KILL
*:mud54
MUDDLE 54 IN OPERATION.
LISTENING-AT-LEVEL 1 PROCESS 1
<RESTORE "MADMAN;MADADV JUN14">$
Welcome to adventure.
You are in an open field west of a big white house, with a closed, locked
front door.
>

Debugging the game

In the file ROOMS 154 we find the following line: <SETG WINNERS '["BKD" "TAA" "MARC" "PDL"]>. These users obviously have some special privileges (BKD = Bruce Daniels, TAA = Tim Anderson, MARC = Marc Blank and PDL = Peter David Lebling). Well, if you login as one of these users you will be able to stop the execution of the game with Ctrl-Gwithout exiting the MDL interpreter. In this state you can inspect and change variables, redefine and run functions and restart the game. This will be useful later on.

*:mud54
MUDDLE 54 IN OPERATION.
LISTENING-AT-LEVEL 1 PROCESS 1
<RESTORE "MADMAN;MADADV JUN14">$
Welcome to adventure.
You are in an open field west of a big white house, with a closed, locked
front door.
>
*ERROR*
CONTROL-G?
LISTENING-AT-LEVEL 2 PROCESS 1
,HERE$
#ROOM [WHOUS "West of House." NORTH SOUTH WEST EAST]
<3 ,ROOMS>$
#ROOM [LOBBY "Dam Lobby" SOUTH NORTH EAST MATCH GUIDE ]
<SETG HERE <3 ,ROOMS>>$
#ROOM [LOBBY "Dam Lobby" SOUTH NORTH EAST MATCH GUIDE ]
<RDCOM>$
This room appears to have been the waiting room for groups touring
the dam.  There are exits here to the north and east marked 'Private',
though the doors are open, and an exit to the south.
There is a matchbook whose cover says 'Visit Beautiful FCD#3' here.
Some guidebooks entitled 'Flood Control Dam #3' are on reception desk.
>

Examining the MADADV.SAVE in a text- or hexeditor

It is fairly easy to locate in the compiled file where each function is. If we use the function INVENT from ROOMS 154 as an example.

<DEFINE INVENT ("AUX" (ANY <>)) 
   #DECL ((ANY) <OR ATOM FALSE>)
   <MAPF <>
	 <FUNCTION (X) 
		 #DECL ((X) OBJECT)
		 <COND (<OVIS? .X>
			<OR .ANY
			    <PROG ()
				  <TELL "You are carrying:">
				  <SET ANY T>>>
			<PRINC "A ">
			<PRINC <ODESC2 .X>>
			<OR <EMPTY? <OCONTENTS .X>> <PRINC " with ">>
			<MAPR <>
			      <FUNCTION (Y) 
				      #DECL ((Y) <LIST [REST OBJECT]>)
				      <PRINC <ODESC2 <1 .Y>>>
				      <COND (<G? <LENGTH .Y> 1> <PRINC " and ">)
					    (<0? <LENGTH .Y>> <PRINC ".">)>>
			      <OCONTENTS .X>>
			<CRLF>)>>
	 <AOBJS ,WINNER>>
   <OR .ANY <TELL "You are empty handed.">>>

It looks something like this in the compiled file.

<unprintable>You are carrying:<unprintable>
A <unprintable> with <unprintable> and <unprintable>
.<unprintable>You are empty handed.<unprintable>

This makes it possible to verify that all the texts are included and correct in the converted file. The becomes especially valuable when trying to convert the functions that are in AACT.

Next: Part 2 - Extracting the dictionary

8 Likes

Recreating Zork 285 from MDL to ZIL - Part 2 - Extracting the dictionary

This is part 2 in an ongoing series. The previous part: Part 1 - Introduction and the sources is here.

In this part we will extract and examine the buzzwords, directions and verbs. The nouns will be examined and added later when we extract the OBJECTs in the next part.

The WORDS-oblist and what is an OBLIST in MDL?

Using the trick (logging in as one of the WINNERs and then use Ctrl-G to stop execution of the game) described in the previous part to debug the compiled game in the MUD54 interpreter we can examine the WORDS variable.

,WORDS$
#OBLIST ![(VAULT!-WORDS LEAP!-WORDS BREAK!-WORDS DESCR!-WORDS GET!-WORDS CHUCK!-WORDS GO!-WORDS
TO!-WORDS THIS!-WORDS OUT!-WORDS WALK-IN!-WORDS) (WELL!-WORDS PRAY!-WORDS FREE!-WORDS POKE!-WORDS
ATTAC!-WORDS EXIT!-WORDS SOUTH!-WORDS AT!-WORDS TURN!-WORDS THE!-WORDS GRATE!-WORDS GIVE!-WORDS) (
PRESS!-WORDS DAMAG!-WORDS INJUR!-WORDS FROB!-WORDS HURL!-WORDS PULL!-WORDS LIGHT!-WORDS IN!-WORDS
DOWN!-WORDS TAKE!-WORDS THROW!-WORDS) (JAB!-WORDS POUR!-WORDS EXTIN!-WORDS CLIMB!-WORDS UP!-WORDS) (
SCAN!-WORDS FILL!-WORDS CLOBB!-WORDS FONDL!-WORDS QUIT!-WORDS SCORE!-WORDS CARRY!-WORDS HOLD!-WORDS
WAVE!-WORDS E!-WORDS CROSS!-WORDS OVER!-WORDS PLUG!-WORDS RUB!-WORDS WALK!-WORDS) (KICK!-WORDS
CARES!-WORDS STOP!-WORDS ENTER!-WORDS NW!-WORDS) (READ!-WORDS TAUNT!-WORDS TRAVE!-WORDS N!-WORDS
AN!-WORDS A!-WORDS MOVE!-WORDS) (HIT!-WORDS BITE!-WORDS CORK!-WORDS PATCH!-WORDS DONAT!-WORDS
SPILL!-WORDS NE!-WORDS CLOSE!-WORDS TREAS!-WORDS EAST!-WORDS UNTIE!-WORDS MUNG!-WORDS) (SINBA!-WORDS
KILL!-WORDS W!-WORDS) (SKIM!-WORDS HURT!-WORDS RUN!-WORDS U!-WORDS SW!-WORDS PUSH!-WORDS
EXORC!-WORDS) (TOUCH!-WORDS HAND!-WORDS LEAVE!-WORDS S!-WORDS TEMPL!-WORDS JUMP!-WORDS) (KNOT!-WORDS
HACK!-WORDS INVEN!-WORDS D!-WORDS SE!-WORDS OFF!-WORDS TIE!-WORDS LOOK!-WORDS) (XORCI!-WORDS
SHOVE!-WORDS HELP!-WORDS INFO!-WORDS LIST!-WORDS RELEA!-WORDS LIFT!-WORDS NORTH!-WORDS ON!-WORDS
WEST!-WORDS OPEN!-WORDS DROP!-WORDS)!]

We see that this variable contains an OBLIST with all the verbs, directions and buzzwords. So what is an OBLIST?

Well, OBLISTs are lookup lists of ATOMs and the link between each ATOMs name and its content. OBLISTs are usually used to keep track of all names in current context so that you can use the same name for a local variable inside a function as a variable in another function or a variable at the global level without conflict. But an OBLIST is a perfectly legal datatype to use for other purposes, as it is used here. The data in an OBLIST can be referred to with the !-<OBLIST>-syntax and if we can examine one of the ATOMs in the WORDS-oblist like this:

,TAKE!-WORDS$
#VERB [TAKE!-WORDS TAKE T]

TAKE is obviously an ATOM of the VERB-type.

For more on MDL and OBLISTs I recommend The MDL Programming Language by S. W. Galley and Greg Pfister.

The BUZZ-, DIRECTION- and VERB-type

In the file DEFS 81 [Jun 30 1977] are the definitions for the BUZZ-, DIRECTION- and VERB-type:

<NEWTYPE BUZZ STRING>

<NEWTYPE DIRECTION ATOM>

<NEWTYPE VERB
	 VECTOR
	 '<<PRIMTYPE VECTOR> ATOM
			     <OR APPLICABLE FALSE>
			     VDECL
			     STRING
			     <OR ATOM FALSE>
			     FIX>> 

<MSETG VNAME 1>
<MSETG VFCN 2>
<MSETG VARGS 3>
<MSETG VSTR 4>
<MSETG VACTION? 5>
<MSETG VMAX 6>

The BUZZ and DIRECTION are pretty straight forward. BUZZ is a simple string and DIRECTION is a new ATOM. VERB on the other hand is a VECTOR with six slots. The slots are named VNAME, VFCN, VARGS, VSTR, VACTION? and VMAX respectively. We will return to these later.

Extracting more information from WORDS

This is a small MDL-function that will extract out more information from WORDS.

<DEFINE DICTIONARY ("AUX" (WTMP ,WORDS))
    <REPEAT (WOBL)
        <SET WOBL <1 .WTMP>>
        <REPEAT ()
            <PRIN1 <1 .WOBL>>
            <PRINC "  ">
            <COND (<GASSIGNED? <1 .WOBL>>
                <COND (<TYPE? ,<1 .WOBL> VERB>
                        <PRINC "#VERB  ">
                        <PRIN1 <1 ,<1 .WOBL>>> <PRINC "  ">         ;"VNAME"
                        <COND (<TYPE? <2 ,<1 .WOBL>> RSUBR-ENTRY>
                            <PRIN1 <2 <2 ,<1 .WOBL>>>> <PRINC "  ">);"VFCN"
                              (T <PRIN1 <3 ,<1 .WOBL>>> <PRINC "  ">)>
                        <PRIN1 <3 ,<1 .WOBL>>> <PRINC "  ">         ;"VARGS"
                        <PRIN1 <4 ,<1 .WOBL>>> <PRINC "  ">         ;"VSTR"
                        <PRIN1 <5 ,<1 .WOBL>>> <PRINC "  ">         ;"VACTION?"
                        <PRIN1 <6 ,<1 .WOBL>>> <PRINC "  ">)        ;"VMAX"
                      (T <PRIN1 ,<1 .WOBL>>)>)>
            <TERPRI>
            <SET WOBL <REST .WOBL>>
            <COND (<EMPTY? .WOBL> <RETURN>)>
            <AGAIN>>
        <SET WTMP <REST .WTMP>>
        <COND (<EMPTY? .WTMP> <RETURN>)>
        <AGAIN>>>

And if you run this (and reformat the output a bit) you get the following table:

                TYPE        VNAME           VFCN        VARGS   VSTR         VACTION?   VMAX
                ----        -----           ----        -----   ----         --------   ----
VAULT!-WORDS    #VERB       JUMP!-WORDS     LEAPER          0   ""                  T      2
LEAP!-WORDS     #VERB       JUMP!-WORDS     LEAPER          0   ""                  T      2
BREAK!-WORDS    #VERB       MUNG!-WORDS     ACT-HACK        0   ""                  T      2
DESCR!-WORDS    #VERB       LOOK!-WORDS     ROOM-DESC       0   ""                  T      2
GET!-WORDS      #VERB       TAKE!-WORDS     TAKE            0   "Take what?"        T      2
CHUCK!-WORDS    #VERB       THROW!-WORDS    DROP            0   "Throw what?"       T      2
GO!-WORDS       #VERB       WALK!-WORDS     WALK            1   ""             #FALSE      1
TO!-WORDS       #BUZZ "TO"
THIS!-WORDS     #BUZZ "THIS"
OUT!-WORDS      #DIRECTION  EXIT!-WORDS
WALK-IN!-WORDS  #VERB       WALK-IN!-WORDS  0               0   ""                  T      2
WELL!-WORDS     #VERB       WELL!-WORDS     WELL            0   ""                  T      2
PRAY!-WORDS     #VERB       PRAY!-WORDS     PRAYER          0   ""                  T      2
FREE!-WORDS     #VERB       UNTIE!-WORDS    ACT-HACK        1   "Untie what?"       T      2
POKE!-WORDS     #VERB       MUNG!-WORDS     ACT-HACK        0   ""                  T      2
ATTAC!-WORDS    #VERB       MUNG!-WORDS     ACT-HACK        0   ""                  T      2
EXIT!-WORDS     #DIRECTION  EXIT!-WORDS
SOUTH!-WORDS    #DIRECTION  SOUTH!-WORDS
AT!-WORDS       #BUZZ "AT"
TURN!-WORDS     #BUZZ "TURN"
THE!-WORDS      #BUZZ "THE"
GRATE!-WORDS
GIVE!-WORDS     #VERB       GIVE!-WORDS     DROP            0   "Give what?"        T      2
PRESS!-WORDS    #VERB       PUSH!-WORDS     ACT-HACK        1   "Push what?"        T      2
DAMAG!-WORDS    #VERB       MUNG!-WORDS     ACT-HACK        0   ""                  T      2
INJUR!-WORDS    #VERB       MUNG!-WORDS     ACT-HACK        0   ""                  T      2
FROB!-WORDS     #VERB       MUNG!-WORDS     ACT-HACK        0   ""                  T      2
HURL!-WORDS     #VERB       THROW!-WORDS    DROP            0   "Throw what?"       T      2
PULL!-WORDS     #VERB       MOVE!-WORDS     MOVE            0   "Move what?"        T      2
LIGHT!-WORDS    #VERB       ON!-WORDS       LAMP-ON         0   ""                  T      2
IN!-WORDS       #DIRECTION  ENTER!-WORDS
DOWN!-WORDS     #DIRECTION  DOWN!-WORDS
TAKE!-WORDS     #VERB       TAKE!-WORDS     TAKE            0   "Take what?"        T      2
THROW!-WORDS    #VERB       THROW!-WORDS    DROP            0   "Throw what?"       T      2
JAB!-WORDS      #VERB       MUNG!-WORDS     ACT-HACK        0   ""                  T      2
POUR!-WORDS     #VERB       POUR!-WORDS     DROP            1   "Pour what?"        T      2
EXTIN!-WORDS    #VERB       OFF!-WORDS      LAMP-OFF        0   ""                  T      2
CLIMB!-WORDS    #DIRECTION  CLIMB!-WORDS
UP!-WORDS       #DIRECTION  UP!-WORDS
SCAN!-WORDS     #VERB       READ!-WORDS     READER          1   "Read what?"        T      2
FILL!-WORDS     #VERB       FILL!-WORDS     FILL            0   ""                  T      2
CLOBB!-WORDS    #VERB       MUNG!-WORDS     ACT-HACK        0   ""                  T      2
FONDL!-WORDS    #VERB       RUB!-WORDS      ACT-HACK        0   ""                  T      2
QUIT!-WORDS     #VERB       QUIT!-WORDS     FINISH          0   ""                  T      2
SCORE!-WORDS    #VERB       SCORE!-WORDS    SCORE           0   ""                  T      2
CARRY!-WORDS    #VERB       TAKE!-WORDS     TAKE            0   "Take what?"        T      2
HOLD!-WORDS     #VERB       TAKE!-WORDS     TAKE            0   "Take what?"        T      2
WAVE!-WORDS     #VERB       WAVE!-WORDS     ACT-HACK        1   "Wave what?"        T      2
E!-WORDS        #DIRECTION  EAST!-WORDS
CROSS!-WORDS    #DIRECTION  CROSS!-WORDS
OVER!-WORDS     #BUZZ "OVER"
PLUG!-WORDS     #VERB       PLUG!-WORDS     ACT-HACK        0   ""                  T      2
RUB!-WORDS      #VERB       RUB!-WORDS      ACT-HACK        0   ""                  T      2
WALK!-WORDS     #VERB       WALK!-WORDS     WALK            1   ""             #FALSE      1
KICK!-WORDS     #VERB       MUNG!-WORDS     ACT-HACK        0   ""                  T      2
CARES!-WORDS    #VERB       RUB!-WORDS      ACT-HACK        0   ""                  T      2
STOP!-WORDS     #VERB       PLUG!-WORDS     ACT-HACK        0   ""                  T      2
ENTER!-WORDS    #DIRECTION  ENTER!-WORDS
NW!-WORDS       #DIRECTION  NW!-WORDS
READ!-WORDS     #VERB       READ!-WORDS     READER          1   "Read what?"        T      2
TAUNT!-WORDS    #VERB       MUNG!-WORDS     ACT-HACK        0   ""                  T      2
TRAVE!-WORDS    #DIRECTION  CROSS!-WORDS
N!-WORDS        #DIRECTION  NORTH!-WORDS
AN!-WORDS       #BUZZ "AN"
A!-WORDS        #BUZZ "A"
MOVE!-WORDS     #VERB       MOVE!-WORDS     MOVE            0   "Move what?"        T      2
HIT!-WORDS      #VERB       MUNG!-WORDS     ACT-HACK        0   ""                  T      2
BITE!-WORDS     #VERB       MUNG!-WORDS     ACT-HACK        0   ""                  T      2
CORK!-WORDS     #VERB       PLUG!-WORDS     ACT-HACK        0   ""                  T      2
PATCH!-WORDS    #VERB       PLUG!-WORDS     ACT-HACK        0   ""                  T      2
DONAT!-WORDS    #VERB       GIVE!-WORDS     DROP            0   "Give what?"        T      2
SPILL!-WORDS    #VERB       POUR!-WORDS     DROP            0   "Pour what?"        T      2
NE!-WORDS       #DIRECTION  NE!-WORDS
CLOSE!-WORDS    #VERB       PLUG!-WORDS     ACT-HACK        0   ""                  T      2
TREAS!-WORDS    #VERB       TREAS!-WORDS    TREAS           0   ""                  T      2
EAST!-WORDS     #DIRECTION  EAST!-WORDS
UNTIE!-WORDS    #VERB       UNTIE!-WORDS    ACT-HACK        1   "Untie what?"       T      2
MUNG!-WORDS     #VERB       MUNG!-WORDS     ACT-HACK        0   ""                  T      2
SINBA!-WORDS    #VERB       SINBA!-WORDS    SINBAD          0   ""                  T      2
KILL!-WORDS     #VERB       MUNG!-WORDS     ACT-HACK        0   ""                  T      2
W!-WORDS        #DIRECTION  WEST!-WORDS
SKIM!-WORDS     #VERB       READ!-WORDS     READER          1   "Read what?"        T      2
HURT!-WORDS     #VERB       MUNG!-WORDS     ACT-HACK        0   ""                  T      2
RUN!-WORDS      #VERB       WALK!-WORDS     WALK            1   ""             #FALSE      1
U!-WORDS        #DIRECTION  UP!-WORDS
SW!-WORDS       #DIRECTION  SW!-WORDS
PUSH!-WORDS     #VERB       PUSH!-WORDS     ACT-HACK        1   "Push what?"        T      2
EXORC!-WORDS    #VERB       EXORC!-WORDS    ACT-HACK        0   ""                  T      2
TOUCH!-WORDS    #VERB       RUB!-WORDS      ACT-HACK        0   ""                  T      2
HAND!-WORDS     #VERB       GIVE!-WORDS     DROP            0   "Give what?"        T      2
LEAVE!-WORDS    #DIRECTION  EXIT!-WORDS
S!-WORDS        #DIRECTION  SOUTH!-WORDS
TEMPL!-WORDS    #VERB       TEMPL!-WORDS    TREAS           0   ""                  T      2
JUMP!-WORDS     #VERB       JUMP!-WORDS     LEAPER          0   ""                  T      2
KNOT!-WORDS     #VERB       TIE!-WORDS      ACT-HACK        1   "Tie what?"         T      2
HACK!-WORDS     #VERB       MUNG!-WORDS     ACT-HACK        0   ""                  T      2
INVEN!-WORDS    #VERB       INVEN!-WORDS    INVENT          0   ""                  T      2
D!-WORDS        #DIRECTION  DOWN!-WORDS
SE!-WORDS       #DIRECTION  SE!-WORDS
OFF!-WORDS      #VERB       OFF!-WORDS      LAMP-OFF        0   ""                  T      2
TIE!-WORDS      #VERB       TIE!-WORDS      ACT-HACK        1   "Tie what?"         T      2
LOOK!-WORDS     #VERB       LOOK!-WORDS     ROOM-DESC       0   ""                  T      2
XORCI!-WORDS    #VERB       EXORC!-WORDS    ACT-HACK        0   ""                  T      2
SHOVE!-WORDS    #VERB       PUSH!-WORDS     ACT-HACK        1   "Push what?"        T      2
HELP!-WORDS     #VERB       HELP!-WORDS     HELP            0   ""                  T      2
INFO!-WORDS     #VERB       INFO!-WORDS     INFO            0   ""                  T      2
LIST!-WORDS     #VERB       INVEN!-WORDS    INVENT          0   ""                  T      2
RELEA!-WORDS    #VERB       UNTIE!-WORDS    ACT-HACK        1   "Untie what?"       T      2
LIFT!-WORDS     #VERB       MOVE!-WORDS     MOVE            0   "Move what?"        T      2
NORTH!-WORDS    #DIRECTION  NORTH!-WORDS
ON!-WORDS       #VERB       ON!-WORDS       LAMP-ON         0   ""                  T      2
WEST!-WORDS     #DIRECTION  WEST!-WORDS
OPEN!-WORDS     #VERB       OPEN!-WORDS     ACT-HACK        0   ""                  T      2
DROP!-WORDS     #VERB       DROP!-WORDS     DROP            0   "Drop what?"        T      2

(The GRATE!-WORDS isn’t properly defined and is not understood in game and can be ignored.)

Converting to ZIL

BUZZWORDS

These are pretty simple, you just add them to the vocabulary with the BUZZ keyword. [defs.zil]

<BUZZ TO THIS AT TURN THE OVER AN A>

DIRECTIONS

The directions are organised in patterns like this:

NORTH!-WORDS    #DIRECTION  NORTH!-WORDS
N!-WORDS        #DIRECTION  NORTH!-WORDS

You can read this as N is a synonym to NORTH. Add the directions to the vocabulary with the DIRECTION keyword and add the synonyms with the SYNONYM keyword. [defs.zil]

<DIRECTIONS NORTH EAST WEST SOUTH NE NW SE SW UP DOWN ENTER EXIT CLIMB CROSS>
<SYNONYM NORTH N>
<SYNONYM EAST E>
<SYNONYM WEST W>
<SYNONYM SOUTH S>
<SYNONYM UP U>
<SYNONYM DOWN D>
<SYNONYM EXIT OUT>
<SYNONYM ENTER IN>
<SYNONYM CROSS TRAVE>

If you are observant you will notice that LEAVE is missing as a synonym to EXIT. This is because LEAVE will be in conflict with the noun LEAVES and therefore we will need to make a special handling of this direction later when we parse.

VERBS

Verbs are a little more complicated and look like this.

                TYPE        VNAME           VFCN        VARGS   VSTR         VACTION?   VMAX
                ----        -----           ----        -----   ----         --------   ----
TAKE!-WORDS     #VERB       TAKE!-WORDS     TAKE            0   "Take what?"        T      2
GET!-WORDS      #VERB       TAKE!-WORDS     TAKE            0   "Take what?"        T      2
CARRY!-WORDS    #VERB       TAKE!-WORDS     TAKE            0   "Take what?"        T      2
HOLD!-WORDS     #VERB       TAKE!-WORDS     TAKE            0   "Take what?"        T      2

As one can see they are also grouped around an action with a couple of synonyms. The VFCN is the function associated with this action, VARGS, VSTR, VACTION? and VMAX are values used during the parsing. In ZIL we define the verbs with the SYNTAX and the SYNONYM keywords. VARGS, VSTR, VACTION? and VMAX are implemented as ROUTINEs that will return the correct value as in the table. [aact.zil] [defs.zil]

<SYNTAX TAKE = TAKE>
<SYNONYM TAKE GET CARRY HOLD>

<ROUTINE VARGS (V)
	<COND (<OR <VNO=? .V ,W?WALK ,W?UNTIE ,W?PUSH ,W?POUR ,W?READ> 
			   <VNO=? .V ,W?WAVE ,W?TIE>> 
			 <RETURN 1>)
		  (T <RETURN 0>)>>

<ROUTINE VMAX (V)
	<COND (<VNO=? .V ,W?WALK> <RETURN 1>)
		  (T <RETURN 2>)>>
		  
<ROUTINE VACTION? (V)
	<COND (<VNO=? .V ,W?WALK> <RFALSE>)
		  (T <RTRUE>)>>

<ROUTINE VSTR (V)
	<COND (<VNO=? .V ,W?TAKE> <TELL "Take what?" CR>)
		  (<VNO=? .V ,W?THROW> <TELL "Throw what?" CR>)
		  (<VNO=? .V ,W?UNTIE> <TELL "Untie what?" CR>)
		  (<VNO=? .V ,W?GIVE> <TELL "Give what?" CR>)
		  (<VNO=? .V ,W?PUSH> <TELL "Push what?" CR>)
		  (<VNO=? .V ,W?MOVE> <TELL "Move what?" CR>)
		  (<VNO=? .V ,W?POUR> <TELL "Pour what?" CR>)
		  (<VNO=? .V ,W?READ> <TELL "Read what?" CR>)
		  (<VNO=? .V ,W?WAVE> <TELL "Wave what?" CR>)
		  (<VNO=? .V ,W?TIE> <TELL "Tie what?" CR>)
  		  (<VNO=? .V ,W?DROP> <TELL "Drop what?" CR>)
		  (T <RFALSE>)>>

<ROUTINE VNO (V) <RETURN <GETB .V 7>>>

<ROUTINE VNO=? (V1 V2 "OPT" V3 V4 V5 V6 V7) 
	<COND (<OR <=? <VNO .V1> <VNO .V2>>
			   <AND <N=? .V3 0> <=? <VNO .V1> <VNO .V3>>>
			   <AND <N=? .V4 0> <=? <VNO .V1> <VNO .V4>>>
			   <AND <N=? .V5 0> <=? <VNO .V1> <VNO .V5>>>
			   <AND <N=? .V6 0> <=? <VNO .V1> <VNO .V6>>>
			   <AND <N=? .V7 0> <=? <VNO .V1> <VNO .V7>>>>
			 <RTRUE>)
		  (T <RFALSE>)>>

VNO=? is a helper ROUTINE that compares up to 7 verbs and returns true if they are the same. <VNO=? W?GET W?TAKE> will, for example, return true.

The VNO-routine returns the V1 part of the word. For Z4 and later versions of ZIL the vocabulary words have the following information for each word:

Byte 0-5 Up to 9 zchars in the word
Byte 6   Part-of-speech. BUZZ, DIRECTION, PREPOSITION, ADJECTIVE, VERB or NOUN
Byte 7   V1. Word number 0-255.
Byte 8   V2. Used together with V1 for nouns to get up to 65536 nouns.

Read more about the vocabulary in the ZILF Reference Manual under the VOC keyword.

Finally, Every SYNTAX definition only uses up to 5 characters. This is because we only want 5 characters to be significant for the parser. Normally, in z5, ZIL uses 9 significant characters.

<SYNTAX INVEN = INVENT>
<SYNONYM INVEN LIST>

Next: Part 3 - Extracting definitions for rooms and objects

6 Likes

Recreating Zork 285 from MDL to ZIL - Part 3 - Extracting definitions for rooms and objects

This is part 3 in an ongoing series. The previous part: Part 2 - Extracting the dictionary is here.

In this part we will extract all the definitions for rooms and objects.

The ROOM-type

In the file DEFS 81 [Jun 30 1977] we find this definitions for the ROOM-type (I’ve added a description for each position in the vector):

; "ROOM DEFINITIONS
    1. ROOM ID (NAME)
    2. LONG DESCRIPTION
    3. SHORT DESCRIPTION
    4. VISITED SWITCH
    5. ENDOGENOUS LIGHT SOURCE SWITCH
    6. EXITS FROM ROOM
    7. ROOM-ACTION FUNCTION
"

<NEWTYPE ROOM
	 VECTOR
	 '<<PRIMTYPE VECTOR> ATOM
			     STRING
			     STRING
			     <OR ATOM FALSE>
			     <OR ATOM FALSE>
			     EXIT
			     <LIST [REST OBJECT]>
			     <OR APPLICABLE FALSE>
			     <PRIMTYPE WORD>
			     FIX>>

<MSETG RID 1>     ;"Up to five character room identification"
<MSETG RDESC1 2>  ;"Long description"
<MSETG RDESC2 3>  ;"Short description"
<MSETG RSEEN? 4>  ;"Have room been visited?"
<MSETG RLIGHT? 5> ;"Do room have light?"
<MSETG REXITS 6>  ;"Exits from room (ROOM, CEXIT or NEXIT)"
<MSETG ROBJS 7>   ;"Objects in room (VECTOR of OBJECTS, done the other way around in ZIL)"
<MSETG RACTION 8> ;"Room-action FUNCTION"
<MSETG RVARS 9>   ;"Variables for ROOM (precursor to room flags)"
<MSETG RVAL 10>   ;"Value for visiting room (for scoring)"

; "EXITS ARE DIRECTION/ROOM-CEXIT PAIRS"

<NEWTYPE EXIT 
	 VECTOR
	 '<<PRIMTYPE VECTOR> [REST ATOM <OR ROOM CEXIT NEXIT>]>>

; "CONDITION EXITS ARE CONDITION FLAG, DESCRIPTION, AND ROOM"
   
<NEWTYPE CEXIT
	 VECTOR
	 '<<PRIMTYPE VECTOR> ATOM ROOM <OR FALSE STRING>>>

<MSETG CXFLAG 1>
<MSETG CXROOM 2>
<MSETG CXSTR 3>

<NEWTYPE NEXIT STRING>

The EXITs are defined with an ATOM specifying the DIRECTION and then a ROOM, a CEXIT or NEXIT. A CEXIT is an ATOM that evaluates to true
or false and the ROOM to go to if true and the message to print if false. A NEXIT is a message to print when player tries to move in that direction.

Extracting all the information about the ROOMs

If we examine the ATOM ´´´ROOMS´´´, we find:

,ROOMS$
(#ROOM [TREAS "Treasure Room" DOWN CHALI ] #ROOM [MAINT "Maintenance Room" SOUTH WEST BUTTO LEAK
PUTTY WRENC SCREW ] #ROOM [LOBBY "Dam Lobby" SOUTH NORTH EAST MATCH GUIDE ] #ROOM [TEMP2 "Altar"
WEST BOOK CANDL ] #ROOM [TEMP1 "Temple" WEST EAST BELL ] #ROOM [LLD2 "Land of the Living Dead" EXIT
WEST] #ROOM [MPEAR "Pearl Room" EXIT WEST PEARL ] #ROOM [RIDDL "Riddle Room" DOWN EAST] #ROOM [DEAD6
 "Dead end" EAST] #ROOM [DEAD5 "Dead end" SW] #ROOM [CAVE4 "Engravings Cave" NORTH SE] #ROOM [GALLE
"Gallery" NORTH SOUTH PAINT ] #ROOM [STUDI "Studio" NORTH NW UP] #ROOM [MTORC "Torch Room" WEST DOWN
 TORCH ] #ROOM [SLIDE "Slide Room" EAST DOWN] #ROOM [MGRAI "Grail Room" WEST EAST UP GRAIL ] #ROOM [
LLD1 "Entrance to Hades" EAST UP ENTER GHOST ] #ROOM [MIRR2 "Mirror Room" WEST NORTH EAST REFLE ]
#ROOM [CAVE2 "Cave" NORTH WEST DOWN] #ROOM [CRAW3 "Narrow Crawlway" SOUTH SW NORTH] #ROOM [PASS4
"Winding Passage" EAST NORTH] #ROOM [MIRR1 "Mirror Room" WEST NORTH EAST REFLE ] #ROOM [CRAW2
"Steep Crawlway" SOUTH SW] #ROOM [PASS3 "Cold Passage" EAST WEST NORTH] #ROOM [ECHO "Loud Room" EAST
 WEST UP BAR ] #ROOM [CAVE3 "Damp Cave" SOUTH EAST WEST] #ROOM [PASS5 "North-South Passage" NORTH NE
 SOUTH] #ROOM [CHAS3 "Ancient Chasm" SOUTH EAST NORTH WEST] #ROOM [DAM "Dam" SOUTH EAST NORTH DAM
BOLT BUBBL ] #ROOM [CAVE1 "Cave" NORTH DOWN] #ROOM [RUBYR "Ruby Room" SOUTH RUBY ] #ROOM [ICY
"Glacier Room" NORTH EAST WEST ICE ] #ROOM [ATLAN "Atlantis Room" SE UP TRIDE ] #ROOM [STREA
"Stream" EAST NORTH] #ROOM [CANY1 "Deep Canyon" SE EAST SOUTH] #ROOM [RESEN "Reservoir North" NORTH
CROSS SOUTH] #ROOM [EGYPT "Egyptian Room" UP EAST COFFI ] #ROOM [DOME "Dome Room" EAST DOWN CLIMB]
#ROOM [CRAW1 "Rocky Crawl" WEST EAST NW] #ROOM [CHAS1 "Chasm" SOUTH EAST DOWN] #ROOM [RESES
"Reservoir South" SOUTH WEST CROSS NORTH UP TRUNK ] #ROOM [CYCLO "Cyclops Room" WEST NORTH UP CYCLO
] #ROOM [DEAD4 "Dead end." SOUTH] #ROOM [MAZ12
"You are in a maze of twisty little passages, all alike." WEST SW EAST UP NORTH] #ROOM [MAZ13
"You are in a maze of twisty little passages, all alike." EAST DOWN SOUTH WEST] #ROOM [MAZ10
"You are in a maze of twisty little passages, all alike." EAST WEST UP] #ROOM [MAZ11
"You are in a maze of twisty little passages, all alike." DOWN NW SW UP GRATE ] #ROOM [DEAD3
"Dead end." NORTH] #ROOM [MAZ15 "You are in a maze of twisty little passages, all alike." WEST SOUTH
 NE] #ROOM [MAZE8 "You are in a maze of twisty little passages, all alike." NE WEST SE] #ROOM [MAZ14
 "You are in a maze of twisty little passages, all alike." WEST NW NE SOUTH] #ROOM [MAZE9
"You are in a maze of twisty little passages, all alike." NORTH EAST DOWN SOUTH WEST NW] #ROOM [
MAZE7 "You are in a maze of twisty little passages, all alike." UP WEST NE EAST SOUTH] #ROOM [MAZE6
"You are in a maze of twisty little passages, all alike." DOWN EAST WEST UP] #ROOM [DEAD2
"Dead end." WEST] #ROOM [DEAD1 "Dead end." SOUTH] #ROOM [MAZE5
"You are in a maze of twisty little passages, all alike." EAST NORTH SW BONES BAGCO KEYS ] #ROOM [
MAZE3 "You are in a maze of twisty little passages, all alike." WEST NORTH UP] #ROOM [MAZE4
"You are in a maze of twisty little passages, all alike." WEST NORTH EAST] #ROOM [MAZE2
"You are in a maze of twisty little passages, all alike." SOUTH NORTH EAST] #ROOM [MAZE1
"You are in a maze of twisty little passages, all alike." WEST NORTH SOUTH EAST] #ROOM [CRAW4
"North-South Crawlway" NORTH SOUTH EAST UP] #ROOM [CHAS2 "West of Chasm" WEST NORTH SOUTH DOWN]
#ROOM [CELLA "Cellar" EAST SOUTH UP WEST DOOR ] #ROOM [BLROO "Strange Passage" SOUTH EAST] #ROOM [
ATTIC "Attic" DOWN ROPE KNIFE ] #ROOM [LROOM "Living Room" EAST WEST DOWN DOOR TROPH LAMP RUG ]
#ROOM [CLEAR "Clearing" SW NORTH EAST WEST SOUTH DOWN GRATE PILE ] #ROOM [KITCH "Kitchen" EAST WEST
EXIT UP DOWN WINDO FOOD BOTTL ] #ROOM [FORE2 "Forest" NORTH EAST SOUTH WEST] #ROOM [FORE3 "Forest"
NORTH EAST SOUTH WEST] #ROOM [EHOUS "Behind House." NORTH SOUTH EAST WEST ENTER WINDO ] #ROOM [WHOUS
 "West of House." NORTH SOUTH WEST EAST] #ROOM [FORE1 "Forest" NORTH EAST SOUTH WEST] #ROOM [SHOUS
"South of House." WEST EAST SOUTH NORTH] #ROOM [NHOUS "North of House." WEST EAST NORTH SOUTH]
#ROOM [PASS1 "East-West Passage" EAST WEST DOWN NORTH] #ROOM [RAVI1 "Deep Ravine" SOUTH DOWN EAST
WEST] #ROOM [MTROL "The Troll Room" WEST EAST NORTH SOUTH TROLL ] #ROOM [CAROU "Round room" NORTH
SOUTH EAST WEST NW NE SE SW OUT])

Obviously there is more information and with this little MDL-function we will be able to extract it.

<DEFINE ATLAS ("AUX" (RMTMP ,ROOMS))
    <REPEAT ()
        <PRIN1 <1 <1 .RMTMP>>>
        <PRINT <2 <1 .RMTMP>>>
        <PRINT <3 <1 .RMTMP>>>
        <PRINT <4 <1 .RMTMP>>>
        <PRINT <5 <1 .RMTMP>>>
        <PRINT <6 <1 .RMTMP>>>
        <PRINT <7 <1 .RMTMP>>>
        <PRINT <8 <1 .RMTMP>>>
        <PRINT <9 <1 .RMTMP>>>
        <PRINT <10 <1 .RMTMP>>>
        <TERPRI>
        <TERPRI>
        <SET RMTMP <REST .RMTMP>>
        <COND (<EMPTY? .RMTMP> <RETURN>)>
        <AGAIN>>>

If we run it we get the following:

TREAS!-ROOMS
"This is a large room, whose north wall is solid granite.  A number of
discarded bags, which crumble at your touch, are scattered about on the
floor."
"Treasure Room"
#FALSE ()
#FALSE ()
#EXIT [DOWN!-WORDS #ROOM [CYCLO "Cyclops Room" WEST NORTH UP CYCLO ]]
(#OBJECT [CHALI chalice])
%<RSUBR-ENTRY '[MSETG TREASURE-ROOM #DECL ("VALUE" <OR FALSE HACK>)] 9083>
0
25

MAINT!-ROOMS
"You are in what appears to have been the maintenance room for Flood
Control Dam #3, judging by the assortment of tool chests around the
room.  Apparently, this room has been ransacked recently, for most of
the valuable equipment is gone. On the wall in front of you is a panel
of buttons, which are labelled in EBCDIC. However, they are of different
color: Red, Brown, Yellow, and Blue. The doors to this room are in the
west and south ends."
"Maintenance Room"
#FALSE ()
#FALSE ()
#EXIT [SOUTH!-WORDS #ROOM [LOBBY "Dam Lobby" SOUTH NORTH EAST MATCH GUIDE ]
WEST!-WORDS #ROOM [LOBBY "Dam Lobby" SOUTH NORTH EAST MATCH GUIDE ]]
(#OBJECT [BUTTO ] #OBJECT [LEAK ] #OBJECT [PUTTY tube] #OBJECT [WRENC wrench]
#OBJECT [SCREW screwdriver])
%<RSUBR-ENTRY '[MSETG MAINT-ROOM #DECL ("VALUE" ANY)] 9723>
0
0

(See Appendix A for full output.)

For “Treasure Room” (the first room) this translates to:

RID     = TREAS
RDESC1  = "This is a large room, whose north wall is solid granite.  A number of 
           discarded bags, which crumble at your touch, are scattered about on the 
	   floor."
RDESC2  = "Treasure Room"
RSEEN?  = False
RLIGHT? = False
REXITS  = Down to "Cyclops Room"
ROBJS   = Chalice
RACTION = TREASURE-ROOM
RVARS   = 0
RVAL    = 25

Converting ROOMs to ZIL

We begin by adding new property definitions to be able to support these inside ROOMs (defs.zil).

;"ROOM DEFINITIONS
  Good practice is to define new object properties with PROPDEF. It's not
  requiered but you then could have a default value for them and wouldn't
  need to specify the property when they have the default value."
  . 
<PROPDEF RDESC1 <>>   ;"LONG DESCRIPTION, <> = handled by routine"
<PROPDEF RDESC2 <>>   ;"SHORT DESCRIPTION"
<PROPDEF RSEEN? <>>   ;"VISITED SWITCH"
<PROPDEF RLIGHT? <>>  ;"ENDOGENOUS LIGHT SOURCE SWITCH"
<PROPDEF RACTION <>>  ;"ROOM-ACTION FUNCTION"
<PROPDEF RVARS 0>		
<PROPDEF RVAL 0>      ;"VALUE FOR VISITING"

And then we can actually define up all ROOMs and their connections (aact.zil). We
have to wait with the OBJECTs because thay are added the other way around in ZIL, i.e. each OBJECT
have a pointer to its parent OBJECT instead. We also use false (<>) in RDESC1 instead of an empty string to refer the long description to a routine.
Note that in ZIL we define the rooms in the reverse order. This is because the order is significant for the thiefs movement and in ZIL normally
added in reverse order. We will examine this more when we return to the thief.

<ROOM MAINT
      (IN ROOMS)
      (RDESC1 
"You are in what appears to have been the maintenance room for Flood|
Control Dam #3, judging by the assortment of tool chests around the|
room.  Apparently, this room has been ransacked recently, for most of|
the valuable equipment is gone. On the wall in front of you is a panel|
of buttons, which are labelled in EBCDIC. However, they are of different|
color: Red, Brown, Yellow, and Blue. The doors to this room are in the|
west and south ends.")
      (RDESC2 "Maintenance Room")
      (RSEEN? <>)
      (RLIGHT? <>)
      (SOUTH TO LOBBY)
      (WEST TO LOBBY)
      (RACTION MAINT-ROOM)
      (RVARS 0)
      (RVAL 0)>

<ROOM TREAS-R           ;"Was TREAS"
      (IN ROOMS)
      (RDESC1 
"This is a large room, whose north wall is solid granite.  A number of|
discarded bags, which crumble at your touch, are scattered about on the|
floor.")
      (RDESC2 "Treasure Room")
      (RSEEN? <>)
      (RLIGHT? <>)
      (DOWN TO CYCLO-R)
      (RACTION TREASURE-ROOM)
      (RVARS 0)
      (RVAL 25)>

Notes:

  • TREAS is renamed TREAS-R to avoid name conflict with the routine TREAS.
  • | in ZIL forces a linebreak.

Maybe you already noticed but all texts inside this game always prints two spaces after
a full stop. This is a relic from
the typewriter days and when all fonts where monospaced. Normally ZILF will remove
these double spaces and replace them with a single one. This line in zork_285.zil
makes sure we preserves all spaces:

;"Force the two spaces after a full stop to be printed."
<SETG PRESERVE-SPACES? T>

The OBJECT-type

Just like for rooms OBJETs have a type defintion in the file DEFS 81 [Jun 30 1977]. The definition in defs.81 are actually a bit newer and the one from June 14 only have 13 slots, as below:

 1 OID       ATOM                   ;"unique name, SETG'd to this"
 2 ODESC1    STRING                 ;"description when not carried"
 3 ODESC2    STRING                 ;"short description"
 4 ODESCO    <OR STRING FALSE>      ;"description when untouched"
 5 OACTION   RAPPLIC                ;"object-action"
 6 OCONTENTS <LIST [REST OBJECT]>   ;"list of contents"
 7 OCAN      <OR FALSE OBJECT>      ;"what contains this"
 8 OFLAGS    <PRIMTYPE WORD>        ;"flags THIS MUST BE SAME OFFSET AS AFLAGS!"
                1 VISIBLE
                2 READABLE
 9 OTOUCH?   <OR ATOM FALSE>        ;"has this been touched?"
10 OLIGHT?   FIX                    ;"light producer?"
                -1 OFF
                 1 ON
                 0 NO LIGHT PRODUCER
11 OFVAL     FIX                    ;"value for finding"
12 OTVAL     FIX                    ;"value for putting in trophy case"
13 OREAD     <OR FALSE STRING>      ;"reading material">

Extracting all the information about the OBJECTs

If we examine the ATOM ´´´OBJECTS´´´, we find:

,OBJECTS$
(#OBJECT [THIEF ] #OBJECT [PAINT painting] #OBJECT [CHALI chalice] #OBJECT [CYCLO ] #OBJECT [SCREW
screwdriver] #OBJECT [WRENC wrench] #OBJECT [PUTTY tube] #OBJECT [LEAK ] #OBJECT [BUTTO ] #OBJECT [
GUIDE guidebook] #OBJECT [MATCH matchbook] #OBJECT [BUBBL ] #OBJECT [BOLT ] #OBJECT [DAM ] #OBJECT [
CANDL pair of candles] #OBJECT [BOOK book] #OBJECT [BELL bell] #OBJECT [GRAIL grail] #OBJECT [TORCH
torch] #OBJECT [TRIDE diamond trident] #OBJECT [RUBY ruby] #OBJECT [GHOST ] #OBJECT [REFLE ]
#OBJECT [ICE ] #OBJECT [COFFI gold coffin] #OBJECT [TRUNK trunk with jewels] #OBJECT [GRATE ]
#OBJECT [PEARL pearl necklace] #OBJECT [BAR platinum bar] #OBJECT [KEYS set of skeleton keys]
#OBJECT [BAGCO bag of coins] #OBJECT [BONES ] #OBJECT [TROLL ] #OBJECT [PILE pile of leaves]
#OBJECT [RUG carpet] #OBJECT [LAMP lamp] #OBJECT [DOOR ] #OBJECT [KNIFE knife] #OBJECT [ROPE rope]
#OBJECT [WATER water in BOTTL] #OBJECT [TROPH trophy case] #OBJECT [BOTTL bottle WATER ] #OBJECT [
FOOD .lunch] #OBJECT [WINDO ])

As with ROOMs we can use a MDL-function to extract more information.

<DEFINE CATALOG ("AUX" (OBJTMP ,OBJECTS))
    <REPEAT ()
        <PRIN1 <1 <1 .OBJTMP>>>
        <PRINT <2 <1 .OBJTMP>>>
        <PRINT <3 <1 .OBJTMP>>>
        <PRINT <4 <1 .OBJTMP>>>
        <PRINT <5 <1 .OBJTMP>>>
        <PRINT <6 <1 .OBJTMP>>>
        <PRINT <7 <1 .OBJTMP>>>
        <PRINT <8 <1 .OBJTMP>>>
        <PRINT <9 <1 .OBJTMP>>>
        <PRINT <10 <1 .OBJTMP>>>
        <PRINT <11 <1 .OBJTMP>>>
        <PRINT <12 <1 .OBJTMP>>>
        <PRINT <13 <1 .OBJTMP>>>
        <TERPRI>
        <TERPRI>
        <SET OBJTMP <REST .OBJTMP>>
        <COND (<EMPTY? .OBJTMP> <RETURN>)>
        <AGAIN>>>

If we run it we get the following (listing only PAINT & WATER):

PAINT!-OBJECTS
"A masterpiece by a neglected genius is here."
"painting"
"Fortunately, there is still one chance for you to be a vandal, for on
the far wall is a work of unparalleled beauty."
#FALSE ()
()
#FALSE ()
1
#FALSE ()
0
4
7
#FALSE ()

WATER!-OBJECTS
"Water"
"water"
"There is some water here"
%<RSUBR-ENTRY '[MSETG WATER-FUNCTION #DECL ("VALUE" ANY "OPTIONAL" <OR ATOM FALSE>)] 10425>
()
#OBJECT [BOTTL bottle WATER ]
1
#FALSE ()
0
0
0
#FALSE ()

(See Appendix B for full output.)

For “painting” this translates to:

OID       = PAINT
ODESC1    = "A masterpiece by a neglected genius is here."
ODESC2    = "painting"
ODESCO    = "Fortunately, there is still one chance for you to be a vandal, for on
             the far wall is a work of unparalleled beauty."
OACTION   = False
OCONTENTS = Empty list
OCAN      = False
OFLAGS    = 1
OTOUCH?   = False
OLIGHT?   = 0
OFVAL     = 4
OTVAL     = 7
OREAD     = False

There is also an OBLIST for the objects that connect the objects with heir synonyms. Another little MDL-function:

<DEFINE OBJ-SYNONYMS ("AUX" (WTMP <MOBLIST OBJECTS>))
    <REPEAT (WOBL)
        <SET WOBL <1 .WTMP>>
        <REPEAT ()
            <PRIN1 <1 .WOBL>>
            <PRINC "     ">
            ;"Unassigned are probably ADJECTIVES." 
            <COND (<GASSIGNED? <1 .WOBL>> <PRIN1 ,<1 .WOBL>>)>
            <TERPRI>
            <SET WOBL <REST .WOBL>>
            <COND (<EMPTY? .WOBL> <RETURN>)>
            <AGAIN>>
        <SET WTMP <REST .WTMP>>
        <COND (<EMPTY? .WTMP> <RETURN>)>
        <AGAIN>>>

We can extract the synonyms.

<OBJ-SYNONYMS>
HOLE!-OBJECTS      #OBJECT [LEAK ]
MAN!-OBJECTS       #OBJECT [THIEF ]
ROBBE!-OBJECTS     #OBJECT [THIEF ]
CUP!-OBJECTS       #OBJECT [CHALI chalice]
TUBE!-OBJECTS      #OBJECT [PUTTY tube]
CASKE!-OBJECTS     #OBJECT [COFFI gold coffin]
DOOR!-OBJECTS      #OBJECT [DOOR ]
WINDO!-OBJECTS     #OBJECT [WINDO ]
SWITC!-OBJECTS     #OBJECT [BUTTO ]
THUG!-OBJECTS      #OBJECT [THIEF ]
THIEF!-OBJECTS     #OBJECT [THIEF ]
FLINT!-OBJECTS     #OBJECT [MATCH matchbook]
GRATE!-OBJECTS     #OBJECT [GRATE ]
LEAF!-OBJECTS      #OBJECT [PILE pile of leaves]
PILE!-OBJECTS      #OBJECT [PILE pile of leaves]
BLADE!-OBJECTS     #OBJECT [KNIFE knife]
ROPE!-OBJECTS      #OBJECT [ROPE rope]
KNIFE!-OBJECTS     #OBJECT [KNIFE knife]
TRAPD!-OBJECTS     #OBJECT [DOOR ]
MONST!-OBJECTS     #OBJECT [CYCLO ]
ONE-E!-OBJECTS     #OBJECT [CYCLO ]
MATCH!-OBJECTS     #OBJECT [MATCH matchbook]
CANDL!-OBJECTS     #OBJECT [CANDL pair of candles]
FIRE!-OBJECTS      #OBJECT [TORCH torch]
FLAME!-OBJECTS     #OBJECT [TORCH torch]
ICE!-OBJECTS       #OBJECT [ICE ]
TRUNK!-OBJECTS     #OBJECT [TRUNK trunk with jewels]
BODY!-OBJECTS      #OBJECT [BONES ]
SKELE!-OBJECTS     #OBJECT [BONES ]
BAGCO!-OBJECTS     #OBJECT [BAGCO bag of coins]
YELLO!-OBJECTS
DRIP!-OBJECTS      #OBJECT [LEAK ]
BANDI!-OBJECTS     #OBJECT [THIEF ]
ART!-OBJECTS       #OBJECT [PAINT painting]
PAINT!-OBJECTS     #OBJECT [PAINT painting]
GUIDE!-OBJECTS     #OBJECT [GUIDE guidebook]
DAM!-OBJECTS       #OBJECT [DAM ]
BOOK!-OBJECTS      #OBJECT [BOOK book]
SPIRI!-OBJECTS     #OBJECT [GHOST ]
PLATI!-OBJECTS     #OBJECT [BAR platinum bar]
BONES!-OBJECTS     #OBJECT [BONES ]
TROLL!-OBJECTS     #OBJECT [TROLL ]
CASE!-OBJECTS      #OBJECT [TROPH trophy case]
SANDW!-OBJECTS     #OBJECT [FOOD .lunch]
BOTTL!-OBJECTS     #OBJECT [BOTTL bottle WATER ]
FOOD!-OBJECTS      #OBJECT [FOOD .lunch]
RED!-OBJECTS
CRIME!-OBJECTS     #OBJECT [THIEF ]
CROOK!-OBJECTS     #OBJECT [THIEF ]
PUTTY!-OBJECTS     #OBJECT [PUTTY tube]
BUTTO!-OBJECTS     #OBJECT [BUTTO ]
BUBBL!-OBJECTS     #OBJECT [BUBBL ]
GLACI!-OBJECTS     #OBJECT [ICE ]
LANTE!-OBJECTS     #OBJECT [LAMP lamp]
LAMP!-OBJECTS      #OBJECT [LAMP lamp]
CONTA!-OBJECTS     #OBJECT [BOTTL bottle WATER ]
SLUIC!-OBJECTS     #OBJECT [DAM ]
BELL!-OBJECTS      #OBJECT [BELL bell]
IVORY!-OBJECTS     #OBJECT [TORCH torch]
NECKL!-OBJECTS     #OBJECT [PEARL pearl necklace]
BAR!-OBJECTS       #OBJECT [BAR platinum bar]
KEYS!-OBJECTS      #OBJECT [KEYS set of skeleton keys]
TROPH!-OBJECTS     #OBJECT [TROPH trophy case]
LUNCH!-OBJECTS     #OBJECT [FOOD .lunch]
RUG!-OBJECTS       #OBJECT [RUG carpet]
GATES!-OBJECTS     #OBJECT [DAM ]
WRENC!-OBJECTS     #OBJECT [WRENC wrench]
DIAMO!-OBJECTS     #OBJECT [TRIDE diamond trident]
BLUE!-OBJECTS
NUT!-OBJECTS       #OBJECT [BOLT ]
GUNK!-OBJECTS      #OBJECT [PUTTY tube]
LEAK!-OBJECTS      #OBJECT [LEAK ]
GRAIL!-OBJECTS     #OBJECT [GRAIL grail]
MIRRO!-OBJECTS     #OBJECT [REFLE ]
GREEN!-OBJECTS     #OBJECT [PILE pile of leaves]
HEMP!-OBJECTS      #OBJECT [ROPE rope]
TRAP-!-OBJECTS     #OBJECT [DOOR ]
MAFIA!-OBJECTS     #OBJECT [THIEF ]
CHALI!-OBJECTS     #OBJECT [CHALI chalice]
BOLT!-OBJECTS      #OBJECT [BOLT ]
CHEST!-OBJECTS     #OBJECT [TRUNK trunk with jewels]
GHOST!-OBJECTS     #OBJECT [GHOST ]
BAG!-OBJECTS       #OBJECT [BAGCO bag of coins]
LIQUI!-OBJECTS     #OBJECT [WATER water in BOTTL]
DINNE!-OBJECTS     #OBJECT [FOOD .lunch]
GRATI!-OBJECTS     #OBJECT [GRATE ]
GATE!-OBJECTS      #OBJECT [DAM ]
JEWEL!-OBJECTS     #OBJECT [TRUNK trunk with jewels]
REFLE!-OBJECTS     #OBJECT [REFLE ]
ORIEN!-OBJECTS     #OBJECT [RUG carpet]
COIL!-OBJECTS      #OBJECT [ROPE rope]
PITCH!-OBJECTS     #OBJECT [BOTTL bottle WATER ]
WATER!-OBJECTS     #OBJECT [WATER water in BOTTL]
SNACK!-OBJECTS     #OBJECT [FOOD .lunch]
PEPPE!-OBJECTS     #OBJECT [FOOD .lunch]
PORTA!-OBJECTS     #OBJECT [DOOR ]
LEAVE!-OBJECTS     #OBJECT [PILE pile of leaves]
PLANT!-OBJECTS     #OBJECT [PILE pile of leaves]
H2O!-OBJECTS       #OBJECT [WATER water in BOTTL]
MASTE!-OBJECTS     #OBJECT [PAINT painting]
GOBLE!-OBJECTS     #OBJECT [CHALI chalice]
CYCLO!-OBJECTS     #OBJECT [CYCLO ]
FIEND!-OBJECTS     #OBJECT [GHOST ]
COINS!-OBJECTS     #OBJECT [BAGCO bag of coins]
CARPE!-OBJECTS     #OBJECT [RUG carpet]
BROWN!-OBJECTS
BAGMA!-OBJECTS     #OBJECT [THIEF ]
SHADY!-OBJECTS     #OBJECT [THIEF ]
CRIMI!-OBJECTS     #OBJECT [THIEF ]
CANVA!-OBJECTS     #OBJECT [PAINT painting]
SCREW!-OBJECTS     #OBJECT [SCREW screwdriver]
TORCH!-OBJECTS     #OBJECT [TORCH torch]
FORK!-OBJECTS      #OBJECT [TRIDE diamond trident]
TRIDE!-OBJECTS     #OBJECT [TRIDE diamond trident]
RUBY!-OBJECTS      #OBJECT [RUBY ruby]
COFFI!-OBJECTS     #OBJECT [COFFI gold coffin]
PEARL!-OBJECTS     #OBJECT [PEARL pearl necklace]
SACK!-OBJECTS      #OBJECT [FOOD .lunch]

YELLO, RED, BLUE & BROWN are not connected to any object and are here as some sort of first attempt to define adjectives.

Now we have all the ingredients to define the objects in ZIL.

Converting OBJECTs to ZIL

First we add the new property definitions to objects to support the OBJECTSs (defs.zil).

;"OBJECT DEFINITIONS"

<PROPDEF ODESC1 <>>     ;"DESCRIPTION WHEN ON GROUND"
<PROPDEF ODESC2 <>>     ;"SHORT DESCRIPTION"
<PROPDEF ODESC0 <>>     ;"DESCRIPTION WHEN UNTOUCHED"
<PROPDEF OACTION <>>    ;"APPLICABLE"
<PROPDEF OFLAGS 0>      ;"RANDOM FLAGS
                            1 VISIBLE
                            2 READABLE"
<PROPDEF OTOUCH? <>>    ;"HAS THIS BEEN TOUCHED?"
<PROPDEF OLIGHT? 0>     ;"LIGHT PRODUCER?
                           -1 OFF
                            1 ON
                            0 NO LIGHT PRODUCER"
<PROPDEF OFVAL 0>       ;"VALUE FOR FINDING IT"
<PROPDEF OTVAL 0>       ;"VALUE FOR PUTTING IN TROPHY CASE"
<PROPDEF ORAND <>>      ;"RANDOM SLOT"
<PROPDEF OREAD <>>      ;"TEXT WHEN READING"

As with the ROOMs we use <> instead of an empty string.
The result is in (aact.zil).
The big difference between the definitions in MDL and in ZIL is that in ZIL the location of the objects is specified in the object instead of being defined in the container (ROBJS in room or OCONTENTS in objects). WATER is, for example, (IN BOTTL) instead of defined in the OCONTENTS of BOTTL and PAINT is (IN GALLE) instead of defined in the ROBJS of GALLE.
Note that, as with the rooms, the objects are in the reverse order. This is because the order will affect in which order untoched objects are listed in room descriptions.

<OBJECT WATER 
	(IN BOTTL)
	(SYNONYM WATER LIQUI H2O)
	(ODESC1 "Water")
	(ODESC2 "water")
	(ODESC0 "There is some water here")
	(OACTION WATER-FUNCTION)
	(OFLAGS 1)
	(OTOUCH? <>)
	(OLIGHT? 0)
	(OFVAL 0)
	(OTVAL 0)
	(ORAND <>)
	(OREAD <>)>

<OBJECT PAINT 
    (IN GALLE)
    (SYNONYM PAINT ART MASTE CANVA)
    (ODESC1 "A masterpiece by a neglected genius is here.")
    (ODESC2 "painting")
    (ODESC0 
"Fortunately, there is still one chance for you to be a vandal, for on|
the far wall is a work of unparalleled beauty.")
    (OACTION <>)
    (OFLAGS 1)
    (OTOUCH? <>)
    (OLIGHT? 0)
    (OFVAL 4)
    (OTVAL 7)   
    (ORAND <>)
    (OREAD <>)>

Finally we need to add the “adjectives” to the vocabulary. If we don’t do this, these words will be unrecognised by the parser.

;"Add these as adjectives. In the original they are only defined as ATOMs 
  in the OBJECTS OBLIST."
<VOC "RED" ADJECTIVE>
<VOC "BLUE" ADJECTIVE>
<VOC "YELLO" ADJECTIVE>
<VOC "BROWN" ADJECTIVE>

Next: Part 4 - The main loop

2 Likes

Appendix A (1/2) - ROOMs

TREAS!-ROOMS
"This is a large room, whose north wall is solid granite.  A number of
discarded bags, which crumble at your touch, are scattered about on the
floor."
"Treasure Room"
#FALSE ()
#FALSE ()
#EXIT [DOWN!-WORDS #ROOM [CYCLO "Cyclops Room" WEST NORTH UP CYCLO ]]
(#OBJECT [CHALI chalice])
%<RSUBR-ENTRY '[MSETG TREASURE-ROOM #DECL ("VALUE" <OR FALSE HACK>)] 9083>
0
25

MAINT!-ROOMS
"You are in what appears to have been the maintenance room for Flood
Control Dam #3, judging by the assortment of tool chests around the
room.  Apparently, this room has been ransacked recently, for most of
the valuable equipment is gone. On the wall in front of you is a panel
of buttons, which are labelled in EBCDIC. However, they are of different
color: Red, Brown, Yellow, and Blue. The doors to this room are in the
west and south ends."
"Maintenance Room"
#FALSE ()
#FALSE ()
#EXIT [SOUTH!-WORDS #ROOM [LOBBY "Dam Lobby" SOUTH NORTH EAST MATCH GUIDE ] WEST!-WORDS #ROOM [LOBBY
 "Dam Lobby" SOUTH NORTH EAST MATCH GUIDE ]]
(#OBJECT [BUTTO ] #OBJECT [LEAK ] #OBJECT [PUTTY tube] #OBJECT [WRENC wrench] #OBJECT [SCREW
screwdriver])
%<RSUBR-ENTRY '[MSETG MAINT-ROOM #DECL ("VALUE" ANY)] 9723>
0
0

LOBBY!-ROOMS
"This room appears to have been the waiting room for groups touring
the dam.  There are exits here to the north and east marked 'Private',
though the doors are open, and an exit to the south."
"Dam Lobby"
#FALSE ()
T
#EXIT [SOUTH!-WORDS #ROOM [DAM "Dam" SOUTH EAST NORTH DAM BOLT BUBBL ] NORTH!-WORDS #CEXIT [
DROWNED!-FLAG #ROOM [MAINT "Maintenance Room" SOUTH WEST BUTTO LEAK PUTTY WRENC SCREW ]
"The room is full of water:  if you enter, you'll drown."] EAST!-WORDS #CEXIT [DROWNED!-FLAG #ROOM [
MAINT "Maintenance Room" SOUTH WEST BUTTO LEAK PUTTY WRENC SCREW ]
"The room is full of water:  if you enter, you'll drown."]]
(#OBJECT [MATCH matchbook] #OBJECT [GUIDE guidebook])
#FALSE ()
0
0

TEMP2!-ROOMS
"You are in the east end of a large temple.  In front of you is what
appears to be an altar."
"Altar"
#FALSE ()
T
#EXIT [WEST!-WORDS #ROOM [TEMP1 "Temple" WEST EAST BELL ]]
(#OBJECT [BOOK book] #OBJECT [CANDL pair of candles])
#FALSE ()
0
0

TEMP1!-ROOMS
"You are in the west end of a large temple.  On the south wall is an
ancient inscription, probably a prayer in a long-forgotten language.
The north wall is granite.  The entrance at the west end of the room is
through huge marble pillars."
"Temple"
#FALSE ()
T
#EXIT [WEST!-WORDS #ROOM [MGRAI "Grail Room" WEST EAST UP GRAIL ] EAST!-WORDS #ROOM [TEMP2 "Altar"
WEST BOOK CANDL ]]
(#OBJECT [BELL bell])
#FALSE ()
0
0

LLD2!-ROOMS
"You have entered the Land of the Living Dead, a large desolate room.
Although it is apparently uninhabited, you can hear the sounds of
thousands of lost souls weeping and moaning.  In the east corner are
stacked the remains of dozens of previous adventurers who were less
fortunate than yourself (so far)."
"Land of the Living Dead"
#FALSE ()
T
#EXIT [EXIT!-WORDS #ROOM [LLD1 "Entrance to Hades" EAST UP ENTER GHOST ] WEST!-WORDS #ROOM [LLD1
"Entrance to Hades" EAST UP ENTER GHOST ]]
()
#FALSE ()
0
30

MPEAR!-ROOMS
"This is a room the size of a broom closet which appears to be
a dead end.  The only exit is to the west."
"Pearl Room"
#FALSE ()
#FALSE ()
#EXIT [EXIT!-WORDS #ROOM [RIDDL "Riddle Room" DOWN EAST] WEST!-WORDS #ROOM [RIDDL "Riddle Room" DOWN
 EAST]]
(#OBJECT [PEARL pearl necklace])
#FALSE ()
0
0

RIDDL!-ROOMS
"This is a room which is bare on all sides.  There is an exit down.  To the east is a
great door made of stone.  Above the stone, the following words are written:
'No man shall enter this room without solving this riddle:
  What is tall as a house,
          round as a cup,
          and all the king's horses can't draw it up?'"
"Riddle Room"
#FALSE ()
#FALSE ()
#EXIT [DOWN!-WORDS #ROOM [CAVE4 "Engravings Cave" NORTH SE] EAST!-WORDS #CEXIT [RIDDLE-FLAG!-FLAG
#ROOM [MPEAR "Pearl Room" EXIT WEST PEARL ] "Your way is blocked by an invisible force."]]
()
#FALSE ()
0
0

DEAD6!-ROOMS
"Dead end"
"Dead end"
#FALSE ()
#FALSE ()
#EXIT [EAST!-WORDS #ROOM [CHAS3 "Ancient Chasm" SOUTH EAST NORTH WEST]]
()
#FALSE ()
0
0

DEAD5!-ROOMS
"Dead end"
"Dead end"
#FALSE ()
#FALSE ()
#EXIT [SW!-WORDS #ROOM [CHAS3 "Ancient Chasm" SOUTH EAST NORTH WEST]]
()
#FALSE ()
0
0

CAVE4!-ROOMS
"You have entered a cave with passages leading north and southeast.  There
are old engravings on the walls here."
"Engravings Cave"
#FALSE ()
#FALSE ()
#EXIT [NORTH!-WORDS #ROOM [CAROU "Round room" NORTH SOUTH EAST WEST NW NE SE SW OUT] SE!-WORDS
#ROOM [RIDDL "Riddle Room" DOWN EAST]]
()
#FALSE ()
0
0

GALLE!-ROOMS
"You are in an art gallery (apparently the painter generated more than
mess).  Most of the paintings which were here have been stolen by
vandals with exceptional taste.  The vandals left through either the
north or south exits."
"Gallery"
#FALSE ()
T
#EXIT [NORTH!-WORDS #ROOM [CHAS2 "West of Chasm" WEST NORTH SOUTH DOWN] SOUTH!-WORDS #ROOM [STUDI
"Studio" NORTH NW UP]]
(#OBJECT [PAINT painting])
#FALSE ()
0
0

STUDI!-ROOMS
"You are in what appears to have been an artist's studio.  The walls and
floors are splattered with paints of 69 different colors.  Strangely
enough, nothing of value is hanging here.  At the north and northwest of
the room are open doors (also covered with paint).  An extremely dark
and narrow chimney leads up from a fireplace; although you might be able
to get up it, it seems unlikely you could get back down."
"Studio"
#FALSE ()
#FALSE ()
#EXIT [NORTH!-WORDS #ROOM [CRAW4 "North-South Crawlway" NORTH SOUTH EAST UP] NW!-WORDS #ROOM [GALLE
"Gallery" NORTH SOUTH PAINT ] UP!-WORDS #CEXIT [LIGHT-LOAD!-FLAG #ROOM [KITCH "Kitchen" EAST WEST
EXIT UP DOWN WINDO FOOD BOTTL ] "The chimney is too narrow for you and all of your baggage."]]
()
%<RSUBR-ENTRY '[MSETG STUDIO-FUNCTION #DECL ("VALUE" <OR ATOM FALSE>)] 7428>
0
0

MTORC!-ROOMS
""
"Torch Room"
#FALSE ()
#FALSE ()
#EXIT [WEST!-WORDS #ROOM [MTORC "Torch Room" WEST DOWN TORCH ] DOWN!-WORDS #ROOM [CRAW4
"North-South Crawlway" NORTH SOUTH EAST UP]]
(#OBJECT [TORCH torch])
%<RSUBR-ENTRY '[MSETG TORCH-ROOM #DECL ("VALUE" ANY)] 8364>
0
0

SLIDE!-ROOMS
"You are in a small chamber, which appears to have been part of a coal
mine. On the south wall of the chamber the letters \"Granite Wall\" are
etched in the rock. To the east is a long passage and there is a steep
metal slide twisting downward. From the appearance of the slide, an
attempt to climb up it would be impossible."
"Slide Room"
#FALSE ()
#FALSE ()
#EXIT [EAST!-WORDS #ROOM [PASS3 "Cold Passage" EAST WEST NORTH] DOWN!-WORDS #ROOM [CELLA "Cellar"
EAST SOUTH UP WEST DOOR ]]
()
#FALSE ()
0
0

MGRAI!-ROOMS
"You are standing in a small circular room with a pedestal.  A set of
stairs leads up, and passages leave to the east and west."
"Grail Room"
#FALSE ()
#FALSE ()
#EXIT [WEST!-WORDS #ROOM [CAROU "Round room" NORTH SOUTH EAST WEST NW NE SE SW OUT] EAST!-WORDS
#ROOM [CRAW3 "Narrow Crawlway" SOUTH SW NORTH] UP!-WORDS #ROOM [TEMP1 "Temple" WEST EAST BELL ]]
(#OBJECT [GRAIL grail])
#FALSE ()
0
0

LLD1!-ROOMS
""
"Entrance to Hades"
#FALSE ()
T
#EXIT [EAST!-WORDS #CEXIT [LLD-FLAG!-FLAG #ROOM [LLD2 "Land of the Living Dead" EXIT WEST]
"Some invisible force prevents you from passing through the gate."] UP!-WORDS #ROOM [CAVE2 "Cave"
NORTH WEST DOWN] ENTER!-WORDS #CEXIT [LLD-FLAG!-FLAG #ROOM [LLD2 "Land of the Living Dead" EXIT WEST
] "Some invisible force prevents you from passing through the gate."]]
(#OBJECT [GHOST ])
%<RSUBR-ENTRY '[MSETG LLD-ROOM #DECL ("VALUE" ANY)] 8798>
0
0

MIRR2!-ROOMS
""
"Mirror Room"
#FALSE ()
T
#EXIT [WEST!-WORDS #ROOM [PASS4 "Winding Passage" EAST NORTH] NORTH!-WORDS #ROOM [CRAW3
"Narrow Crawlway" SOUTH SW NORTH] EAST!-WORDS #ROOM [CAVE2 "Cave" NORTH WEST DOWN]]
(#OBJECT [REFLE ])
%<RSUBR-ENTRY '[MSETG MIRROR-ROOM #DECL ("VALUE" ANY)] 7960>
0
0

CAVE2!-ROOMS
"You are in a tiny cave with entrances west and north, and a dark,
forbidding staircase leading down."
"Cave"
#FALSE ()
#FALSE ()
#EXIT [NORTH!-WORDS #ROOM [CRAW3 "Narrow Crawlway" SOUTH SW NORTH] WEST!-WORDS #ROOM [MIRR2
"Mirror Room" WEST NORTH EAST REFLE ] DOWN!-WORDS #ROOM [LLD1 "Entrance to Hades" EAST UP ENTER
GHOST ]]
()
%<RSUBR-ENTRY '[MSETG CAVE2-ROOM #DECL ("VALUE" ANY)] 10039>
0
0

CRAW3!-ROOMS
"You are in a narrow crawlway.  The crawlway leads from north to south.
However the south passage divides to the south and southwest."
"Narrow Crawlway"
#FALSE ()
#FALSE ()
#EXIT [SOUTH!-WORDS #ROOM [CAVE2 "Cave" NORTH WEST DOWN] SW!-WORDS #ROOM [MIRR2 "Mirror Room" WEST
NORTH EAST REFLE ] NORTH!-WORDS #ROOM [MGRAI "Grail Room" WEST EAST UP GRAIL ]]
()
#FALSE ()
0
0

PASS4!-ROOMS
"You are in a winding passage.  It seems that there is only an exit on the
east end although the whirring from the carousel room can be heard faintly
to the north."
"Winding Passage"
#FALSE ()
#FALSE ()
#EXIT [EAST!-WORDS #ROOM [MIRR2 "Mirror Room" WEST NORTH EAST REFLE ] NORTH!-WORDS #NEXIT
"You can hear the whir of the carousel room here but can find no entrance."]
()
#FALSE ()
0
0

MIRR1!-ROOMS
""
"Mirror Room"
#FALSE ()
#FALSE ()
#EXIT [WEST!-WORDS #ROOM [PASS3 "Cold Passage" EAST WEST NORTH] NORTH!-WORDS #ROOM [CRAW2
"Steep Crawlway" SOUTH SW] EAST!-WORDS #ROOM [CAVE1 "Cave" NORTH DOWN]]
(#OBJECT [REFLE ])
%<RSUBR-ENTRY '[MSETG MIRROR-ROOM #DECL ("VALUE" ANY)] 7960>
0
0

CRAW2!-ROOMS
"You are in a steep and narrow crawlway.  There are two exits nearby to
the south and southwest."
"Steep Crawlway"
#FALSE ()
#FALSE ()
#EXIT [SOUTH!-WORDS #ROOM [MIRR1 "Mirror Room" WEST NORTH EAST REFLE ] SW!-WORDS #ROOM [PASS3
"Cold Passage" EAST WEST NORTH]]
()
#FALSE ()
0
0

PASS3!-ROOMS
"You are in a cold and damp corridor where a long east-west passageway
intersects with a northward path."
"Cold Passage"
#FALSE ()
#FALSE ()
#EXIT [EAST!-WORDS #ROOM [MIRR1 "Mirror Room" WEST NORTH EAST REFLE ] WEST!-WORDS #ROOM [SLIDE
"Slide Room" EAST DOWN] NORTH!-WORDS #ROOM [CRAW2 "Steep Crawlway" SOUTH SW]]
()
#FALSE ()
0
0

ECHO!-ROOMS
"You are in a large room with a ceiling which cannot be detected from
the ground. There is a narrow passage from east to west and a stone
stairway leading upward.  The room is extremely noisy.  In fact, it is
difficult to hear yourself think."
"Loud Room"
#FALSE ()
#FALSE ()
#EXIT [EAST!-WORDS #ROOM [CHAS3 "Ancient Chasm" SOUTH EAST NORTH WEST] WEST!-WORDS #ROOM [PASS5
"North-South Passage" NORTH NE SOUTH] UP!-WORDS #ROOM [CAVE3 "Damp Cave" SOUTH EAST WEST]]
(#OBJECT [BAR platinum bar])
%<RSUBR-ENTRY '[MSETG ECHO-ROOM #DECL ("VALUE" ANY)] 11019>
0
0

CAVE3!-ROOMS
"You are in a cave.  Passages exit to the south and to the east, but the
cave narrows to a tiny crack to the west.  The earth is particularly
damp here."
"Damp Cave"
#FALSE ()
#FALSE ()
#EXIT [SOUTH!-WORDS #ROOM [ECHO "Loud Room" EAST WEST UP BAR ] EAST!-WORDS #ROOM [DAM "Dam" SOUTH
EAST NORTH DAM BOLT BUBBL ] WEST!-WORDS #NEXIT "It is too narrow for most insects."]
()
#FALSE ()
0
0

PASS5!-ROOMS
"You are in a high north-south passage, which forks to the northeast."
"North-South Passage"
#FALSE ()
#FALSE ()
#EXIT [NORTH!-WORDS #ROOM [CHAS1 "Chasm" SOUTH EAST DOWN] NE!-WORDS #ROOM [ECHO "Loud Room" EAST
WEST UP BAR ] SOUTH!-WORDS #ROOM [CAROU "Round room" NORTH SOUTH EAST WEST NW NE SE SW OUT]]
()
#FALSE ()
0
0

CHAS3!-ROOMS
"A chasm, evidently produced by an ancient river, runs through the
cave here.  Passages lead off in all directions."
"Ancient Chasm"
#FALSE ()
#FALSE ()
#EXIT [SOUTH!-WORDS #ROOM [ECHO "Loud Room" EAST WEST UP BAR ] EAST!-WORDS #ROOM [CHAS3
"Ancient Chasm" SOUTH EAST NORTH WEST] NORTH!-WORDS #ROOM [DEAD5 "Dead end" SW] WEST!-WORDS #ROOM [
DEAD6 "Dead end" EAST]]
()
#FALSE ()
0
0

DAM!-ROOMS
""
"Dam"
#FALSE ()
#FALSE ()
#EXIT [SOUTH!-WORDS #ROOM [CANY1 "Deep Canyon" SE EAST SOUTH] EAST!-WORDS #ROOM [CAVE3 "Damp Cave"
SOUTH EAST WEST] NORTH!-WORDS #ROOM [LOBBY "Dam Lobby" SOUTH NORTH EAST MATCH GUIDE ]]
(#OBJECT [DAM ] #OBJECT [BOLT ] #OBJECT [BUBBL ])
%<RSUBR-ENTRY '[MSETG DAM-ROOM #DECL ("VALUE" ANY)] 9515>
0
0

CAVE1!-ROOMS
"You are in a small cave with an entrance to the north and a stairway
leading down."
"Cave"
#FALSE ()
#FALSE ()
#EXIT [NORTH!-WORDS #ROOM [MIRR1 "Mirror Room" WEST NORTH EAST REFLE ] DOWN!-WORDS #ROOM [ATLAN
"Atlantis Room" SE UP TRIDE ]]
()
#FALSE ()
0
0

RUBYR!-ROOMS
"You are in a small chamber behind the remains of the Great Glacier."
"Ruby Room"
#FALSE ()
#FALSE ()
#EXIT [SOUTH!-WORDS #ROOM [ICY "Glacier Room" NORTH EAST WEST ICE ]]
(#OBJECT [RUBY ruby])
#FALSE ()
0
0

ICY!-ROOMS
""
"Glacier Room"
#FALSE ()
#FALSE ()
#EXIT [NORTH!-WORDS #ROOM [STREA "Stream" EAST NORTH] EAST!-WORDS #ROOM [EGYPT "Egyptian Room" UP
EAST COFFI ] WEST!-WORDS #CEXIT [GLACIER-FLAG!-FLAG #ROOM [RUBYR "Ruby Room" SOUTH RUBY ] #FALSE ()]
]
(#OBJECT [ICE ])
%<RSUBR-ENTRY '[MSETG GLACIER-ROOM #DECL ("VALUE" ANY)] 6606>
0
0

ATLAN!-ROOMS
"You are in an ancient room, long buried by the Reservoir."
"Atlantis Room"
#FALSE ()
#FALSE ()
#EXIT [SE!-WORDS #ROOM [RESEN "Reservoir North" NORTH CROSS SOUTH] UP!-WORDS #ROOM [CAVE1 "Cave"
NORTH DOWN]]
(#OBJECT [TRIDE diamond trident])
#FALSE ()
0
0

STREA!-ROOMS
"You are standing on a path alongside a flowing stream.  The path
travels to the north and the east."
"Stream"
#FALSE ()
#FALSE ()
#EXIT [EAST!-WORDS #ROOM [RESES "Reservoir South" SOUTH WEST CROSS NORTH UP TRUNK ] NORTH!-WORDS
#ROOM [ICY "Glacier Room" NORTH EAST WEST ICE ]]
()
#FALSE ()
0
0

CANY1!-ROOMS
"You are along the south edge of a deep canyon.  Passages lead off
to the east, south, and southeast.  You can here the sound of
flowing water below."
"Deep Canyon"
#FALSE ()
#FALSE ()
#EXIT [SE!-WORDS #ROOM [RESES "Reservoir South" SOUTH WEST CROSS NORTH UP TRUNK ] EAST!-WORDS
#ROOM [DAM "Dam" SOUTH EAST NORTH DAM BOLT BUBBL ] SOUTH!-WORDS #ROOM [CAROU "Round room" NORTH
SOUTH EAST WEST NW NE SE SW OUT]]
()
#FALSE ()
0
0

RESEN!-ROOMS
""
"Reservoir North"
#FALSE ()
#FALSE ()
#EXIT [NORTH!-WORDS #ROOM [ATLAN "Atlantis Room" SE UP TRIDE ] CROSS!-WORDS #CEXIT [LOW-TIDE!-FLAG
#ROOM [RESES "Reservoir South" SOUTH WEST CROSS NORTH UP TRUNK ]
"You are not equipped for swimming."] SOUTH!-WORDS #CEXIT [LOW-TIDE!-FLAG #ROOM [RESES
"Reservoir South" SOUTH WEST CROSS NORTH UP TRUNK ] "You are not equipped for swimming."]]
()
%<RSUBR-ENTRY '[MSETG RESERVOIR-NORTH #DECL ("VALUE" ANY)] 6952>
0
0

EGYPT!-ROOMS
"You are in a room which looks like an Egyptian tomb."
"Egyptian Room"
#FALSE ()
#FALSE ()
#EXIT [UP!-WORDS #ROOM [ICY "Glacier Room" NORTH EAST WEST ICE ] EAST!-WORDS #CEXIT [
EGYPT-FLAG!-FLAG #ROOM [CRAW1 "Rocky Crawl" WEST EAST NW]
"The passage is too narrow to accomodate coffins."]]
(#OBJECT [COFFI gold coffin])
#FALSE ()
0
0

DOME!-ROOMS
""
"Dome Room"
#FALSE ()
#FALSE ()
#EXIT [EAST!-WORDS #ROOM [CRAW1 "Rocky Crawl" WEST EAST NW] DOWN!-WORDS #CEXIT [DOME-FLAG!-FLAG
#ROOM [MTORC "Torch Room" WEST DOWN TORCH ] "You cannot go down without fracturing many bones."]
CLIMB!-WORDS #CEXIT [DOME-FLAG!-FLAG #ROOM [MTORC "Torch Room" WEST DOWN TORCH ]
"You cannot go down without fracturing many bones."]]
()
%<RSUBR-ENTRY '[MSETG DOME-ROOM #DECL ("VALUE" ANY)] 8410>
0
0

CRAW1!-ROOMS
"You are in a crawlway with a three-foot high ceiling.  Your footing is
very unsure here due to the assortment of rocks underfoot.  Passages can be
seen in the east, west, and northwest corners of the passage."
"Rocky Crawl"
#FALSE ()
#FALSE ()
#EXIT [WEST!-WORDS #ROOM [RAVI1 "Deep Ravine" SOUTH DOWN EAST WEST] EAST!-WORDS #ROOM [DOME
"Dome Room" EAST DOWN CLIMB] NW!-WORDS #ROOM [EGYPT "Egyptian Room" UP EAST COFFI ]]
()
#FALSE ()
0
0

CHAS1!-ROOMS
"A chasm runs southwest to northeast.  You are on the south edge; the
path exits to the south and to the east."
"Chasm"
#FALSE ()
#FALSE ()
#EXIT [SOUTH!-WORDS #ROOM [RAVI1 "Deep Ravine" SOUTH DOWN EAST WEST] EAST!-WORDS #ROOM [PASS5
"North-South Passage" NORTH NE SOUTH] DOWN!-WORDS #NEXIT "Are you out of your mind?"]
()
#FALSE ()
0
0

RESES!-ROOMS
""
"Reservoir South"
#FALSE ()
#FALSE ()
#EXIT [SOUTH!-WORDS #CEXIT [EGYPT-FLAG!-FLAG #ROOM [RAVI1 "Deep Ravine" SOUTH DOWN EAST WEST]
"The coffin will not fit through this passage."] WEST!-WORDS #ROOM [STREA "Stream" EAST NORTH]
CROSS!-WORDS #CEXIT [LOW-TIDE!-FLAG #ROOM [RESEN "Reservoir North" NORTH CROSS SOUTH]
"You are not equipped for swimming."] NORTH!-WORDS #CEXIT [LOW-TIDE!-FLAG #ROOM [RESEN
"Reservoir North" NORTH CROSS SOUTH] "You are not equipped for swimming."] UP!-WORDS #CEXIT [
EGYPT-FLAG!-FLAG #ROOM [CANY1 "Deep Canyon" SE EAST SOUTH]
"The stairs are too steep for carrying the coffin."]]
(#OBJECT [TRUNK trunk with jewels])
%<RSUBR-ENTRY '[MSETG RESERVOIR-SOUTH #DECL ("VALUE" ANY)] 6897>
0
0

CYCLO!-ROOMS
""
"Cyclops Room"
#FALSE ()
#FALSE ()
#EXIT [WEST!-WORDS #ROOM [MAZ15 "You are in a maze of twisty little passages, all alike." WEST SOUTH
 NE] NORTH!-WORDS #CEXIT [MAGIC-FLAG!-FLAG #ROOM [BLROO "Strange Passage" SOUTH EAST]
"The north wall is solid rock."] UP!-WORDS #CEXIT [CYCLOPS-FLAG!-FLAG #ROOM [TREAS "Treasure Room"
DOWN CHALI ] "The cyclops doesn't look like he'll let you past."]]
(#OBJECT [CYCLO ])
%<RSUBR-ENTRY '[MSETG CYCLOPS-ROOM #DECL ("VALUE" ANY)] 10885>
0
0

DEAD4!-ROOMS
"Dead end."
"Dead end."
#FALSE ()
#FALSE ()
#EXIT [SOUTH!-WORDS #ROOM [MAZ12 "You are in a maze of twisty little passages, all alike." WEST SW
EAST UP NORTH]]
()
#FALSE ()
0
0

MAZ12!-ROOMS
"You are in a maze of twisty little passages, all alike."
"You are in a maze of twisty little passages, all alike."
#FALSE ()
#FALSE ()
#EXIT [WEST!-WORDS #ROOM [MAZE5 "You are in a maze of twisty little passages, all alike." EAST NORTH
 SW BONES BAGCO KEYS ] SW!-WORDS #ROOM [MAZ11
"You are in a maze of twisty little passages, all alike." DOWN NW SW UP GRATE ] EAST!-WORDS #ROOM [
MAZ13 "You are in a maze of twisty little passages, all alike." EAST DOWN SOUTH WEST] UP!-WORDS
#ROOM [MAZE9 "You are in a maze of twisty little passages, all alike." NORTH EAST DOWN SOUTH WEST NW
] NORTH!-WORDS #ROOM [DEAD4 "Dead end." SOUTH]]
()
#FALSE ()
0
0

MAZ13!-ROOMS
"You are in a maze of twisty little passages, all alike."
"You are in a maze of twisty little passages, all alike."
#FALSE ()
#FALSE ()
#EXIT [EAST!-WORDS #ROOM [MAZE9 "You are in a maze of twisty little passages, all alike." NORTH EAST
 DOWN SOUTH WEST NW] DOWN!-WORDS #ROOM [MAZ12
"You are in a maze of twisty little passages, all alike." WEST SW EAST UP NORTH] SOUTH!-WORDS
#ROOM [MAZ10 "You are in a maze of twisty little passages, all alike." EAST WEST UP] WEST!-WORDS
#ROOM [MAZ11 "You are in a maze of twisty little passages, all alike." DOWN NW SW UP GRATE ]]
()
#FALSE ()
0
0

MAZ10!-ROOMS
"You are in a maze of twisty little passages, all alike."
"You are in a maze of twisty little passages, all alike."
#FALSE ()
#FALSE ()
#EXIT [EAST!-WORDS #ROOM [MAZE9 "You are in a maze of twisty little passages, all alike." NORTH EAST
 DOWN SOUTH WEST NW] WEST!-WORDS #ROOM [MAZ13
"You are in a maze of twisty little passages, all alike." EAST DOWN SOUTH WEST] UP!-WORDS #ROOM [
MAZ11 "You are in a maze of twisty little passages, all alike." DOWN NW SW UP GRATE ]]
()
#FALSE ()
0
0

MAZ11!-ROOMS
""
"You are in a maze of twisty little passages, all alike."
#FALSE ()
#FALSE ()
#EXIT [DOWN!-WORDS #ROOM [MAZ10 "You are in a maze of twisty little passages, all alike." EAST WEST
UP] NW!-WORDS #ROOM [MAZ13 "You are in a maze of twisty little passages, all alike." EAST DOWN SOUTH
 WEST] SW!-WORDS #ROOM [MAZ12 "You are in a maze of twisty little passages, all alike." WEST SW EAST
 UP NORTH] UP!-WORDS #CEXIT [KEY-FLAG!-FLAG #ROOM [CLEAR "Clearing" SW NORTH EAST WEST SOUTH DOWN
GRATE PILE ] "The grating is locked"]]
(#OBJECT [GRATE ])
%<RSUBR-ENTRY '[MSETG MAZE-11 #DECL ("VALUE" ANY)] 9001>
0
0

DEAD3!-ROOMS
"Dead end."
"Dead end."
#FALSE ()
#FALSE ()
#EXIT [NORTH!-WORDS #ROOM [MAZE8 "You are in a maze of twisty little passages, all alike." NE WEST
SE]]
()
#FALSE ()
0
0

MAZ15!-ROOMS
"You are in a maze of twisty little passages, all alike."
"You are in a maze of twisty little passages, all alike."
#FALSE ()
#FALSE ()
#EXIT [WEST!-WORDS #ROOM [MAZ14 "You are in a maze of twisty little passages, all alike." WEST NW NE
 SOUTH] SOUTH!-WORDS #ROOM [MAZE7 "You are in a maze of twisty little passages, all alike." UP WEST
NE EAST SOUTH] NE!-WORDS #ROOM [CYCLO "Cyclops Room" WEST NORTH UP CYCLO ]]
()
#FALSE ()
0
0

MAZE8!-ROOMS
"You are in a maze of twisty little passages, all alike."
"You are in a maze of twisty little passages, all alike."
#FALSE ()
#FALSE ()
#EXIT [NE!-WORDS #ROOM [MAZE7 "You are in a maze of twisty little passages, all alike." UP WEST NE
EAST SOUTH] WEST!-WORDS #ROOM [MAZE8 "You are in a maze of twisty little passages, all alike." NE
WEST SE] SE!-WORDS #ROOM [DEAD3 "Dead end." NORTH]]
()
#FALSE ()
0
0

MAZ14!-ROOMS
"You are in a maze of twisty little passages, all alike."
"You are in a maze of twisty little passages, all alike."
#FALSE ()
#FALSE ()
#EXIT [WEST!-WORDS #ROOM [MAZ15 "You are in a maze of twisty little passages, all alike." WEST SOUTH
 NE] NW!-WORDS #ROOM [MAZ14 "You are in a maze of twisty little passages, all alike." WEST NW NE
SOUTH] NE!-WORDS #ROOM [MAZE7 "You are in a maze of twisty little passages, all alike." UP WEST NE
EAST SOUTH] SOUTH!-WORDS #ROOM [MAZE7 "You are in a maze of twisty little passages, all alike." UP
WEST NE EAST SOUTH]]
()
#FALSE ()
0
0

MAZE9!-ROOMS
"You are in a maze of twisty little passages, all alike."
"You are in a maze of twisty little passages, all alike."
#FALSE ()
#FALSE ()
#EXIT [NORTH!-WORDS #ROOM [MAZE6 "You are in a maze of twisty little passages, all alike." DOWN EAST
 WEST UP] EAST!-WORDS #ROOM [MAZ11 "You are in a maze of twisty little passages, all alike." DOWN NW
 SW UP GRATE ] DOWN!-WORDS #ROOM [MAZ10 "You are in a maze of twisty little passages, all alike."
EAST WEST UP] SOUTH!-WORDS #ROOM [MAZ13 "You are in a maze of twisty little passages, all alike."
EAST DOWN SOUTH WEST] WEST!-WORDS #ROOM [MAZ12
"You are in a maze of twisty little passages, all alike." WEST SW EAST UP NORTH] NW!-WORDS #ROOM [
MAZE9 "You are in a maze of twisty little passages, all alike." NORTH EAST DOWN SOUTH WEST NW]]
()
#FALSE ()
0
0

MAZE7!-ROOMS
"You are in a maze of twisty little passages, all alike."
"You are in a maze of twisty little passages, all alike."
#FALSE ()
#FALSE ()
#EXIT [UP!-WORDS #ROOM [MAZ14 "You are in a maze of twisty little passages, all alike." WEST NW NE
SOUTH] WEST!-WORDS #ROOM [MAZE6 "You are in a maze of twisty little passages, all alike." DOWN EAST
WEST UP] NE!-WORDS #ROOM [DEAD1 "Dead end." SOUTH] EAST!-WORDS #ROOM [MAZE8
"You are in a maze of twisty little passages, all alike." NE WEST SE] SOUTH!-WORDS #ROOM [MAZ15
"You are in a maze of twisty little passages, all alike." WEST SOUTH NE]]
()
#FALSE ()
0
0

MAZE6!-ROOMS
"You are in a maze of twisty little passages, all alike."
"You are in a maze of twisty little passages, all alike."
#FALSE ()
#FALSE ()
#EXIT [DOWN!-WORDS #ROOM [MAZE5 "You are in a maze of twisty little passages, all alike." EAST NORTH
 SW BONES BAGCO KEYS ] EAST!-WORDS #ROOM [MAZE7
"You are in a maze of twisty little passages, all alike." UP WEST NE EAST SOUTH] WEST!-WORDS #ROOM [
MAZE6 "You are in a maze of twisty little passages, all alike." DOWN EAST WEST UP] UP!-WORDS #ROOM [
MAZE9 "You are in a maze of twisty little passages, all alike." NORTH EAST DOWN SOUTH WEST NW]]
()
#FALSE ()
0
0

DEAD2!-ROOMS
"Dead end."
"Dead end."
#FALSE ()
#FALSE ()
#EXIT [WEST!-WORDS #ROOM [MAZE5 "You are in a maze of twisty little passages, all alike." EAST NORTH
 SW BONES BAGCO KEYS ]]
()
#FALSE ()
0
0

DEAD1!-ROOMS
"Dead end."
"Dead end."
#FALSE ()
#FALSE ()
#EXIT [SOUTH!-WORDS #ROOM [MAZE4 "You are in a maze of twisty little passages, all alike." WEST
NORTH EAST]]
()
#FALSE ()
0
0

MAZE5!-ROOMS
"You are in a maze of twisty little passages, all alike."
"You are in a maze of twisty little passages, all alike."
#FALSE ()
#FALSE ()
#EXIT [EAST!-WORDS #ROOM [DEAD2 "Dead end." WEST] NORTH!-WORDS #ROOM [MAZE3
"You are in a maze of twisty little passages, all alike." WEST NORTH UP] SW!-WORDS #ROOM [MAZE6
"You are in a maze of twisty little passages, all alike." DOWN EAST WEST UP]]
(#OBJECT [BONES ] #OBJECT [BAGCO bag of coins] #OBJECT [KEYS set of skeleton keys])
#FALSE ()
0
0

MAZE3!-ROOMS
"You are in a maze of twisty little passages, all alike."
"You are in a maze of twisty little passages, all alike."
#FALSE ()
#FALSE ()
#EXIT [WEST!-WORDS #ROOM [MAZE2 "You are in a maze of twisty little passages, all alike." SOUTH
NORTH EAST] NORTH!-WORDS #ROOM [MAZE4 "You are in a maze of twisty little passages, all alike." WEST
 NORTH EAST] UP!-WORDS #ROOM [MAZE5 "You are in a maze of twisty little passages, all alike." EAST
NORTH SW BONES BAGCO KEYS ]]
()
#FALSE ()
0
0

MAZE4!-ROOMS
"You are in a maze of twisty little passages, all alike."
"You are in a maze of twisty little passages, all alike."
#FALSE ()
#FALSE ()
#EXIT [WEST!-WORDS #ROOM [MAZE3 "You are in a maze of twisty little passages, all alike." WEST NORTH
 UP] NORTH!-WORDS #ROOM [MAZE1 "You are in a maze of twisty little passages, all alike." WEST NORTH
SOUTH EAST] EAST!-WORDS #ROOM [DEAD1 "Dead end." SOUTH]]
()
#FALSE ()
0
0

MAZE2!-ROOMS
"You are in a maze of twisty little passages, all alike."
"You are in a maze of twisty little passages, all alike."
#FALSE ()
#FALSE ()
#EXIT [SOUTH!-WORDS #ROOM [MAZE1 "You are in a maze of twisty little passages, all alike." WEST
NORTH SOUTH EAST] NORTH!-WORDS #ROOM [MAZE4
"You are in a maze of twisty little passages, all alike." WEST NORTH EAST] EAST!-WORDS #ROOM [MAZE3
"You are in a maze of twisty little passages, all alike." WEST NORTH UP]]
()
#FALSE ()
0
0

MAZE1!-ROOMS
"You are in a maze of twisty little passages, all alike."
"You are in a maze of twisty little passages, all alike."
#FALSE ()
#FALSE ()
#EXIT [WEST!-WORDS #ROOM [MTROL "The Troll Room" WEST EAST NORTH SOUTH TROLL ] NORTH!-WORDS #ROOM [
MAZE1 "You are in a maze of twisty little passages, all alike." WEST NORTH SOUTH EAST] SOUTH!-WORDS
#ROOM [MAZE2 "You are in a maze of twisty little passages, all alike." SOUTH NORTH EAST] EAST!-WORDS
#ROOM [MAZE4 "You are in a maze of twisty little passages, all alike." WEST NORTH EAST]]
()
#FALSE ()
0
0
1 Like

Appendix A (2/2) - OBJECTs

CRAW4!-ROOMS
"You are in a north-south crawlway; a passage goes to the east also.
There is a hole above, but it provides no opportunities for climbing."
"North-South Crawlway"
#FALSE ()
#FALSE ()
#EXIT [NORTH!-WORDS #ROOM [CHAS2 "West of Chasm" WEST NORTH SOUTH DOWN] SOUTH!-WORDS #ROOM [STUDI
"Studio" NORTH NW UP] EAST!-WORDS #ROOM [MTROL "The Troll Room" WEST EAST NORTH SOUTH TROLL ]
UP!-WORDS #NEXIT "Not even a human fly could get up it."]
()
#FALSE ()
0
0

CHAS2!-ROOMS
"You are on the west edge of a chasm, the bottom of which cannot be
seen. The east side is sheer rock, providing no exits.  A narrow passage
goes west, and the path you are on continues to the north and south."
"West of Chasm"
#FALSE ()
#FALSE ()
#EXIT [WEST!-WORDS #ROOM [CELLA "Cellar" EAST SOUTH UP WEST DOOR ] NORTH!-WORDS #ROOM [CRAW4
"North-South Crawlway" NORTH SOUTH EAST UP] SOUTH!-WORDS #ROOM [GALLE "Gallery" NORTH SOUTH PAINT ]
DOWN!-WORDS #NEXIT "The chasm probably leads straight to the infernal regions."]
()
#FALSE ()
0
0

CELLA!-ROOMS
""
"Cellar"
#FALSE ()
#FALSE ()
#EXIT [EAST!-WORDS #ROOM [MTROL "The Troll Room" WEST EAST NORTH SOUTH TROLL ] SOUTH!-WORDS #ROOM [
CHAS2 "West of Chasm" WEST NORTH SOUTH DOWN] UP!-WORDS #NEXIT
"The trap door has been barred from the other side." WEST!-WORDS #NEXIT
"You try to ascend the ramp, but it is impossible, and you slide back down."]
(#OBJECT [DOOR ])
%<RSUBR-ENTRY '[MSETG CELLAR #DECL ("VALUE" ANY)] 7369>
0
25

BLROO!-ROOMS
"You are in a long passage.  To the south is one entrance.  On the east there
is an old wooden door, with a large hole in it (about cyclops sized)."
"Strange Passage"
#FALSE ()
#FALSE ()
#EXIT [SOUTH!-WORDS #ROOM [CYCLO "Cyclops Room" WEST NORTH UP CYCLO ] EAST!-WORDS #ROOM [LROOM
"Living Room" EAST WEST DOWN DOOR TROPH LAMP RUG ]]
()
#SUBR *000000727265*
0
10

ATTIC!-ROOMS
"You are in the attic.  The only exit is stairs that lead down."
"Attic"
#FALSE ()
#FALSE ()
#EXIT [DOWN!-WORDS #ROOM [KITCH "Kitchen" EAST WEST EXIT UP DOWN WINDO FOOD BOTTL ]]
(#OBJECT [ROPE rope] #OBJECT [KNIFE knife])
#FALSE ()
0
0

LROOM!-ROOMS
""
"Living Room"
#FALSE ()
T
#EXIT [EAST!-WORDS #ROOM [KITCH "Kitchen" EAST WEST EXIT UP DOWN WINDO FOOD BOTTL ] WEST!-WORDS #
CEXIT [MAGIC-FLAG!-FLAG #ROOM [BLROO "Strange Passage" SOUTH EAST] "The door is nailed shut."]
DOWN!-WORDS #CEXIT [TRAP-DOOR!-FLAG #ROOM [CELLA "Cellar" EAST SOUTH UP WEST DOOR ] #FALSE ()]]
(#OBJECT [DOOR ] #OBJECT [TROPH trophy case] #OBJECT [LAMP lamp] #OBJECT [RUG carpet])
%<RSUBR-ENTRY '[MSETG LIVING-ROOM #DECL ("VALUE" ANY)] 7007>
0
0

CLEAR!-ROOMS
""
"Clearing"
#FALSE ()
T
#EXIT [SW!-WORDS #ROOM [EHOUS "Behind House." NORTH SOUTH EAST WEST ENTER WINDO ] NORTH!-WORDS
#ROOM [CLEAR "Clearing" SW NORTH EAST WEST SOUTH DOWN GRATE PILE ] EAST!-WORDS #ROOM [CLEAR
"Clearing" SW NORTH EAST WEST SOUTH DOWN GRATE PILE ] WEST!-WORDS #ROOM [FORE3 "Forest" NORTH EAST
SOUTH WEST] SOUTH!-WORDS #ROOM [FORE2 "Forest" NORTH EAST SOUTH WEST] DOWN!-WORDS #CEXIT [
KEY-FLAG!-FLAG #ROOM [MAZ11 "You are in a maze of twisty little passages, all alike." DOWN NW SW UP
GRATE ] #FALSE ()]]
(#OBJECT [GRATE ] #OBJECT [PILE pile of leaves])
%<RSUBR-ENTRY '[MSETG CLEARING #DECL ("VALUE" ANY)] 7275>
0
0

KITCH!-ROOMS
""
"Kitchen"
#FALSE ()
T
#EXIT [EAST!-WORDS #CEXIT [KITCHEN-WINDOW!-FLAG #ROOM [EHOUS "Behind House." NORTH SOUTH EAST WEST
ENTER WINDO ] #FALSE ()] WEST!-WORDS #ROOM [LROOM "Living Room" EAST WEST DOWN DOOR TROPH LAMP RUG ]
EXIT!-WORDS #CEXIT [KITCHEN-WINDOW!-FLAG #ROOM [EHOUS "Behind House." NORTH SOUTH EAST WEST ENTER
WINDO ] #FALSE ()] UP!-WORDS #ROOM [ATTIC "Attic" DOWN ROPE KNIFE ] DOWN!-WORDS #NEXIT
"Only Santa Claus climbs down chimneys."]
(#OBJECT [WINDO ] #OBJECT [FOOD .lunch] #OBJECT [BOTTL bottle WATER ])
%<RSUBR-ENTRY '[MSETG KITCHEN #DECL ("VALUE" ATOM)] 6549>
0
10

FORE2!-ROOMS
"You are in a dimly lit forest, with large trees all around.  To the
east, there appears to be sunlight."
"Forest"
#FALSE ()
T
#EXIT [NORTH!-WORDS #ROOM [SHOUS "South of House." WEST EAST SOUTH NORTH] EAST!-WORDS #ROOM [CLEAR
"Clearing" SW NORTH EAST WEST SOUTH DOWN GRATE PILE ] SOUTH!-WORDS #ROOM [FORE2 "Forest" NORTH EAST
SOUTH WEST] WEST!-WORDS #ROOM [FORE1 "Forest" NORTH EAST SOUTH WEST]]
()
#FALSE ()
0
0

FORE3!-ROOMS
"You are in a dimly lit forest, with large trees all around.  To the
east, there appears to be sunlight."
"Forest"
#FALSE ()
T
#EXIT [NORTH!-WORDS #ROOM [FORE2 "Forest" NORTH EAST SOUTH WEST] EAST!-WORDS #ROOM [CLEAR "Clearing"
 SW NORTH EAST WEST SOUTH DOWN GRATE PILE ] SOUTH!-WORDS #ROOM [CLEAR "Clearing" SW NORTH EAST WEST
SOUTH DOWN GRATE PILE ] WEST!-WORDS #ROOM [NHOUS "North of House." WEST EAST NORTH SOUTH]]
()
#FALSE ()
0
0

EHOUS!-ROOMS
""
"Behind House."
#FALSE ()
T
#EXIT [NORTH!-WORDS #ROOM [NHOUS "North of House." WEST EAST NORTH SOUTH] SOUTH!-WORDS #ROOM [SHOUS
"South of House." WEST EAST SOUTH NORTH] EAST!-WORDS #ROOM [CLEAR "Clearing" SW NORTH EAST WEST
SOUTH DOWN GRATE PILE ] WEST!-WORDS #CEXIT [KITCHEN-WINDOW!-FLAG #ROOM [KITCH "Kitchen" EAST WEST
EXIT UP DOWN WINDO FOOD BOTTL ] #FALSE ()] ENTER!-WORDS #CEXIT [KITCHEN-WINDOW!-FLAG #ROOM [KITCH
"Kitchen" EAST WEST EXIT UP DOWN WINDO FOOD BOTTL ] #FALSE ()]]
(#OBJECT [WINDO ])
%<RSUBR-ENTRY '[MSETG EAST-HOUSE #DECL ("VALUE" ATOM)] 6445>
0
0

WHOUS!-ROOMS
"You are in an open field west of a big white house, with a closed, locked
front door."
"West of House."
T
T
#EXIT [NORTH!-WORDS #ROOM [NHOUS "North of House." WEST EAST NORTH SOUTH] SOUTH!-WORDS #ROOM [SHOUS
"South of House." WEST EAST SOUTH NORTH] WEST!-WORDS #ROOM [FORE1 "Forest" NORTH EAST SOUTH WEST]
EAST!-WORDS #NEXIT "The door is locked, and there is evidently no key."]
()
#FALSE ()
0
0

FORE1!-ROOMS
"You are in a forest, with trees in all directions around you."
"Forest"
#FALSE ()
T
#EXIT [NORTH!-WORDS #ROOM [FORE1 "Forest" NORTH EAST SOUTH WEST] EAST!-WORDS #ROOM [FORE3 "Forest"
NORTH EAST SOUTH WEST] SOUTH!-WORDS #ROOM [FORE2 "Forest" NORTH EAST SOUTH WEST] WEST!-WORDS #ROOM [
FORE1 "Forest" NORTH EAST SOUTH WEST]]
()
#FALSE ()
0
0

SHOUS!-ROOMS
"You are facing the south side of a white house. There is no door here,
and all the windows are barred."
"South of House."
#FALSE ()
T
#EXIT [WEST!-WORDS #ROOM [WHOUS "West of House." NORTH SOUTH WEST EAST] EAST!-WORDS #ROOM [EHOUS
"Behind House." NORTH SOUTH EAST WEST ENTER WINDO ] SOUTH!-WORDS #ROOM [FORE2 "Forest" NORTH EAST
SOUTH WEST] NORTH!-WORDS #NEXIT "The windows are all barred."]
()
#FALSE ()
0
0

NHOUS!-ROOMS
"You are facing the north side of a white house.  There is no door here,
and all the windows are barred."
"North of House."
#FALSE ()
T
#EXIT [WEST!-WORDS #ROOM [WHOUS "West of House." NORTH SOUTH WEST EAST] EAST!-WORDS #ROOM [EHOUS
"Behind House." NORTH SOUTH EAST WEST ENTER WINDO ] NORTH!-WORDS #ROOM [FORE3 "Forest" NORTH EAST
SOUTH WEST] SOUTH!-WORDS #NEXIT "The windows are all barred."]
()
#FALSE ()
0
0

PASS1!-ROOMS
"You are in a narrow east-west passageway.  There is a narrow stairway
leading down at the north end of the room."
"East-West Passage"
#FALSE ()
#FALSE ()
#EXIT [EAST!-WORDS #ROOM [CAROU "Round room" NORTH SOUTH EAST WEST NW NE SE SW OUT] WEST!-WORDS
#ROOM [MTROL "The Troll Room" WEST EAST NORTH SOUTH TROLL ] DOWN!-WORDS #ROOM [RAVI1 "Deep Ravine"
SOUTH DOWN EAST WEST] NORTH!-WORDS #ROOM [RAVI1 "Deep Ravine" SOUTH DOWN EAST WEST]]
()
#FALSE ()
0
5

RAVI1!-ROOMS
"You are in a deep ravine at a crossing with a east-west crawlway.  Some
stone steps are at the south of the ravine and a steep staircase descends."
"Deep Ravine"
#FALSE ()
#FALSE ()
#EXIT [SOUTH!-WORDS #ROOM [PASS1 "East-West Passage" EAST WEST DOWN NORTH] DOWN!-WORDS #ROOM [RESES
"Reservoir South" SOUTH WEST CROSS NORTH UP TRUNK ] EAST!-WORDS #ROOM [CHAS1 "Chasm" SOUTH EAST DOWN
] WEST!-WORDS #ROOM [CRAW1 "Rocky Crawl" WEST EAST NW]]
()
#FALSE ()
0
0

MTROL!-ROOMS
"You are in a small room with passages off in all directions.  Bloodstains
and deep scratches (perhaps made by an axe) mar the walls."
"The Troll Room"
#FALSE ()
#FALSE ()
#EXIT [WEST!-WORDS #ROOM [CELLA "Cellar" EAST SOUTH UP WEST DOOR ] EAST!-WORDS #CEXIT [
TROLL-FLAG!-FLAG #ROOM [CRAW4 "North-South Crawlway" NORTH SOUTH EAST UP]
"The troll fends you off with a menacing gesture."] NORTH!-WORDS #CEXIT [TROLL-FLAG!-FLAG #ROOM [
PASS1 "East-West Passage" EAST WEST DOWN NORTH] "The troll fends you off with a menacing gesture."]
SOUTH!-WORDS #CEXIT [TROLL-FLAG!-FLAG #ROOM [MAZE1
"You are in a maze of twisty little passages, all alike." WEST NORTH SOUTH EAST]
"The troll fends you off with a menacing gesture."]]
(#OBJECT [TROLL ])
#FALSE ()
0
0

CAROU!-ROOMS
"You are in a circular room, with passages off in eight directions.
Your compass needle is spinning wildly, and you can't get your bearings."
"Round room"
#FALSE ()
#FALSE ()
#EXIT [NORTH!-WORDS #CEXIT [FROBOZZ!-FLAG #ROOM [CAVE4 "Engravings Cave" NORTH SE] ""] SOUTH!-WORDS
#CEXIT [FROBOZZ!-FLAG #ROOM [CAVE4 "Engravings Cave" NORTH SE] ""] EAST!-WORDS #CEXIT [FROBOZZ!-FLAG
#ROOM [MGRAI "Grail Room" WEST EAST UP GRAIL ] ""] WEST!-WORDS #CEXIT [FROBOZZ!-FLAG #ROOM [PASS1
"East-West Passage" EAST WEST DOWN NORTH] ""] NW!-WORDS #CEXIT [FROBOZZ!-FLAG #ROOM [CANY1
"Deep Canyon" SE EAST SOUTH] ""] NE!-WORDS #CEXIT [FROBOZZ!-FLAG #ROOM [PASS5 "North-South Passage"
NORTH NE SOUTH] ""] SE!-WORDS #CEXIT [FROBOZZ!-FLAG #ROOM [PASS4 "Winding Passage" EAST NORTH] "2"]
SW!-WORDS #CEXIT [FROBOZZ!-FLAG #ROOM [MAZE1
"You are in a maze of twisty little passages, all alike." WEST NORTH SOUTH EAST] ""] OUT!-WORDS #
CEXIT [FROBOZZ!-FLAG #ROOM [PASS3 "Cold Passage" EAST WEST NORTH] ""]]
()
%<RSUBR-ENTRY '[MSETG CAROUSEL-ROOM #DECL ("VALUE" ANY)] 8161>
0
0
1 Like

Appendix B - OBJECTs

THIEF!-OBJECTS
"There is a suspicious-looking individual, holding a bag, leaning against
one wall."
""
#FALSE ()
%<RSUBR-ENTRY '[MSETG ROBBER-FUNCTION #DECL ("VALUE" ANY)] 12398>
()
#FALSE ()
1
#FALSE ()
0
0
0
#FALSE ()

PAINT!-OBJECTS
"A masterpiece by a neglected genius is here."
"painting"
"Fortunately, there is still one chance for you to be a vandal, for on
the far wall is a work of unparalleled beauty."
#FALSE ()
()
#FALSE ()
1
#FALSE ()
0
4
7
#FALSE ()

CHALI!-OBJECTS
"There is a silver chalice, intricately engraved, here."
"chalice"
#FALSE ()
#FALSE ()
()
#FALSE ()
1
#FALSE ()
0
10
10
#FALSE ()

CYCLO!-OBJECTS
""
""
#FALSE ()
%<RSUBR-ENTRY '[MSETG CYCLOPS #DECL ("VALUE" ANY)] 10680>
()
#FALSE ()
1
#FALSE ()
0
0
0
#FALSE ()

SCREW!-OBJECTS
"There is a screwdriver here."
"screwdriver"
#FALSE ()
#FALSE ()
()
#FALSE ()
1
#FALSE ()
0
0
0
#FALSE ()

WRENC!-OBJECTS
"There is a wrench here."
"wrench"
#FALSE ()
#FALSE ()
()
#FALSE ()
1
#FALSE ()
0
0
0
#FALSE ()

PUTTY!-OBJECTS
"There is an object which looks like a tube of toothpaste here."
"tube"
#FALSE ()
#FALSE ()
()
#FALSE ()
1
#FALSE ()
0
0
0
#FALSE ()

LEAK!-OBJECTS
""
""
""
%<RSUBR-ENTRY '[MSETG OBJECT-ZORK #DECL ("VALUE" ANY)] 5623>
()
#FALSE ()
0
#FALSE ()
0
0
0
#FALSE ()

BUTTO!-OBJECTS
""
""
""
%<RSUBR-ENTRY '[MSETG OBJECT-ZORK #DECL ("VALUE" ANY)] 5623>
()
#FALSE ()
0
#FALSE ()
0
0
0
#FALSE ()

GUIDE!-OBJECTS
"There are tour guidebooks here."
"guidebook"
"Some guidebooks entitled 'Flood Control Dam #3' are on reception desk."
#FALSE ()
()
#FALSE ()
3
#FALSE ()
0
0
0
"\"                Guide Book to
                Flood Control Dam #3

  Flood Control Dam #3 (FCD#3) was constructed in year 783
of the Great Underground Empire to harness the destructive
power of the Frigid River.  This work was supported by a grant
of 37 million zorkmids from the Central Bureaucracy and
your omnipotent local tyrant Lord Dimwit Flathead the Excessive.
This impressive structure is composed of 3.7 cubic feet
of concrete, is 256 feet tall from the center, and 193 feet
wide at the top.  The reservoir created behind the dam has
a volume of 37 million cubic feet, an area of 12 million
square feet, and a shore line of 3.6 million feet.
  The construction of FCD#3 took 112 days from ground
breaking to the dedication. It required a work force of
384 slaves, 34 slave drivers, 12 engineers, 2 turtle doves,
and a partridge in a pear tree. The work was managed by
a command team composed of 2345 bureaucrats, 2347 secretaries
(at least two of which can type), 12,256 paper shufflers,
52,469 rubber stampers, 245,193 red tape processors, and
nearly one million dead trees.
  We will now point out some of the more interesting features
of FCD#3 as we conduct you on a guided tour of the facilities:
        1) You start your tour here in the Dam Lobby.
           You will notice on your right that ........."

MATCH!-OBJECTS
"There is a matchbook whose cover says 'Visit Beautiful FCD#3' here."
"matchbook"
#FALSE ()
#FALSE ()
()
#FALSE ()
3
#FALSE ()
0
0
0
#FALSE ()

BUBBL!-OBJECTS
""
""
""
%<RSUBR-ENTRY '[MSETG OBJECT-ZORK #DECL ("VALUE" ANY)] 5623>
()
#FALSE ()
0
#FALSE ()
0
0
0
#FALSE ()

BOLT!-OBJECTS
""
""
""
%<RSUBR-ENTRY '[MSETG OBJECT-ZORK #DECL ("VALUE" ANY)] 5623>
()
#FALSE ()
0
#FALSE ()
0
0
0
#FALSE ()

DAM!-OBJECTS
""
""
""
%<RSUBR-ENTRY '[MSETG OBJECT-ZORK #DECL ("VALUE" ANY)] 5623>
()
#FALSE ()
0
#FALSE ()
0
0
0
#FALSE ()

CANDL!-OBJECTS
"There are two candles here."
"pair of candles"
"On the two sides of the black book are burning candles."
%<RSUBR-ENTRY '[MSETG CANDLES #DECL ("VALUE" ANY)] 10099>
()
#FALSE ()
1
#FALSE ()
1
0
0
#FALSE ()

BOOK!-OBJECTS
"There is a large black book here."
"book"
"On the altar is a large black book, open to page 569."
#FALSE ()
()
#FALSE ()
3
#FALSE ()
0
0
0
"               COMMANDMENT #12592
Say to all ye who go about on the face of our land
intoning unto all ye meet:   \"Hello sailor\",
now and even unto the ends of the earth. For verily
by the wrath of the gods shalt thou repent thy sin.
And surely thy eye shall be put out with a sharp stick.
Depart hence thou monster with bad breath."

BELL!-OBJECTS
"There is a small brass bell here."
"bell"
"Lying in a corner of the room is a small brass bell."
#FALSE ()
()
#FALSE ()
1
#FALSE ()
0
0
0
#FALSE ()

GRAIL!-OBJECTS
"There is an extremely valuable (perhaps original) grail here."
"grail"
#FALSE ()
#FALSE ()
()
#FALSE ()
1
#FALSE ()
0
2
5
#FALSE ()

TORCH!-OBJECTS
"There is an ivory torch here."
"torch"
"Sitting on the pedestal is a flaming torch, made of ivory."
#FALSE ()
()
#FALSE ()
1
#FALSE ()
1
14
6
#FALSE ()

TRIDE!-OBJECTS
"Neptune's own diamond trident is here."
"diamond trident"
"On the shore lies Neptune's own diamond trident."
#FALSE ()
()
#FALSE ()
1
#FALSE ()
0
4
11
#FALSE ()

RUBY!-OBJECTS
"There is a moby ruby lying on the floor."
"ruby"
#FALSE ()
#FALSE ()
()
#FALSE ()
1
#FALSE ()
0
15
8
#FALSE ()

GHOST!-OBJECTS
""
""
#FALSE ()
%<RSUBR-ENTRY '[MSETG GHOST-FUNCTION #DECL ("VALUE" ANY)] 8954>
()
#FALSE ()
1
#FALSE ()
0
0
0
#FALSE ()

REFLE!-OBJECTS
""
""
#FALSE ()
%<RSUBR-ENTRY '[MSETG MIRROR-MIRROR #DECL ("VALUE" ANY)] 8006>
()
#FALSE ()
1
#FALSE ()
0
0
0
#FALSE ()

ICE!-OBJECTS
"A mass of ice fills the western half of the room."
""
#FALSE ()
%<RSUBR-ENTRY '[MSETG GLACIER #DECL ("VALUE" ANY)] 6690>
()
#FALSE ()
1
#FALSE ()
0
0
0
#FALSE ()

COFFI!-OBJECTS
"There is a solid-gold (heavy) coffin, used for the burial of Ramses II, here."
"gold coffin"
#FALSE ()
%<RSUBR-ENTRY '[MSETG COFFIN #DECL ("VALUE" FALSE)] 8746>
()
#FALSE ()
1
#FALSE ()
0
3
7
#FALSE ()

TRUNK!-OBJECTS
"There is an old trunk here, bulging with assorted jewels."
"trunk with jewels"
"Lying half buried in the mud is an old trunk, bulging with jewels."
#FALSE ()
()
#FALSE ()
0
#FALSE ()
0
15
8
#FALSE ()

GRATE!-OBJECTS
""
""
""
%<RSUBR-ENTRY '[MSETG OBJECT-ZORK #DECL ("VALUE" ANY)] 5623>
()
#FALSE ()
0
#FALSE ()
0
0
0
#FALSE ()

PEARL!-OBJECTS
"There is a pearl necklace here with hundreds of large pearls."
"pearl necklace"
#FALSE ()
#FALSE ()
()
#FALSE ()
1
#FALSE ()
0
9
5
#FALSE ()

BAR!-OBJECTS
"There is a large platinum bar here."
"platinum bar"
#FALSE ()
#FALSE ()
()
#FALSE ()
1
#FALSE ()
0
12
10
#FALSE ()

KEYS!-OBJECTS
"There is a set of skeleton keys here."
"set of skeleton keys"
#FALSE ()
#FALSE ()
()
#FALSE ()
1
#FALSE ()
0
0
0
#FALSE ()

BAGCO!-OBJECTS
"An old leather bag, bulging with coins, is here."
"bag of coins"
#FALSE ()
#FALSE ()
()
#FALSE ()
1
#FALSE ()
0
10
5
#FALSE ()

BONES!-OBJECTS
"A skeleton is lying here, beside a burned-out lantern and a rusty knife."
""
#FALSE ()
%<RSUBR-ENTRY '[MSETG SKELETON #DECL ("VALUE" ATOM)] 7539>
()
#FALSE ()
1
#FALSE ()
0
0
0
#FALSE ()

TROLL!-OBJECTS
"A nasty-looking troll, brandishing a bloody axe, blocks all passages
out of the room."
""
#FALSE ()
%<RSUBR-ENTRY '[MSETG TROLL #DECL ("VALUE" ANY)] 7714>
()
#FALSE ()
1
#FALSE ()
0
0
0
#FALSE ()

PILE!-OBJECTS
"There is a pile of leaves on the ground."
"pile of leaves"
"There is a pile of leaves on the ground."
#FALSE ()
()
#FALSE ()
1
#FALSE ()
0
0
0
#FALSE ()

RUG!-OBJECTS
""
"carpet"
#FALSE ()
%<RSUBR-ENTRY '[MSETG RUG #DECL ("VALUE" ANY)] 7472>
()
#FALSE ()
1
#FALSE ()
0
0
0
#FALSE ()

LAMP!-OBJECTS
"There is a brass lantern here."
"lamp"
"A brass lantern is on the trophy case."
%<RSUBR-ENTRY '[MSETG LANTERN #DECL ("VALUE" ANY)] 7899>
()
#FALSE ()
1
#FALSE ()
-1
0
0
#FALSE ()

DOOR!-OBJECTS
""
""
""
%<RSUBR-ENTRY '[MSETG OBJECT-ZORK #DECL ("VALUE" ANY)] 5623>
()
#FALSE ()
0
#FALSE ()
0
0
0
#FALSE ()

KNIFE!-OBJECTS
"There is a nasty-looking knife lying here."
"knife"
"On a table is a nasty-looking knife."
#FALSE ()
()
#FALSE ()
1
#FALSE ()
0
0
0
#FALSE ()

ROPE!-OBJECTS
"There is a large coil of rope here."
"rope"
"A large coil of rope is lying in the corner."
%<RSUBR-ENTRY '[MSETG ROPE-FUNCTION #DECL ("VALUE" ANY)] 10614>
()
#FALSE ()
1
#FALSE ()
0
0
0
#FALSE ()

WATER!-OBJECTS
"Water"
"water"
"There is some water here"
%<RSUBR-ENTRY '[MSETG WATER-FUNCTION #DECL ("VALUE" ANY "OPTIONAL" <OR ATOM FALSE>)] 10425>
()
#OBJECT [BOTTL bottle WATER ]
1
#FALSE ()
0
0
0
#FALSE ()

TROPH!-OBJECTS
"There is a trophy case here."
"trophy case"
#FALSE ()
%<RSUBR-ENTRY '[MSETG TROPHY-CASE #DECL ("VALUE" ANY)] 6658>
()
#FALSE ()
1
#FALSE ()
0
0
0
#FALSE ()

BOTTL!-OBJECTS
"A clear glass bottle is here."
"bottle"
"A bottle is sitting on the table."
%<RSUBR-ENTRY '[MSETG BOTTLE-FUNCTION #DECL ("VALUE" ANY)] 10235>
(#OBJECT [WATER water in BOTTL])
#FALSE ()
1
#FALSE ()
0
0
0
#FALSE ()

FOOD!-OBJECTS
"Sack smelling of hot peppers, is here."
".lunch"
"On the table is a elongated brown sack, smelling of hot peppers."
#FALSE ()
()
#FALSE ()
1
#FALSE ()
0
0
0
#FALSE ()

WINDO!-OBJECTS
""
""
""
%<RSUBR-ENTRY '[MSETG OBJECT-ZORK #DECL ("VALUE" ANY)] 5623>
()
#FALSE ()
0
#FALSE ()
0
0
0
#FALSE ()
2 Likes

Recreating Zork 285 in ZIL - Part 4 - The main loop

This is part 4 in an ongoing series. The previous part: Part 3 - Extracting definitions for rooms and
objects is here.

In this part we will examine initializing and the main loop.

SAVE-IT and some other magic

The SAVE- and RESTORE-functions in the MDL environment basically works so that you call SAVE, the whole current state (stack, program counter and memory)
of the environment is saved, just like a snapshot. Then when RESTORE is called, this snapshot is restored and the program execution continues with the
function following the SAVE. The file MADMAN;MADADV JUN14, for example, is exactly one of these snapshots.

ROOMS 154 [Jun 14 1977] starts with the following functions:

<DEFINE SAVE-IT ("OPTIONAL" (FN "MADMAN;MADADV SAVE"))
  <COND (<=? <SAVE .FN> "SAVED"> T)
        (T
     <COND (<=? <SUBSTRUC <UNAME> 0 3> "___">
        <QUIT>)>
     <PUT <1 <BACK ,INCHAN>> 6 #LOSE 13> ; "FIRST, TAKE OFF THE BACK...."
     <START "WHOUS">)>>

<DEFINE HANDLE (FRM "TUPLE" ZORK)
  <COND (<MEMBER <UNAME> ,WINNERS>
     <AND <GASSIGNED? SAVEREP> <SETG REP ,SAVEREP>>
     <INT-LEVEL 0>
     <RESET ,INCHAN>
     <PUT <1 <BACK ,INCHAN>> 6 #LOSE 27>)
    (T
     <COND (<AND <NOT <EMPTY? .ZORK>><==? <1 .ZORK> CONTROL-G?!-ERRORS>>
        <INT-LEVEL 0>
        <FINISH>
        <ERRET T .FRM>)
           (<TELL
"I'm sorry, you seem to have encountered an error in the program.
Send mail to BKD@DM or TAA@DM describing what it was you tried to do.">
     <SCORE>
     <QUIT>)>)>>

<SETG WINNERS '["BKD" "TAA" "MARC" "PDL"]>

<GDECL (WINNERS) <VECTOR [REST STRING]>>

<OR <LOOKUP "COMPILE" <ROOT>>
    <LOOKUP "GLUE" <GET PACKAGE OBLIST>>
    <SETG ERRH <HANDLER <GET ERROR!-INTERRUPTS INTERRUPT> ,HANDLE>>>

<GDECL (MOVES) FIX>

<DEFINE START (RM)
    #DECL ((RM) STRING)
    <SETG DEATHS 0>
    <SETG MOVES 0>
    <SETG WINNER <CHTYPE [<SETG HERE <FIND-ROOM .RM>> () 0] ADV>>
    <SETG SAVEREP ,REP>
    <SETG REP ,RDCOM>
    <TELL "Welcome to adventure.">
    ,NULL>

In SAVE-IT we see that when we RESTORE and a SAVE was successfull the execution continues with checking that you are a valid user. Next the
line <PUT <1 <BACK ,INCHAN>> 6 #LOSE 13> does some magic and replaces the ESC-key with the RETURN-key. Finally it calls START with
the parameter “WHOUS” (“West of House”, i.e. the starting room).

START does some additional magic that I won’t go into in detail but it essentially sets up a call to RDCOM via SAVEREP. There is also defined an
interrupt-function (HANDLE) that intercepts errors and CTRL-G. If you are among the WINNERS you are returned to the MDL environment
with the ESC-key restored, otherwise the game, and the environment quits.

This are things that we don’t relly need to bother with in ZIL.

Converting to ZIL

In zork_285.zil and rooms.zil
this translates to (remenber that the routine GO is the starting point for ZIL programs):

<ROUTINE GO ()
	<FIXED-FONT-ON>  ;"Fixed font for a more 'terminal' feeling."
	<SAVE-IT>>

;"In MDL this function saved the workspace and the started the game. Due to how MDL 
  handle SAVE/RESTORE this had the effect that when the workspace was restored the
  execution started with the code following the call to the SAVE function. In practice
  this meant that a RESTORE loaded and started the game."
<ROUTINE SAVE-IT () 
    <START-F ,WHOUS>>

;"START is a reserved word in ZIL and not valid as a name on a routine."
<ROUTINE START-F (RM)      ;"Was START"
	<SETG DEATHS 0>
	<SETG MOVES 0>
	<PUTP ,WINNER ,P?AROOM <SETG HERE .RM>>
    <CRLF>
    <TELL "Welcome to adventure." CR>
	<RDCOM>>

The beauty of AND, OR and COND

Before we continue it may be worth to examine more on how AND, OR and COND works. This is applicable to both MDL and ZIL. Three fundamental
things to remember are these:

  • All functions returns a value.
  • A value are either false (0 = false for ZIL), all other values are considered true.
  • Only statements needed to determine the final result are executed.
    The last point means that AND work like this:
<AND <statement-1>
     <statement-2>
     <statement-3>
     <statement-4>
     ...>

Statement-1 is executed, if the return value is true then statement-2 is executed. This is continued until one of the statements returns false, then AND is
considered false and no more statements are executed.

Example (ZIL):

<ROUTINE READABLE? (OBJ)
    <AND <BTST <GETP .OBJ ,P?OFLAGS> 2>
         <GETP .OBJ ,P?OREAD>>>

AND first tests if bit 2 of OFLAGS on the OBJ is set (BTST) if this bit is 0 then the result is false and READABLE? returns false (0), otherwise the OREAD
property on OBJ is returned (the text, presumably).

For OR it is the other way around:

<OR <statement-1>
    <statement-2>
    <statement-3>
    <statement-4>
    ...>

All the statements are executed in order until one of them returns true, then OR is considered true and no more statements are executed.

Example:

<OR ,PATCHED <CRLF>>

This statement is used a lot in the game to print an extra CR when the game is in the unpatched mode. Basically OR tries the variable PATCHED and if it is true then
the CRLF is not executed but if it is false then it is.

COND is a little bit like AND except that it tests on a first clause to deterime if a statement should be executed. It continues to execute clauses until one of
them is true, then the following statement is executed and all the following clauses are skipped. Remember that clauses also are statement that are executed
and that they also returns true or false.

<COND (<clause-1> <statement>)
      (<clause-2> <statement>)
      (<clause-3> <statement>)
      (<clause-4> <statement>)
      ...>

Example (ZIL):

<ROUTINE TREAS () 
    <COND (<AND <W=? <1 ,PRSVEC> ,W?TREAS> <=? ,HERE ,TEMP1>>
            <GOTO ,TREAS-R>
            <ROOM-DESC>)
          (<AND <=? <1 ,PRSVEC> ,W?TEMPL> <=? ,HERE ,TREAS-R>>
            <GOTO ,TEMP1>
            <ROOM-DESC>)
          (T <TELL "Nothing happens." CR>)>>

Here we test if the verb is TREAS and the player is in the TEMP1 room then we move the player to the TREAS-R room. If this first clause returns false we test
the next clause and that is if the verb is TREAS and the player is in the TREAS-R then we move the player to the TEMP1 room. The third clause is T and it is
always true (by definition), this means that if the above clauses fails this statement is executed as a fallback and the text “Nothing happens.” is printed.
In practice the T works as an ELSE-statement in other languages (in ZIL ELSE actually is a statement that always returns true, so T could be replaced with ELSE
in the above example).

Remember that the beauty is in the eyes of the beholder…

The main loop - RDCOM

RDCOM is in the file ROOMS 154 [Jun 14 1977]. This function is the heart of the program and I will go through it in some detail.

<DEFINE RDCOM ("AUX" RVEC RM INPLEN (INBUF ,INBUF)) 
    #DECL ((RVEC) <OR FALSE VECTOR> (RM) ROOM (INPLEN) FIX (INBUF) STRING)
    <PUT ,OUTCHAN 13 1000>
    <ROOM-INFO T>
    <REPEAT ()
        <SET RM ,HERE>
        <RESET ,INCHAN>
        <PRINC ">">
        <SETG TELL-FLAG <>>
        <SET INPLEN
             <READSTRING .INBUF
                 ,INCHAN
                 <STRING <ASCII 27> <ASCII 13> <ASCII 15>>>>
        <SETG MOVES <+ ,MOVES 1>>
        <COND (<AND <EPARSE <LEX .INBUF <REST .INBUF .INPLEN> T> <>>
                    <TYPE? <1 <SET RVEC ,PRSVEC>> VERB>>
               <COND (<APPLY <VFCN <1 .RVEC>>>
                  <COND (<AND <RACTION .RM>
                      <APPLY <RACTION .RM>>>)>)>)>
        <OR ,TELL-FLAG
            <TELL "Nothing happens.">>
        <MAPF <>
              <FUNCTION (X) 
                  #DECL ((X) HACK)
                  <COND (<HACTION .X> <APPLY <HACTION .X> .X>)>>
              ,DEMONS>>>

After the keyword "AUX" follow a declaration of local variables. These can be initialized as (INBUF ,INBUF) that sets the local variable INBUF to the global variable INBUF.
The #DECL is only a type declaration of the variables.
Then the current room description is printed before starting the loop with REPEAT. Every turn prints a prompt, clears the TELL-FLAG, increases the MOVES counter and inputs a string, INBUF, from the player.
The <EPARSE <LEX .INBUF <REST .INBUF .INPLEN> T> <>> extracts a verb, a primary noun and a secondary noun and places them in a vector (“array”), PRSVEC, that have three slots. If the result of this returns true and the word in the first slot is a valid verb, <TYPE? <1 <SET RVEC ,PRSVEC>> VERB>, we do the verbs function. If the verbs function returns true and there is a room function we call the rooms function.
If nothing have been printed to the screen above (the TELL-FLAG is still false), “Nothing happens.” is printed.
Finally all the interrupts (DEMONS) are called.
In pseudo-code:

print room description
loop_start
   print prompt
   input text
   advance move counter
   parse text
   if parse ok and verb ok do verb action
   if parse ok and verb ok do eventual room action
   if nothing have been printed yet, print "Nothing happens."
   do DEMONS (interrupts)
loop_end

Converting to ZIL

One of the big differences between MDL and ZIL is that ZIL don’t have a string data type. This will make the parsing a bit different (more on this in later parts) but basically this means that instead of strings we have to work with pointers that point to the words in the vocabulary. PRSVEC in ZIL is, for example, a TABLE, <GLOBAL PRSVEC <ITABLE 8 (BYTE)>>, with 4 word slots (the first unused because ZIL is 0-base instead of 1-base in MDL). Aside from this the conversion to ZIL is pretty straightforward.

<ROUTINE RDCOM ("AUX" RM)
    <PUTB ,INBUF 0 ,INBUF-SIZE>    ;"Max length of INBUF"
    <PUTB ,LEXV 0 10>              ;"Max # words that will be parsed"
    <ROOM-INFO T>
    <REPEAT ()
        <SET RM ,HERE>
        <TELL ">">
        <SETG TELL-FLAG <>>
        <LEX>
        <SETG MOVES <+ ,MOVES 1>>
        ;"EPARSE, extract PRSVEC (PRSA, PRSO & PRSI)"
        <COND (<AND <EPARSE ,LEXV <>> <WT? <1 ,PRSVEC> ,PS?VERB>>
            ;"PARSE OK - DO VERB-ACTION"
            <COND (<APPLY <VFCN <1 ,PRSVEC>>>
                ;"VERB-ACTION OK - DO ROOM-ACTION"
                <AND <GETP .RM ,P?RACTION> <APPLY <GETP .RM ,P?RACTION>>>)>)>
        <OR ,TELL-FLAG <TELL "Nothing happens." CR>>
        ;"Run DEMONS"
        <REPEAT ((I 0))
            <COND (<G? <SET I <+ .I 1>> <0 ,DEMONS>> <RETURN>)>
            <APPLY <GET ,DEMONS .I>>>>

Next: Part 5 - Parsing the input

3 Likes

Recreating Zork 285 in ZIL - Part 5 - Parsing the input

This is part 5 in an ongoing series. The previous part: Part 4 - The main loop is here.

In this part we will examine how the vocabulary is organized in the z-machine and how to parse the players input.

Vocabulary entries in the Z-machine

For Z-machines version 4 and later a word occupies 9 bytes for each entry in the vocabulary, 6 bytes for z-chars and 3 data bytes. For version 3 and earlier only 4 bytes are used for z-chars, making each entry 7 bytes long. The first data byte is used for the part-of-speech flags and the other two have different uses for different word types.

If we define the following (ZIL):

<SYNTAX TAKE = V-TAKE>
<SYNONYM TAKE GET>
<BUZZ THE AN A>
<DIRECTIONS NORTH EAST WEST SOUTH>
<OBJECT LANTERN
    (SYNONYM LANTERN LAMP)
    (ADJECTIVE USELESS)>

The words end up in the vocabulary like this:

Version 4 and later:
Byte                                  Byte 0-5                                                              Byte 6                    Byte 7    Byte 8
  0   1   2   3   4   5   6   7   8   Up to 9 z-chars padded with z-char 5 (00101, ^ here)                  Part-of-speech flag       V1        V2                                                            
100 208  40 165 148 165  65 255   0 = 0 11001 00110 10000 0 01010 00101 00101 1 00101 00101 00101 take^^^^^ P1?VERB+PS?VERB              255         0  
 49  89  20 165 148 165  65 255   0 = 0 01100 01010 11001 0 00101 00101 00101 1 00101 00101 00101 get^^^^^^ P1?VERB+PS?VERB              255         0
 68 211 101  87 204 165 128   1   0 = 0 10001 00110 10011 0 11001 01010 10111 1 10011 00101 00101 lantern^^ PS?OBJECT                      1         0
 68 210  84 165 148 165 128   1   0 = 0 10001 00110 10010 0 10101 00101 00101 1 00101 00101 00101 lamp^^^^^ PS?OBJECT                      1         0
107  10  69  88 224 165  32   0   0 = 0 11010 11000 01010 0 10001 01010 11000 1 11000 00101 00101 useless^^ PS?ADJECTIVE                   0         0
101 170  20 165 148 165   4 255   0 = 0 11001 01101 01010 0 00101 00101 00101 1 00101 00101 00101 the^^^^^^ PS?BUZZ-WORD                 255         0
 26 101  20 165 148 165   4 254   0 = 0 00110 10011 00101 0 00101 00101 00101 1 00101 00101 00101 an^^^^^^^ PS?BUZZ-WORD                 254         0
 24 165  20 165 148 165   4 253   0 = 0 00110 00101 00101 0 00101 00101 00101 1 00101 00101 00101 a^^^^^^^^ PS?BUZZ-WORD                 253         0
 78 151 101 165 148 165  19  63   0 = 0 10011 10100 10111 0 11001 01101 00101 1 00101 00101 00101 north^^^^ P1?DIRECTION+PS?DIRECTION     63         0
 40 216 100 165 148 165  19  62   0 = 0 01010 00110 11000 0 11001 00101 00101 1 00101 00101 00101 east^^^^^ P1?DIRECTION+PS?DIRECTION     62         0
113  88 100 165 148 165  19  61   0 = 0 11100 01010 11000 0 11001 00101 00101 1 00101 00101 00101 west^^^^^ P1?DIRECTION+PS?DIRECTION     61         0
 98 154 101 165 148 165  19  60   0 = 0 11000 10100 11010 0 11001 01101 00101 1 00101 00101 00101 south^^^^ P1?DIRECTION+PS?DIRECTION     60         0

Version 3:
Byte                          Byte 0-3                                       Byte 4                   Byte 5    Byte 6
  0   1   2   3   4   5   6   Up to 6 z-chars                                Part-of-speech flag       V1        V2                                                            
100 208 168 165  65 255   0 = 0 11001 00110 10000 1 01010 00101 00101 take^^ P1?VERB+PS?VERB             255         0  
 49  89 148 165  65 255   0 = 0 01100 01010 11001 1 00101 00101 00101 get^^^ P1?VERB+PS?VERB             255         0
 68 211 229  87 128   1   0 = 0 10001 00110 10011 1 11001 01010 10111 lanter PS?OBJECT                     1         0
 68 210 212 165 128   1   0 = 0 10001 00110 10010 1 10101 00101 00101 lamp^^ PS?OBJECT                     1         0
107  10 197  88  34 255   0 = 0 11010 11000 01010 1 10001 01010 11000 useles PS?ADJECTIVE                  0         0
101 170 148 165   4 255   0 = 0 11001 01101 01010 1 00101 00101 00101 the^^^ PS?BUZZ-WORD                255         0
 26 101 148 165   4 254   0 = 0 00110 10011 00101 1 00101 00101 00101 an^^^^ PS?BUZZ-WORD                254         0
 24 165 148 165   4 253   0 = 0 00110 00101 00101 1 00101 00101 00101 a^^^^^ PS?BUZZ-WORD                253         0
 78 151 229 165  19  31   0 = 0 10011 10100 10111 1 11001 01101 00101 north^ P1?DIRECTION+PS?DIRECTION    31         0
 40 216 228 165  19  30   0 = 0 01010 00110 11000 1 11001 00101 00101 east^^ P1?DIRECTION+PS?DIRECTION    30         0
113  88 228 165  19  29   0 = 0 11100 01010 11000 1 11001 00101 00101 west^^ P1?DIRECTION+PS?DIRECTION    29         0
 98 154 229 165  19  28   0 = 0 11000 10100 11010 1 11001 01101 00101 south^ P1?DIRECTION+PS?DIRECTION    28         0

Byte 0-3: Word of 6 (v3) or 9 (v4-) z-chars. Words are padded with the shift z-char 5 (00101) to fill up.  
or   0-5: First bit of each WORD (2 bytes) is 0 except for the last WORD where it is 1 to indicate the end.

Byte 6  : The part-of-speech flags with the following meaning:
            P1?OBJECT       0   Object data stored in V1.
            P1?VERB         1   Verb data stored in V1 if set, otherwise in V2.
            P1?ADJECTIVE    2   Adjective data stored in V1 if set, otherwise in V2.
            P1?DIRECTION    3   Direction data stored in V1 if set, otherwise in V2.
            PS?BUZZ-WORD    4   Word type buzz. Word data stored in V1 contains 
                                buzz-word number counting downwards from 255.
            PS?PREPOSITION  8   Word type preposition. Word data stored in V1 contains 
                                preposition number counting downwards from 255.
            PS?DIRECTION   16   Word type direction. Word data stored in V1 or V2 contains 
                                direction number counting downwards from 63 (ver 4-) or 31 (ver 3).
            PS?ADJECTIVE   32   Word type adjective. Word data stored in V1 or V2 contains 
                                adjective number counting downwards from 255 in version 3, 
                                always 0 in later versions.
            PS?VERB        64   Word type verb. Word data stored in V1 or V2 contains 
                                verb number counting downwards from 255.
            PS?OBJECT     128   Word type object. Word data stored in V1 or V2. 
                                Word data is always 1.

To illustrate how the flags are used, consider that your game needs to understand the following sentence:

“Plant the potplant in the plant pot” (version 3)
“Plant the pot plant in the plant pot” (version 4 and later)

The “plant” is a verb and an adjective in version 3. In version 4 and later it is also an object.

This ends up in the vocabulary as this:

Version 3:
Byte                          Byte 0-3                                       Byte 4                         Byte 5  Byte 6
  0   1   2   3   4   5   6   Up to 6 z-chars                                Part-of-speech flag                V1        V2                                                            
 86  38 207  37  97 254 255 = 0 10101 10001 00110 1 10011 11001 00101 plant^ P1?VERB+PS?VERB+PS?ADJECTIVE      254       255    

"plant" is both verb and adjective. P1?VERB means that verb number is stored in V1 and adjective number in V2.

Version 4-
Byte                                  Byte 0-5                                                              Byte 6                    Byte 7    Byte 8
  0   1   2   3   4   5   6   7   8   Up to 9 z-chars                                                       Part-of-speech flag       V1        V2                                                            
 86  38  79  37 148 165 225 254   1 = 0 10101 10001 00110 0 10011 11001 00101 1 00101 00101 00101 plant^^^^ PS?OBJECT+PS?VERB+           254         1  
                                                                                                            PS?ADJECTIVE+P1?VERB

"plant" is verb, object and adjective. P1?VERB means that verb number is stored in V1 and object data in V2, adjective data (0) is discarded.

Note that the P1?-part always is defined for word types that have word numbers.

In Infocom’s games there is often a function to extract word type and word number, WT?:

;"Check whether word pointed at by PTR is the correct part of speech.
   The second argument is the part of speech (,PS?<part of speech>).  The
   3rd argument (,P1?<part of speech>), if given, causes the value
   for that part of speech to be returned."

<ROUTINE WT? (PTR BIT "OPTIONAL" (B1 5) "AUX" (OFFS ,P-P1OFF) TYP)
    <COND (<BTST <SET TYP <GETB .PTR ,P-PSOFF>> .BIT>
           <COND (<G? .B1 4> <RTRUE>)
             (T
              <SET TYP <BAND .TYP ,P-P1BITS>>
              <COND (<NOT <EQUAL? .TYP .B1>> <SET OFFS <+ .OFFS 1>>)>
              <GETB .PTR .OFFS>)>)>>

WT? works like this:

<WT? ,W?TAKE ,PS?VERB>               ---> Returns 1 (True)
<WT? ,W?TAKE ,PS?OBJECT>             ---> Returns 0 (False)
<WT? ,W?TAKE ,PS?VERB ,P1?VERB>      ---> Returns 255 (Word-ID)

Zork 285 have a simpler version of WT? and a function, W=? (defs.zil), that compares two words and return true if they are the same. They assume that the word number is stored in V1.

;"WORD of TYPE?"
<ROUTINE WT? (WORD PS)
    <COND (<0? .WORD> <RFALSE>)>
    <COND (<BTST <GETB .WORD 6> .PS> <RTRUE>)  ;"6 = Part of speach flags for EZIP-"
          (T <RFALSE>)>>

;"Word-2 is the same or synonym to word-2"
<ROUTINE W=? (W1 W2)
    <COND (<AND <OR <AND <WT? .W1 ,PS?VERB> <WT? .W2 ,PS?VERB>>
                    <AND <WT? .W1 ,PS?DIRECTION> <WT? .W2 ,PS?DIRECTION>>>
                <=? <GETB .W1 7> <GETB .W2 7>>>
                    <RTRUE>)
          (<AND <WT? .W1 ,PS?OBJECT> <WT? .W2 ,PS?OBJECT>
                <=? <FIND-OBJ .W1> <FIND-OBJ .W2>>>
                    <RTRUE>)
          (<AND <WT? .W1 ,PS?ADJECTIVE> <WT? .W2 ,PS?ADJECTIVE>
                <=? .W1 .W2>>
                    <RTRUE>)
          (T <RFALSE>)>>

<WT? ,W?TAKE ,PS?VERB>      --> True
<WT? ,W?TAKE ,PS?OBJECT>    --> False
<W=? ,W?TAKE ,W?GET>        --> True
<W=? ,W?TAKE ,W?LAMP>       --> False

Splitting the input string - LEX

The LEX function (ROOMS 154 [Jun 14 1977]) is quite simple. It takes an input string and splits it in up to 10 separate uppercase words of up to 5 characters long and stores them in the string vector, LEXV.

<LEX "throw the knife at the troll">$
,LEXV$
["THROW" "THE" "KNIFE" "AT" "THE" "TROLL" "" "" "" ""]

On ZIL, or the z-machine, there is no string datatype so this have to be handled in a different way with READ and LEX (read and tokenize in Inform). READ and LEX are described in more detail in the YZIP Specifications but in short the two byte-tables, INBUF and LEXV, ends up like this after a READ (byte 0, the max length and max number of words, are both tables initialized in RDCOM):

INBUF contains:
    Byte 0   Max number of chars read into the buffer
         1   Actual number of chars read into the buffer
         2-  The typed chars all converted to lowercase

LEXV contains:
    Byte 0   Max number of words parsed
         1   Actual number of words parsed
         2-3 Address to first word in vocabulary (0 if word is not in it)
         4   Length of first word
         5   Start position (in INBUF) of first word
         6-9 Second word
         ...

If the string "throw the knife at the troll" is typed into the READ input, the result in INBUF and LEXV are:

INBUF:
 Byte 0   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15  16  17  18  19  20  21  22  23  24  25  26  27  28  29  
    100  28  't' 'h' 'r' 'o' 'w' ' ' 't' 'h' 'e' ' ' 'k' 'n' 'i' 'f' 'e' ' ' 'a' 't' ' ' 't' 'h' 'e' ' ' 't' 'r' 'o' 'l' 'l' 
LEXV:
 Byte 0   1 2-3     4 5 6-7   8 9 10-11   12 13 14-15 16 17 18-19 20 21 22-23   24 25
     10   6 W?THROW 5 2 W?THE 3 8 W?KNIFE  5 12 W?AT   2 18 W?THE  3 21 W?TROLL  5 25

LEX does only the tokenization on and existing byte-table exactly as READ but without the input.

Finally, ZIL allow and recognizes words up to 9 characters and the original MDL only uses 5 characters. This “dumbing down” of the parsing is done by copying the original input to RAWBUF, replace all characters in INBUF beyond 5 with a space and then redo the tokenization (the characters .,:;?! are also replaced by space and ignored, i.e. hardcoded buzz-words).

;"Inputs text and puts the result in INBUF and LEXV"
<ROUTINE LEX ("AUX" LEN WS WE WC)
    <DO (I 1 ,INBUF-SIZE) <PUTB ,INBUF .I 0>>  ;"Clear INBUF"
    <DO (I 1 ,LEXV-SIZE) <PUTB ,LEXV .I 0>>    ;"Clear LEXV"
    <READ ,INBUF ,LEXV>
    ;"Some (Lectrote 1.3.5/Parchment) terps that uses ZVM don't preserve this 
      when resuming from an autosave. This makes sure it's reset between sessions"
    <FIXED-FONT-ON>  
    <DO (I 1 ,INBUF-SIZE) <PUTB ,RAWBUF .I <GETB ,INBUF .I>>>  ;"Copy INBUF to RAWBUF beefore manipulating it."
    ;"Replace all .,:;?! in input with SPACE, See BRKS in ROOMS.154..LEX"
    <SET LEN <+ <GETB ,INBUF 1> 1>>
    <DO (I 0 .LEN) 
        <COND (<OR <=? <GETB ,INBUF .I> !\.>
                   <=? <GETB ,INBUF .I> !\,>
                   <=? <GETB ,INBUF .I> !\:>
                   <=? <GETB ,INBUF .I> !\;>
                   <=? <GETB ,INBUF .I> !\!>
                   <=? <GETB ,INBUF .I> !\?>> <PUTB ,INBUF .I 32>)>>
    ;"Limit words in input to max 5 characters"
    <SET WC <GETB ,LEXV 1>> ;"# of parsed words"
    <DO (I 1 .WC)
        <SET LEN <GETB ,LEXV <* .I 4>>>
        <SET WS <GETB ,LEXV <+ <* .I 4> 1>>>
        <SET WE <+ .WS <- .LEN 1>>>
        <COND (<G? .LEN 5>
            <SET WS <+ .WS 5>>
            <DO (J .WS .WE) <PUTB ,INBUF .J 32>>)>>     
    ;"Redo parsing"
    <LEX ,INBUF ,LEXV>>

And finally… The parsing - EPARSE

In MDL the EPARSE function (ROOMS 154 [Jun 14 1977]) takes the vector LEXV and tries to identify a verb (PRSA), a direct object (PRSO) and an indirect object PRSI). If the parsing is successful EPARSE returns true and the vector PRSVEC contains the PRSA in the first slot, PRSO in the second and PRSI in the third. If the parsing fails, an error message is printed and EPARSE returns false. The error messages can be turned off if the input parameter, SILENT?, is true (this is used by the “Echo Room”).

<LEX "Throw the knife at the troll">$
["THROW" "THE" "KNIFE" "AT" "THE" "TROLL" "" "" "" ""]
<EPARSE ,LEXV <>>$
T
,PRSVEC$
[#VERB [THROW!-WORDS DROP T] #OBJECT [KNIFE knife] #OBJECT [TROLL ]]

With directions, the verb WALK is inserted as PRSA.

<LEX "n">$
["N" "" "" "" "" "" "" "" "" ""]
<EPARSE ,LEXV <>>$
T
,PRSVEC$
[#VERB [WALK!-WORDS WALK <>] #DIRECTION NORTH!-WORDS #FALSE ()]

The following parser errors are handled:

Missing verb but have object:                           "What should I do with it?"
Missing verb and unknown word:                          "I don't know how to do that."
Direction at wrong position or unexpected:              "I can't parse that."
Unknown word:                                           "I don't know that word."
No verb and no object:                                  "Huh?"
Verb not WALK and PRSO is direction:                    "You can't do that!"
Verb is WALK and PRSO is not direction:                 "Go where?"
Illegal number of arguments for this verb:              Print VSTR
Illegal number of arguments for this verb (no VSTR):    "I don't understand."

Examples:

Welcome to adventure.
You are in an open field west of a big white house, with a closed, locked
front door.
>lamp
What should I do with it?
>sword
I don't know how to do that.
>get troll south
I can't parse that.
>get sword
I don't know that word.
>the
Huh?
>get south
You can't do that!
>go troll
Go where?
>tie
Tie what?
>walk
I don't understand.
>

The VARGS, VSTR, VACTION? and VMAX are attributes defined along with the verb and stored in the verb datatype, see Part 2 - Extracting the dictionary. In ZIL they are redefined as functions (defs.zil) that returns the same data. VNO and VNO=? are helper functions. VNO simply extracts the V1 byte from a vocabulary entry and VNO=? compares up to 7 V1 bytes of entries and returns true if they all are the same.

;"VARGS, VSTR, VACTION?, VMAX

  In MDL this is stored along with the verb. ZIL has a more sophisticated
  way of handling syntax. These routines returns the right value for the
  different verbs as defined in the original."
<ROUTINE VARGS (V)
    <COND (<OR <VNO=? .V ,W?WALK ,W?UNTIE ,W?PUSH ,W?POUR ,W?READ> 
               <VNO=? .V ,W?WAVE ,W?TIE>> 
             <RETURN 1>)
          (T <RETURN 0>)>>

<ROUTINE VMAX (V)
    <COND (<VNO=? .V ,W?WALK> <RETURN 1>)
          (T <RETURN 2>)>>
          
<ROUTINE VACTION? (V)
    <COND (<VNO=? .V ,W?WALK> <RFALSE>)
          (T <RTRUE>)>>

<ROUTINE VSTR (V)
    <COND (<VNO=? .V ,W?TAKE> <TELL "Take what?" CR>)
          (<VNO=? .V ,W?THROW> <TELL "Throw what?" CR>)
          (<VNO=? .V ,W?UNTIE> <TELL "Untie what?" CR>)
          (<VNO=? .V ,W?GIVE> <TELL "Give what?" CR>)
          (<VNO=? .V ,W?PUSH> <TELL "Push what?" CR>)
          (<VNO=? .V ,W?MOVE> <TELL "Move what?" CR>)
          (<VNO=? .V ,W?POUR> <TELL "Pour what?" CR>)
          (<VNO=? .V ,W?READ> <TELL "Read what?" CR>)
          (<VNO=? .V ,W?WAVE> <TELL "Wave what?" CR>)
          (<VNO=? .V ,W?TIE> <TELL "Tie what?" CR>)
          (<VNO=? .V ,W?DROP> <TELL "Drop what?" CR>)
          (T <RFALSE>)>>

<ROUTINE VNO (V) <RETURN <GETB .V 7>>>

<ROUTINE VNO=? (V1 V2 "OPT" V3 V4 V5 V6 V7) 
    <COND (<OR <=? <VNO .V1> <VNO .V2>>
               <AND <N=? .V3 0> <=? <VNO .V1> <VNO .V3>>>
               <AND <N=? .V4 0> <=? <VNO .V1> <VNO .V4>>>
               <AND <N=? .V5 0> <=? <VNO .V1> <VNO .V5>>>
               <AND <N=? .V6 0> <=? <VNO .V1> <VNO .V6>>>
               <AND <N=? .V7 0> <=? <VNO .V1> <VNO .V7>>>>
             <RTRUE>)
          (T <RFALSE>)>>

Pseudo-code for EPARSE:

Initialize variables
Loop over all words in LEXV
    if we don't have verb:
        word is verb; set verb
        word is direction; set verb=WALK, obj1=direction; exit loop with error
        word is buzz-word; do nothing
        word is object or adjective; print "What should I do with it?"; exit loop with error
        word is unknown; print "I don't know how to do that."; exit loop with error
        word type is unknown; print ""I can't parse that.""; exit loop with error
    if we do have a verb:
        word is buzz-word; do nothing
        word is object or adjective; if ob1 is empty set obj1=word else set obj2=word
        word is direction; if obj1 is not empty print "I can't parse that." else set obj1=direction; exit loop with error
Loop_end
if loop exit was without error and LEXV is not empty:
    verb is not set; print "Huh!" and exit with false
    verb is not WALK and obj1=direction; print "You can't do that!"; exit with false
    verb is walk and obj1 is not direction; print "Go where?"; exit with false
    number of objects is outside definition for this verb;
        if VSTR exits; print VSTR; exit with false
        if VSTR don't exits; print "I don't understand."; exit with false
    exit with true
else exit with false

Finally, EPARSE in rooms.zil:

;"Try to parse the LEX-buffer and fill PRSVEC with verb, direct object and
 indirect object. If successfull TRUE is returned; otherwise FALSE.
   SV      The lexbuffer to parse (not really a vector but name remains
           for historcal purpose.
   SILENT? If TRUE no messages will be printed (for echo)."
<ROUTINE EPARSE (SV SILENT?
               "AUX" (VEC ,PRSVEC) (PRS T) (CNT <GETB .SV 1>) (VERB? <>)
                      W ARG-MIN ARG-MAX (ARG-NUM 0))
   ;"Clear PRSVEC"
   <PUT .VEC 1 <>>
   <PUT .VEC 2 <>>
   <PUT .VEC 3 <>>
   <COND (<0? .CNT>)                               ;"Empty input; return PRS (T)"
         (<DO (I 1 .CNT)                           ;"Traverse over all words in input."
           <SET W <GET .SV <+ <* <- .I 1> 2> 1>>>  ;"Current word"
           <COND (<NOT .VERB?>                     ;"VERB? not set; Search for VERB or DIRECTION, ignore BUZZ."
               <COND (<WT? .W PS?VERB>
                       <SET VERB? .W>
                       <SET ARG-MAX <VMAX .VERB?>>
                       <SET ARG-MIN <VARGS .VERB?>>
                       <PUT .VEC 1 .VERB?>)
                     (<OR <WT? .W PS?DIRECTION> <=? .W ,W?LEAVE>>
                       <COND (<=? .W ,W?LEAVE> <SET .W ,W?OUT>)>
                       <SET VERB? ,W?WALK>
                       <SET ARG-MAX 1>
                       <SET ARG-MIN 1>
                       <SET ARG-NUM 1>
                       <PUT .VEC 1 .VERB?>
                       <PUT .VEC 2 .W>
                       <SET PRS T> <SET .I .CNT>)
                     (<WT? .W PS?BUZZ-WORD>)
                     (<OR <WT? .W PS?OBJECT> <WT? .W PS?ADJECTIVE> <0? .W>>
                       <COND (<OR <WT? .W PS?OBJECT> <WT? .W PS?ADJECTIVE>>
                               <OR .SILENT?
                               <TELL "What should I do with it?" CR>>)     ;"VERB? is not set but found an OBJECT."
                            (<OR .SILENT?
                               <TELL "I don't know how to do that." CR>>)> ;"VERB? is not set and found an unknown word."
                       <SET PRS <>> <SET .I .CNT>)                   
                     (<OR .SILENT? <TELL "I can't parse that." CR>>        ;"Illegal PoS. Shouldn't happen."
                       <SET PRS <>> <SET .I .CNT>)>)
                 (<NOT <0? .W>>                                            ;"VERB is found and legal word"
               <COND (<WT? .W PS?BUZZ-WORD>)                   ;"Ignore BUZZ"
                     (<OR <WT? .W PS?OBJECT> <WT? .W PS?ADJECTIVE>>
                       <SET ARG-NUM <+ .ARG-NUM 1>>
                       <COND (<2 .VEC> <PUT .VEC 3 .W>)
                             (T <PUT .VEC 2 .W>)>)
                     (<WT? .W PS?DIRECTION>
                       <COND (<2 .VEC>
                               <OR .SILENT? <TELL "I can't parse that." CR>>   ;"GO KNIFE SOUTH"
                               <SET PRS <>> <SET .I .CNT>)
                             (<PUT .VEC 2 .W>
                               <SET ARG-NUM <+ .ARG-NUM 1>>
                               <SET PRS T> <SET .I .CNT>)>)>)
                 (T <OR .SILENT? <TELL "I don't know that word." CR>>              ;"Unknown word"
               <SET PRS <>> <SET .I .CNT>)>>)>
   <COND (<AND .PRS <NOT <0? .CNT>>>
           <COND (<NOT .VERB?> <OR .SILENT? <TELL "Huh?" CR>> <SET PRS <>>);"No VERB?"
                 (<AND <L=? .ARG-NUM .ARG-MAX> <G=? .ARG-NUM .ARG-MIN>>    ;"Verb present and arguments in allowed range."
               <COND (<VACTION? .VERB?>
                   <COND (<WT? <2 .VEC> PS?DIRECTION>
                       <OR .SILENT? <TELL "You can't do that!" CR>>        ;"If VACTION? = T (Verb is not WALK) and PRSO is DIRECTION. GET SOUTH."
                       <SET PRS <>>)>)
                     (T
                   <COND (<NOT <WT? <2 .VEC> PS?DIRECTION>>
                       <OR .SILENT? <TELL "Go where?" CR>>                 ;"If VACTION? = FALSE (Verb is WALK) and PRSO is not DIRECTION. GO TROLL."
                       <SET PRS <>>)>)>)
                 (T 
               <COND (<NOT <OR .SILENT? <VSTR .VERB?>>>                        ;"Verb present and illegal argument range print VSTR. TIE."
                   <OR .SILENT? <TELL "I don't understand." CR>>)>         ;"Verb present and illegal argument range and empty VSTR. GO."
               <SET PRS <>>)>)>
   .PRS>

And as a reminder on how COND works - Statements like this:

    <COND (<WT? .W PS?BUZZ-WORD>)
          (<WT? .W PS?OBJECT> <Do things...>)
          (<WT? .W PS?DIRECTION> <Do things...>)
          (T <Do things ...>)>

Means that if .W is a buzz-word, because this COND is missing a <Do things...>-part, nothing is done but no more conditionals are tested.

In ZIL the PRSVEC is defined as a table with four WORD (16 bits) slots. The first slot is unused to retain the same indexes as in MDL. If we use the same sentence as above, "throw the knife at the troll", PRSVEC will contain:

Byte 0 1 2-3     4-5     6-7
     0 0 W?THROW W?KNIFE W?TROLL

Next: Part 6 - The object table and how the player moves around

4 Likes

Recreating Zork 285 in ZIL - Part 6 - The object table and how the player moves around

This is part 6 in an ongoing series. The previous part: “Part 5 - Parsing the input” is here.

In this part we will examine how the objects and their properties are stored in the Z-machine. We will also look at what available ZIL functions there are to manipulate the objects and properties. In the end we will see how this information is applied when we study how the player can use directions to move around the world model.

The object table and the property tables

The object table and the property tables are described in chapter 12 of The Z-Machine Standards Document. Much of the information in this part comes from there and it is essential reading for understanding the Z-machine.

The starting point of the object table is stored in bytes 10-11 of the game header (the game header is described in chapter 11 of The Z-Machine Standards Document). This value is considered read-only but you can read it from inside ZIL by either the function <LOWCORE OBJECT> or simply the function <GET 0 5> (i.e. get the fifth 2-byte word). Normally this is a value you won’t need.
The object table is divided into three parts following each other in the order; the property default table, the object tree table and the property tables.

There are differences between version 3 and the later versions that will affect the size of these tables. These differences are:

  • Version 3 only allows up to 32 attribute flags; later versions can have up to 48.
  • Version 3 only allows up to 255 objects; later versions can address up to 65535 (one byte versus two bytes) but because the object table is in dynamic memory and must reside under address $FFFF and the minimum size of an object is 16 bytes, the in practice the upper limit of objects is well under 4096.
  • Version 3 only allows up to 31 properties; later versions can have up to 63.
    In the examples below we will primarily focus later versions but point out the differences and leave the version 3 format as an exercise to the reader.

The simple ZIL program below creates a couple of objects and properties and will be used as an example henceforth.

<VERSION XZIP>          ;"Version 5"
<CONSTANT RELEASEID 1>

<DIRECTIONS NORTH EAST WEST SOUTH UP DOWN>

<GLOBAL MAGIC-FLAG <>>

<ROUTINE WINDOW-EXIT () <RTRUE>>
<ROUTINE LIVING-ROOM-F () <RTRUE>>

;"If defined, this CONSTANT is assigned the highest object number during compilation."
<CONSTANT LAST-OBJECT <>> 

<OBJECT ROOMS>

<ROOM LIVING-ROOM  ;"ROOM and OBJECT are synonyms for same function."
      (IN ROOMS)
      (DESC "Living Room")
      (LDESC "You are in the living room. There is a doorway to the east.")
      (NORTH "You can't enter the chimney!")
      (EAST TO KITCHEN)
      (WEST TO STRANGE-PASSAGE IF MAGIC-FLAG ELSE "The door is nailed shut.")
      (DOWN TO CELLAR IF TRAP-DOOR IS OPEN)
      (SOUTH PER WINDOW-EXIT)
      (ACTION LIVING-ROOM-F)
      (FLAGS LANDBIT LIGHTBIT)>  

<ROOM KITCHEN
      (IN ROOMS)
      (DESC "Kitchen")
      (WEST TO LIVING-ROOM)>

<ROOM STRANGE-PASSAGE
      (IN ROOMS)
      (DESC "Strange Passage")>

<ROOM CELLAR
      (IN ROOMS)
      (DESC "Cellar")>
      
<OBJECT LANTERN
    (DESC "brass lantern")
    (IN LIVING-ROOM)
    (SYNONYM de)
    (ADJECTIVE BRASS)>

<OBJECT SWORD
    (IN LIVING-ROOM)
    (DESC "sword")
    (SYNONYM SWORD)>

<OBJECT TRAP-DOOR
    (DESC "trap-door")
    (IN LIVING-ROOM)
    (SYNONYM TRAP-DOOR DOOR TRAPDOOR)
    (FLAGS OPENBIT)>
    
<ROUTINE GO () 
    <CRLF>
    <TELL "<LOWCORE OBJECT>=" N <LOWCORE OBJECT> CR>
    <TELL "P?NORTH=" N ,P?NORTH CR>
    <TELL "P?EAST=" N ,P?EAST CR>   
    <TELL "P?WEST=" N ,P?WEST CR>   
    <TELL "P?SOUTH=" N ,P?SOUTH CR> 
    <TELL "P?UP=" N ,P?UP CR>   
    <TELL "P?DOWN=" N ,P?DOWN CR>   
    <TELL "P?SYNONYM=" N ,P?SYNONYM CR> 
    <TELL "P?ADJECTIVE=" N ,P?ADJECTIVE CR> 
    <TELL "P?LDESC=" N ,P?LDESC CR> 
    <TELL "P?ACTION=" N ,P?ACTION CR>   
    <TELL "ROOMS=" N ,ROOMS CR> 
    <TELL "LIVING-ROOM=" N ,LIVING-ROOM CR> 
    <TELL "KITCHEN=" N ,KITCHEN CR> 
    <TELL "STRANGE-PASSAGE=" N ,STRANGE-PASSAGE CR> 
    <TELL "CELLAR=" N ,CELLAR CR>   
    <TELL "LANTERN=" N ,LANTERN CR> 
    <TELL "SWORD=" N ,SWORD CR> 
    <TELL "TRAP-DOOR=" N ,TRAP-DOOR CR> 
    <TELL "LAST-OBJECT=" N ,LAST-OBJECT CR>>

If we compile and run the program it produces a listing of constants that will be usefull. The compilation also created four abbreviations in this order; “P?”, " the ", “door” and "You ".

<LOWCORE OBJECT>=282
P?NORTH=63
P?EAST=62
P?WEST=61
P?SOUTH=60
P?UP=59
P?DOWN=58
P?SYNONYM=57
P?ADJECTIVE=56
P?LDESC=55
P?ACTION=54
ROOMS=8
LIVING-ROOM=7
KITCHEN=6
STRANGE-PASSAGE=5
CELLAR=4
LANTERN=3
SWORD=2
TRAP-DOOR=1
LAST-OBJECT=8

The property default table

The property default table starts at the address from the game header and holds the 31 (version 3) or 63 (version 4 and later) default values for each property, occupying 62 bytes or 126 bytes, respectively. These are the values returned when reading a property on an object that don’t have that property explicitly defined. The default values are initialized to 0 (false) but can be set to other values by PROPDEF. <PROPDEF SIZE 5> will, for example, give all objects SIZE=5 if SIZE is not explicitly defined to another value for that object.

In our example game the property default table starts at address 282, hexadecimal $011A, and contains 126 bytes of 0, meaning that all 63 properties have a default value of 0.

011A-0197 00 00 00 00 ... 00 00

ZIL functions that operates on the property default table are; PROPDEF.

The object tree table

The object tree table follows directly after the property defaults table and consists of an entry of 9 bytes (version 3) or 14 bytes (version 4 or later) for each object. The first entry is object number 1 (object number 0 is considered “nothing” or “null”).

The layout of the table as described in §12.3.1 and §12.3.2 of The Z-Machine Standards Document) is:

Version 3:
    the 32 attribute flags    parent     sibling     child   properties
   ---32 bits in 4 bytes---   ---3 bytes------------------  ---2 bytes--

Version 4 and later:
    the 48 attribute flags    parent     sibling     child   properties
   ---48 bits in 6 bytes---   ---3 words, i.e. 6 bytes----  ---2 bytes--   

In our example the object tree table starts at address $0198 ($011A + $8E) and look like this:

Byte 0-5                 6-7     8-9     10-11   12-13
     48 bit flags        Parent  Sibling Child   Properties
0198 00 00 00 00 00 01   00 07   00 02   00 00   02 08      Object 1 TRAP-DOOR, Bit 0, OPENBIT, is set
01A6 00 00 00 00 00 00   00 07   00 00   00 00   02 18      Object 2 SWORD
01B4 00 00 00 00 00 00   00 07   00 01   00 00   02 21      Object 3 LANTERN
01B2 00 00 00 00 00 00   00 08   00 05   00 00   02 36      Object 4 CELLAR
01D0 00 00 00 00 00 00   00 08   00 06   00 00   02 3E      Object 5 STRANGE-PASSAGE
01DE 00 00 00 00 00 00   00 08   00 00   00 00   02 4C      Object 6 KITCHEN
01EC 00 00 00 00 00 06   00 08   00 04   00 03   02 57      Object 7 LIVING-ROOM, Bit 1 and 2 are set (LIGHTBIT & LANDBIT)
01FA 00 00 00 00 00 00   00 00   00 00   00 07   02 86      Object 8 ROOMS

It is easy to identify ROOMS at the top in the object tree, it points to its first child, LIVING-ROOM, that in its turn points to a sibling chain, LIVING-ROOM --> CELLAR --> STRANGE-PASSAGE --> KITCHEN, and its first child, LANTERN, and so on.

Notice that flags don’t need to be explicitly defined, instead the compiler assigns them a free slot during compilation. In the above table we can see that OPENBIT, LIGHTBIT and LANDBIT are assigned bits 0-2, respectively.

Also note that object numbers and attribute flags are assigned in reverse order; The last one specified in the source file get number 1, the next to last number 2 and so on.

ZIL functions that operates on the object tree table are; PROPDEF. FIRST?, IN?, LOC, MAP-CONTENTS, MOVE, NEXT?, PRINTD and REMOVE.

The property tables

The property tables immediately follow the object tree table and each object has its own property table. Each object in the object tree table have a pointer (byte 7-8 for version 3 and byte 12-13 for later versions) that point to the start address for that objects properties. Each property table starts with a header that holds the short description of the object (the DESC-part). The layout of the header is described in §12.4 of The Z-Machine Standards Document:

   text-length     text of short name of object
  -----byte----   --some even number of bytes---

§12.4 also state “where the text-length is the number of 2-byte words making up the text, which is stored in the usual format. (This means that an object’s short name is limited to 765 Z-characters.)”. The limit of 765 z-chars isn’t entirely correct because it is perfectly legal to use abbreviations and abbreviations don’t have that limit, you could circumvent the limitation if you really want it.

The actual properties follow after the header for this object in descending order of the property number. Property number 0 indicates the end of the table for this object. The layout of each property, from The Z-Machine Standards Document, §12.4.1 and §12.4.2, is:

Version 3:
   size byte     the actual property data
                ---between 1 and 8 bytes--

Version 4 and later: 
   size and number       the actual property data
  --1 or 2 bytes---     --between 1 and 64 bytes--

For version 3 the size byte have the property number in bits 0-4 (1-31) and the property size in bits 5-7 (add 1 to this value to get actual size of 1-8).

Version 4 and later can have 1 or 2 bytes for size and number depending on the size of the property.
If the size of the property is 1 or 2 bytes, only 1 byte is used for size and number. In this case bit 7 of the size and number byte is clear, bit 6 tells the property size (0=1 byte, 1=2 bytes) and bits 0-5 is the property number.
If the size of the property is more than 2 bytes, 2 bytes is used for size and number. In this case bit 7 of the first byte is set; bit 6 of the first byte is unused but is always clear by convention and bits 0-5 of the first byte is the size of the property. In the second byte, bits 6-7 are unused but bit 7 is always set by convention and bit 6 is set in ZIL and clear in Inform, bits 0-5 holds the property number. A size of 0 is considered to mean that the size is 64 bytes.

In our example the property tables looks like this:

0208 03 66 E6 54 BC 84 45               Length=5 2-byte words, "trap-door" 
     B9 86 02 FD 02 B5 03 06            89 86 --> Prop#=57, Size=6, W?TRAP-DOOR W?DOOR W?TRAPDOOR
     00                                 Null

0218 02 63 94 DD 25                     Length=2 2-byte words, "sword"
     79 02 F4                           79 --> Prop#=57, Size=2, W?SWORD
     00                                 Null

0221 05 1E E6 63 00 44 D3 65 57 CC A5   Length=5 2-byte words, "brass lantern"
     B9 84 02 D9 02 D0                  89 84 --> Prop#=57, Size=4, W?LANTERN W?LAMP
     78 02 AC                           78    --> Prop#=56, Size=2, W?BRASS
     00                                 Null

0236 03 11 0A 46 26 DC A5               Length=3 2-byte words, "Cellar"
     00                                 Null

023E 06 13 19 5C D3 31 40 12 A6 63 06 B1 45 Length=6 2-byte words, "Strange Passage"
     00                                     Null

024C 03 12 0E 65 0D AA 65               Length=3 2-byte words, "Kitchen"
     7D 00 07                           7D    --> Prop#=61, Size=2, UEXIT: goto room 7
     00                                 Null

0257 05 12 2E 6D D3 30 04 5E 94 C8 A5   Length=5 2-byte words, "Living Room"
     BF 83 01 3A 00                     BF 83 --> Prop#=63, Size=3, NEXIT: Message at 4x$013A
     7E 00 06                           7E    --> Prop#=62, Size=2, UEXIT: goto room 6
     BD 85 00 05 01 2B 10               BD 85 --> Prop#=61, Size=5, CEXIT: goto room 5 if variable 10,MAGIC-FLAG, is true else print message at 4x$012B
     BC 84 00 C9 00 00                  BC 84 --> Prop#=60, Size=4, FEXIT: goto room returned by routine at 4x$00C9
     BA 86 00 04 00 01 00 00            8A 86 --> Prop#=58, Size=6, DEXIT: goto room 4 if object 1 has bit 1, OPENBIT, set else print message at $0000 (no msg)
     77 01 2F                           77    --> Prop#=55, Size=2, LDESC: Message at 4x$012F
     76 00 CA                           76    --> Prop#=54, Size=2
     00                                 Null

0286 01 94 A5                           Length=1 2-byte words, ""
     00                                 Null

The first objects at $0208 have the header 03 66 E6 54 BC 84 45. The first byte is the length in 2-byte words of the following text. This 6 bytes in binary are and translates to the following z-characters; 0 11001='t' 10111='r' 00110='a' 0 10101='p' 00101=shift to A2 11100='-' 1 00001 00010=use abbrev 2 ('door') 00101=padding. After the property header comes the first property, B9 86 02 FD 02 B5 03 06. $B9 = 1 0 111001, meaning bit 7 is set (size in next byte) and property number is 57 (bits 0-5). $86 = 1 0 000110 means that the size is 6 bytes (bits 0-5). The 02 FD 02 B5 03 06 is pointers to W?TRAP-DOOR, W?DOOR and W?TRAPDOOR in the vocabulary, respectively. $00 = property 0 means that this is the end of this property table.

The next object at $0218 is “sword” and its first property is 79 02 F4. The size and number byte is $79 = 0 1 111001. Because bit 7 is clear the size is in bit 6 that is set, meaning a size of 2 bytes. The property number in bits 0-5 is 57. The property data is a pointer to the word W?SWORD in the vocabulary.

Directions in the property data

Drections are a bit of a special case and ZIL have a predefined default record size and pattern for the direction properties. This definition is redefinable if one would want to but the default format is as below (version 4 and later, version 3 have different values for the size).

<PROPDEF DIRECTIONS <>
  (DIR TO R:ROOM = (UEXIT 2) (REXIT <ROOM .R>))
  (DIR SORRY S:STRING = (NEXIT 3) (NEXITSTR <STRING .S>) <BYTE 0>)
  (DIR PER F:FCN = (FEXIT 4) (FEXITFCN <WORD .F>) <WORD 0>)
  (DIR TO R:ROOM IF G:GLOBAL "OPT" ELSE S:STRING = (CEXIT 5) (REXIT <ROOM .R>) (CEXITSTR <STRING .S>) (CEXITFLAG <GLOBAL .G>))
  (DIR TO R:ROOM IF D:OBJECT IS OPEN "OPT" ELSE S:STRING = (DEXIT 6) (REXIT <ROOM .R>) (DEXITOBJ <OBJECT .D>) (DEXITSTR <STRING .S>))
  (DIR R:ROOM = (UEXIT 2) (REXIT <ROOM .R>))
  (DIR S:STRING = (NEXIT 3) (NEXITSTR <STRING .S>) <BYTE 0>)>

The syntax of PROPDEF is explained in more detail in the ZILF Reference Guide but simply explained the DIR is a token placeholder for any direction type words (part of speech = direction) and the part after the = are the record size and then follows the record data. An expression like (UEXIT 2) both sets the record size to 2 and assigns UEXIT the value 2.
As you probably already noticed the different exit types have different sizes. This is by design and the size is used to distinguish which type of exit it is.

These are the five different type of exits that can be attached to a direction property and is stored in the object property tables as on object at $0257 above:

Name  Size 
UEXIT   2  "Unconditional exit" - goto the room stored in property position 0-1 
                                  Syntax: ([DIR] TO [ROOM]) or ([DIR] [ROOM])
NEXIT   3  "Non-exit"           - no exit but print message at packed address in property position 0-1 
                                  instead of standard message. Padded with 0 in position 2 to get to size=3.
                                  Syntax: ([DIR] "[message]") or ([DIR] SORRY "[message]")
FEXIT   4  "Function exit"      - goto the room returned by routine at packed address in property position 0-1.
                                  If function returns false, don't move. Padded with 0 in position 2-3 to get to size=4.
                                  Syntax: ([DIR] PER [ROUTINE-EXIT]) 
CEXIT   5  "Conditional exit"   - goto the room stored in property position 0-1 if value in variable in position 4 is true
                                  else print message in packed address in position 2-3. If position 2-3 is 0 then print
                                  the standard message.
                                  Syntax: ([DIR] TO [ROOM] IF [VARIABLE] [ELSE "[message]"])
DEXIT   6  "Door exit"          - goto the room stored in property position 0-1 if object with object number stored in 
                                  position 2-3 meets condition (check done in parser library) else print message in 
                                  packed address in position 4-5. 
                                  If position 4-5 is 0 then print the standard message.
                                  Syntax: ([DIR] TO [ROOM] IF [OBJECT] IS OPEN [ELSE "[message]"])

In Zork 285 only UEXIT, NEXIT and CEXIT are used.

ZIL functions that operates on the object property tables are; GETP, GETPT, MAP-DIRECTIONS, NEXTP, PTSIZE and PUTP

How the player moves around, WALK

In MDL when the player types a direction, for example n, this will be parsed into PRSVEC as WALK, NORTH, <>. The verb WALK has the VFCN = WALK so that function will be called. In ZIL EPARSE will parse the input into PRSVEC as 0, W?WALK, W?NORTH, 0 instead. The line <SYNTAX WALK = WALK> in aact.zil is the link between the verb WALK and the function WALK in rooms.zil. How this link, via the syntax line, works in ZIL will be covered in the next part. Here we concentrate on the WALK function and how it extracts the direction properties from the room objects and moves the player to the new room.

The orginal function in MDL (ROOMS 154 [Jun 14 1977]):

<DEFINE WALK ("AUX" LEAVINGS NRM (WHERE <CHTYPE <2 ,PRSVEC> ATOM>) (ME ,WINNER)
            (RM <1 .ME>))
    #DECL ((WHERE) ATOM (ME) ADV (RM) ROOM (LEAVINGS) <OR ROOM CEXIT NEXIT>
           (NRM) <OR FALSE
             <<PRIMTYPE VECTOR> [REST ATOM <OR ROOM NEXIT CEXIT>]>>)
    <COND (<AND <NOT <LIT? .RM>> <PROB 75>>
           <COND (<SET NRM <MEMQ .WHERE <REXITS .RM>>>
              <SET LEAVINGS <2 .NRM>>
              <COND (<AND <TYPE? .LEAVINGS ROOM>
                  <LIT? .LEAVINGS>>
                 <GOTO .LEAVINGS>
                 <ROOM-INFO <>>)
                (<AND <TYPE? .LEAVINGS CEXIT>
                  ,<CXFLAG .LEAVINGS>
                  <LIT? <SET LEAVINGS <CXROOM .LEAVINGS>>>>
                 <GOTO .LEAVINGS>
                 <ROOM-INFO <>>)
                (<TELL
"Dear, dear.  You seem to have fallen into a bottomless pit.">
                 <SLEEP 4>
                 <JIGS-UP <PICK-ONE ,NO-LIGHTS>>)>)
             (<TELL
"Dear, dear.  You seem to have fallen into a bottomless pit.">
              <SLEEP 4>
              <JIGS-UP <PICK-ONE ,NO-LIGHTS>>)>)
          (<SET NRM <MEMQ .WHERE <REXITS .RM>>>
           <SET LEAVINGS <2 .NRM>>
           <COND (<TYPE? .LEAVINGS ROOM>
              <GOTO .LEAVINGS>
              <ROOM-INFO <>>)
             (<TYPE? .LEAVINGS CEXIT>
              <COND (,<CXFLAG .LEAVINGS>
                 <GOTO <CXROOM .LEAVINGS>>
                 <ROOM-INFO <>>)
                (<CXSTR .LEAVINGS> <TELL <CXSTR .LEAVINGS>>)
                (<TELL "There is no way to go in this direction.">)>)
             (T <TELL .LEAVINGS>)>)
          (<TELL "There is no way to go in this direction.">)>>

And in psuedo-code:

if no light and chance <= 75% then 
    if UEXIT and destination has light then move player to destination.
    elseif CEXIT then
        if flag true and destination has light then move player to destination.
        else print "Dear, dear.  You seem to have fallen into a bottomless pit." and end game.
    else print "Dear, dear.  You seem to have fallen into a bottomless pit." and end game.
elseif UEXIT then move player to destination.
elseif CEXIT then
    if flag true then move player to destination.
    else print message "There is no way to go in this direction."
elseif NEXIT 
    print NEXIT-message
else print
    print message "There is no way to go in this direction."

In practice this simply means that you only got a 25% chance to survive if you, in a dark room, walk in direction that has no exit.

Converted to ZIL this becomes (rooms.zil):

<ROUTINE WALK ("AUX" (RM ,HERE) WHERE PTS NRM STR LEAVINGS)
    <IFFLAG (DEBUG <TELL "WALK" CR>)>
    <AND <GETP .RM ,P?RACTION> <APPLY <GETP .RM ,P?RACTION>>>  ;"Extra call to RACTION with W?WALK (CAROUSEL-ROOM)" 
    <SET WHERE <GETB <2 ,PRSVEC> 7>>  ;"P1? for direction word"
    <COND (<AND <NOT <LIT? .RM>> <PROB 75>>
            <COND (<SET NRM <GETPT .RM .WHERE>>
                <SET PTS <PTSIZE .NRM>>
                <SET LEAVINGS <GET .NRM ,REXIT>>  ;"Where to?"
                <COND (<AND <=? .PTS ,UEXIT>  ;"There's an UEXIT there and the room is lit. 75% chance for success." 
                            <LIT? .LEAVINGS>>
                        <GOTO .LEAVINGS> 
                        <ROOM-INFO <>>) 
                      (<AND <=? .PTS ,CEXIT>  ;"There's an CEXIT there with true FLAG and the room is lit. 75% chance for success."
                            <VALUE <GETB .NRM ,CEXITFLAG>>
                            <LIT? .LEAVINGS>>
                        <GOTO .LEAVINGS> 
                        <ROOM-INFO <>>)
                      (T 
                        <TELL "Dear, dear.  You seem to have fallen into a bottomless pit." CR>
                        <SLEEP 4>
                        <JIGS-UP <PICK-ONE ,NO-LIGHTS>>)>)
                  (T
                    <TELL "Dear, dear.  You seem to have fallen into a bottomless pit." CR>
                    <SLEEP 4>
                    <JIGS-UP <PICK-ONE ,NO-LIGHTS>>)>)
          (<SET NRM <GETPT .RM .WHERE>>
                <SET PTS <PTSIZE .NRM>>
                <SET LEAVINGS <GET .NRM ,REXIT>>                  ;"Where to?"
                <COND (<=? .PTS ,UEXIT> 
                        <GOTO .LEAVINGS> 
                        <ROOM-INFO <>>)
                      (<=? .PTS ,CEXIT>
                        <COND (<VALUE <GETB .NRM ,CEXITFLAG>>       ;"FLAG is true"
                                <GOTO .LEAVINGS> 
                                <ROOM-INFO <>>)
                              (<SET STR <GET .NRM ,CEXITSTR>>      ;"FLAG is false"
                                <TELL .STR CR>)
                              (T 
                                <TELL "There is no way to go in this direction." CR>)>)
                      (<=? .PTS ,NEXIT> 
                        <TELL .LEAVINGS CR>)>)
          (<TELL "There is no way to go in this direction." CR>)>>

Remember that the below constants have these values from the property definition of DIRECTIONS (above):

<CONSTANT UEXIT 2>
<CONSTANT NEXIT 3>
<CONSTANT CEXIT 5>
<CONSTANT REXIT 0>
<CONSTANT NEXITSTR 0>
<CONSTANT CEXITFLAG 4>
<CONSTANT CEXITSTR 1>

The conversion to ZIL is pretty straightforward and I will just point out a couple of things:

  • The line <AND <GETP .RM ,P?RACTION> <APPLY <GETP .RM ,P?RACTION>>> is there to make an extra call to the room action-routine, if there is one, before we leave the room. This is there to give the “Carousel Room” opportunity to scramble the exits before we extract the exit.
  • <SET WHERE <GETB <2 ,PRSVEC> 7>> puts the directions property number in WHERE.
  • <SET NRM <GETPT .RM .WHERE>> puts that direction property on current room into NRM.
  • <SET PTS <PTSIZE .NRM>> puts the size of the property, i.e. if the exit is a UEXIT, NEXIT or CEXIT, into PTS.
  • LEAVINGS is the new room (or the NEXIT string).
  • LIT?, ROOM-INFO and GOTO are helper functions that pretty much do what they are called.

Next: Part 7 - Matching the syntax

2 Likes

Recreating Zork 285 in ZIL - Part 7 - Matching the syntax

This is part 7 in an ongoing series. The previous part: “Part 6 - The object table and how the player moves around” is here.

In this part we will examine how to access the syntax for the verbs in ZIL. In MDL you might remember that each verb was a vector that in slot 2 had the name, VFCN, of the function that handles this verb. In MDL we simply extract this name and call the function. In ZIL it is a little bit more complicated.

The syntax tables

When ZIL encounters a SYNTAX instruction it stores the verb and the prepositions in the vocabulary and populates the VERBS, ACTIONS and PREACTIONS tables. The VERBS table is a table of word pointers to a syntax table of that verb. Verb with verb number 255 is in the first slot and verb with verb number 254 in the second slot and so on. The syntax tables start with a byte that tells how many syntaxes there is from this verb, then follows each syntax occupying 8 bytes each. The 8 bytes contains, in this order; number of objects, preposition word number 1, preposition word number 2, FIND bit for object 1, FIND bit for object 2, search options for object 1, search options for object 2 and action number. The action number is the index in the ACTIONS table and the PREACTIONS table that contains the routine address for the action and the preaction, respectively.

To illustrate; if we compile and run this program:

<VERSION XZIP>          ;"Version 5"
<CONSTANT RELEASEID 1>

;"Examples from Zork 1"
<SYNTAX TAKE OBJECT (FIND TAKEBIT) (ON-GROUND IN-ROOM MANY) = V-TAKE PRE-TAKE>
<SYNONYM TAKE GET HOLD CARRY REMOVE GRAB CATCH>

<SYNTAX GIVE OBJECT (MANY HELD HAVE) TO OBJECT (FIND ACTORBIT) (ON-GROUND) = V-GIVE PRE-GIVE>
<SYNTAX GIVE OBJECT (FIND ACTORBIT) (ON-GROUND) OBJECT (MANY HELD HAVE) = V-SGIVE PRE-SGIVE>
<SYNONYM GIVE DONATE OFFER FEED>

;"KILL and ATTACK are seperated to overcome limit in z3 on number of synonyms."
<SYNTAX KILL OBJECT (FIND ACTORBIT) (ON-GROUND IN-ROOM) WITH OBJECT (FIND WEAPONBIT) (HELD CARRIED HAVE) = V-ATTACK>
<SYNONYM KILL MURDER SLAY DISPATCH>

<SYNTAX ATTACK OBJECT (FIND ACTORBIT) (ON-GROUND IN-ROOM) WITH OBJECT (FIND WEAPONBIT) (HELD CARRIED HAVE) = V-ATTACK>
<SYNONYM ATTACK FIGHT HURT INJURE HIT>

<ROUTINE V-TAKE () <>>
<ROUTINE PRE-TAKE () <>>
<ROUTINE V-GIVE () <>>
<ROUTINE PRE-GIVE () <>>
<ROUTINE V-SGIVE () <>>
<ROUTINE PRE-SGIVE () <>>
<ROUTINE V-ATTACK () <>>

<ROUTINE GO () 
    <CRLF>
    <TELL "Verb# W?TAKE=" N <GETB ,W?TAKE 7> CR>
    <TELL "Verb# W?GIVE=" N <GETB ,W?GIVE 7> CR>
    <TELL "Verb# W?KILL=" N <GETB ,W?KILL 7> CR>
    <TELL "Verb# W?ATTACK=" N <GETB ,W?ATTACK 7> CR>
    <TELL "VERBS[0]=" N <0 ,VERBS> CR>
    <TELL "VERBS[1]=" N <1 ,VERBS> CR>
    <TELL "VERBS[2]=" N <2 ,VERBS> CR>
    <TELL "VERBS[3]=" N <3 ,VERBS> CR>
    <TELL "ACTIONS[0]=" N <0 ,ACTIONS> CR>
    <TELL "ACTIONS[1]=" N <1 ,ACTIONS> CR>
    <TELL "ACTIONS[2]=" N <2 ,ACTIONS> CR>
    <TELL "ACTIONS[3]=" N <3 ,ACTIONS> CR>
    <TELL "PREACTIONS[0]=" N <0 ,PREACTIONS> CR>
    <TELL "PREACTIONS[1]=" N <1 ,PREACTIONS> CR>
    <TELL "PREACTIONS[2]=" N <2 ,PREACTIONS> CR>
    <TELL "PREACTIONS[3]=" N <3 ,PREACTIONS> CR>
>

We get this output:

Verb# W?TAKE=255
Verb# W?GIVE=254
Verb# W?KILL=253
Verb# W?ATTACK=252
VERBS[0]=673
VERBS[1]=682
VERBS[2]=699
VERBS[3]=708
ACTIONS[0]=180
ACTIONS[1]=182
ACTIONS[2]=184
ACTIONS[3]=186
PREACTIONS[0]=181
PREACTIONS[1]=183
PREACTIONS[2]=185
PREACTIONS[3]=0

We see that the VERBS table contains $02A1 $02AA $02BB $02C4 and if we look at these addresses:

       # NO P1 P2 F1 F2 O1 O2 ACT
02A1: 01 01 00 00 2F 00 34 F0 00    <SYNTAX TAKE OBJECT (FIND TAKEBIT) (ON-GROUND IN-ROOM MANY) = V-TAKE PRE-TAKE>
02AA: 02 02 00 00 2E 00 10 86 02    <SYNTAX GIVE OBJECT (FIND ACTORBIT) (ON-GROUND) OBJECT (MANY HELD HAVE) = V-SGIVE PRE-SGIVE>
         02 00 FF 00 2E 86 10 01    <SYNTAX GIVE OBJECT (MANY HELD HAVE) TO OBJECT (FIND ACTORBIT) (ON-GROUND) = V-GIVE PRE-GIVE>
02BB: 01 02 00 FE 2E 2D 30 C2 03    <SYNTAX KILL OBJECT (FIND ACTORBIT) (ON-GROUND IN-ROOM) WITH OBJECT (FIND WEAPONBIT) (HELD CARRIED HAVE) = V-ATTACK>
02C4: 01 02 00 FE 2E 2D 30 C2 03    <SYNTAX ATTACK OBJECT (FIND ACTORBIT) (ON-GROUND IN-ROOM) WITH OBJECT (FIND WEAPONBIT) (HELD CARRIED HAVE) = V-ATTACK>

#               Number of SYNTAX lines for this verb
NO  NOBJ    0   Number of OBJECT
P1  PREP1   1   First preposition word number
P2  PREP2   2   Second preposition word number
F1  FIND1   3   1st OBJECTs FIND (which attribute flag) 
F2  FIND2   4   2nd OBJECTs FIND (which attribute flag)
O1  OPTS1   5   1st OBJECTs search oprions, see below
O2  OPTS2   6   2st OBJECTs search options, see below
ACT ACTION  7   Points to row in ,ACTIONS/,PREACTIONS with right action/preaction-routine

FIND limits search scope for GWIM to specific attribute flag
 
Search flag options:
Bit 0
    1   HAVE		Object must be in inventory
    2   MANY		Object can be multiple objects
    3   TAKE		Try to take object
    4   ON-GROUND	Set search scope for GWIM to objects at rooms top-level*
    5   IN-ROOM		Set search scope for GWIM recursively to all open containers on ground*
    6   CARRIED		Set search scope for FWIM recursively to all open containers in inventory*
    7   HELD		Set search scope for GWIM to objects at inventory top-level*
	
GWIM (Get What I Mean): If search scope narrows down to one object that meets the critera, then action is performed on that object.

* ZilLib in ZILF doesn't distinguish between HELD and CARRIED or ON-GROUND and IN-ROOM. They all search from top-level and 
  recursively in open containers in inventory or room, respectively.

Default search scope is (ON-GROUND IN-ROOM HELD CARRIED), i.e. <SYNTAX FOO OBJECT> is the same as <SYNTAX FOO OBJECT (ON-GROUND IN-ROOM HELD CARRIED)>.

The verb GIVE has the verb number = 254 and if we look at slot 1 (calculated as 255-254) in VERBS we find that the syntax table for GIVE is at $02AA. The first byte at this address is 2, meaning that there are two different syntaxes for this verb. The first syntax, 02 00 00 2E 00 10 86 02, tells that there are two objects, no prepositions, the first objects FIND are attribute flag 46, there is no FIND on the second object, options for first object has bit 4 set (ON-GROUND), options for the second object have bits 1, 2 and 7 set (HAVE MANY HELD) and the action and preaction routine are in slot 2 of ACTIONS and PREACTIONS, respectively. The second syntax, 02 00 FF 00 2E 86 10 01, tells that there are two objects, no preposition for first object, preposition with number 255 (W?TO) for second object, there is no FIND on the first object, the second objects FIND is attribute flag 46, options for first object have bits 1, 2 and 7 set (HAVE MANY HELD), options for the second object has bit 4 set (ON-GROUND) and the action and preaction routine are in slot 1 of ACTIONS and PREACTIONS, respectively.

The matching between the player’s input to the right syntax and the support for FIND and the searcher options are all up to the parser library to supply.

Converting VFCN to ZIL and call the corresponding verb action

In MDL VFCN is simply a constant that points to the slot where the verb function’s name is stored in the verb vector. In ZIL VFCN instead is a function that extracts the function from the syntax table. Zork 285 only use the actions and don´t use any prepositions, finds, search options or preactions. Every syntax in Zork 285 is in the form <SYNTAX TAKE = TAKE> and there is only one syntax per verb. This simplifies the syntax matching quite a bit for VFCN (defs.zil):

<ROUTINE VFCN (V "AUX" SYNTAX-PTR)
    <COND (<WT? .V PS?VERB>
        <SET SYNTAX-PTR <GET ,VERBS <- 255 <GETB .V 7>>>>
        <SET SYNTAX-PTR <+ .SYNTAX-PTR 1>>
        <RETURN <GET ,ACTIONS <GETB .SYNTAX-PTR 7>>>)>
    <RETURN 0>>

This means that the instruction <APPLY <VFCN <1 ,PRSVEC>>> in RDCOM (rooms.zil) will call the verb function for the verb in the first slot in PRSVEC, if there is any.

Next: Part 8 - Verb actions: look, take and drop

2 Likes

Recreating Zork 285 in ZIL - Part 8 - Verb actions: Look, take and drop

This is part 8 in an ongoing series. The previous part: “Part 7 - Matching the syntax” is here.

In this part we will return to the main loop and start examining the verb actions. From the description of the main loop in part 4 we see that after the input is parsed, the verb action is first to handle the input.

print room description
loop_start
   print prompt
   input text
   advance move counter
   parse text
   if parse ok and verb ok do verb action
      if verb action ok do eventual room action
   if nothing have been printed yet, print "Nothing happens."
   do DEMONS (interrupts)
loop_end

The verb actions

Below are the verb listed and grouped, with its corresponding action from part 2, in a more conceptual way.

LOOK
====
LOOK!-WORDS     ROOM-DESC

INVENTORY
=========
TAKE!-WORDS     TAKE
THROW!-WORDS    DROP
DROP!-WORDS     DROP
GIVE!-WORDS     DROP
POUR!-WORDS     DROP
INVEN!-WORDS    INVENT

LAMP
====
ON!-WORDS       LAMP-ON
OFF!-WORDS      LAMP-OFF

MAGIC
=====
PRAY!-WORDS     PRAYER
TREAS!-WORDS    TREAS
SINBA!-WORDS    SINBAD
TEMPL!-WORDS    TREAS
WELL!-WORDS     WELL

SPECIAL
=======
QUIT!-WORDS     FINISH
SCORE!-WORDS    SCORE
HELP!-WORDS     HELP
INFO!-WORDS     INFO

ACT-HACK
========
EXORC!-WORDS    ACT-HACK
MUNG!-WORDS     ACT-HACK
UNTIE!-WORDS    ACT-HACK
PUSH!-WORDS     ACT-HACK
RUB!-WORDS      ACT-HACK
WAVE!-WORDS     ACT-HACK
PLUG!-WORDS     ACT-HACK
TIE!-WORDS      ACT-HACK
OPEN!-WORDS     ACT-HACK

OTHER
=====
JUMP!-WORDS     LEAPER
MOVE!-WORDS     MOVE
READ!-WORDS     READER
FILL!-WORDS     FILL

The verb action ACT-HACK is a special case. These verbs will call the direct and indirect objects action function (if there is one). We will return to ``ÀCT-HACK``` in a later part.

Printing the rooms description

The verb LOOK has the action ROOM-DESC that calls ROOM-INFO with parameter that forces full description to be printed. ROOM-INFO is also called when the player enters a room. I we list the relevant functions side by side it is easy to see how similar they are (ROOMS 154 and rooms.zil).

MDL                                                                         ZIL
===                                                                         ===
<DEFINE ROOM-DESC () <ROOM-INFO T>>                                         <ROUTINE ROOM-DESC () <ROOM-INFO T>>

<DEFINE ROOM-INFO (FULL "AUX" (RM ,HERE))                                   <ROUTINE ROOM-INFO (FULL "AUX" (RM ,HERE))
   #DECL ((RM) ROOM (FULL) <OR ATOM FALSE>)                                     
                                                                                <IFFLAG (DEBUG <TELL "ROOM-INFO" CR>)>
   <SETG TELL-FLAG T>                                                           <SETG TELL-FLAG T>
   <PROG ()
     <COND (<NOT <LIT? .RM>>                                                    <COND (<NOT <LIT? .RM>>
        <TELL                                                                           <TELL "It is now completely dark.  You will probably fall into a pit." CR>
"It is now completely dark.  You will probably fall into a pit.">
        <RETURN>)                                                                       <RTRUE>)
           (<AND <RSEEN? .RM> <PROB 80> <NOT .FULL>> <TELL <RDESC2 .RM>>)             (<AND <GETP .RM ,P?RSEEN?> <PROB 80> <NOT .FULL>> <TELL <GETP .RM ,P?RDESC2> CR>)
           (<AND <EMPTY? <RDESC1 .RM>> <RACTION .RM>>                                 (<AND <NOT <GETP .RM ,P?RDESC1>> <GETP .RM ,P?RACTION>>
        <PUT ,PRSVEC 1 ,LOOK!-WORDS>                                                    <PUT ,PRSVEC 1 ,W?LOOK>
        <APPLY <RACTION .RM>>)                                                          <APPLY <GETP .RM ,P?RACTION>>)
           (<TELL <RDESC1 .RM>>)>                                                     (T <TELL <GETP .RM ,P?RDESC1> CR>)>
     <PUT .RM ,RSEEN? T>                                                        <PUTP .RM ,P?RSEEN? T>

                                                                                ;"Extra CR Before listing painting in gallery if it's untouched and the only one on the floor."
                                                                                <AND <NOT ,PATCHED> <=? .RM ,GALLE> <NOT <GETP ,PAINT ,P?OTOUCH?>> <=? <FIRST? ,GALLE> ,PAINT> <CRLF>>

     <MAPF <>                                                                   <MAP-CONTENTS (X .RM)           ;"List all objects in room."
           <FUNCTION (X "AUX" Y) 
               #DECL ((X) OBJECT (Y) <OR FALSE OBJECT>)
               <COND (<OVIS? .X>                                                    <COND (<OVIS? .X>
                  <LONG-DESC-OBJ .X>                                                    <LONG-DESC-OBJ .X>
                  <CRLF>                                                                <TELL CR>
                                                                                        <COND (<N=? .X ,THIEF>  ;"Don't list object carried by thief"
                  <MAPF <>                                                                  <MAP-CONTENTS (Y .X)
                    <FUNCTION (Y) 
                        #DECL ((Y) OBJECT)
                        <LONG-DESC-OBJ .Y>                                                      <LONG-DESC-OBJ .Y>
                        <PRINC " [in the ">                                                     <TELL " [in the " <GETP .X ,P?ODESC2> "]" CR>>)>)>>
                        <PRINC <ODESC2 .X>>
                        <PRINC "]">
                        <CRLF>>
                    <OCONTENTS .X>>)>>
           <ROBJS .RM>>

                                                                                <AND <NOT ,PATCHED> <=? .RM ,LROOM ,MIRR1 ,MIRR2 ,CYCLO-R ,LLD1> <CRLF>>
                                                                                
                                                                                 ;"Trigger call to RACTION with WALK-IN when entering a room."  
     <AND <RACTION .RM>                                                         <AND <GETP .RM ,P?RACTION>
          <NOT .FULL>                                                                <NOT .FULL>                ;"If FULL is <> then call is from WALK"
          <PUT ,PRSVEC 1 ,WALK-IN!-WORDS>                                            <PUT ,PRSVEC 1 ,W?WALK-IN>
          <APPLY <RACTION .RM>>>>>                                                   <APPLY <GETP .RM ,P?RACTION>>>>

"GIVE LONG DESCRIPTION OF OBJECT"                                           ;"GIVE LONG DESCRIPTION OF OBJECT"

<DEFINE LONG-DESC-OBJ (OBJ) #DECL ((OBJ) OBJECT)                            <ROUTINE LONG-DESC-OBJ (OBJ)
                                                                                <IFFLAG (DEBUG <TELL "LONG-DESC-OBJ" CR>)>
  <COND (<OR <OTOUCH? .OBJ> <NOT <ODESCO .OBJ>>>                                <COND (<OR <GETP .OBJ ,P?OTOUCH?> <NOT <GETP .OBJ ,P?ODESC0>>>
     <PRINC <ODESC1 .OBJ>>)                                                             <TELL <GETP .OBJ ,P?ODESC1>>)
    (<PRINC <ODESCO .OBJ>>)>>                                                         (<TELL <GETP .OBJ ,P?ODESC0>>)>>

A couple of notes:

  • <IFFLAG (DEBUG <TELL "ROOM-INFO" CR>)> is a line that will print debug information if the debug flag is set when compiling and can be ignored here.
  • The TELL-FLAG is a global variable that is set to false before each new input from the user. Then if a function prints something to the screen it is set to true. If the flag is still false after all parsing is done the main loop will print “Nothing happens.”.
  • Lines that start with <AND <NOT ,PATCHED>... is only there to sometimes force in an odd extra line feed in order to replicate the erratic behavior of the original.

The first block is to print the room description. There are a couple of alternatives:

  1. If the room is dark and there is no light source print the “dark” message and exit.
  2. If we been here before and not a full description (LOOK) is ordered; then there is an 80 percent chance only the short description (RDESC2) is printed.
  3. If there is no long description (RDESC1); then we call the rooms action function with the verb LOOK and let the room action handle it.
  4. Otherwise we print the long room description (RDESC1).

The next block iterates over all objects in the room and print the description of all visible ones. It also prints the contents, one level deep, of all containers except the thief.

Finally in the last block, if the room description is printed because we arrived here by movement, the room action is called with the special verb WALK-IN to allow handling when the player enters a room.

A side note: There is probably a bug with the PROG-block in the original MDL code. The intention was probably to only enclose the first and the second block with the PROG. That would make the RETURN when printing the “dark” message exit the PROG-block but allow the call to room action with WALK-IN. As it is written now, where the PROG-block encloses all the function, the WALK-IN is skipped when printing the “dark” message. This doesn’t have any significant impact on this game but makes a small difference.
Now:

You are in the living room.  There is a door to the east, a wooden door
with strange gothic lettering to the west, which appears to be nailed
shut, and a large oriental rug in the center of the room.
There is a trophy case here.
A brass lantern is on the trophy case.

>move rug
With a great effort, the carpet is moved to one side of the room.
With the rug removed, the dusty cover of a closed trap-door appears.
>open
The door reluctantly opens to reveal a rickety staircase descending
into darkness.
>d
It is now completely dark.  You will probably fall into a pit.
>

As (probably) intended:

You are in the living room.  There is a door to the east, a wooden door
with strange gothic lettering to the west, which appears to be nailed
shut, and a large oriental rug in the center of the room.
There is a trophy case here.
A brass lantern is on the trophy case.
>move rug
With a great effort, the carpet is moved to one side of the room.
With the rug removed, the dusty cover of a closed trap-door appears.
>open
The door reluctantly opens to reveal a rickety staircase descending
into darkness.
>d
It is now completely dark.  You will probably fall into a pit.
The trap door crashes shut behind you, and you hear someone barring it.

Handling the inventory

Picking up and dropping objects; and listing what you carry are basic actions for all adventure games.

Starting with TAKE, the checks done are in the order:

  1. If the direct object is not a noun but an adjective; print message an exit.
  2. If no direct object is supplied but room only contains one object; use that. Otherwise print message and exit.
  3. If direct object is inside a container; use container instead.
  4. Test if object is takeable, in room and that there is room in the inventory; otherwise print message and exit.
  5. Give objects action routine opportunity to handle the TAKE.
  6. If there is no action routine or the routine returns false; move object to inventory and print message.
MDL                                                                         ZIL
===                                                                         ===
<DEFINE TAKE ("AUX" (WIN ,WINNER) (VEC ,PRSVEC) (RM <AROOM .WIN>) NOBJ      <ROUTINE TAKE ("AUX" (WIN ,WINNER) (VEC ,PRSVEC) (RM <GETP .WIN ,P?AROOM>) (OBJ <2 .VEC>))
            (OBJ <2 .VEC>))
    #DECL ((WIN) ADV (VEC) VECTOR (OBJ NOBJ) PRSOBJ (RM) ROOM)
                                                                                <IFFLAG (DEBUG <TELL "TAKE" CR>)>
    <COND (<TYPE? .OBJ ATOM>                                                    <COND (<AND .OBJ <NOT <WT? .OBJ ,PS?OBJECT>>>
           <TELL "There's none for the taking.">)                                       <TELL "There's none for the taking." CR>
                                                                                        <RTRUE>)> 
          (<AND <NOT .OBJ>                                                      <COND (<AND <NOT .OBJ> <NOT <NEXT? <FIRST? .RM>>>>  ;"If noun is missing and only one item in room, use that item."
            <==? <LENGTH <ROBJS .RM>> 1>
            <SET OBJ <1 <ROBJS .RM>>>                                                   <SET OBJ <FIRST? .RM>>)
            <>>)
                                                                                      (T <SET OBJ <FIND-OBJ .OBJ>>)>    ;"Set OBJ to the actual OBJECT."
          (<NOT .OBJ>                                                           <COND (<NOT .OBJ>
           <TELL                                                                        <TELL "Take what?" CR>
"Take what?">)                                                                          <RTRUE>)>
          (<AND <SET NOBJ <OCAN .OBJ>> <SET OBJ .NOBJ> <>>)                     <COND (<OCAN .OBJ> <SET OBJ <OCAN .OBJ>>)>  ;"If OBJ is in container in ROOM, use container instead."
          (<MEMQ .OBJ <ROBJS .RM>>                                              <COND (<AND <IN? .OBJ .RM> <CAN-TAKE? .OBJ>>
                                                                                        ;"Strangely because OBJ isn't counted itself, only items contained by it. You usually 
                                                                                          can carry 9 objects (LOAD-MAX is 8) but if the 9th you try to pick up is a 
                                                                                          container (bottle) you are denied."
           <COND (<G? <+ <LENGTH <AOBJS .WIN>> <LENGTH <OCONTENTS .OBJ>>>               <COND (<G? <+ <LENGTH .WIN> <LENGTH .OBJ>> ,LOAD-MAX>
              ,LOAD-MAX>
              <TELL                                                                             <TELL "Your load is too heavy.  You will have to leave something behind." CR>)
"Your load is too heavy.  You will have to leave something behind.">)
             (<NOT <APPLY-OBJECT .OBJ>>                                                       (<NOT <APPLY-OBJECT .OBJ>>    ;"Give OBJECT-routine oppertunity to handle action."
              <COND (<PUT .WIN                                                                  <MOVE .OBJ .WIN>
                  ,AOBJS
                  (.OBJ !<AOBJS .WIN>)>
                 <PUT .OBJ ,OTOUCH? T>                                                          <PUTP .OBJ ,P?OTOUCH? T>
                 <PUT .RM ,ROBJS <SPLICE-OUT .OBJ <ROBJS .RM>>>
                 <COND (<G? <OFVAL .OBJ> 0>
                    <PUT .WIN                                                                   <PUTP .WIN ,P?ASCORE
                     ,ASCORE
                     <+ <ASCORE .WIN> <OFVAL .OBJ>>>                                                <+ <GETP .WIN ,P?ASCORE> <GETP .OBJ ,P?OFVAL>>>
                    <PUT .OBJ ,OFVAL 0>)>                                                       <PUTP .OBJ ,P?OFVAL 0>
                 <TELL "Taken.">)>)>)                                                           <TELL "Taken." CR>)>)
          (<MEMQ .OBJ <AOBJS .WIN>>                                                   (<IN? .OBJ .WIN> <TELL "You already have it." CR>)
           <TELL "You already have it.">)
          (<TELL "I can't see one here.">)>>                                          (T <TELL "I can't see one here." CR>)>>

DROP is pretty much the reverse of TAKE, but remember that THROW, POUR and GIVE also directs here.

  1. If the direct object is not a noun but an adjective; print message an exit.
  2. If no direct object is supplied but the inventory only contains one object; use that. Otherwise print message and exit.
  3. If there is an indirect object and the direct object is not carried; switch direct and indirect object. This means that both GIVE WATER TO TROLL and GIVE TROLL THE WATER gets parsed as GIVE WATER TROLL.
  4. Test if object is carried and then move it to room.
  5. If the verb are GIVE or THROW and there is no indirect object but an enemy in the room; use that enemy as indirect object.
  6. Give object action routine opportunity to handle the DROP.
  7. If there is no action routine or the routine returns false; print correct message.
MDL                                                                         ZIL
===                                                                         ===
<DEFINE DROP ("AUX" (WINNER ,WINNER) (AOBJS <AOBJS .WINNER>)                <ROUTINE DROP ("AUX" (WINNER ,WINNER) (VEC ,PRSVEC) (RM <GETP .WINNER ,P?AROOM>) (OBJ <2 .VEC>) NOBJ)
          (VEC ,PRSVEC) (RM <AROOM .WINNER>) (OBJ <2 .VEC>) CAN NOBJ)
    #DECL ((VEC) VECTOR (OBJ) PRSOBJ (CAN) <OR FALSE OBJECT> 
           (RM) ROOM (NOBJ) ANY)
    <COND (<TYPE? .OBJ ATOM>                                                    <COND (<AND .OBJ <NOT <WT? .OBJ ,PS?OBJECT>>>
           <TELL "You don't have one to drop.">)                                        <TELL "You don't have one to drop." CR>
                                                                                        <RTRUE>)>
          (<AND                                                                 <COND (<AND <NOT .OBJ> <NOT <NEXT? <FIRST? .WINNER>>>>  ;"If noun is missing and only one item is carried, use that item."
            <==? <LENGTH .AOBJS> 1>
        <NOT <2 .VEC>>
        <PUT .VEC 2 <SET OBJ <1 .AOBJS>>>                                               <SET OBJ <FIRST? .WINNER>>)
        <>>)
                                                                                      (T <SET OBJ <FIND-OBJ .OBJ>>)>    ;"Set OBJ to the actual OBJECT."
          (<AND                                                                 <COND (<AND <3 .VEC>  ;"If indirect object is given and OBJ is not carried, switch objects."
            <3 .VEC>
        <NOT <OR <MEMQ .OBJ .AOBJS>                                                         <NOT <OR <IN? .OBJ .WINNER> 
             <MEMQ <OCAN .OBJ> .AOBJS>>>                                                         <IN? <OCAN .OBJ> .WINNER>>>>
        <PUT .VEC 2 <3 .VEC>>                                                           <SET NOBJ <3 .VEC>>
        <PUT .VEC 3 .OBJ>                                                               <PUT .VEC 3 <2 .VEC>>
                                                                                        <PUT .VEC 2 .NOBJ>
        <SET OBJ <2 .VEC>>                                                              <SET OBJ <FIND-OBJ .NOBJ>>)>
        <>>)
          (<NOT .OBJ> <TELL "Drop what?">)                                      <COND (<NOT .OBJ>
                                                                                        <TELL "Drop what?" CR>
                                                                                        <RTRUE>)>
          (<OR <MEMQ <OCAN .OBJ> .AOBJS>                                        <COND (<OR <IN? .OBJ .WINNER> <IN? <OCAN .OBJ> .WINNER>>
           <MEMQ .OBJ .AOBJS>>
           <COND (<SET CAN <OCAN .OBJ>>                                     
              <PUT .CAN ,OCONTENTS <SPLICE-OUT .OBJ <OCONTENTS .CAN>>>      
              <PUT .OBJ ,OCAN <>>)
             (<PUT .WINNER ,AOBJS <SPLICE-OUT .OBJ .AOBJS>>)>                           <MOVE .OBJ .RM>
           <PUT .RM ,ROBJS (.OBJ !<ROBJS .RM>)>
           <AND <MEMQ <VNAME <1 .VEC>> '[GIVE!-WORDS THROW!-WORDS]>                     <COND (<AND <NOT <3 .VEC>>  ;"If no indirect object given but enemy present, use it as indirect."
            <NOT <3 .VEC>>                                                                          <OR <W=? <1 .VEC> ,W?GIVE>
                                                                                                        <W=? <1 .VEC> ,W?THROW>>>
            <PUT .VEC 3 <VICTIMS? .RM>>>                                                        <PUT .VEC 3 <VICTIMS?>>)>                               
           <COND (<OBJECT-ACTION>)                                                      <COND (<OBJECT-ACTION>)  ;"Give OBJECT-routine oppertunity to handle action."
             (<==? <VNAME <1 .VEC>> DROP!-WORDS> <TELL "Dropped.">)                           (<W=? <1 .VEC> ,W?DROP> <TELL "Dropped." CR>)
             (<==? <VNAME <1 .VEC>> THROW!-WORDS> <TELL "Thrown.">)>)                         (<W=? <1 .VEC> ,W?THROW> <TELL "Thrown." CR>)>)
          (<TELL "You are not carrying that.">)>>                                     (T <TELL "You are not carrying that." CR>)>>

INVENT is very simple. We only loop over all objects in the inventory and print their short description. If any object is a container we also list the content of that container (only one level down).

MDL                                                                 ZIL
===                                                                 ===
<DEFINE INVENT ("AUX" (ANY <>))                                     <ROUTINE INVENT ("AUX" (ANY <>) MULT) 
   #DECL ((ANY) <OR ATOM FALSE>)
                                                                        <COND (<G? <LENGTH ,WINNER> 0>
   <MAPF <>                                                                     <MAP-CONTENTS (X ,WINNER)           ;"List all objects in player."
     <FUNCTION (X) 
         #DECL ((X) OBJECT)
         <COND (<OVIS? .X>                                                          <COND (<OVIS? .X>
            <OR .ANY                                                                        <OR .ANY <TELL "You are carrying:" CR>>
                <PROG ()
                  <TELL "You are carrying:">
                  <SET ANY T>>>                                                             <SET ANY T>
            <PRINC "A ">                                                                    <TELL "A " <GETP .X ,P?ODESC2>>
            <PRINC <ODESC2 .X>>
            <OR <EMPTY? <OCONTENTS .X>> <PRINC " with ">>                                   <OR <NOT <LENGTH .X>> <TELL " with ">>
                                                                                            <SET MULT <>>
            <MAPR <>                                                                        <MAP-CONTENTS (Y .X)
                  <FUNCTION (Y) 
                      #DECL ((Y) <LIST [REST OBJECT]>)
                      <PRINC <ODESC2 <1 .Y>>>                                                   <TELL <GETP .Y ,P?ODESC2>>
                      <COND (<G? <LENGTH .Y> 1> <PRINC " and ">)                                <COND (.MULT <TELL " and ">)>   
                                                                                                <SET MULT T>>
                        (<0? <LENGTH .Y>> <PRINC ".">)>>                                    <COND (<G? <LENGTH .X> 1> <TELL ".">)>
                  <OCONTENTS .X>>
            <CRLF>)>>                                                                       <TELL CR>)>>)
     <AOBJS ,WINNER>>
   <OR .ANY <TELL "You are empty handed.">>>                                  (T <TELL "You are empty handed." CR>)>>

Next: Part 9 - Verb actions: lamp, magic, special and others

1 Like

Stupid question, perhaps, but does “MADADV” mean that Zork/Dungeon’s working title was “Mad Adventure” or something similar?