Sunday, April 21, 2019

Chapter 20. Conclusion - The End

The creativity in designing the original ZORK on MDP main frame computer can be almost directly seen in the microprocessor version of all Infocom games. The structuring of rooms and objects, storing grammatical information, and the innovative parser led to a surprisingly complex and interactive game that could fit on a floppy disk. The use of a virtual Z-machine was the first known use of a virtualization in a home computer as well. A present day interactive fiction creator can now design their own games without having to start from scratch. But an ambitious programmer today can now use these essential data structures and routines to create their own fairly complex IF system if they want. While better game engines with more powerful parser and complex games already exist, the sophistication of these early text adventure games in just about 60K of memory is a testament to all of the programmers’ skill with ZIL and the efficiency of Z-code. Hopefully, the information here has helped cracked the inner workings of these legendary games and made them more enjoyable and appreciated decades later.

Chapter 19. Common Routines and Predicates

19.1 Introduction

Numerous routines are found in most or all Infocom games and provide important functions that are not specific to a certain game or action. Predicates are a special type of routines that return true or false based upon the given arguments. “Learning ZIL” does give numerous examples. No systematic search has been made to find all of these special routines found in all Infocom games. The known ones can be divided into four main categories:
  • Tables
  • Objects
  • Game States
  • Movements

19.2 Table Routines and Predicates

There are three common table routines used in Infocom games since Zork 1:
  • ZMEMQB (byte, address to table)
  • ZMEMQ (word, address to table, last element to check, first element to check)
  • PICK-ONE (address to table)
ZMEMQB searches for a given byte in a table of bytes. The interested byte and table are passed as arguments. After getting the number of items in the table, the routine will step through each byte. If the given byte is found, it returns true. Otherwise, it will return false.
ZMEMQ is similar to ZMEMQ but searches for a specific word in a table of word up to the last element indicated. It also accepts one additional argument: first element number to check. If this argument is not gien, then it will use the beginning of the table. For example:
ZMEMQ(0x3EA0, TBL, 6, 2)
would search for the word 0x3EA0 in a table of words starting with elements 2 through 6.
PICK-ONE, the original version, uses a table of words where one of these elements is randomly picked and returned. The new PICK-ONE keeps track of what elements have already been picked and will not pick them again until all the others have been picked. This is accomplished by “sorting” the table of elements. Any picked word is swapped with the first unpicked word. When only 0 unpicked words remain, the number of picked elements is reset which causes the entire process to be repeated. It is quite ingenious.
  1. Get total number entries (0th word) and number of previously picked string address (1st word) of string address in the table
  2. Decrease total number of entries by 1 as one of the entries is the number of picked string addresses
  3. Calculate the address of the first unpicked string address and number of unpicked string addresses
  4. Random pick a number with maximum number being the number of unpicked string addresses
  5. Get the string address at that random location
  6. Swap that string address with the one located in the first original unpicked string addresses group
  7. Update the number of picked items (1st word in the table)
  8. If all the items have been picked (number of picked items equal total number of available address), then reset this value to zero.
The picked word is then returned. Some games use both PICK-ONE returns as different situations can demand different versions of the routine.

19.3 Object Predicates

As for the predicates, these will see if a given object can be interacted with in specific ways.
  • LIT?
  • HELD?
  • SEE-INSIDE?
  • ACCESSIBLE?
  • VISIBLE?
  • UNTOUCHABLE?
  • GLOBAL-IN?
  • TOUCHING? (not used)
