This file is part of the TADS Author's Manual.
Copyright © 1987, 1996 by Michael J. Roberts. All rights reserved.

The manual was converted to HTML by N. K. Guy, tela design.


Chapter Four


Writing Adventures

The previous chapter described TADS as a general-purpose object-oriented programming language, with only a few references to its application as a text adventure system. This chapter introduces the "player command parser," which is the core of the user interface to your TADS game.

The player command parser reads commands from the player, resolves those commands into objects in your game program, and invokes methods in those objects according to those commands. The TADS parser is highly sophisticated; it provides advanced features to the player while relieving your game of the need to implement those features. To provide you with a way to customize many of the parser's functions, though, the system requires that you define certain functions and objects.

This chapter describes the features of the parser, how it works, and how it interacts with your game program. The special functions and objects that your game must define are also explained. In short, this chapter describes the complete run-time environment of your game program.

October 23 note from tela design - the content of this chapter has been largely superseded by Part II of the 2.2 Release Notes.


The "preparse" Function

Each time the player enters a complete line of text, and before the TADS player command parser starts looking at the line of text, a special user-defined function called preparse is executed.

The function takes a single argument, which is a string containing the text that the player typed on the command line. The complete text is provided, unchanged, preserving the original spaces, punctuation, and capitalization. The actor, verb, and other objects are still unknown when preparse() is called, since the parser hasn't seen the string yet.

Your preparse function can change the command string to any new string it desires, or it can abort the command altogether, or it can choose to do nothing and let the original player's command be processed as normal.

If preparse() returns a string value, the new string replaces the original command that the player typed. The parser processes the new string as though the player had typed it.

If the function returns nil, the command is aborted altogether, and the player is immediately prompted for a new command.

If the function returns true, the original string typed by the player is used unchanged.

This function is optional. If it's not defined by your game program, the compiler will issue a warning, but your game will still compile and run. The preparse function is useful only in certain special situations; most games won't need it. For example, you could use preparse to implement a special effect room where anything typed by the player (even complete nonsense) is just echoed until a special command is typed.


The Player Command Parser

The player interacts with your TADS adventure game strictly at the level of English sentences. The game describes the player's whereabouts, and the player responds with a command (an imperative sentence) to do something; the game then responds with a description of the results of this action. The player then issues a new command, and the game responds to this one. Play proceeds in this manner until the player gets tired of this and quits (or completes the game).

The most important part of the game as far as the player is concerned is the story; that is, the descriptions of locations, people, and things, and the responses to commands. The adventure writer's job is to make this part interesting.

However, a very important part of the game, which is ideally invisible to the player, is the parser. The parser inspects the player's commands, and attempts to make them intelligible to the game as implemented by the adventure writer. We say that the parser is "ideally invisible" because the player shouldn't have to worry about - or even notice - the parser. Players never notice the parser when it understands what they say; it's when the parser can't understand, or worse, misunderstands a command, that the player notices the parser.

The job of making the parser invisible is mostly in the hands of the TADS run-time system, which tries to ensure that many different kinds of sentences are understood. However, the adventure writer is responsible for making sure that many synonyms are recognized for each word, because different people have different vocabularies. A good rule of thumb is that game should understand all the words it uses, particularly those referring to important objects.

All player commands have a verb. A verb is all some commands have; for example, the commands "look" and "east" have only a verb.

Other commands take a direct object as well: "take the book" and "drop the box." Note that the article "the" is optional. Unlike some adventure parsers, the TADS parser will not ignore the article; it will ensure that articles are used only with nouns, but the article will obviously not affect the meaning of the noun.

Some commands take an indirect object as well: "put the book in the box" and "give the librarian the book." The first command uses a preposition to identify the indirect object; the second uses the positions of the words in the sentence to specify which object is which.

Some verbs are a little more complicated; consider "pick up the book" and "pick it up." In these cases, the preposition is considered part of the verb, so the verb part is "pick up" and the direct object is "the book."

Note that the TADS parser can only handle prepositions that consist of a single word. Unfortunately, English has many constructions in which multiple prepositions are needed; for example, "take the book out of the bag" has two prepositions in a row.

There's a special mechanism that allows you to define words that are "glued together" when they appear in certain combinations. This mechanism is intended to allow for prepositions that consist of multiple words, but which can be treated as a single word unit. For example, "out of" can be converted to a single word. In order to define words that can be converted into a unit when used together, you use the compoundWord statement. This statement appears in your game file outside of functions and objects; adv.t defines a large list of compound words, which should be sufficient for most games.

A compoundWord statement looks like this:

  compoundWord 'out' 'of' 'outof'; 

This tells the parser that when it sees the two-word sequence "out of", it should convert it to the single word "outof". The compound word "outof" is now usable in vocabulary (such as in a preposition property).

Note that you can't directly define three-word (or longer) compound words, but you can do so indirectly by constructing the longer words out of two-word directives. For example, to convert "out from under" into "outfromunder", you'd do this:

  compoundWord 'out' 'from' 'outfrom';
  compoundWord 'outfrom' 'under' 'outfromunder'; 

Note that the resulting word (the third word in a compoundWord command) doesn't have to be the second word appended to the first; older versions of TADS built their compound words automatically in this manner, though, so it is a reasonable convention to follow. You could just as easily define the compound word built from "out from" to be 'out-from', or 'asdf', or anything else.

There's one more feature: the player can issue a command to another character in the game (called an "actor") by prefacing a sentence with the character's name and a comma, as in "Librarian, take the book off of the shelf."

When parsing a player's command, the entire sentence is converted to lower case letters before anything else is done, so the case of the player's command doesn't matter. In addition, the player need only type the first six letters of any word; if he chooses to type more, though, the extra letters are not simply ignored. This allows words that are longer than six letters to be ambiguous with other words in their first six letters and still be correctly identified. This is especially useful for dealing with plurals of long words; hence, if a player types "barrel", it will be identified as "barrel" rather than "barrels".

Names of objects are made up of a noun and one or more adjectives. For example, "book," "red book," and "large red book" might all refer to a particular object.

In addition, the word "of" may connect additional qualifying phrases to an object's name. For example, "the large pile of yellow straw." Internally, when designing a game, the adventure writer simply lists the adjectives and nouns that can be used in reference to an object, and doesn't have to worry about placement of the words. In the example above, the player would say that the pile of straw is identified by the nouns "pile" and "straw", and the adjectives "large" and "yellow". Hence, all of these phrases are also recognized as the pile of straw: "straw," "pile," "pile of straw," "large pile," and "yellow straw." Note that the use of the word "of" is completely transparent to the game program; the parser automatically removes the "of" and rearranges the words appropriately.

