Sunday, April 21, 2019

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.

No comments:

Post a Comment