LIT? is the main predicate in Zork 1. It checks if the WINNER can see around in a given location by looking at the ONBIT attribute. If the ONBIT is clear (location is dark), the routine will do a secondary check if the given location is the same as the WINNER’s location. The secondary check will look for any objects in the given location. If any are found, then the location is considered lit and the routine will return TRUE.
HELD? is also in Zork 1 and checks the location of the given object’s location. If it is not the WINNER, this location replaces the given object and the routine loops back to get the new location of the just found location. This continues until the location is the WINNER (return TRUE) or the location of the object is blank (return FALSE). Other versions such as in Zork 3 uses a recursive method to find the ultimate location of an object and also look for the ROOMS and GLOBAL objects which result in a false response. HGTG introduced a second argument which was a room or object. This would be used instead of the WINNER to determine if the given object was ultimately located in it. If no second argument was given, the routine would check if the given object was inside the WINNER.
SEE-INSIDE? checks for specific attributes on a given object to see if its contents are visible. This applies to containers such as a box or a surface object like a table where objects can be placed on it. The routine was first used with Sorcerer and checked three attributes: INVISIBLE, OPEN, and TRANSPARENT. The contents of an invisible object cannot be seen. An object that is open or transparent can have its content seen from the outside. Wishbringer added an initial check to see if the object is a surface which always leads to a true response. If object was not a surface, open, or transparent, the routine would check if the object is an Actor and not the WINNER. Objects contained in an actor are always visible. Hollywood Hijinx would check if the given object was a container. If it is false, then the routine jumps to the section to check if the given object is an Actor.
ACCESSIBLE? was first introduced in Zork 3 and checks if the given object can be used. There are objects that are visible but cannot be used (an object inside a closed and transparent container). It would return true if the given object was present in the current location or is one of the game’s GLOBAL objects.
VISIBLE? was also introduced in Sorcerer and sees if the given object can be used or seen by the WINNER. The routine checks if the given object is accessible using the ACCESSIBLE? predicate. If that is false, the routine checks if the WINNER can see inside the given object’s location (for example if it is in a clear box) by using SEE-INSIDE? If that is true, the routine will then recursively call VISIBLE? with the object’s location. Wishbringer used a slightly different approach with checking to see if the object was invisible first. If not, the location of the object is found with META-LOC (a room or GLOBAL object). If it is on the WINNER, in the current room, or a GLOBAL object, then it is considered visible. If the object is located somewhere else, the routine calls ACCESSIBLE? to see if it is accessible. If so, then consider it visible. Sherlock only uses a single modified ACCESSIBLE? command that also calls a modified SEE-INSIDE? routine. So this mimics the original form.
UNTOUCHABLE? is a curious predicate that seems to be only used in LGOP to screen for various special situations where objects are in the same room but cannot be touched. For example, an object inside of inside of a cage that is also in the room with the WINNER is considered untouchable. If the given object is in the current location, held by the WINNER, or contained inside the WINNER, then it would return FALSE (ie. it is touchable).  
GLOBAL-IN? is a simple predicate that takes two arguments, an object and location, and checks if the object is a local-global for that location. Essentially, the routine uses the ZMEMQB or SCAN_TABLE opcode to look for the given object in the GLOBAL property of the location.
TOUCHING? is described in “Learning ZIL” as a predicate that takes an object and sees if it needs to be “touched” to perform the current action, PRSA. There is no evidence in any released game of a separate predicate that performs this function. Many action routines do a similar type of check, however.

19.4 Object Routines

The most common set of routines relate to objects. These include routines and predicates. The routines return specific values:
  • ROB
  • WEIGHT
  • META-LOC
  • FIND-IN
Another routine CCOUNT (container count) and REMOVE-CAREFULLY are described in Mini-Zork too.
ROB moves all the objects from one location (room or container) to another or empty destination. It will step through each object in one location and insert into another (or null) and then move to the next sibling object.
WEIGHT will add up the “weights” of all the objects in an object. This could be the PLAYER or a container. The routine goes through each child object in the given object. If the child object is a container, WEIGHT is called recursively on that child object. For each object, the weight property is read and added to the total for that object and returned. This routine is useful to see if the PLAYER or container cannot hold any more objects. The default weight for an object is set as one of the default property values for the object table.
Starting with Zork 1, META-LOC would originally find the room that an object is located. If the object was on the PLAYER or non-existent, it would return false. Starcross used different coding and also add defaulted to the “Bridge” for any non-attached object. The Witness expanded the return values to also LOCAL-GLOBALS and GLOBALS objects.
FIND-IN will try to find an object in the given location with the given flag number set. It will return the first object found. If no object exists with that given flag set, FALSE is returned. “Learning ZIL” does mention that if more than one object has the given flag set, FIND-IN would also return false. However, there are no examples of a routine that keeps track of how many matches were found. All routines will exit after finding that first match. Some of the later games also except a string with the other arguments. This string will be displayed with the matched object name.
Two other routines in Mini-Zork are not documented by “Learning ZIL”. CCOUNT will count the number of objects in the given location. It will not count any objects inside containers. REMOVE-CAREFULLY removes the given object unless “it” is given which clears out the IT-OBJECT variable. The routine then calls NOW-DARK?.

19.5 Game State Routines and Predicates

Various routine will interact or change the different states in the game such as if the player is dead or reset the status line.
  • JIGS-UP (string)
  • INIT-STATUS-LINE (boolean ClearScreen?)
  • UPDATE-STATUS-LINE
  • NOW-DARK?
  • NOW-LIT?
  • VERB?
  • GAME-VERB?
JIGS-UP is in every game in some fashion. It is called when the PLAYER is killed or the game is over. After the final score is displayed, a string is passed to it wish is displayed along with all the possible choices for the PLAYER to proceed such as restart the game or quit. If the game is restarted, the opcode RESTART is executed which will restart the interpreter and start execution with the GO routine.
The status line routines, INIT-STATUS-LINE and UPDATE-STATUS-LINE, are described in “Learning ZIL”as a common routine, but only Sherlock seems to use it. Usually, the status line updates are handled by the routine for the READ opcode and is part of the Z-machine interpreter.
NOW-DARK? and NOW-LIT? are routines and not predicates. No arguments are passed to them. LGOP appears to be the first game that used these. However, there is little evidence that subsequent games incorporated this routine. NOW-DARK? sees if the current location is lit. If so, then it will clear the LIT global variable and provide a warning message. NOW-LIT? is the exact opposite except it will also call the V-LOOK routine after setting LIT.
VERB? is a predicate that returns TRUE if PRSA is equal to any of the given verbs in the predicate arguments. This does not actually call a routine but is expanded into a set of multiple commands based upon how many verbs are given.
GAME-VERB? is a simple predicate that checks if the current PRSA is one of the “game verbs” or a verb that does not trigger the CLOCKER routine. While “Learning ZIL” mentions a GAME-VERB list, it is unclear if this is a global list in ZIL or hard coded into the routine.

