This file is part of the TADS 2 Author’s Manual.
Copyright © 1987 - 2002 by Michael J. Roberts. All rights reserved.
Edited by NK Guy, tela design.


The Text Adventure Development System


Overview Documentation

This is a brief overview of TADS, a program that makes it easier to write your own text adventure games. TADS is offered as freeware, which means that you use the software without charge.

It’s also a summary of the information in the full TADS Author’s Manual. (Before TADS became freeware software this file was the only part of the documentation that was available to users who hadn’t registered.)


Ditch Day Drifter

I’ve included a large sample game, Ditch Day Drifter, with TADS. The source for the game is in the file DITCH.T. The game was included as a sample of what TADS can do, and to help you see how you can go about writing your own game with TADS. After reading through this description of TADS, you may find it helpful to look at DITCH.T to see more examples of how to write TADS code.

If you want to play Ditch Day Drifter, you need to compile it first. On MS-DOS machines, go to the TADS directory and type this:

  tc ditch

The “tc” command runs the TADS Compiler, which converts the game source file into a binary executable format. Run the game with this command:

  tr ditch

On UNIX systems the compiler is called tadsc. On Macintosh systems, start the compiler by double-clicking on the application named “TADS Compiler”. The compiler will display a dialog box with several parameters. Click on the “Select” button next to the “Filename” box, then select the file DITCH.T using the normal file selector dialogue box that appears. Then, click on the “Compile” button. When the compiler finishes, click on the “Quit” button. Now you can run the game either by double-clicking on the new document DITCH.GAM, or by double-clicking on the application “TADS Run-Time”, then selecting DITCH.GAM from the file selector dialogue.


What is TADS?

TADS is a tool that makes it easier to write text adventure games. The system consists of a compiler, which reads source code written in the TADS language, checks it for errors, and converts it to an internal representation; and a run-time system, which reads commands typed by the player, and controls the interaction between the player and your game program.

It’s easier to write an adventure using TADS than with a general purpose language for several reasons.


Writing games with TADS

To write a game with TADS, you start off by writing your game’s source file with a text editor. You can use any editor that can produce a plain text file. Once you’ve written your source file, you compile it using the TADS compiler. If the game had no errors, you can run the game using the TADS run-time system. As you write your game, you’ll probably write a little, compile it, try it out, then go back and add more, compile and try that, and so on.


Getting Started - A Sample Game

To help you get started writing your own games with TADS, we’ll go through the implementation of a sample game. This example is similar to the one used in Chapter One of the TADS Author’s Manual.

We’ll start with about the simplest game possible: two rooms, and no items. We could start with only one room, but then there wouldn’t be anything to do while playing the game. With two rooms, at least the player can move between the rooms.

If you want to try running this game, create a file containing the program text shown below using your text editor or word processor. The TADS compiler will accept an ASCII file saved by any text editor. If you’re using a word processor, you may have to choose a special option to save the file as plain text, without any formatting codes; refer to your word processor’s documentation for details.

Note that the lines of equal signs are just borders to show where the example starts and ends; use only the part between the lines of equal signs when creating the file.

  /* This is a comment, just like in C */
  #include <adv.t>             /* read basic adventure game definitions file */
  #include <std.t>                  /* read starting standard functions file */

  startroom: room                     /* the game always starts in startroom */
    sdesc = "Outside cave"              /* the Short DESCription of the room */
    ldesc = "You're standing in the bright sunlight just
            outside of a large, dark, foreboding cave, which
            lies to the north. "
    north = cave                 /* the room called "cave" lies to the north */
  ;

  cave: room
    sdesc = "Cave"
    ldesc = "You're inside a dark and musty cave.  Sunlight
            pours in from a passage to the south. "
    south = startroom
  ;

To run this example, all you have to do is compile it with TC, the TADS Compiler, then run it with TR, the TADS Run-time system. If you named your sample program file “mygame.t”, on most operating systems you can compile it with this command:

  tc mygame

and you can run it with this command:

  tr mygame

Let’s look at the sample program line by line.

The first line is a #include command. This command inserts another source file into your program. The file “adv.t” (included with the TADS package) is a set of basic definitions that can be used by most adventure games. By including adv.t in your game, you don’t need to worry about defining words such as “the”, a large set of verbs (such as “take”, “north”, and other common verbs), and many “object classes” (described later).

Note one critical detail - if you copied and pasted the text into your word processor or text editor, be absolutely certain that the #include command has no space characters in front of it. The # must be the first symbol on the line.

The next line includes the file “std.t” (which is also included with the TADS package), which contains additional definitions. The reason for placing some definitions in the separate file std.t is that you will almost always want to change most of the definitions in std.t in a finished game, whereas the definitions in adv.t can be used unchanged by many games. (Even though most finished games will customize the definitions in std.t, the definitions are good enough to get us started with this sample game.)

The next line says “startroom: room”. This tells the compiler that you’re defining a room named “startroom”. A room is nothing special to TADS, but adv.t, which we previously included, defines what a room is. A room is one of the object classes that we mentioned earlier.

The next line defines the “sdesc” for this room. The sdesc is a short description; for a room, it is displayed whenever a player enters the room. The next line is the “ldesc”, or long description; it is displayed when the player enters the room for the first time, or asks for the full description with the “look” command. Finally, the “north” definition says that another room, called “cave”, is reached when the player types “north” while in startroom.