One more special case occurs when numbers are used as adjectives. In defining an object's adjectives, if you want a number to be used as an adjective, simply include it in the adjective list in single quotes just like any other adjective. When the player is typing commands, however, it is handled a little differently than other adjectives: the number can follow the noun. This is useful in cases with numbered items, since it's more natural to place the number after the noun. For example, elevator buttons would be called "button 1," "button 2," and so forth. (Of course, referring to them as "1 button," "2 button," and so on, is accepted as well, but it's somewhat ambiguous, in that it sounds like a number of buttons rather than the number of the button.) As with "of," this special case is transparent to the game program, since only the parser needs to worry about the order of the words referring to an object.

Sometimes, there will be ambiguous objects; for example, a red book and a blue book. If the player types simply "book", the parser will try its best to figure out which book the player means. First, it will attempt to identify all the nouns that are meaningful with the command the player is trying to issue, which frequently narrows it down enough that no further information is required. For example, suppose the player is carrying the blue book, and the red book is in the current room; the parser will know that "take the book" refers to the red book, because the blue book doesn't make sense with the take command. However, if both books are in the room, the parser won't know what to do, so it will ask:

  Which book do you mean, the red book, or the blue book? 

At this point, the player can answer "red" or "blue," or "the red book," or even "the blue one," in which case the parser will complete the command. He can also say "both" or "all," or even "the red one and the blue one," and the parser will take both books. Now, the player could have said this in the first place, assuming the writer provided a plural: "take the books."

This brings us to the subject of multiple direct objects. The player can list several objects he wants to take, or perform another action on, simply by using a comma between objects: "Take the red book, the box, and the lamp off the shelf."

He can also abbreviate this to some extent with the words "everything" and "all" (which are synonymous); for example, "Take everything off the shelf," or "drop all." If he wants to be a little more specific, he can do something like this: "Drop everything except the keys on the desk," or "Take all but the rusty knife, the useless lantern, and the skeleton."

From the adventure writer's standpoint, there's no more work to be done when the player uses multiple direct objects, because the parser breaks up the command into simpler commands. For example, if the player types, "Take the red book, the blue book, and the nasty knife," the parser pre-digests this, so the game sees three separate commands: "Take the red book," then "take the blue book," and then "take the nasty knife." (In fact, the game sees an even simpler representation, because the parser does even more work than this. The final digested form of a command is discussed later.)

Note that we've ended the last few commands with a period. This is optional, but it does tell the parser that the sentence is finished, which can be useful if the player wants to put several sentences into a single command:

  Take everything; go east, then take the lamp and look at it! 

Semicolons, exclamation marks, periods, question marks, and the word "then" all mean the same thing: the sentence is finished. Commas and the word "and," in addition to being useful for listing objects to a single command, can be used to separate commands as well, so long as the words that follow the comma or "and" can't be interpreted as another object.

This also brings up "it," and its partner "them." These words can be used to refer to the last direct object, or, in some cases, an object that the game referred to in its description of a place or event. (The built-in function setit() can be used by the adventure writer to define what "it" means in the next command.) "Them" works like "it," but refers to the last list of objects if multiple direct objects were used, as in, "Take the red book and the blue book and put them in the box."

In addition to "it" and "them," the parser understands "him" and "her" to refer to actors. If an object has the property isHim set to true, the parser allows the player to refer to the object as "him". If the object has isHer set to true, the player can use "her" to refer to the object. If neither isHim nor isHer is set to true for the object, only "it" can be used to refer to the object. Note that both isHim and isHer can be set to true, in which case either "him" or "her" can be used with the object. By default, both are nil.

There's one more issue, which is a bit specialized: what happens if the player uses quoted strings or numbers in the command? For example:

  turn dial to 651
  type "hello" on the computer keyboard 

Clearly, it wouldn't be convenient for the game designer to list all possible numbers the player might dial, or all possible strings the player might type on the computer keyboard. Instead, such items are treated as special pseudo objects; these objects appear to the game as ordinary (but specific) objects, which have a special property which allows the game to learn the value (such as 651 or "hello"). In this way, the game designer can deal with numbers and strings in a general way, letting the parser do all the work.

We've described the parser from the user's perspective, with some insights into how the game program sees commands. Now we'll describe the lower layers of the work the parser does.


Associating Words with Objects

Once the parser is through with a command, assuming all went well with the grammar of the sentence, the command has been digested into only five parts: an actor, a verb, a direct object, an indirect object, and a preposition. Of these, only the actor and verb are necessarily present; the others may be nil. All of these items are either nil or are an object defined by the game program.

The parser resolves words to objects based on vocabulary. The words that make up the vocabulary available to the user consist of the several built-in words (such as "and" and "then") and of the words defined in the game. A vocabulary word is created simply by associating a special property with an object:

  RedBook: object
    noun = 'book'
    adjective = 'red' 'large'
    plural = 'books'
  ;

In this object definition, the special property names noun, adjective, and plural are defined to be one or more words, enclosed in single quotes; these words become vocabulary that the player can use, and specify the context in which the words can be used. When a player uses the words "red book" in a sentence, the parser will know that he is referring to the object RedBook.

Verbs are defined similarly, with a slight catch. Recall that some verbs had a preposition built into them. These are defined like this:

  TakeVerb: object
    verb = 'take' 'pick up'
  ;

This associates the word "take" with the object TakeVerb, as well as the words "pick up". When a player types a command such as "pick it up", the parser will decide that the verb is the object TakeVerb. This notation, in which two words appear in a single vocabulary word string, is special to verbs and is not allowed elsewhere.

It is important at this point to distinguish between two types of objects. The first type of object is the kind that appears in the game program; these have superclasses and properties, and are manipulated by code. The other type of object is the kind the player thinks of when he says "take the book." So, when we call a verb an object, we mean it in the first sense, but when we talk about the user specifying an object with nouns and adjectives, we're using the other sense.

There are two other kinds of vocabulary words: prepositions and articles. For example:

  OutP: object
    preposition = 'out'
  ;
  AAnThe: object
    article = 'the' 'a' 'an'
  ;

Note that the second object doesn't distinguish between the articles; this is perfectly all right, because they'll never make it down to the game program. They are defined merely to make the command syntax more natural for the user. Prepositions, on the other hand, should be distinguished from each other, because "put the book in the safe" is different from "put the book on the safe".

Note also that using a preposition in a verb doesn't make it a preposition; hence, even though we defined 'pick up' as a verb earlier, we should still define a separate preposition for "up":

  UpP: object
    preposition = 'up'
  ;

This brings us to ambiguity. Most adventures allow you to say "up" when you mean you want to "go up". Hence, we should define a verb:

  GoUp: object
    verb = 'go up' 'up'
  ;

This might look like a problem, because we've defined a word as a preposition, as a verb's preposition, and as a verb! Fortunately, the parser understands the importance of context and will not be confused in this case. This shouldn't be taken as license to define every word as every part of speech; the parser can get confused and interpret sentences strangely if contextual disambiguation is overused. If you run into strange interpretations of sentences, you might check for part-of-speech ambiguities.

The vocabulary properties, verb, noun, adjective, article, preposition, and plural, have special characteristics. They are not properties in the normal sense, for a number of reasons. First, they are specially recognized as creating vocabulary words and associating them with objects. Second, you might have noticed the non-standard syntax: a list of words follows the property name without being enclosed in square brackets. In addition, you cannot reference vocabulary properties as you can with other properties; for example, the following example will not work:

  say( dobj.noun );          // This won't work! 

For this reason, all objects which have vocabulary associated with them should have a short description, or sdesc property. So, the full definition of the "up" preposition above should be:

  UpP: object
    preposition = 'up'
    sdesc = "up"
  ;

In constructing certain complaints about bad grammar or other problems, the parser will occasionally need the sdesc of a preposition, noun or verb, so be sure each object has one. Articles never need an sdesc as far as the parser is concerned. sdesc is a built-in property that the parser uses to display the short description of certain objects, but it, unlike vocabulary properties, behaves like a normal property.

Vocabulary words are normally made up solely of letters. Numbers are also allowed, so long as the word starts with at least one letter or is made up entirely of numbers. (Note that only adjectives can be made up entirely of numbers.) Hence, `a123' is valid for any part of speech, and `123' is valid as an adjective. In addition, hyphens (`-') and single quotes are allowed within words, as long as the first character of the word is a letter. Finally, a word can have a period (`.') as its last character; this allows words such as "Mr." and middle initials in names to be used as vocabulary words.

In addition to sdesc, nouns should define the properties thedesc and adesc, which display the name of the object preceded by its definite article and indefinite article, respectively. In most cases, thedesc is simply the word "the" followed by the object's sdesc; likewise, adesc is "a" followed by the sdesc.

  Thing: object
    thedesc =
    {
      "the "; self.sdesc;
    }
    adesc =
    {
      "a "; self.sdesc;
    }
  ;

Note that adesc needs to be overridden for certain object. For example, objects whose sdesc starts with a vowel should use "an" rather than "a." Objects which represent collections or people or quantities of something often need a special adesc to make sense: "some milk" is better than "a milk," and "Lloyd" should be used rather than "a Lloyd."


Executing the Command

A command is decomposed into its actor, verb, direct object, indirect object, and preposition. Hence, a command such as "pick up the red book" might decompose as follows: the actor is Me, the player actor; the verb is takeVerb; the direct object is RedBook; the indirect object and preposition are nil.

So, what does the game do with this information? Once again, the parser helps the game designer by doing much of the work. First, the parser determines which objects are implicated in the command, then checks their availability with the validIo and validDo methods of the verb (see the discussion of disambiguation, below). If the objects can be successfully identified and are valid for the command, the parser sends a set of messages to these objects to inform them that they have been used in the player's command. The sequence of events is shown below in pseudo-code.

  actor.roomCheck(verb)
  actor.actorAction(verb, direct object, preposition, indirect object)
  actor.location.roomAction(actor, verb, direct object, preposition, indirect object)
  if (An indirect object was specified)
    direct object.verDoVerb(actor, indirect object)
    if (No Output resulted from verDoVerb)
      indirect object.verIoVerb(actor)
      if (No output resulted from verIoVerb)
        indirect object.ioVerb(actor, direct object)
  else if (A direct object was specified)
    direct object.verDoVerb(actor)
    if (No output resulted from verDoVerb)
      direct object.doVerb(actor)
  else
    verb.action(actor)
  Run each daemon that is currently active
  Run and remove each fuse that has burned down

This may look complicated, but you don't generally have to worry about very much of it. Normally, you will only have to customize one or two of these steps to obtain a desired effect in your game, and let the standard definitions take care of the rest. Let's go through the sequence in detail.

The first step checks with the actor to ensure that the verb is even remotely allowed here, by calling the actor's roomCheck method with the verb object as its parameter. The roomCheck method returns true if the verb is acceptable, or nil if not. This check is intended to be the coarsest possible level of check; its purpose is to allow rooms with special conditions, such as darkness, to disallow all activity for most verbs. You will almost never override this method, as suitable definitions are already provided for all of the common types of rooms (see the "Adventure Definitions" appendix). Note that roomCheck is sent to the actor, which generally simply returns its location's roomCheck value.

Next, we inform the actor implicated in the command, via actorAction, that it's been asked to do something; the Me actor, the player, doesn't usually do much of anything here, but other actors would generally respond in one way or another to the request. In general, actors other than Me won't carry out arbitrary requests, as a matter of game design; this is their chance to object, by printing out a message (such as "The guard looks at you harshly and continues to block your way"), and then exit. When one of these routines executes an exit command, control passes down to the daemons and fuses, and everything else is skipped.

Assuming the actor agrees to the command and does not issue an exit command, the actor's location is informed of the event via its roomAction method. In general, the room doesn't care what goes on inside it, so the roomAction usually doesn't do anything. Some rooms, however, might restrict or change the actions performed by actors in them. A room with a trap, for example, could use roomAction to set off the trap if (or unless) certain commands are issued. As with actorAction, if the room simply wants to disallow the command, it can print a message and use the exit command.

The difference between roomCheck and roomAction is that roomCheck is called before any object disambiguation is performed. None of the objects associated with the command are known when roomCheck is called. roomAction, on the other hand, is called after the objects have been disambiguated. Both checks are needed. When the room is dark or the command will fail for some other reason, you don't want the parser offering a list of the objects that the command can act upon, since the player might not even be aware that some of the objects are present - so a check before disambiguation is needed. A check after disambiguation is also needed, since the room may need to take different actions depending on the exact objects used in the player's command.

If the room has no objection, we next inform the indirect and direct objects of the command. Generally, these will actually carry out the function of a command (whereas the actor and room methods will do little more than approve or reject the command). Depending on whether two objects, one, or none are specified in the player's command, a different series of messages is sent.

If no objects are in the command, the verb simply receives an action message; the actor is the only parameter, as it's the only relevant information. Verbs such as "north" and "look" take no objects, so their functionality is handled by their action routines.

If only a direct object is present, then there is no verb action method invoked. Instead, the direct object itself carries out the command.

The object, like the actor and room, gets a chance to object to the whole command; this is via its verDoVerb method, which stands for "verify direct object usage for Verb." For example, if the verb was "take," the verDoTake method will be invoked. (The actual name of this method is defined in the verb itself; the doAction property names the method that will be invoked. If doAction = 'Take' is specified in the takeVerb object, the method invoked will be verDoTake.) The verification method is special, because it can object to the command simply by printing a message - no exit command is necessary. The object verification routines are designed this way for convenience; since they don't actually carry out the action of the verb, the only reason it would want to display anything is to object, so the system detects this fact and treats it like an exit command.

Assuming the verification succeeds, the direct object's doVerb method is invoked. (As with the verification, the name of the method is derived from the verb's doAction property; if takeVerb has doAction = 'Take' defined, a "take" command will send a doTake message to the direct object.) This method is responsible for carrying out the function of the verb and reporting to the user on the command's success; for example, the doTake method may move the direct object to the user's possessions, and display "Taken."

When both a direct and indirect object are present, the verification method of the indirect object is called first; if that doesn't generate any messages, the verification method of the direct object is called, and if that doesn't generate any messages either, the ioVerb method of the indirect object is invoked. (Note that the verIoVerb, verDoVerb, and ioVerb method names are derived differently from the way single-object command methods are derived. Each verb accepting multiple objects has an ioAction property for each preposition it accepts between the objects. For example, "put the book on the table" would only work if the putVerb object defines a property ioAction( onPrep ). If such a property is defined, its value is used as the Verb suffix in each method name. For example, if putVerb defines ioAction(onPrep) = 'PutOn', then the messages sent are verIoPutOn, verDoPutOn, and ioPutOn.)

Once all of this processing is completed, all of the daemons are executed once, then all of the fuses that have burned down are executed and removed from the active fuse list. Note that messages to objects scheduled with the notify() function are treated the same as other fuses and daemons; hence, messages to objects scheduled with calls to notify() with turns set to zero are handled at the same time as other daemons, and other notify() calls are handled with the other fuses.

Note that the abort command causes the command to be entirely stopped, and not even the fuses or daemons are executed. It is generally used when a special system function is carried out, such as saving a game, which should not be counted as a turn.


Disambiguation

One of the more difficult parts of understanding the player's input is deciding which object is being referred to when several objects go by the same name. For convenience, the player does not want to have to spell out his desires in infinitessimal detail. For example, if the player is carrying a red book, and there is a blue book in the room, he should be able to say "take the book" without having to specify the "blue book." Likewise, while in an airlock with two doors, one of which is open, the command "close the door" should be sufficient, without needing to specify the open door. The process of deciding based on context which object the player is referring to when multiple objects use the same vocabulary words is called "disambiguation."

When a player refers to a "book," the parser will note all of the objects that share that vocabulary word. If the player is more specific, the system will consider only those objects which go by all of the words the player used. If multiple objects are still implicated, the parser performs two levels of disambiguation before asking the player for more detail. First, the system will eliminate from consideration those objects that don't pass the validDo or validIo test, as appropriate. If a unique object still doesn't result, the system will next apply a silent verDoVerb or verIoVerb test, as appropriate, to each object still being considered; the system suppresses the messages from these routines, if any, so the player doesn't see them. However, the parser does note whether output would have resulted; any objects that would produce output fail the test, and any that wouldn't pass the test. If no objects pass the test, all remain under consideration; if one or more objects pass the test, only those passing remain under consideration.

If, at this point, only one object remains, the parser decides that the player must mean this one object. If more than one object remains, the system will ask the player which one he meant, with a question such as "Which book do you mean, the red book, or the blue book?"

The idea behind using validDo and validIo is simple; if the object is not accessible to the player, he cannot meaningfully use it in a command, so it is discarded. This also simplifies later verification code, because it doesn't need to check that the actor in the command can access the objects mentioned.

The idea behind verDoVerb and verIoVerb is a little more complicated. Since these methods are supposed to issue a message if the object in question doesn't make sense - for example, if the player tries to take an object he is already carrying, or close a door that is not open - the parser can guess that the player probably didn't mean an object that fails these tests. So, if the player types "take the book," the red book, which the player is already carrying, fails the test and is eliminated, but the blue book, which is in the room waiting to be taken, passes the test. Likewise, if the player types "close the door," the door that is closed fails the test, while the open door passes.

Using this scheme, the system can generally make reasonably intelligent guesses about what the player meant to say. It is important to design your verDoVerb and verIoVerb routines with this function in mind, as well as with their primary function of ensuring that the object is valid with the verb.


Visibility and Accessibility

Sometimes, you want to make objects that are visible, but not accessible. For example, if a marble is inside a closed glass jar, the marble is visible to the player (since the glass jar is transparent, its contents can be seen), but the marble can't be manipulated without opening the jar.

To support this, the parser makes a distinction between objects which simply aren't visible, and objects which are visible but not accessible. The distinction is minor, and really only affects what kind of error message the parser displays when you can't access an object. When the marble is not visible and not accessible (such as when it's in a different room), the parser will display the message "I don't see any marble here." However, when the marble is visible but not accessible (such as when it's in a closed glass jar), the parser will call the method cantReach(actor) in the marble object.

The parser determines whether the marble is visible by calling its isVisible(vantagePoint) method. If the method returns true, the marble is visible; if it returns nil, the marble cannot be seen from the vantage point. The argument vantagePoint is the actor in the current command.

Note that certain verbs use isVisible to determine whether an object is valid or not (that is, the verb's validDo method is defined in terms of isVisible). For example, inspectVerb allows you to examine an object that you can't manipulate, as long as you can see it.

The default isVisible method (defined in adv.t for the object thing) is somewhat complicated, but the basic rule is: return true if the object's location is the vantage object, or if the location's contents are visible and its location is visible from the vantage object, or if the vantage has a location and the vantage's location's contents are visible and the object is visible from the vantage's location; otherwise return nil. Stated more simply, this is the first law of optics: if you can see me, then I can see you. (Being a computer program, TADS turns this easily understood concept into a complicated recursive algorithm.)

The default isVisible will suffice for most occasions, as will the default cantReach method. You can also use new definitions of isVisible to achieve special effects; for example, you could use it to implement a view screen that shows objects in another location; you can look at the objects on the view screen, but you can't otherwise manipulate them.


Default Objects

The special properties doDefault and ioDefault can be used to supply default information for commands. The purpose of doDefault is to return a list of all objects which are sensible with the current command. For example, if the command is "take," all the objects which are immediately accessible to the actor, excluding, of course, those the actor is already carrying, apply to the command. Likewise, the default direct objects to "drop" are those that the object is carrying.

When a player types a command without a direct object, the parser looks at doDefault for the verb. It then applies the disambiguation function appropriate to the verb to the resulting list, making up a new list of those objects passing the disambiguation function. If there is exactly one direct object remaining in the list, the parser decides that the player is referring to the one sensible object, so it supplies it in the command by assumption (displaying the assumption to the player). The ioDefault property does the same for indirect objects.

For example, if the player types merely "take," the system will first determine that an object is needed because takeVerb has no action() method. It will then consult takeVerb to see if it has a default preposition given by prepDefault; if it does, it assumes that this will be a two-object verb and adds the default preposition to the command. In the case of "take," there will be no default preposition, so the system knows this will be a one-object command. Next, the appropriate verDoVerb routine is identified based on the verb and preposition. In this example, verDoTake is the chosen routine. Now the system calls the doDefault method in takeVerb, and then applies verDoTake to each object returned. Each object that passes the verDoTake test (i.e., produces no output) is kept under consideration. If only one object is left, the object becomes the default object for the command. If no objects, or more than one object, are left in the list, the system will ask the user what he wants to take.


"All"

Another use of doDefault is in determining the list of objects meant by "all" in a player's command. "All" is assumed to refer to everything in the list generated by doDefault.

For most verbs, the standard doDefault and ioDefault methods inherited from deepverb will be sufficient (refer to the "Adventure Definitons" Appendix for information on the deepverb class). takeVerb and dropVerb, because they are such common cases, customize the doDefault method to be a little smarter; this allows "take all" to ignore objects already being carried, and "drop all" to ignore objects that are not being carried.


Object-Level Verb Synonyms

It is often desirable to make certain verbs synonymous for a particular object. For example, if you have a touch-pad on a control panel, the player may try to push the touch-pad instead of merely touching it. With the old system, you had to tediously point the verDoPush and doPush methods to call the object's verDoTouch and doTouch methods, respectively:

  verDoPush( actor ) =
  {
    self.verDoTouch( actor );
  }
  doPush( actor ) =
  {
    self.doTouch( actor );
  }

A new convenience feature has been added that will do this for you automatically. The doSynonym and ioSynonym pseudo-properties allow you to specify a list of property root names that are to be synonymous with a particular property root name. For the above example, we would now write this:

  doSynonym( 'Touch' ) = 'Push' 

This means that whenever a verDoPush or doPush method is called for this object, the object's verDoTouch or doTouch method (respectively) is called instead. You can specify more than one synonymous root name (on the right-hand side of the equals sign); for example, if you also wanted to make verDoTap synonymous with verDoTouch, and doTap synonymous with doTouch, you'd change the definition above to this:

  doSynonym( 'Touch' ) = 'Push' 'Tap' 

The ioSynonym works the same way.


Built-in Properties

The section on the player command parser described several special built-in property names that the parser uses. This section lists all of the special property names that the TADS run-time system defines, and explains how each is used.


action

action(actor): the method of a verb invoked when the verb is used without objects. The method should carry out the action of the verb, checking first to ensure that the verb should be allowed at this time. With system verbs, such as save, the command generally shouldn't count as a turn, so it should use the abort keyword even if completes successfully, to prevent the per-turn daemons and fuses from running. Non-system verbs, such as jump, shouldn't use abort if they complete successfully.


actorAction

actorAction(verb, direct object, preposition, indirect object): sent to the actor in a command by the parser. This method should determine if the actor is interested in accepting the command. If so, it needn't do anything, since the command will be handled by the verb or objects as usual; if not, it should display a complaint and use exit to prevent further execution of the command.


adjective

adjective: vocabulary list of the adjectives that can refer to an object.


article

article: vocabulary list of article words (such as "the" and "a").


cantReach

cantReach(actor): display an appropriate message when the actor can't reach the object (self). The parser calls this method to display a message when an object is visible, but not accessible (for example, when a marble is inside a closed glass jar, the marble is visible, but can't be manipulated). The default version defined adv.t for the thing object is suitable for most situations.


contents

contents: a list of objects that an object contains. This should not be initialized; see the discussion of the location property. However, it should be kept current; that is, whenever an object's location property changes, its old container's contents list should have the object removed, and the new container's contents list should have the object added.


doAction

doAction: the property of a verb that defines the suffix applied to verDo and do to form the method name sent to the direct object when the verb is used with only a direct object. This must be a single-quoted character string. If not defined, the verb is not allowed to be used with just one object. For example, if the "take" verb defines doAction = 'Take', then verDoTake and doTake messages are sent to the direct object of a "take object" command.


doDefault

doDefault(actor, prep, indirectObject): the default direct objects for a verb. When "all" is used, this entire list is substituted for "all." When no direct object is used at all, if this list is unambiguous (i.e., has only one entry), it is used by default as the direct object of the command.


ioAction

ioAction(preposition): the property of a verb that defines the suffix applied to verIo, verDo, and io to form the method name sent to the indirect object when the verb is used with two objects and the given preposition. The value must be a single-quoted character string. For example, if the "ask" verb defines ioAction(aboutPrep) = 'AskAbout', then verIoAskAbout and ioAskAbout messages are sent to the indirect object of a "ask direct-object about indirect-object" command; a verDoAskAbout message is sent to the direct object. Note that the preposition argument is the name of a preposition object defined elsewhere; the object defines the vocabulary word, with its preposition attribute, that the player uses.


ioDefault

ioDefault: the default indirect objects for a verb.


isHim

isHim: Returns true if the object (self) is a male actor and should therefore be referred to with "him" in player commands. If isHim is nil, the object cannot be referred to as "him". It is legal to set both isHim and isHer to true, in which case either "him" or "her" can be used. If both are nil, only "it" can be used with the object (this is the default).


isHer

isHer:

isHer: Returns true if the object (self) is a female actor and should therefore be referred to with "her" in player commands. If isHer is nil, the object cannot be referred to as "her". It is legal to set both isHim and isHer to true, in which case either "him" or "her" can be used. If both are nil, only "it" can be used with the object (this is the default).


isVisible

isVisible(vantage): returns true if the object (self) is visible from the object vantage, or nil if it is not visible. The implementation defined in adv.t in the thing object is suitable for situations involving transparent containers; different implementations may be useful for achieving special effects.


location

location: the container of an object. May be nil or another object. At the start of a game, all objects are moved into their location; that is, for all objects with a location defined as a simple object (not a method or nil), object.location.contents has the object added to it. This ensures consistency between location and contents properties. Note that class objects and objects with methods for their location property are ignored in this procedure. Warning: Never assign the location property of an object directly at run-time. Always use the moveInto(newlocation) method instead. For example, if you want to move the knife into the player's current location, do not code knife.location := Me.location, but do this instead: knife.moveInto(Me.location). The moveInto method ensures that the contents properties of the old and new containers of the object are updated to reflect the object's new location.


locationOK

locationOK: a flag that specifies that the location of an object is intentionally something other than an object. Normally, if an object's location property is not an object, the compiler flags this as a warning, since the compiler is unable to set the contents list of the location to contain the current object. However, it is sometimes necessary to make location a method to achieve special effects; for example, theFloor, defined in adv.t (see the "Adventure Definitions" Appendix for details), uses a method for its location so that the same object appears to exist in every room. By setting locationOK to true in theFloor, the game informs the compiler that the non-standard location value is intentional, which suppresses the warning message. This property has no other effect.


noun

noun: vocabulary list of the nouns that can refer to an object.


plural

plural: a vocabulary list giving the plurals by which an object may be called.


prepDefault

prepDefault: specifies the default preposition for a verb. Used when no doAction property is defined, but the player specified only a direct object in his command. The parser will attempt to add the default preposition given to the command and then try to get an indirect object (first from the verb's ioDefault list, then by asking the player).


preposition

preposition: vocabulary list of preposition words.


roomAction

roomAction(actor, verb, direct object, preposition, indirect object): sent to the object given by the actor's location property for all commands. See the section on the sequence of messages sent by the parser in response to a player command.


roomCheck

roomCheck(verb): sent to the Me object (the player actor) at the very beginning of processing a sentence. The property should return true if the verb is acceptable in the player's room, nil otherwise; it should also display a message in the latter case explaining why the verb is not acceptable. This property is used largely to prevent unnecessary processing from taking place in a dark or otherwise limiting room, and it should do nothing apart from checking the most basic features of the room. Note that roomCheck is called before disambiguating the objects in the command, so it must decide whether the command is appropriate or not regardless of objects; roomAction is called after the objects are known, so it can apply more specific criteria to the command.


sdesc

sdesc: a self-printing short description. May be a double-quoted string, or a method that prints a short description.


statusLine

statusLine: this is related to sdesc for rooms or other objects (such as chairs and vehicles) that may contain the player. This should print the short description of the location, which should fit on a single line, without the list of objects in the location. Most locations that are descendants of the room object defined in adv.t will display statusLine first when sent a lookAround message (a method defined in adv.t for room objects).

On some platforms, the TADS run-time system displays a status line across the top of the screen showing the player his current location; this line is updated before every turn. On these platforms, the run-time system uses the statusLine method of the player's current location to display the status line information.

Note that statusLine prints only the "left half" of the status line, which typically gives the player's current location. The right half is displayed by the setscore function.


thedesc

thedesc: a short description with a definite article. Generally just defined as a method showing "the" and the sdesc. Like sdesc, the thedesc must be self-printing.


validDo

validDo(actor, object): sent to a verb to check that a direct object is valid for a command. The method should return true if the object is valid as a direct object for the verb and actor, or nil if it is not. Generally speaking, this routine should do nothing more than check that the object is accessible to the actor; it is not necessary for validDo to check whether the object makes sense with the verb. Hence, it should return true if the actor is trying to take something he is already carrying, while it should return nil if trying to take something that is in a different room. The job of deciding whether the object makes sense is done by the object's verDoVerb method.


validIo

validIo(actor, object, seqno): sent to a verb to check that an indirect object is valid for a command. Like validDo, the method returns true if the object is valid as an indirect object for the verb and actor, or nil if it is not. As with validDo, this method is responsible only for verifying accessibility, not for checking if the object makes sense with the verb; hence, if the actor is trying to put something into an object that is accessible, the method should return true regardless of whether the object in question is a container or not.

The seqno parameter is the "sequence number" of the object being tested. Any time the player makes an ambiguous object reference (that is, uses a set of nouns and adjectives that could refer to more than one object), the possible objects are tested with validIo one at a time; each object in the set is given a separate sequence number, starting with 1. These sequence numbers are arbitrary, and are generally not even used. However, certain verbs don't care which of a set of ambiguous objects is chosen; these verbs use seqno to accept only the first object and reject the rest. For example, askVerb in adv.t does this (see the "Adventure Definitions" Appendix for information on adv.t).


value

value: A special property for the strObj and numObj objects giving their value when used in a player command. For example, "turn the dial to 651" makes numObj the indirect object, and sets its value property to 651.


verb

verb: list of the vocabulary words for a verb object. Like all vocabulary properties, the value must be a list of single-quoted strings, optionally enclosed in square brackets. (If the object is a class object, the square brackets are required.) The special two-word notation, as in verb = 'pick up', may be used for verbs with a prepositional part.


Required Objects and Functions

The TADS run-time system requires the game program to define several objects and functions. This allows the user control over the details of these objects, but their names are fixed by the system. The required objects and functions are listed here.


againVerb

againVerb: a special verb that specifies the vocabulary for the "again" command, which is used to repeat the last command. Normally, the verb property of this object is set to accept "g" and "again" for this command.


init

init(): A function that is executed once when the game is started. This function should move the Me actor (the player) into his starting room, start any daemons and fuses that are needed, and display the title, copyright message, and introductory text for the game.

See also the related preinit() function, which should be used for any time-consuming computations that need to be performed before the game starts.


Me

Me: The player's actor. When no actor is specified in a player command, the Me object is assumed. The Me object receives all of the same messages from the parser that any other actor does.

Note that adv.t defines the class basicMe as a reasonable implementation of Me. If you do not wish further customization of Me in your game, you can use basicMe unchanged by including this in your game: "Me: basicMe;".


numObj

numObj: The special object used when a command involves a number; its property value will be set to the number the player used in the command. For example, if the player types "Turn the dial to 617," the indirect object will be numObj, and numObj.value will be 617.

Note that adv.t defines the class basicNumObj as a reasonable implementation of numObj. If you do not wish further customization of numObj, you can use basicNumObj unchanged by including this in your game: "numObj: basicNumObj;".


pardon

pardon(): A special function that is called whenever the player enters a blank line. It normally just displays "I beg your pardon?" The function is up to the game author to implement simply to allow customization of this message.


parseError

parseError(errnum, errstr): This function is called whenever a parser error message is to be displayed. This function is optional; if it's not defined in your game, the system will display a default message. This function allows you to change any parser error message and replace it with your own message.

The argument errnum is a number that specifies which message is needed; errstr is the text of the default message that the parser would normally display. The reason that the number is provided is that it serves as a version-independent key to the error message; even if a small change is made to the actual text of a message, its basic meaning will remain the same from version to version, so you can count on errnum to indicate the meaning of the message. The reason that errstr is provided is to allow you to make a small textual change to the message without replacing it altogether; for example, if you want parser messages to be enclosed in square brackets, you could simply append square brackets to errstr and return the resulting string.

This function returns either nil or a string value. If it returns nil, the default message is displayed. If it returns a string value, the string is displayed instead of the default message.

The error numbers and default messages are shown below. Errors in the range 1-99 are normal parser error messages. Numbers 100-109 are special messages that are used to construct disambiguation queries; several error numbers make up a single error display, so you should probably exclude these if you are performing a transformation such as adding parentheses around error messages. The same is true of the other ranges above 100. 200 is a special message that is used when a cantReach message is about to be generated. Here is an example of a parseError function that simply encloses all normal parser error messages in square brackets, leaving the special messages (those numbered 100 and above) unchanged:

  parseError: function( errno, str )
  {
    if ( errno >= 100 ) return (nil );   /* leave special messages alone */
    return('[' + str + ']');            /* enclose message in [brackets] */
  }

Note that the C-style formatting sequences %c and %s are used in these messages. You can leave out the formatting sequences in your messages if you wish, but you should never include one in a message that doesn't have one in the default version, and you should always make sure that they are of the correct number, type, and order if you do provide them. Note that you don't need to do anything with the formatting sequences - just preserve any sequences that are present, and the system will fill in the correct values before displaying your message.

Normal error messages
1 : "I don't understand the punctuation "%c"."
2 : "I don't know the word "%s"."
3 : "The word "%s" refers to too many objects."
4 : "I think you left something out after "all of"."
5 : "There's something missing after "both of"."
6 : "I expected a noun after "of"."
7 : "An article must be followed by a noun."
8 : "You used "of" too many times."
9 : "I don't see any %s here."
10 : "You're referring to too many objects with "%s"."
11 : "You're referring to too many objects."
12 : "You can only speak to one person at a time."
13 : "I don't know what you're referring to with '%s'."
14 : "I don't know what you're referring to."
15 : "I don't see what you're referring to."
16 : "I don't see that here."
17 : "There's no verb in that sentence!"
18 : "I don't understand that sentence."
19 : "There are words after your command I couldn't use."
20 : "I don't know how to use the word "%s" like that."
21 : "There appear to be extra words after your command."
22 : "There seem to be extra words in your command."
23 : "internal error: verb has no action, doAction, or ioAction"
24 : "I don't recognize that sentence."
25 : "You can't use multiple indirect objects."
26 : "There's no command to repeat."

Special disambiguation error messages
100 : "Let's try it again: "
101 : "Which %s do you mean, "
102 : ", "
103 : "or "
104 : "?"

Special: used to complain that an object is no good for a verb
110 : "I don't know how to "
111 : " "
112 : " anything "
113 : "to"
114 : " "
115 : "."

Special: used after each object when multiple objects are used
120 : ": "

Special: used to note objects being used by default
130 : "("
131 : ")"
132 : " "

Special: used to ask for an object when one can't be defaulted
140 : "What do you want to "
141 : " it "
142 : " to "
143 : "?"

Special: used when an obj is unreachable: obj.sdesc ": " obj.cantReach
200 : ": "


preinit

preinit(): This function is related to init. Immediately after successfully compiling your program, the compiler will call preinit(); the binary file containing the compiled game is then written.

preinit() is provided solely for performance; if the function is not defined in your program, the compiler will display a warning message but will not be otherwise affected. Since the function is called before the binary file is written out, it allows you to perform any pre-game compilations before the player runs your game. The init() function, on the other hand, runs after the player has loaded the binary file, so the player must wait for any computations the init() function performs to complete before the game can begin.

Note that preinit() should not display any messages, or set any fuses, daemons, or alerts, because the game has not begun yet, and any such actions would be lost when the binary file is written. Instead, the preinit() function is intended to be used to set up any lists of objects you need to compute at the start of the game (for example, a list of light-providing objects could be compiled by searching through all objects, using firstobj() and nextobj(), and storing in a list all of those objects with an islamp property set to true).


preparse

preparse(str): This function is called immediately after the player enters a line of text, and before the player command parser starts to look at the command line. The argument is a string containing the complete, untouched text of the player's command line; the text is intact, complete with the original spaces, punctuation, and capitalization.

If the function returns nil, the command is aborted - the parser never sees the command. If the function returns a string, the new string replaces the original text of the player's command, and is processed by the parser as though the player typed it. If the function returns true, the original player's command is parsed as normal.

See the beginning of this chapter for more information on preparse. This function is optional; if it's not defined, the compiler will issue a warning, but the game will compile successfully.


strObj

strObj: When a player uses a quoted string in a command, this special object is used, and its property value is set to the string the player used in the command. For example, if the player enters type "launch" on the console, the direct object is strObj, and strObj.value is set to the string 'launch'.

Note that adv.t defines the class basicStrObj as a reasonable implementation of strObj. If you do not wish further customization of strObj, you can use basicStrObj unchanged by including this in your game: "strObj: basicStrObj;".


takeVerb

takeVerb: the verb used to take objects. This is no different from any other verb, and the action of the verb must be implemented as normal by the game program, but the parser must know about this verb, so its name is required to be takeVerb. In general, it is recommended that verbs and other objects follow this general style of naming, so this requirement will fit smoothly into most games. (The reason the parser needs to know about this verb is that it uses its validDo method to determine if an actor is present and thus can be addressed.)


The Output Formatter

To relieve the game programmer of the details of how many characters to put on a line, the TADS run-time system automatically filters output with a formatter. The functioning of the formatter is generally transparent to the programmer, but once in a while a special effect is desired; special control sequences are recognized by the formatter which give the programmer full control of output formatting.

When the TADS compiler reads the text from your source file, it ignores redundant spaces, and treats line breaks as spaces. Hence, when your text is output by the system, it will be rearranged from the way it was input. This frees you of the need to keep your source text formatted nicely.

Normally, the formatter will simply fill up each line with as many words as it can, and then go to a new line. It will automatically "word wrap" the output; that is, line breaks will always occur between words. Hence, you can write your text without any line breaks at all, and it will come out looking reasonably good. The formatter will also break text at hyphens that appear in your text when it would allow the line to be filled with more text (although the formatter will never attempt to hyphenate words that aren't hyphenated in your text). When writing text, the formatter will automatically insert two spaces after periods, exclamation marks, colons, and question marks, as is conventional. In other places, only one space will appear.

Note that you can override the automatic insertion of double spaces by using a quoted space (see the Language Reference chapter's explanation of special control sequences in text) after the punctuation mark that would normally cause a double space to be inserted. You can also insert several spaces in a row using the quoted space. Refer to the Language Reference chapter's section on double-quoted strings for the other special control sequences that you can use to control the output formatter.


Format Strings

TADS doesn't treat the player object specially in most places. This allows you to implement other actors that carry out orders from the player fairly easily: the player can specify an actor to which to direct a command, and if the actor follows it, the actor is specified in all of the parser's method calls to the objects involved in the command.

In order to make the text displays that result from a non-player actor's actions make sense, all of the messages have to be phrased in terms of the actor actually carrying out the command, rather than "you." It would be extremely inconvenient if you had to use actor.thedesc constantly in these messages, so TADS provides a much more convenient mechanism called "format strings."

A format string is a special sequence of characters that signals the output formatter to substitute a property of the current command's actor. Instead of using "you" and related words, the messages in adv.t use format strings. Your game program should do the same thing where appropriate (i.e., any place that an actor other than the player may be taking some action or described in a message). When the output formatter sees the format strings, it automatically replaces them with a term appropriate to the current actor.

You can define your own format strings, although adv.t defines most of the format strings you'll probably need. Basically, a format string associates a special output sequence with a property name. When the format string, enclosed in percent signs (%), appears in a message being displayed, the output formatter removes the format string (including the percent signs) and replaces it by calling the property associated with the format string. For example, adv.t defines a format string as follows:

  formatstring 'you' fmtYou; 

This tells the system that whenever it sees the sequence:

  %you% 

in text to be displayed, it removes the entire sequence, and replaces it with the text that results from evaluating the fmtYou property in the current actor.

In the new adv.t, messages are phrased in terms of these format strings. For example, the new movement error message is "%You% can't go that way." When the player just types "east," the %You% is replaced by You, which is the result of evaluating fmtYou for the object Me, so the message is "You can't go that way," just as before. However, when the player types "joe, east," the message is now "Joe can't go that way."

Note that the capitalization of the text replacing the format string follows the capitalization of the format string itself. Hence, %you% is replaced with you, while %You% is replaced with You.

In adv.t, format strings are defined for most of the words that you'll need to use in messages that are dependent on the actor. Some of these words are dependent on the actor in order to keep sentences grammatical; for example, "You aren't wearing your spacesuit" would change to "Joe isn't wearing his spacesuit" - the words "you", "aren't", and "your" all need to change. Using the format strings defined in adv.t, you'd rewrite the message sentence as:

  "%You% %are%n't wearing %your% spacesuit." 

Your game will continue to work without using format strings in your own messages. You need not make use of this feature (although you will automatically use it, to some extent, because adv.t uses it in all of its messages). However, it's quite easy to use format strings instead of hardcoded text, and it makes your game quite a lot more adaptable to multiple actors.


Special Words

The parser treats a number of words as special. In older versions of TADS, these words were built in to the system, so your game program had no way to change them. A new construct has been added that allows you to replace these words with words of your own choosing (this may be useful if you are writing an adventure in a language other than English, for example).

The command that lets you override the built-in words is called specialWords. This command can appear anywhere that one of the other special commands (such as formatString or compoundWord) could appear. Following the specialWords keyword is a list of all of the special words, in a fixed order. You must specify the words in the order shown below, and you must provide at least one word for every position. However, you may provide more than one word for each position. Each position is separated by a comma; synonyms (multiple words for one position) are separated by an equals sign; and the entire list is terminated by a semicolon. The default list, which matches the built-in list if no specialWords list is used at all, looks like this:

  specialWords
  'of',                /* used in phrases such as "piece of paper" */
  'and',     /* conjunction for noun lists or to separate commands */
  'then',                      /* conjunction to separate commands */
  'all' = 'everything',       /* refers to every accessible object */
  'both',             /* used with plurals, or to answer questions */
  'but' = 'except',                /used to exclude items from ALL */
  'one',                /* used to answer questions: "the red one" */
  'ones',                 /* likewise for plurals: "the blue ones" */
  'it',                /* refers to last single direct object used */
  'them',                     /* refers to last direct object list */
  'him',               /* refers to last masculine actor mentioned */
  'her'                 /* refers to last feminine actor mentioned */
  ;

When a thought takes one's breath away, a lesson on grammar seems an impertinence.
Thomas Wentworth Higginson (1890)

A smile is the chosen vehicle for all ambiguities.
HERMAN MELVILLE, Pierre, book IV (1852)


Chapter Three Table of Contents Chapter Five