19.6 Movement Routines

Since moving the PLAYER or actors is a very common action, Infocom games use three common routines to perform it:
  • GOTO (room number)
  • DO-WALK (direction)
  • OTHER-SIDE
GOTO essentially moves the user to the requested location while still calling the destinations action routine with M-ENTER and getting a description. It does not check if the actor or WINNER is able to directly get to the location. Some people consider it like teleporting into a location.
DO-WALK is similar to GOTO but it uses PERFORM which performs all necessary checks (unlike GOTO). DO-WALK sets the direction of movement (P-WALK-DIR) and calls the V-WALK using PERFORM with that DIR.
OTHER-SIDE is given an object number of a door and returns the room number on the opposite side of the first door in the room. The routine steps through all the exit properties (highest to lowest) of the given room until it finds a door object (the property will be 5 bytes long). It the door object for that property matches the given door object, it will then return the room number associated with that exit.

Chapter 18. The Describers - For Rooms and Objects

18.1 Introduction

There are several routines in an Infocom games that specialize in describing locations and objects which are an essential part of any game. “Learning ZIL” mentions DESCRIBE-ROOM and DESCRIBE-OBJECTS, but there are also DESCRIBE-OBJECT, PRINT-CONT, and later PRINT-CONTENTS which complete the needed routines to display an object and its contents. For example, LOOK would call DESCRIBE-ROOM and DESCRIBE-OBJECTS with the verbose argument set. Other commands, like INVENTORY or LOOK-INSIDE, would call PRINT-CONT on the WINNER or PRSO, respectively.

18.2 PRINT-CONT

  • Arguments: Object or Room number, Verbose flag, LEVEL number
  • Returns: TRUE if there is some descriptive output, FALSE if none given
PRINT-CONT will describe the contents of the objects contained in the given object or room. The routine will first describe each object by display the string in the FDESC (First DESCription) property of the object. Any object that also can be seen inside (using SEE-INSIDE? predicate) will have PRINT-CONT recursively called on it. If the routine is being used to display the inventory of the WINNER’s possession, it will skip the display of the FDESC’s of all the objects. PRINT-CONT will then see if a special header (such as “You are carrying:” or “The <object> contains: “) needs to be displayed by seeing. This is done when an inventory is requested or the objects have already been “touched”. This also needs to be the first text displayed for that particular routine call. PRINT-CONT will then call DESCRIBE-OBJECT on each object to have it described. If any of these objects are an integral part of another object, then PRINT-CONT is called recursively on this integral object to see if anything else can be described. Once all the objects are checked again, PRINT-CONT will also see if the WINNER is in a vehicle and if that vehicle is part of the contents of the original requested object. If so, then PRINT-CONT will be called on that vehicle. While a verbose flag argument can be passed, it is never used. The LEVEL argument determines the indent of the descriptive text uses spaces from the IDENTS table (maximum of 5). As the routine does further recursion, the level number increases along with the corresponding indent.

18.3 DESCRIBE-OBJECT

  • Arguments: Object or Room number, Verbose flag, LEVEL number
  • Returns: TRUE if extra descriptive output given from internal objects, FALSE if none given
DESCRIBE-OBJECT will try to display a descriptive text about an object using the FDESC (if the object is untouched) or LDESC. If neither is available, a generic description is given, “There is a” for level 0 or “A” for all other levels. If the WINNER is also in a vehicle, then a clarifying “(in the room)” is given to remind the WINNER that the object is NOT in the vehicle. Also any objects that have visible contents will be also called with PRINT-CONT to display the contents of that object. Againa, the Verbose flag is not used in the routine but passed to PRINT-CONT if it is called.

18.4 DESCRIBE-OBJECTS

  • Arguments: Verbose flag
  • Returns: TRUE if there is some descriptive output, FALSE if none given
DESCRIBE-OBJECTS  will try to display a descriptive text for all the objects in the current location (HERE). It will not display a description of the location itself. If that location is not lit, the routine will return with the error message “I can’t see anything in the dark.”  If any objects do exist in the current location, DESCRIBE-OBJECTS will call PRINT-CONT with this location and the current VERBOSE level if one is not given. Return values are the same as PRINT-CONT.

18.5 DESCRIBE-ROOM

  • Arguments: TRUE (if called by LOOK action)
  • Returns: TRUE if room description given, FALSE if too dark to give description
DESCRIBE-ROOM will initially check if the room is dark, displaying a too-dark error message if it is. The routine will then display the room name and decide if a further descriptions should be given. If it was not called by a LOOK command or SUPERBRIEF is SET, then no further description is given. If the WINNER is in a vehicle which is also in the room, the routine will indicate that with the clarifying “(You are in the <vehicle>.)”. DESCRIBE-ROOM will then check if the Verbose flag is set, or the room is untouched. Either situation would have the routine try to call the room’s ACTION routine with M-LOOK to provide a description. If this fails, the room’s LDESC string will be used if possible. If a room is untouched, then that room’s TOUCHBIT will be set.