Now let’s add a few items that can be manipulated by the player. We’ll add a solid gold skull, and a pedestal for it to sit on. Add these definitions at the end of the source you’ve already created.

  pedestal: surface, fixeditem
    sdesc = "pedestal"
    noun = 'pedestal'
    location = cave
  ;

  goldSkull: item
    sdesc = "gold skull"
    noun = 'skull' 'head'
    adjective = 'gold'
    location = pedestal
  ;

As with the “room” class, the “surface”, “fixeditem”, and “item” classes are not built in to TADS itself, but are defined in the includedadv.t file. (so they’re customizable if you like) Note that you can create an object using more than one class; in the example, the pedestal object is both a surface and a fixeditem. A surface is an object that can have other objects placed upon it; a fixeditem is an object that can’t be picked up and carried by the player. The goldSkull object is simply an item, which is an object that the player can pick up and carry around.

As with a room, the sdesc property gives a short description of the object; it should simply be the name of the object. There is no ldesc property defined for these objects, so they get the default long descriptions defined by their classes. The ldesc for an item simply says, for example, “It looks like an ordinary gold skull”; for a surface, it lists the objects sitting on the surface.

Since these objects can be manipulated by the player, they must be associated with vocabulary words. The noun and adjective definitions specify the words the player can use to refer to the objects. Note that the sdesc and ldesc properties are enclosed in double quotes, but the vocabulary words are enclosed in single quotes. Note also that a noun or adjective can have multiple vocabulary words.

The objects have another new property as well: location. This simply defines the object that contains the object being defined. In the case of the pedestal, its location is the room “cave”; since the goldSkull is on the pedestal, its location is the object “pedestal”.

Now let’s add another feature to the game: let’s add a trap, so that the player can’t take the gold skull without getting killed. To do this, we’ll replace the goldSkull definition shown above with the new definition below.

  goldSkull: item
    sdesc = "gold skull"
    noun = 'skull' 'head'
    adjective = 'gold'
    location = pedestal
    doTake(actor) =
    {
      "As you lift the skull, a volley of poisonous
      arrows is shot from the walls!  You try to
      dodge the arrows, but they take you by surprise! ";
      die();
    }
  ;

The definition of doTake (which stands for Direct Object Take) has an argument specifying the character who is trying to take the object (since we have no characters besides the player, the actor will be the player’s character, named “Me”). The system calls doTake whenever the player attempts to take the object. Note that this definition of doTake is associated with the object itself; another object could have a different doTake that does something entirely different. In this case, we simply display a message (since the message is enclosed in double quotes, it is simply displayed when this statement is executed), then call the “die” function, which is defined in std.t.

You might wonder why we’ve waited until now to define doTake in the goldSkull object, or you might have just assumed that the system automatically knows what to do if doTake is not defined for an object. In fact, all objects do need a doTake definition, and the system has no default behavior if the definition is missing. However, since most objects will have a very similar doTake definition, it would be extremely tedious to have to type the whole definition for every normal object. Instead, we use something called “inheritance”: by defining the goldSkull to be a member of the “item” class, you tell TADS that the goldSkull object inherits all of the definitions from the item class, in addition to any definitions it makes on its own. The item class (in adv.t) has a definition for doTake that is suitable for most objects. However, if an object of class “item” has its own definition of doTake, the one in the object takes precedence - it “overrides” the default definition from the class.

We don’t have a very good puzzle at this point, because there’s no way the player can get the gold skull without getting killed. So, let’s make it possible for the player to get the skull. We’ll assume that the mechanism that the pedestal uses to set off the poisoned arrow trap is a weight sensor: when the pedestal has too little weight on it, the trap is set off. So, we’ll create a rock that the player can use to keep the trap from going off: the player puts the rock on the pedestal, then can take the skull. Add the new definition below to the end of your source file.

  smallRock: item
    sdesc = "small rock"
    noun = 'rock'
    adjective = 'small'
    location = cave
  ;

Now, we’ll change the definition of doTake in the goldSkull object. Replace the old definition of doTake with the one below.

   doTake( actor ) =
   {
       if ( self.location <> pedestal or smallRock.location = pedestal )
       {
          pass doTake;
       }
       else
       {
          "As you lift the skull, a volley of poisonous
          arrows is shot from the walls!  You try to dodge
          the arrows, but they take you by surprise! ";
          die();
       }
    }

This new doTake definition first checks to see if the object being taken (the special object “self”, which is the object whose doTake definition is being executed), in this case, the gold skull, is already off the pedestal; if it is, we don’t want anything to happen, so we “pass” doTake. We also pass doTake if the small rock is on the pedestal. When we pass doTake, the doTake definition that we inherited from our parent class (in this case, item) is invoked. This allows us to override a definition only under certain special circumstances, but otherwise use the default behavior. If the rock isn’t on the pedestal, and the skull is being removed from the pedestal, the poisoned arrows are released as before.

We’ve included a file named GOLDSKUL.T that includes this sample game, with the complete puzzle involving the gold skull and the pedestal. You can compile and run that file by typing these commands:

  tc goldskul
  tr goldskul


The TADS Language

If you know how to program in a “procedural” language like C or Pascal, most of the TADS language will probably look very familiar. However, the overall structure of a TADS program will probably seem a little strange at first.

