Object Definitions

TADS programs are made mainly of objects.

 

An "object" is a data structure that groups together a set of data values, called properties, and functions, called methods.  Each property and each method has a name, which is a symbol that you can use to refer to the property or method from within the program.  An object also has one or more "superclasses" (also called "base" classes); the superclasses provide defaults for any properties and methods that the object doesn't itself define but one or more of the superclasses do.

 

An object can be either "static" or "dynamic."  A static object is one that is defined directly in your program's source code; it exists throughout execution of the program.  A dynamic object is one that is created in the course of the program's execution with the "new" operator; it comes into existence when created with "new," and exists only as long as it is referenced by any local variables or properties.  Once a dynamic object is no longer referenced anywhere in the executing program, the system automatically deletes the object, since it can no longer be used for anything.

 

Most TADS programs define numerous static objects, because these objects encode the game world that the program implements.  An object can represent a real-world object that the game simulates, or a component of a real-world object, or an entirely abstract programmatic entity.

Basic Object Definition Syntax

The most general way to define an object is like this:

 

   objectName : class1, class2, class3
      prop1 = val1
      prop2 = val2
      method1 ( arg1, arg2 ) { methodBody1 }
      method2 ( arg1, arg2, arg3 ) { methodBody2 }
   ;

 

The first line names the object, and defines its superclass list.  An object must always have at least one superclass, but you can use the special class name "object" if you want a generic object that is not based on another object that your program defines.  Note, however, that if you use "object" as the superclass, it must be the only superclass.

 

If you specify more than one superclass, the order of the classes determines the inheritance order.  The first (left-most) superclass has precedence for inheritance, so any properties or methods that it defines effectively override the same properties and methods defined in subsequent superclasses.

Class Definition Syntax

You can define a class instead of an object by adding the keyword "class" before the object name:

 

   class objectName : class1, class2, ... ;

 

A class definition is otherwise syntactically identical to an ordinary object definition.

 

Classes behave very much like objects, with a few important differences:

 

Anonymous Objects

In many cases, you will not have any need to give an object a name when you define it.  The compiler accommodates these cases with the "anonymous object" syntax.

 

To define an object with no name, simply start the definition with the class list.  Everything else about the object definition is the same as for a named object.  For example:

 

  Item sdesc = "red box" ;

 

Because an anonymous object doesn't have a symbol that you can use to refer to the object, you must use some other mechanism to manipulate the object.  For example, you can use the firstObj() and nextObj() functions, since iterations with these functions include anonymous objects.

Contained Objects

Most TADS games have some sort of "containment" model that relates objects to one another in a location hierarchy.  In these models, each object has a container; containers may in turn be inside other containers.

 

The TADS 3 compiler can keep track of a simple containment hierarchy that gives each object a single container.  This is an optional feature, so games that use more complex containment models than the compiler provides do not have to use this feature; however, games that use a single-container location model can take advantage of the compiler's location tracking mechanism to simplify object definitions.

 

To use the compiler's location tracking, you must tell the compiler which property you are using to specify an object's container.  This is called the "+ property" or "plus property," because the object syntax for a contained object uses plus signs.  To define the plus property, use this statement:

 

  + property locationProp ;

 