18.6 PRINT-CONTENTS

  • Arguments: Object number
  • Returns: TRUE
PRINT-CONTENTS was added with Sorcerer-R6 to quickly display a list of objects in a room or container. The object names would be separated by commas and “and”, if necessary. If only one object was listed, then IT-OBJECT would be updated with that object.

18.7 Update: DESCRIBE-ROOM

Zork 2 added a new RARG (M-FLASH or $04) on a room’s ACTION routine even if verbose is clear (minimal output). This allows a room to display important information even if the room was already touched or verbose is clear. It also added a final call to the WINNER’s vehicle’s ACTION with M-LOOK (if in a vehicle) when describing the current location.

Chapter 17. Basic Screen Output and TELL

17.1 Introduction

Infocom strived for readability, natural feel, and varied responses of its games with its text routines. The games tried to be grammatically correct with display articles before nouns and using plural forms if necessary. Later games would fine tune these routines even more. Infocom did use programming macros for creating the proper text display routines. These macros would then be expanded into the proper ZIL code during compiling. It is difficult to figure out which was the first game to use this without the source code. The macros are seen in the Mini-Zork source code.

17.2 Abbreviations / Frequent words table

Starting with ZIP version 2, Infocom games used abbreviations to help reduce the space taken up by text. In version 2, the ZSCII character $01 signaled an abbreviation is to be display.The next ZSCII character indicates the requested abbreviation. This allows for 32 abbreviations. The abbreviation ZSCII character is consistent throughout the 3 character sets. The previous new-line control character (ZSCII 1) was moved to ZSCII 7 in character set 2.
To allow for more abbreviations, ZIP versions 3 and higher also use characters $02 and $03 to signal an abbreviation. Since each of these special control characters can access 32 abbreviations, these games could have 96 abbreviation, calculated using the formation: (abbreviation character value - 1) * 32 + next character’s ZSCII value. This also left only 2 control characters (ZSCII 4 and 5) remain to change the character set. Those control characters were repurposed to change the character set for next character only. There would be no “shift lock”.
An abbreviation table contains 32 (or 96 for versions 3 or higher) word addresses for the Z-strings. The abbreviation and next characters will then point to an entry in this abbreviation table with an address to a Z-string which will then be displayed. Because each abbreviation uses 2 characters, abbreviations for string longer than 2 characters could help reduce the size of the story file.

17.3 Special PRINT Routines - WORD-PRINT, CLAUSE-PRINT, PREP-PRINT

Zork 1 also included three print routines that work with the given command and prepositions:
  • WORD-PRINT
  • PREP-PRINT
  • CLAUSE-PRINT
WORD-PRINT displays a sequence of characters from INBUF given the starting character and number of characters to print.
PREP-PRINT displays the preposition given the preposition byte number,. The routine uses PREP-FIND to convert the preposition byte number to a Vocabulary address for the preposition. The only except is “through” which is larger than the 6 character limit for ZIP 3 or earlier. PREP-PRINT looks for the corresponding preposition number and just display the entire token.
CLAUSE-PRINT display the entire noun clause using the boundary addresses stored in ITBL.  A preceding preposition can also be displayed if the preposition number is also passed as an argument. The boundary addresses to use are based upon the element numbers in ITBL. Displaying the token from Vocabulary or from INBUF depends on state of O-FLAG. The routine will use strings from the Vocabulary (maximum of 6 characters) if the O-FLAG is set. Otherwise, it will use WORD-PRINT to print the entire token from INBUF.  
display the tokens in LEXV given by the start and end address of the requested noun clause. This limited any token to 6 characters. If the O-FLAG was clear, the routine extracted the length and location of each token in INBUF and displayed the complete token.

17.4 New versions PRINT routines

Deadline introduced several 4 new routines and updated CLAUSE-PRINT to improve the quality of the displayed text.
BUFFER-PRINT is a new routine that displays a sequence of tokens while modifying any truncated or referred tokens. For example, the “mrs” token will then be displayed as “mrs.” while the “it” token is replaced with its referring object. The address of the starting token and token after the last one to display are passed to the routine. The fourth argument is a boolean value to indicate print a preceding space before the first and subsequent tokens.
PRSO-PRINT and PRSI-PRINT are new routines that will display the direct or indirect object clause, respectively. They extract the starting and ending addresses of the clause from ITBL and see if the first token in the clause is “it”. If so, the current PRSO is displayed. Otherwise, these addresses are passed to BUFFER-PRINT.
The last new routine display a token with the first letter capitalized. No official name is known but could be called CAPITALIZE-PRINT. The routine pulls the first character and converts it to uppercase without checking the case first. Then it will use WORD-PRINT to display the remaining part of the token.
The new CLAUSE-PRINT utilizes BUFFER-PRINT to display those representative tokens with their proper names. The routine will take the start and end element numbers from ITBL and extracts the start and end addresses for the tokens to print. These are then sent to BUFFER-PRINT.
Enchanter would later introduce THING-PRINT which takes a boolean argument to choose which object clause to use (true for direct, false for indirect). The routine pulls the start and end addresses for the requested object clause and passes it to BUFFER-PRINT.