In particular, you’ll probably wonder where the program begins. The answer is that a TADS game doesn’t have a beginning in the same sense that a C or Pascal program does.

A C program, like a program written in any procedural language, is a sequence of instructions. The program starts running at the main() function, and carries out the instructions listed there in the order they appear. The program may call subroutines or iterate through loops along the way, but control flows essentially sequentially through the program.

A TADS program, in contrast, is simply a series of object definitions. Each definition specifies the behavior of a single object in the game. An object definition consists of a series of “properties”, which are attributes of the object. A property can contain some simple information, such as the name of the object, or its weight, or can contain programming language code that defines the object’s behavior.

Obviously, the information you specify in your object definitions has to be used somehow - something has to call the program code that you write. In TADS, it’s the player command parser that does the work of deciding which properties to use in which objects in response to the player’s commands.

Later on, we’ll describe in detail how the parser decides which properties to evaluate. In order to take full advantage of the power of TADS by creating your own types of objects, you’ll need to understand how the parser works. However, one of the advantages of using TADS is that you can define objects using existing types without having to know everything about how those types work. So, we’ll start off by showing you how to create some simple objects that you’ll find in almost every adventure game.


Creating a Room

The first type of object you’ll want to create in an adventure game is a room, which is simply a location in the game. The attributes of a room are typically its name, its description, and links to other rooms.

The first thing you’ll need to do in defining a room, or any TADS object, is to choose its object name. This is not a name that is ever displayed to or used by the player - it is a purely internal name that you use to refer to the object within the TADS program. It is similar to the name of a variable in C or Pascal. In TADS, the name of an object must start with a letter, and can consist of letters, numbers, and underscores.

Here’s a sample room definition.

  nsCrawl:  room
    sdesc = "North-South Crawl"
    ldesc = "You're in a narrow, low passage connecting
        caves to the north and south.  A cold, damp
        breeze comes from the north. "
    north = iceRoom
    south = roundRoom
  ;

The first line of the object definition gives the object its name, “nsCrawl”, and specifies the type of the object. Note that the case of the letters in the name is significant - “nsCrawl” is a different name than “NSCrawl” or “nscrawl”.

The next line specifies a property of the object - the sdesc, or short description. It says that the property sdesc is the string “North-South Crawl”. The sdesc is displayed whenever the player enters the room, and is also used on the status line.

In TADS, double-quoted strings are special: they mean that the string is to be displayed whenever the property is evaluated. Double-quoted strings are similar to using a PRINT statement in other languages. So, whenever the sdesc property of the nsCrawl object is evaluated, the string “North-South Crawl” will be displayed to the player.

The next line defines the ldesc, or long description, property. This is the full description of the room that is displayed when the player enters the room for the first time, or whenever the player uses the “look” command to ask for a full description. Note that this property’s value is a double-quoted string, just like the sdesc, to indicate that the string is displayed whenever the ldesc is evaluated.

The next two lines specify the directional properties north and south. These properties are set to refer to other room objects. They specify that the player will end up in the room defined by the object iceRoom by going north, and in roundRoom by going south. Note that other directions are not specified - this simply means that the player can’t go in any other directions. The other possible direction properties are east, west, up, down, in, out, ne, se, nw, and sw (the last four stand for northeast, southeast, northwest, and southwest, respectively).

The final line of the room definition is a semicolon, which tells TADS that the object definition is finished.


Creating Items

In addition to rooms, you’ll want to create objects that the player can pick up and carry around. These are objects of type “item”. When you define an item, you’ll need to specify another set of properties: its short and long descriptions (just like rooms), its location (which says where the object is originally situated), and vocabulary words that specify how the player can refer to the object. Here are a couple of item definitions.

   dollar: item
      location = nsCrawl
      sdesc = "one-dollar bill"
      noun = 'dollar' 'bill'
      adjective = 'one' 'one-dollar' '1' 'dollar'
   ;

   rope: item
      noun = 'rope'
      sdesc = "rope"
      ldesc = "It's about 50 feet long, and seems quite strong; it's
              probably capable of handling several hundred pounds. "
      location = backpack
   ;

The first thing to notice here is that vocabulary words are enclosed in single quotes, not double quotes. Recall that double-quoted strings are displayed whenever evaluated; this is obviously not desirable for vocabulary words. So, always specify vocabulary words with single quotes.

You may also notice that more than one single-quoted string can be used with a vocabulary property. This is a unique feature of vocabulary properties (the vocabulary properties are noun, adjective, plural, verb, preposition, and article). When you use more than one word in a vocabulary property, it simply creates multiple synonyms that can be used to refer to the object.

Note that the properties can be defined in any order. Note also that you don’t need to specify every property; for example, if the ldesc property is not specified with an item, a default message (“It looks like an ordinary one-dollar bill”) is displayed. However, practically every item will define the properties sdesc, location, and noun.

The location property specifies the original location of the object. In many cases, the location will refer to a room object. However, it can also refer to another game item; in these cases, the item should be a container, as described below.


Containers

You will often want to create items that can contain other items. To do this, simply create an item of type “container” rather than “item”. In other respects, a “container” object is the same as an “item”, and you specify the same set of properties. Here’s an example.

   backpack: container
      sdesc = "backpack"
      location = maintenanceRoom
      noun = 'backpack' 'pack' 'bag'
      adjective = 'back'
   ;