This statement must occur as a top-level statement, outside of any object or function definitions, and must precede any objects that make use of the containment syntax.  If you are using this feature, you should put this statement at the start of your source files.  (You must put this statement in each source file if you're using separate compilation.)

 

Once you specify the plus property, the compiler keeps track of objects that define the property.  Whenever an object defines the property and gives it a constant object value, the compiler remembers the object as the current container.

 

Each container has a containment depth, which reflects how deeply the object is nested within other containers.  If an object has no container, its containment depth is 0; this is a "top-level" or "outermost" object in the containment hierarchy.  (In text adventure terms, an outermost object is usually a "room," because there is nothing outside of an outermost object in the game world.)  If an object has a container, and that container is an outermost object, the containment depth is 1.  If an object has a container, which in turn has a container which is an outermost object, the containment depth is 2.

 

The current container changes after every object definition:

 

 

This all looks a great deal more complicated than it really is, as the examples below will show.

 

The compiler keeps track of this information purely for the purposes of the "+" syntax, which lets you define an object as inside another object using a very concise notation.  To specify an object's container, simply place one plus sign before the object definition for each level of containment depth you wish the object to have.  If you want to place the object inside the current outermost container, use a single plus sign:

 

// define the '+' property
// (we need this only once per source file)
+ property location;
 
iceCave: Room
   sdesc = "Ice Cave"
;
 
+ nastyKnife: Item
   sdesc = "nasty knife"
;
 
+ rustyKnife: Item
   sdesc = "rusty knife"
;

 

The definition of the iceCave object establishes iceCave as the current container.  Since this object does not use any plus signs in its definition and also does not have a "location" property, it is an outermost container, so after the iceCave definition, the containment depth is zero.

 

The next definition, nastyKnife, uses one plus sign, which means that it is at containment depth one, which means that it's in the current container at containment depth zero.  This means that its location is iceCave.  The compiler automatically sets the object's "location" property (which is special to the compiler only because of the "+ property location" statement—you could use a different property name if you prefer) to refer to the iceCave object.

 

After the nastyKnife definition, the current container becomes nastyKnife, and the containment depth becomes one (because nastyKnife is within iceCave).

 

The next definition, rustyKnife, once again uses a single plus sign.  Our current container is nastyKnife, but this doesn't mean that rustyKnife goes inside nastyKnife.  Instead, because the current containment depth is one, and we want a container at depth zero, the compiler looks to nastyKnife's container.  Thus, rustyKnife goes inside iceCave.

 

Note that the compiler does not keep track of different levels separately.  In other words, once nastyKnife is defined, the compiler has completely forgotten about iceCave.  The only reason that rustyKnife ends up inside iceCave is that iceCave is the level-zero container of the current container, nastyKnife.  Consider this:

 
jewelCave: Room
   sdesc = "Jewel Cave"
;
 
iceCave: Room
   sdesc = "Ice Cave"
;
 
axe: Item
   sdesc = "axe"
   location = jewelCave
;
 
+ glacier: Item
   sdesc = "glacier"
;

 

On first glance, one might expect that the glacier would end up inside the iceCave, but this is not the case.  Here's why: after the iceCave definition, iceCave is the current container.  Then comes the axe, which establishes axe as the current container at depth one.  So, by the time we get to glacier, the current container is axe; the compiler has forgotten all about iceCave.  The glacier definition indicates that it's at depth one (one plus sign), which means that it goes inside a level zero container; the current container is at depth one, so the compiler must look to its container.  The container of axe is jewelCave.  Hence, the compiler puts the glacier inside jewelCave.  So, when using the plus syntax, be sure that you keep groups of related objects together.

 

You can use the plus syntax to any depth.  Here's an example with several levels of containers; it is left as an exercise to the reader to verify that the containment hierarchy is as you'd expect for the scenario.

 

office: Room
    sdesc = "Office"
;
 
+ desk: Surface
    sdesc = "desk"
;
 
++ fileBox: Container
    sdesc = "file box"
;
 
+++ greenFile: Container
    sdesc = "green file folder"
;
 
++++ letter: Readable
    sdesc = "letter"
;
 
++++ memo: Readable
    sdesc = "memo"
;
 
+++ redFile: Container
    sdesc = "red file folder"
;
 
++ pen: Item
    sdesc = "pen"
;
 
+ chair: Chair
    sdesc = "chair"
;

Anonymous Contained Objects

You can combine the anonymous object syntax and the contained object syntax for an especially concise way of defining objects.  We could rewrite the example above much more compactly:

 

office: Room
    sdesc = "Office"
;
 
+ Surface sdesc = "desk" ;
 
++ Container sdesc = "file box" ;
 
+++ Container sdesc = "green file folder" ;
++++ Readable sdesc = "letter" ;
++++ Readable sdesc = "memo" ;
 
+++ Container sdesc = "red file folder" ;
 
++ Item sdesc = "pen" ;
 
+ Chair sdesc = "chair" ;

Object Templates

In addition to the generic property list syntax, the TADS compiler provides an alternative property definition syntax using object templates.  An object template lets you define an object's properties positionally, rather than by naming each property explicitly in the object definition.  Templates provide a concise syntax for defining properties that you use frequently.

 

To define objects using templates, you must first define the templates themselves.  You define a template using the object template statement:

 

object template item1 item2 ... ;

 

Each item in the list is a placeholder for a property; it specifies the name of the property to assign to the position, and how you will write the property value.  Each item in a template can be written in one of these formats:

 

 

Each item is written as an example of how you will supply the item's value in each object, with the item's property name taking the place of the actual value.  For a single-quoted string, write the property name in single quotes; for a double-quoted string, write the property name in double quotes; for a list, write the property name in square brackets; and when you use an operator, write the operator and then the property name.

 

One thing you cannot use in an object template is a dictionary property.  Dictionary properties are excluded because of the special syntax they use (a dictionary property can have its value set to a list of single-quoted strings, without any grouping brackets for the list).  If you could use a dictionary property in a template, it would be possible to create ambiguous templates, because the compiler might not be able to tell if a single-quoted string were meant to be another entry in the same property list or a separate property in the template.

 

For example, here's a template that specifies three properties: the first is the location, which is marked with an "at" sign; the second is the short description in double quotes; and the third is the long description in double quotes.

 

object template @location "sdesc" "ldesc";

 

Once you define a template, you can use it in object definitions.

 

To use a template, simply put the data definitions for the template's items before the object definition's property list, immediately after the object's class list.  For example, to use the template above, we could write this:

 

poemBook: Book @schoolDesk "poem book" 
   "It's a book of poems. "
   readPoem(num)
   {
      if (num == 1) ; // etc
   }
   poem1 = "The first poem is by someone named Wadswurth. "
;


Note that you don't have to put any properties after the template data for the object, but if you do, you define them using exactly the same syntax that you use for a non-template object.

 

Templates don't have names, and aren't associated with classes.  The compiler figures out which template you want to use purely by the syntax of the object definition.  For the example above, the compiler sees that you want to use template data consisting of a value with the "@" operator followed by two double-quoted strings; the compiler scans its list of templates and comes up with the template we defined earlier.  This means that you must take care not to define two identical templates, because the compiler will not be able to tell them apart.  If you do define identical templates, the compiler will always use the one defined first.

 

You can use templates with anonymous objects, as well as with objects that use the "+" containment specification syntax:

 

+ Container "back-pack" "It's a green back-pack. " ;
++ Item "puzzle cube" "You haven't seen one of these in year. ";

 

The scope of a template is limited to a single source file.  If you are separating your program into several source files, each file must separately define the templates it uses.  The easiest way to define templates in several files is to put the "object template" statements into a header file, and then include the header in each file; this way, you only have to write the templates once, and if you modify them later, you only need to make changes in one place.

 

Object template statements must appear as top-level statements, outside of any function or object definitions.  A template can only be used after it has been defined, so you should normally define your templates near the start of each source file.