17.5 Error message routines

All Infocom games have several default error messages when handling errors with given commands.
  • CANT-USE
  • CANT-ORPHAN
  • UNKNOWN-WORD
CANT-USE was first used in Deadline and would insert an invalid token into the phrase: The word ‘<word>’ can’t be used in that sense.
CANT-OPRHAN was added in Starcross and had the static message: That command was incomplete. Why don't you try again?
UNKNOWN-WORD would display the word that is not found in Vocabulary:
[I don't know the word "<word>” in a way that I don't understand.]
and also updates the OOPS-TABLE with the location of the token that is unrecognized.

17.6 Using the TELL Macro

Source code for Infocom games used macros where a specific keyword and associated data would be replaced with code incorporating that extra data. TELL was the universal way of displaying text. However, this could result in repetitive code to display the same kind of text. So, later design guides for Infocom games describes the use of the TELL macro with multiple modifiers. But, the macros would not be replaced with strings of ZIL code but a call to separate routines for displaying text with any additional modifiers such as proper definite and indefinite articles (depending on the type and number of the object) or ending periods and linefeeds. “Learning ZIL” describes these modifiers.  

Chapter 16. Pardon the Interruptions (part 2)

16.6 Update: New INT and Interrupt Entry

All of the ZIP 1 and 2 games and most of the ZIP 3 games use the same INT routine as seen in Zork 1. CutthroatsHGTG, and Suspect used a slightly modified INT where DEMON pointer is omitted. The first major change was with AMFV where the size of the interrupt entry shrank to 2 words by removing the ENABLE flag. Now, each entry with non-zero TICKS is considered enabled. Most of the ZIP 3 games since LGOP also used this more compact interrupt entry structure. AMFV’s version also kept track of the most recent blank entry which would be used when storing a new entry instead of creating a new one. If new entry needs to be created beyond the limits of the table, an error message is displayed but the entry is still created.
LGOP also introduced a smarter INT routine that build off the one from AMFV. It would “remove” any entries at the top of the table with no more TICKS left by moving C-INT and skipping over these completed entries. Any entries with no more TICKS surrounded by entries that were still enable were not removed. This smarter INT also modified how interrupts during the game initialization are stored. It would convert their TICK numbers (by increasing it by 3 and making it negative) to label these interrupts so they would not be called until CLOCKER had already been executed. So, the first call of CLOCKER would convert these entries back to normal entries (converting the TICKS back to positive values and subtracting 3) and then be checked on the next call to CLOCKER. So, these initial interrupts would be bypassed at the start.
Only a few subsequent Infocom games modified their INT routines beyond what was mentioned. A few added special flag checks that would quit out of INT. Borderzone and Sherlock both used time more than ticks and had modified INT routines to reflect that.  

16.7 Update: New CLOCKER