In addition to container objects, you can create “openable” objects. These are the same as containers, except that they can be opened and closed. When you create a container, there’s a new property, “isopen”, that specifies whether the object is open or closed. If you want the object to be closed initially, set isopen = nil. Here’s an example.

   toolbox: openable
      sdesc = "tool box"
      location = maintenanceRoom
      noun = 'toolbox' 'box'
      adjective = 'tool'
      isopen = nil
   ;


Fixed Items

You will frequently want to create items in the game that the player can’t pick up and carry around, but that are permanent features of a room. For example, if you create a bedroom, you’ll probably want to have a bed and maybe a dresser in the room. It doesn’t make sense for the player to be able to carry off the furniture.

To create an item that the player can’t carry, create an object of type “fixeditem”. You define fixeditem objects that same way that you define item objects; the only difference is that fixeditem objects can’t be carried off by the player.

Here’s an example.

   bed: fixeditem
      location = bedroom
      noun = 'bed'
      sdesc = "Bed"
   ;


Inheritance and adv.t

Up to now, we’ve been describing the various types of objects that you can create, and the properties that you have to define when creating those objects. It may appear that these object types are somehow built in to TADS. In fact, none of these types are special; they’re all defined using the TADS language.

The objects we’ve seen so far - room, item, container, openable, fixeditem - are defined in the file adv.t. This file is a set of object type definitions that are suitable for most games, but they’re not at all special, so you can take them or leave them as you choose. You could even completely rewrite adv.t if you want your game to behave differently.

Note that you can create objects using the types defined in adv.t (or in your own program) very easily - as though the types were somehow built in to TADS. The trick that makes this possible is called “inheritance”. The TADS language allows you to define an object so that it inherits all of the properties of another object. This is what you’re doing when you create an object of type room: you’re telling TADS that it should create a new object exactly like the object “room” that it’s already seen, except that you want to add some new properties of your own. If you look at adv.t, you’ll see that the definition of the room object is very complex; however, you can create objects of type room without having to know very much about the room object itself - all you need to know is what properties you need to add to the new object.

adv.t defines many more objects than we’ve talked about so far. You should refer to adv.t for complete information on the standard adventure objects, and how to use them. Here’s a brief list.

nestedroom - a room within another room
chairitem - a chair (something the player can sit on)
beditem - a bed (something the player can lie down on)
thing - low-level item class (not normally used directly)
item - a simple item the player can pick up and carry
lightsource - something that provides light
hiddenItem - an object that starts off hidden within another object
hider - low-level hider class (not used directly)
underHider - an object that can have objects hidden under it
behindHider - an object that can have objects hidden behind it
searchHider - an object that can have objects hidden within it
fixeditem - an immovable item
readable - something the player can read
fooditem - something the player can eat
dialItem - something the player can turn to various values
switchItem - something the player can turn on and off
room - a location in the game
darkroom - a location without any lighting of its own
Actor - a character in the game
moveableActor - a character that the player can pick up and carry
follower - a pseudo-object that can be used to follow actors
basicMe - a set of definitions suitable for most player actors
decoration - an item that serves no purpose other than decoration
buttonItem - something the player can push
clothingItem - something the player can wear
obstacle - something that blocks travel
doorway - one side of a door
lockableDoorway - a door that can be locked
vehicle - an item that can be used for travel
surface - an item that can have objects placed upon it
container - an item that can have objects placed within it
openable - a container that can be opened and closed
qcontainer - a container that doesn’t list its contents automatically
lockable - an openable that can be locked and unlocked
keyedLockable - a lockable that needs a key to be locked and unlocked
keyItem - an item that can lock and unlock a keyedLockable
transparentItem - an object whose contents are visible
basicNumObj - definition suitable for numObj in most games
basicStrObj - definition suitable for strObj in most games
deepverb - a verb object
travelVerb - a verb object for travel verbs
sysverb - a “system” verb
Prep - a preposition object

Each object in adv.t is fully described in a comment preceding its definition. The comments tell what properties you need to define when creating an object of each type, and how the object will behave.


Methods

We mentioned earlier that a property can contain a simple value, such as a string or a number, or programming code that defines its behavior. When a property contains code, it is called a “method”. With the exception of vocabulary properties, any property can contain programming code instead of a simple value.

Programming code starts with a left brace (“{”), and ends with a matching right brace (“}”). Within the braces, you can use programming language statements that create local variables, call functions and methods, assign values to properties and variables, and control flow with loops and conditionals. The programming code used in TADS methods looks similar to the C language, but there are some differences.

Here’s an example of an object incorporating a method. In this case, the object defines its ldesc property using a method, rather than a simple double-quoted string value, so that the property can be sensitive to the state of a window.

   attic: room
      sdesc = "Attic"
      ldesc =
      {
         "You're in a large, dusty attic.  Old cobwebs hang
         from the rafters.  At the north end of the room is
         a window, which is ";
         if ( atticWindow.isopen )
            "open";
         else
            "closed";
         ". A ladder leads down. ";
      }
      down = hallway
   ;

The ldesc property displays a message that depends on whether the window is open or closed. For example, if the window is open, the room’s long description would look like this:

You're in a large, dusty attic. Old cobwebs hang from the rafters. At the north end of the room is a window, which is open. A ladder leads down.


Expressions

TADS expressions are entered in algebraic notation. For example, to add two numbers, you would code an expression like this:

  3 + 4

You can use parentheses to change the order of evaluation of the parts of an expression. For example, since multiplication has higher precedence than addition, the expression 3 + 4 * 5 evaluates to 23; however, if you write this as (3 + 4) * 5, its value is 35, because the addition is performed before the multiplication.

If you want to assign a value, you use the operator “:=” (a colon followed immediately by an equals sign, without any intervening spaces). For example, to assign the value nil to the isopen property of the object atticWindow, you’d code this:

  atticWindow.isopen := nil;

The period (or “dot”) operator, “.”, is used to take a property of an object. In the example above, it specifies the isopen property of the object atticWindow. You can use the value of a property in an expression, or assign a new value to a property, using this operator.

Here are the TADS operators, shown in the order of evaluation.


&

Takes the “address” of a function or property.


.

Takes a property of an object: obj.prop evaluates property prop of object obj.


[]

List indexing. If var contains the list [ 5 4 3 2 1 ], then var[ 2 ] is 4, var[ 4 ] is 2, var[ 5 ] is 1, and so on.


++

Increment a variable that contains a number, assigning the incremented value back to the variable. If var contains 5, it will contain 6 after var++ is evaluated.


--

Decrement.


not

Logical negation. not true is nil, and not nil is true.


-

Arithmetic negation (when used as a unary prefix operator).


*

Numeric multiplication: 5 * 6 is 30.


/

Numeric integer division: 30 / 5 is 6. Note that any fractional part of the division is discarded, so 5 / 2 is 2.


+

Numeric addition: 3 + 4 is 7.
String concatenation: 'hello' + 'goodbye' yields 'hellogoodbye'.
List concatenation: [ 1 2] + [ 3 4 ] yields [ 1 2 3 4 ].
List extension: [ 1 2 ] + 3 yields [ 1 2 3 ].


-

Numeric subtraction: 10 - 3 is 7.
List removal: [ 1 2 3 ] - 2 yields [ 1 3 ].


=

Equality comparison; a = b yields true if the value of a is the same as the value of b, nil otherwise. Note that the types of a and b must be suitable for comparison; you can compare two numbers, strings, lists, objects, or truth values (true and nil); but comparing a number to a string is meaningless, and results in a run-time error.


<>

Inequality comparison; a <> b is true if a is not equal to b, nil otherwise.


>

Greater than comparison. You can compare the magnitude of two numbers or two strings.


<

Less than comparison.


>=

Greater than or equal.


<=

Less than or equal.


and

Logical product: a and b is true if both a and b are true, nil otherwise. Note that if a is nil, b is not evaluated, because the expression’s result is nil regardless of the value of b.


or

Logical sum: a or b is true if either a or b is true, or if both are true. Note that if a is true, b is not evaluated.


? :

Conditional operator: cond ? a : b yields the value of a if cond is true, b if cond is nil. Note that only one of a or b is evaluated.


:=

Assignment.


+=

Add and assign: the value of the variable or property on the left has the value of the expression on the right added to it. a += b is equivalent to a := a + b.


-=

Subtract and assign.


*=

Multiply and assign.


/=

Divide and assign.


,

Conjunction: the expression on the left of the comma is evaluated, then the expression on the right is evaluated and used as the value of the entire conjunction expression. So, the value of (a, b) is b.


Self

Within a method, there’s a special pseudo-object called “self” that lets you determine the object whose method is being evaluated. This may sound pretty useless, but consider a situation in which you’re defining an object that inherits from another object.

   book: item
      sdesc =
      {
        "The book is "; self.color; ". ";
      }
   ;

   redbook: book
      color = "red"
   ;

   bluebook: book
      color = "blue"
   ;

Here, the superclass object, book, can define a generic sdesc method which automatically adjusts to the individual subclass objects. When redbook.sdesc is evaluated, the actual sdesc code that is executed is inherited from book; however, since it is redbook.sdesc that is being evaluated, self is set to redbook, so when book.sdesc evaluated self.color, “red” is displayed. Likewise, when bluebook.sdesc is evaluated, self is set to bluebook, so self.color displays “blue”. The classes defined in adv.t make extensive use of this mechanism to allow them to define generic attributes of the classes which automatically customize themselves to the objects you create in your game, even though the authors of adv.t couldn’t possibly have known anything about your objects beforehand.


When do Methods Run?

Before we talk about how to write TADS methods in any more detail, we should look at how the TADS parser works, because it’s the parser that decides what methods to call.

When the player types a command to TADS, the parser breaks the command into a series of words. The parser then looks through its tables of vocabulary words to see what objects are associated with those words; remember that the special properties verb, noun, plural, adjective, article, and preposition associate an object with a set of vocabulary words. Once the parser has determined which objects are involved in the command, it calls a series of methods in those objects; these methods do all the work of processing the command. Since the methods are defined in the objects, you can change almost any aspect of the behavior of a TADS game; however, thanks to inheritance, you don’t have to change anything if you don’t want to - you can use the definitions from adv.t as they are, filling in only the necessary descriptive text and properties.