CLOCKER also went through multiple modifications over successive games. The ZIP 2 version added the CLOCK-WAIT flag which skips checking any interrupts (including DEMON ones) when set and is cleared. This feature is only important for the WAIT command which already calls CLOCKER 3 times by default. Without this flag, the MAIN-LOOP will also call CLOCKER after WAIT is completed. So there would be 4 calls to CLOCKER for a WAIT command which is seen in ZIP 1. So the flag helps clear up that confusion.
Some CLOCKER routines (like in Deadline, Trinity, Bureaucracy, Borderzone, and Sherlock) would also increment separate time variables (seconds, minutes, hours, and possibly days) that the game would use for various situations. These routines (like in Deadline and Moonmist) check the number of turns or the elapsed time to see if the game should end prematurely or reset specific counters. Others like Wishbringer would check specific flags that would cause CLOCKER to execute routines of specific entries if their TICK values were below a certain threshold. Later versions (Suspended, Infidel, Enchanter, Zork1, Zork 2, Sorcerer, Moonmist, Ballyhoo, Mini-Zork) increase the number of turns in CLOCKER instead of MAIN-LOOP. The Witness, Seastalker, and Cutthroats return the first non-zero interrupt routine result. However, if one of the routine returns M-FATAL, then that will always be returned.
Planetfall corrected one logic error in the original INT routine regarding entries with negative TICKS. Previously, any entry with a negative TICK value would have its routine execute with the TICK value decreasing as well. So subsequent calls to this entry would make the TICK value more negative. Theoretically, that value could then flip into the positive range because of the way negative numbers are represented. Hexidecimal values of $0001 to $7FFF are positive (1 to 32767) while $8000 to $FFFF are negative (-32768 to -1). If a TICK value of $8000 (-32768) is decreased by 1 again, the new value $7FFF now is 32767 and will not be executed on the next CLOCKER cycle. Planetfall added a specific check for the $FFFF (-1) TICK value which would still execute the routine at the entry’s address but would not decrease the TICK value.
As mentioned before, AMFV uses the shortened 2 word entry. Its CLOCKER also will clear out the routine address of any entry that also has zero TICKS. This allows INT to use these blank entries for new entries. LGOP’s CLOCKER had the ability to “delete” entries that were at the top of the interrupt table. It would lower the starting point of the interrupt table to just past any blank or recently completed entries. If any blank or completed entries were below a still enabled entry, they could not be deleted. Stationfall and Sherlock could decreased TICKS/time value by a different amount for all entries with each call of CLOCKER. This allowed parts of the game to cause interrupts to be executed soon than expected as each call of CLOCKER will more rapidly drop the TICK counts.
HGTG still has the PARSER valid check to decide which pointer to use. If the PARSER fails, the entire table is checked (#00 as pointer) instead of from the newest entry.

16.8 Removing Interrupts with DEQUEUE

  • Arguments: Routine address
  • Return: TRUE if successful, FALSE if unable to find interrupt
DEQUEUE was introduced in LGOP and clears the routine address of the entry with that address. Any dequeued entry would eventually be “removed” by CLOCKER.

16.9 New Predicates for the Interrupts

  • ENABLED?
    • Arguments: Routine address
    • Return: TRUE if matching interrupt is enabled, FALSE if no match found or interrupt is not enabled
  • RUNNING?
    • Arguments: Routine address
    • Return: TRUE if matching interrupt has at least 1 TICK left, FALSE if no TICKS left or no match found
Two new predicates were added, ENABLED? And RUNNING? in Cutthroats to help find the status of specific interrupts. ENABLED? returned TRUE if the ENABLED word in a matching interrupt entry was set. RUNNING? returned TRUE if the TICKS in a matching entry was not zero, including -1. In LGOP, ENABLED? was changed as no ENABLED word exists in its entries. It would check the TICKS value and return TRUE if it was non-zero including -1. LGOP’s version of RUNNING? would return TRUE if the matching entry’s TICK value was 1 or -1. So only entries that will be executed on the next call of CLOCKER are considered running.

Chapter 16. Pardon the Interruptions (part 1)

16.1 Introduction

An interrupt is a special routine that is called after a certain number of turns has lapsed. They can be used to monitor objects and variables in the background. Interrupts will then change other variables and objects or call other routines and actions. Naming of these interrupt routines is done by adding a “I-” prefix to the routine name. The array to hold interrupt entries is a 180 byte (90 word) table where each entry had 3 words: enable flag, number of turns (TICKS), and routine address. This meant the table could only hold 30 entries. Only 3 routines are used to manage this ingenious system: INT to create or retrieve interrupt entries, QUEUE to set the number of turns before the interrupt is called, and CLOCKER which checks all the interrupts and finds those that need to be executed.
One thing to note is that there is no way to delete or replace an interrupt entry. It can be disabled by clearing the enable flag though. So there is a limited number of total interrupts that can be created.

16.2 Creating and Storing interrupts with INT

  • Arguments: Routine address
  • Returns: Address to interrupt entry
INT typically uses the 180 byte interrupt table to manage up to 30 interrupt entries. Some games like Deadline and The Witness use 300 bytes while Cutthroats’s table was 246 bytes in size. The table is filled like a stack, from the highest address to the lowest. So the oldest routines are located in the higher address, or the bottom of the table. The pointer to the newest interrupt entry (C-INTS) then moves toward the front of the buffer. After a routine address is passed to INT,
  1. The routine address in each entry in the interrupt table is checked (starting with the newest entry) with the requested one until there is a match or no more entries exist (when the pointer to the current entry reaches the end of the table).
  2. If there is a match, then the address to this interrupt entry is returned.
  3. If there is no match, then pointer to the newest entry is moved up (decrease by 6 bytes) and now points to a new blank entry. The requested routine address is stored in the appropriate location in the new entry. The address to this new entry is returned.

16.3 QUEUE - Setting Up the Interrupts

  • Arguments: Routine address, Number of turns
  • Returns: Address of interrupt entry
With the first generation interrupt routines, QUEUE was a separate routine to set the number of turns, or TICKS. It would call INT to get the address to the interrupt entry for the request routine address (creating the entry if necessary). Then. it would store the number of ticks for that interrupt in the 2nd word value of that entry and return the address to this interrupt entry. The setting of the enable flag was not done and have to be done using a separate STORE command. The last few ZIP 3 games (Stationfall and The Lurking Horror) and most EZIP games (all except AMFV) and all XZIP games did not use QUEUE. INT would automatically queue those entries or the games would do it without a separate routine.

16.4 CLOCKER - Running Interrupts

  • Arguments: None
  • Returns: TRUE if an interrupt was executed, FALSE if no interrupt was executed
CLOCKER is the main routine that checks each interrupt entry and decreases the number of turns for any enabled entry. It will call the routine at the address stored in any interrupt entry with only 1 TICK left or -1 TICKS which indicates the entry will always be executed. CLOCKER is always called at the end of the MAIN-LOOP.
  • If the given command was valid, CLOCKER will search through the interrupt table for entries with their enable flag set and extract the number of turns left.
    • The search begins with the newest interrupt entry and proceeds to the oldest entry.
  • If the TICKS is zero, CLOCKER goes to the next entry.
  • If the TICKS is not zero (a negative, 1, or greater than 1) then it will be decreased by 1 and saved back into the entry.
  • If the TICKS is still greater than 1, then CLOCKER will then go to the next entry.
  • At this point, CLOCKER will call the routine at the addr stored in the interrupt entry. TICKS will be 1 or a negative number at this point.
If no routine is called, CLOCKER will return FALSE. Otherwise, it will return TRUE no matter how many routines are called. The actual return value of the routine is not returned.

16.5 What about DEMONs?

INT allowed an initially set of interrupts to be added to the interrupt table that are always checked regardless of the PARSER outcome. This is marked by by the C-DEMONS pointer which moves from the end of the interrupt table when new entries are created with the DEMON flag set. C-INTS also moves when an entry is added when the DEMON flag is set. In game design, these important interrupts are added first and end up at the bottom of the interrupt table. All other interrupts but be added above these entries. If PARSER fails on a given command, CLOCKER will this and start checking the interrupts starting with C-DEMON and not C-INT.
Zork 1’s first use of these special, all-run, interrupts were used for the demons and monsters in the game. This term is very similar to “daemons” in operating systems where background processes can run on their own. However, Infocom’s use of the word “demon” came after “daemon” was already used by other programmers. It is probably just a happy coincidence.

Chapter 15. It's Time to Perform with PERFORM

15.1 Introduction

At this point, all necessary information should be checked and processed. All the direct and indirect objects and the action number to use those objects have been found. Many combinations of objects and actions can be handled by the specific action routine. The designer of Infocom games understood that many actions on objects could be handled with a generic verb action routine. Any special circumstances usually depend on the objects used. Therefore, these circumstances could be checked when accessing those objects and not clutter up a generic verb action routine.

15.2 Checks and Order

Since action routines can call PERFORM separately from PARSER to perform functions that mimic a command, PERFORM does not use the global PRSA, PRSO, and PRSI but will be passed a separate action, direct object, and indirect object arguments. It then temporarily saves the current PRSA, PRSO, and PRSI.
  1. PERFORM will check for the IT object in the direct or indirect object. If so, it will be replaced with the previously referenced object for IT.
  2. It will copy all the given arguments (action, direct object, and indirect object values) into the appropriate global variables (PRSA, PRSO, PRSI).
  3. If the given action is not GO, PERFORM will then update the IT object to the just given direct object argument and update the location of the winner to the current location.
  4. If the given action is not AGAIN, PERFORM will update the global variables for the last action number, direct object, and indirect object. These are used by the AGAIN command.
After updating the necessary variables, PERFORM will call various routines to handle the action on the objects. A non-handled action (by returning M-NOT-HANDLE) will be passed to the next possible routine to handle it. The order of handler preference is below:
  1. WINNER’s action routine
  2. WINNER’s location’s action routine with M-BEG argument
  3. Verb (PRSA) pre-action routine
  4. Indirect object (PRSI) action routine
  5. Direct object (PRSO) action routine (skipping if the action is GO)
  6. Verb (PRSA) action routine
ACTION routines for objects and rooms can be passed standard RARG values for a specific type of function to perform. Any needed objects can be found in PRSO and PRSI. The routine will then return an action return value. If the routine can successfully complete a function, then M-HANDLED is return. PERFORM will skip over all other subsequent handlers. If the routine cannot handle a function, M-NOT-HANDLED is returned. PERFORM will then try other handlers to handle the function. The verb action routine is considered the default handle routine and can always handle an action. It usually displays a generic message. Once a function has been handled, the current room’s ACTION routine is sent M-END to handle any remaining functions before the turn is completed. If M-FATAL is ever returned by an action, then PERFORM will exit immediately and return M-FATAL. Also, the current room’s ACTION routine is not called with M-END. Before PERFORM returns, the previous values of PRSA, PRSO, and PRSI are restored.
Room Arguments (RARG)
Action Return Values
  • M-END, 0
  • M-BEG, 1
  • M-LOOK, 3
  • M-FLASH, 4
  • M-OBJDESC, 5
  • M-NOT-HANDLED, 0
  • M-HANDLED, 1
  • M-FATAL, 2
Objects trying to handle actions may need to double check which object is the direct and indirect object. In an example from Infocom:

        TAKE SWORD FROM THE STONE

would have the STONE object process the TAKE action first. In that case, the STONE could interpret the user trying to take the STONE. To prevent this, STONE object could see if it is the PRSI before handling the action.
Later ZIP 3 games also included checking an object’s CONTFCN (container function) before having the direct object try to handle the action. This was only called in those rare situations (such as in Starcross) where the direct object’s container would try to handle an action.

15.3 THIS-IS-IT


Starting with Sorcerer, PERFORM calls THIS-IS-IT after acting upon the PRSO and PRSI to update the IT-OBJECT. The PRSO value is stored as the IT-OBJECT value. Planetfall would introduce saving the location of the IT-OBJECT as well in a separate global variable. Wishbringer added other pronouns objects (HIM-OBJECT, HER-OBJECT, and THEM-OBJECT) along with their associated global variables which would be updated if needed based upon the attributes of the PRSO (such as gender or being plural). The routine is also called from other parts of the game like ITAKE-CHECK and specific action routines. It was only used in LGOP,  MoonmistHollywood HijinxStationfall, and Plundered Hearts.

Chapter 14. Can Many Objects be TAKEn Before Using with TAKE-CHECK and MANY-CHECK

14.1 Introduction

There are now two final checks on the verb by PARSER. The first is to see if the given verb can automatically take objects and act upon them. The second is to see if the given verb can accept multiple objects. Both sets of checks are done on direct (and indirect if present) object clauses.
  • ITAKE-CHECK (through TAKE-CHECK)
  • MANY-CHECK

14.2 ITAKE-CHECK (through TAKE-CHECK): Checking TAKE and HAVE bits

  • Arguments: Address to object table, LOC byte
  • Returns: TRUE if objects do not need to be taken or were taken if necessary, FALSE if errors
TAKE-CHECK calls ITAKE-CHECK on each set of objects (direct objects from P-PRSO and indirect objects from P-PRSI) with the matching syntax’s LOC byte to verify if objects must be in the possession of the WINNER to be used as some verbs require this. So an object in the room but not on the WINNER is not valid. However, any verb syntax entry with a TAKE bit set in the LOC byte is allowed to automatically put any matching object in the room into the WINNER’s inventory and then act upon it. PARSER will see if any objects in an object clause that are not in the Winner’s inventory can be automatically taken. If the object’s TRYTAKEBIT is set, this would prevent PARSER from automatically taking it. For all objects without the TRYTAKEBIT set, PARSER will call the CARRY object routine to move the object into the Winner’s routine. For any objects that can’t be automatically taken, it is possible the verb can still use it. However, PARSER first checks if a HAVE flag is set in the verb syntax entry. This would require the object be in the inventory to use it in the action. If so, then the object cannot be used and an error is displayed. Any error will stop the checking of any remaining objects in the object clause.

14.3 Update: New ITAKE-CHECK

Starcross introduced a new ITAKE-CHECK which tried to catch situations where objects could not be taken. These included:
  • Both TAKE and HAVE bits cleared (objects do not need to be held and can’t be taken) on syntax
  • if HAVE set, then check objects
  • if HAVE not set and TAKE not set then RTRUE
  • if HAVE not set and TAKE set then check objects (SEe if can be taken)
  • The WINNER is not the PLAYER
  • The object is held by the WINNER (using HELD?)
  • The object is something that can’t be taken like “pair of hands”
The new routine would also not display error messages in certain situations such as an ACTOR would try to take an object before using it. It also needed to check if the syntax had LOC TAKE set before calling ITAKE to get the object. (at least try)
Sorcerer checked if the IT-OBJECT was accessible before using it. It also checked to see if the object is held, it would then skip the rest of the routine. For any NOT-HERE-OBJECTs, it would display a specific error message. If an object exists but can’t be used, then it would also updated the IT-OBJECT value.
Seastalker also allowed “him” and “her” pronouns to be “taken” and used. But no checks on their visibility was done until Wishbringer which also added the “them” pronoun. Later games like Hollywood Hijinx would have specific checks for objects if they inside other objects (such as water inside a bucket).
More specific error and confirmation messages were created. For example, these would use the appropriate articles depending on the object and quantifiers depending on the number of objects. They would also indicate if an ACTOR did not have an object.

14.4 MANY-CHECK

  • Arguments: None
  • Returns: TRUE if syntax entry accepts multiple objects, FALSE if not
Some verb syntax entries allow for multiple direct or indirect objects in their usage, such as GET or IGNITE. PARSER will first check how many objects are in the direct and indirect object clauses. If there is only 1, then this check is not needed. If there is more than 1 object and the MANY token is not set for that direct object clause in the syntax entry, then an error message is displayed about the verb not being able to use multiple objects. This same check is then done on the indirect objects if necessary.
Only a few changes were made with this routine. Starting with Zork 1-R75, PARSER would run MANY-CHECK first before TAKE-CHECK which correct a bug were PARSER would attempt to take multiple requested objects first but then error out on the verb if it could not process multiple objects. By flipping the order, PARSER can quickly see if multiple objects can be used on an verb before trying to take then using TAKE-CHECK. This is mentioned in the Infocom Cabinet notes.  Deadline would assume the verb was “tell” if it was not given (occurs when the player is telling an actor to do something). Sorcerer ensured that the complete verb was displayed if the current command is the result of a merge. Finally, LGOP added a new parameter to indicate which noun clause to check. This would eliminate the need to check if a particular clause in the matched syntax can accept multiple objects.