The command is associated with a set of objects; each object is classified according to its function in the command. The objects are the actor, the verb, an optional set of direct objects, and an optional preposition and indirect object. If the player didn’t direct the command to a different character (for example: “floyd, give me the ball”), the player-actor, Me, is assumed.

The sequence of method calls is shown below. The actual objects making up the command are substituted for the items in <angle brackets>. If one of the objects is not used in the command, the corresponding object parameter will be nil (for example, if there is no direct object, <dobj> will be replaced by nil).

In the list below, you’ll also see something named <verb-prefix>. This is a special string that is defined in the deepverb objects; we’ll discuss this below. If the <verb-prefix> is, for example, 'Take', then the method meant by do<verb-prefix> is doTake.

   <actor> . roomCheck( <verb> )
   <actor> . actorAction( <verb>, <dobj>, <prep>, <iobj> )
   <actor> . location . roomAction( <actor>, <verb>, <dobj>, <prep>, <iobj> )

   If an indirect object was specified:
      <dobj> . verDo<verb-prefix>( <actor>, <iobj> )
      If no output resulted from verDo<verb-prefix>:
         <iobj> . verIo<verb-prefix>( <actor> )
         If no output resulted from verIo<verb-prefix>:
            <iobj> . io<verb-prefix>( <actor>, <dobj> )
   Else if a direct object was specified:
      <dobj> . verDo<verb-prefix>( <actor> )
      If no output resulted from verDo<verb-prefix>:
         <dobj> . do<verb-prefix>( <actor> )
   Else:
      <verb> . action( <actor> )

   Run each daemon that is currently active
   Run and remove each fuse that has burned down to zero turns

The <verb-prefix> is read from the verb object when a direct or indirect object is present. The <verb-prefix> is a string specified by the property doAction when only a direct object is present, or from the appropriate ioAction when both a direct and an indirect object are present. For example, consider this verb object:

   takeVerb: deepverb
      verb = 'take'
      sdesc = "take"
      doAction = 'Take'
      ioAction(outofPrep) = 'TakeOut'
      ioAction(awayPrep) = 'TakeAway'
   ;

If the command is “take ball”, only a direct object is present, so the doAction property is used, and hence the <verb-prefix> is 'Take'. This means that the methods called in the direct object will be verDoTake and doTake. In this case, the sequence of methods that the parser calls would be:

   Me.roomCheck( takeVerb )
   Me.actorAction( takeVerb, ball, nil, nil )
   Me.location.roomAction( Me, takeVerb, ball, nil, nil) 
   ball.verDoTake( Me )
   if no ouput resulted from verDoTake:
      ball.doTake( Me )

If the command is “take ball out of box”, the ioAction property matching the preposition “out of” is used; this is 'TakeOut'. So, the properties called in the objects are verDoTakeOut, verIoTakeOut, and ioTakeOut. In this case, the sequence of methods called by the parser would be:

   Me.roomCheck( takeVerb )
   Me.actorAction( takeVerb, ball, box, outofPrep )
   Me.location.roomAction( Me, takeVerb, ball, box, outofPrep )
   ball.verDoTakeOut( Me, box )
   If no output resulted from verDoTakeOut:
      box.verIoTakeOut( Me )
      If no output resulted from verIoTakeOut:
         box.ioTakeOut( Me, ball )

This sequence may look terribly complicated, but generally you won’t have to customize or even think about very much of it. In almost every case, you’ll be able to achieve the effect you want by customizing the verDo<prefix>, do<prefix>, verIo<prefix>, and io<prefix> methods.

Note that the verDo<prefix> and verIo<prefix> methods are intended to be used to verify that the object can be used in this command, and the do<prefix> and io<prefix> methods are supposed to actually carry out the command. The way this works is that the verDo<prefix> and verIo<prefix> methods should display an error message if the object cannot be used for this command, and should do nothing at all if the object is usable. This may sound like a strange way to decide whether the object is valid, but it makes it extremely convenient to write these methods - all you have to do is display an error message if appropriate during verification.


Programming Statements

Within a method, or within a function, you can use a number of statements that control execution.


local <variable-list> ;

This statement can be used only at the very start of a block of code (that is, immediately following an open brace). The “local” statement defines a list of variables that are private to the block of code; these variables cannot be used outside of the block. Variable names follow the same rules as other identifiers, such as object and property names: they must start with a letter, and consist of letters, numbers, and underscores.

Example:

   	ldesc =
	{
	   local cnt, loc;

           cnt := 0;
           loc := nil;
	}

return <expression> ;

Return to the caller. The <expression> is evaluated, and the result is supplied to the caller as the value of the method or function. Control is returned to the caller at the point immediately following the call to the current method or function; no further statements in the current method or function are executed.


return ;

Return to the caller without providing a value.


if ( <expression> ) <statement1> else <statement2>

Evaluate the <expression>; if the value is not nil or 0, execute <statement1>. Otherwise, execute <statement2>. Note that the entire “else” clause is optional; if it is not provided, and the value of <expression> is nil or 0, execution resumes following <statement1>.

Note that <statement1> and <statement2> can either be a single statement, or can be a series of statements enclosed in braces.

Example:

   	if ( torch.islit )
        {
           "You suddenly realize that the odor was coal gas,
            and that you're carrying a burning torch.  You
            try to retreat, but it's too late; the torch
            ignites the gas, resulting in a terrible explosion. ";
            die();
        }
        else
            "You realize that the odor was coal gas.
             Fortunately, there's no open flame here. ";

switch ( <expression> )
{
  case <constant1>: <statements1>;
  case <constant2>: <statements2>;
  default: <statements3>;
}

This statement allows you to choose a number of execution paths, based on the value of an expression. The <expression> is evaluated, then compared to the various constants at the case labels. If a case label matches the value of the expression, execution resumes at the statement following that case label. If no case label matches the value, and a default label is present, execution resumes following the default. The default label is optional.

Note that execution is not interrupted by hitting another case label, but just continues into the statements following it. Note also that a case label need not have any statements at all following it. If you want to exit the switch statement, you must explicitly code a break statement; this causes execution to resume following the end of the switch statement.

Example:

   	switch( x )
        {
	case 1:
	case 2:
	   "x is 1 or 2";
	   break;
	case 3:
	   "x is 3";
           /* note the lack of a break, so we fall through to next case */
	case 4:
	   "x is 3 or 4";
	   break;
	default:
	   "x is 5 or more";
        }

while ( <expression> ) <statement>

Execute <statement> repeatedly as long as the value <expression> is not nil or 0. The <expression> is evaluated before each execution of <statement>, so if the value of <expression> is nil or 0 before the first execution of the loop, the <statement> is not executed at all. As with the if statement, the <statement> can be either a single statement, or a block of statements enclosed in braces.

Example:

   	while ( cnt < length( lst ) )
        {
	   "The next item is: "; say( lst[ cnt ] ); "\n";
           ++cnt;
        }

do <statement> while ( <expression> );

This statement is similar to the while statement, but evaluates <expression> after each iteration of the loop. Thus, the <statement> is always executed at least once, since the <expression> is not tested for the first time until after the first iteration of the loop.

Example:

   	do
        {
	   "cnt = "; say( cnt ); "\n";
           --cnt;
	} while ( cnt > 0 );

for ( <init-expr> ; <cond-expr> ; <reinit-expr> ) <statement>

This is a more general form of loop. First, the <init-expr> is evaluated; this is done only once, before the loop starts iterating. For each iteration of the loop, TADS first evaluates the <cond-expr>. If it is nil or 0, the loop terminates; otherwise, <statement> is executed. Finally, <reinit-expr> is evaluted. Note that any of the expressions may be omitted; if the <cond-expr> is not present, it is as though the expression were always true.

The for statement is often more convenient to code, but it is always possible to write an equivalent loop using the while statement.

Example:

   	for ( cnt := 1 ; cnt < length( lst ) ; ++cnt )
        {
           "the next element is: "; say( lst[ cnt ] ); "\n";
        }

break;

Exits from a while, do-while, or for loop, or from a switch statement. Control immediately resumes following the end of the loop or switch. The break statement is useful when the condition for exiting a loop is most conveniently calculated somewhere in the middle of the loop’s processing.


continue;

Returns to the beginning of a while, do-while, or for loop. The statements following the continue statement are skipped for this iteration of the loop. In a for loop, the <reinit-expr> is evaluated after a continue statement.


pass <property-name>;

When a method is executing, and the method has overridden a method in a superclass object, the pass statement will cause control to be passed to the superclass method that the current method overrides. Note that execution never returns to the current method after a pass statement. Note also that the <property-name> must match the name of the current property.


exit;

Terminate all processing for the current player command, and skip everything up to the daemons and fuses. This statement is used when the current method has done everything necessary to finish the command, and no further processing is desirable.


abort;

Terminate all processing for the current player command, skip the daemons and fuses, and go on to the next command. This statement is normally used by “system” verbs, such as “save” and “restore”, to prevent any time from passing (time in the game is handled by the fuses and daemons).


askdo;

Abort the current command, and ask the player to specify a direct object.


askio( <prep-object> );

Abort the current command, supply <prep-object> as the preposition for the command, and ask the player to specify an indirect object.


Built-in Functions

TADS has a number of built-in functions that you can call from your game program. Some of these functions simply provide useful utility functions, while others affect execution of the game. A brief description of each function is shown below.

askfile( prompt )

asks the player for a filename; “prompt” is a string (single-quoted) that specifies a prompt to display to the player. Where appropriate, the standard system file selector is used to ask the player for the file.


caps()

forces the next character displayed to be capitalized.


car( list )

returns the first element of a list. For example: car( [ 1 2 3 ] ) returns 1.


cdr( list )

returns a list with its first element removed. For example: cdr( [ 1 2 3 ] ) returns [ 2 3 ].


cvtnum( str )

converts a (single-quoted) string containing the textual representation of a number to a numeric value. For example, cvtnum('1234') returns 1234.


cvtstr( num )

converts a number to a string containing a textual representation of the number. For example, cvtstr( 100 ) returns '100'.


datatype( val )

returns the datatype of the “val” (after evaluating the value). The return values are:

1 - number
2 - object
3 - string
5 - nil
7 - list
8 - true
10 - function pointer
13 - property pointer


defined( obj, &prop )

returns true if the object “obj” defines or inherits property “prop”, nil otherwise.


find( value, target )

returns the offset of “target” within “value”. If “value” is a list, this function returns the index of the “target” within the list, or nil if it does not occur in the list. For example, find([5 4 3], 4) returns 2. If “value” is a string, this function returns the offset of the substring “target”, or nil if there is no such substring. For example, find('abcdef', 'cde') returns 3.


firstobj()

begins a loop over all non-class objects in a game. Returns an object. See nextobj( obj ).


firstobj( cls )

begins a loop over all non-class objects with superclass “cls”. See nextobj( obj, cls ).


getarg( num )

return the value of argument number “num” to the current method or function.


incturn()

increments the turn counter, which moves all fuses one turn closer to firing. Normally, this function is called by a daemon function once per turn.


input()

allows the user to enter a line of text, and returns the value as a string.


isclass( obj, cls )

returns true if “obj” inherits from superclass “cls”, nil otherwise.


length( val )

returns the number of characters in a string, or the number of elements in a list.


logging( val )

if “val” is a string, creates the file named by the string, and logs all text displayed on the screen to the file. If “val” is nil, closes the current log file and stops logging.


lower( str )

returns a string consisting of all of the characters of “str” converted to lower case.


nextobj( obj )

returns the object following “obj”, in an arbitrary order, or nil if the last object has been reached. Every non-class object in the game will be returned exactly once by a loop such as this:

   local obj;
   for ( obj := firstobj() ; obj ; obj := nextobj( obj ) ) /* ... */;

nextobj( obj, cls )

returns the next object following “obj” with superclass “cls”, or nil if the last such object has been reached. Every non-class object in the game with superclass cls will be returned by a loop such as this:

   local obj;
   for ( obj := firstobj( cls ) ; obj ; obj := nextobj( obj, cls ) ) /* ... */;


notify( obj, &prop, turns )

establish a notifier. The property “prop” of object “obj” (i.e., obj.prop) is called once after the number of turns specified by “turns” has elapsed. If “turns” is zero, obj.prop is called after every turn.

Up to 200 notifiers can be simultaneously active.


proptype(obj, &prop)

returns the type of property “prop” in object “obj”, without evaluating the property. If the property contains method code, the code is not called by this function, so the return type of the property cannot be determined; instead, proptype simply returns 6, to indicate that the property contains method code. The return values are:

1 - number
2 - object
3 - single-quoted string
5 - nil
6 - code
7 - list
8 - true
9 - double-quoted string
10 - function pointer
13 - property pointer


quit()

end the game.


rand( lim )

return a random number from 1 to “lim”, inclusive.


randomize()

seed the random number generator with a value chosen by the system. This function is called only once, at the beginning of the game, to choose a random number seed. If this function is not called, the sequence of numbers returned by rand() is the same each time the game is played.


remdaemon( function, value )

remove a daemon previously set by the setdaemon() built-in function. The daemon set with the same values of “function” and “value” will be removed; if no such daemon is found, a run-time error is reported.


remfuse( function, value )

remove a fuse previously set by the setfuse() built-in function.


restart()

start the game over from the beginning.


restore( filename )

restore the game position stored in the file named by the string “filename”.


save( filename )

save the current game position to the file named by the string “filename”.


say( value )

display the value, which can be a single-quoted string or a number.


setdaemon( function, value )

set a daemon. “function” will be called once after every turn, with “value” as its argument. “value” is an arbitrary value that is provided to allow you to pass any information to the daemon that it may need. The daemon function should be defined prior to its use in setdaemon().


setfuse( function, time, value )

set a fuse. “function” will be called after the number of turns specified in “time” has elapsed. “value” is an arbitrary value that is passed to the function as its argument; you can specify any value that you find useful here.


setit( obj )

set “it” to the specified object. This changes the object that the player refers to with the word “it”.


setscore( score, turns )

set the score displayed on the status line. “score” and “turns” are numbers.


setscore( str )

specify an arbitrary single-quoted string that will be displayed on the status line in place of the normal score/turns indicator.


substr( str, ofs, len )

returns the substring of “str” starting at offset “ofs” and going for “len” characters. For example, substr('abcdef', 3, 2) returns ‘cd’.


undo()

undoes one turn.


unnotify( obj, &prop )

remove the notifier previously set on obj.prop. If there is no such notifier, a run-time error is reported.


upper( str )

returns the string “str” with its characters converted to upper-case.


yorn()

waits for the player to answer YES or NO. Returns 1 if the player typed YES, 0 if the player typed NO, and -1 if the player typed anything else. Note that only the first character of the player’s response is checked, and the response can be upper- or lower-case.

Note that some interpreters will accept a localized version of the responses, suitable for the system's local language settings, in addition to the English Yes/No responses. This is intended to be more intuitive for users, since some people might unconsciously use their own language for a Yes/No response.

If you're writing a game in a non-English language, you might prefer to use your own custom function (using input() to read a response, then parsing it yourself with the regular expression functions, for example) instead of yorn(). This will give you control over the parsing, so that you can ensure that responses in your game's own language are understood properly.


Where to go from here

There’s a lot more to TADS than we are able to describe here. To learn more, you can start by looking at the example game “Ditch Day Drifter” that we’ve included with the TADS software distribution. While this game doesn’t do everything that you can do with TADS (in fact, since TADS is so flexible, it doesn’t even really scratch the surface), it does contain examples of many common adventure game situations that may help you in designing your own game.

For full details on TADS, you need only read further into TADS 2 Author’s Manual. The rest of the manual includes:


Table of Contents Licence and Copyright Information