The TADS Alternate Library
Version 2.0
Messages and Classes
Copyright 2000 by Kevin Forchione.
This is part of the TADS Alternate Library Authors Manual.
Introduction and Table of Contents
TADS 2 Object-oriented approach
TADS is a very
object-oriented programming language. Although lacking both encapsulation and polymorphism
it does employ a sophisticated system of multiple inheritance. Games are constructed of objects that
are constantly communicating with one another and the parser via messages.
Messages
Objects communicate with one
another by sending messages. Each message can result in a reply, in the form of
a return value. The basic formats for messages are:
objectname.messagename
objectname.message(arg1,
arg2, …);
For example, when a player
types <<look>> the player object’s location is sent the following
command:
actor.location.lookAround(true);
The lookAround() method, in
turn, sends a message to the actor.location.nrmLkAround(), which in turn sends
a message to actor.location.lDesc. In VENTURE the forestClearing lDesc is as
follows:
lDesc =
"This small clearing is covered in a thick bed of grasses
and
heavy frond leaves of the trees that form a near-
impenetrable barrier encircling it. The air is heavy and
sweltering, seemingly drawing out swarms of tiny blue-bottle
flies that dart about drawn to the rotting
vegetation."
The lDesc property defines a
set of rules for handling the message being sent. In this example nothing is
returned from lDesc. Instead the string is evaluated and sent to the text
formatter, which displays the room’s long description.
Classes
Classes are templates for
objects. Each object is an instance of a class. Even the library is built
largely of classes. The Alt library expands upon the basic class library of
ADV.T. A class consists of attributes and methods. These distinctions can be a
little blurred in TADS, because of its lack of data-typing. For instance:
lDesc =
{"This small clearing is covered in a thick bed of grasses
and heavy
frond leaves of the trees that form a near-
impenetrable barrier encircling it. The air is heavy and
sweltering, seemingly drawing out swarms of tiny blue-bottle
flies
that dart about drawn to the rotting vegetation.";}
is clearly a method, but in
the previous example lDesc is defined as an attribute.
We’ve seen a couple of
examples of object definitions. In TADS there is very little distinction
between an object definition and a class definition. Class definitions are
ignored by the parser and such built-in functions as firstobj() and nextobj().
The class keyword is required only when an author wishes that a definition be
treated like a class and not an object by the parser and the definition contains
a location property or vocabulary.
The basic format for a class
is:
class classname: classlist
properties
;
The class keyword
differentiates the definition from an object definition. The classlist can
consist of any number of classnames separated by commas. A class that does not
inherit from any other classes must inherit from object. Properties
consist of attributes and methods, and are optional – a class can be defined
with no properties at all, as demonstrated by the Alt Room class definition.
class Room: Roomable, Surface
;
In this example Room doesn’t
define any properties of its own, but inherits those of Roomable and Surface
classes.
Properties are further
divided into attributes and methods. Because TADS doesn’t enforce datatyping,
however, the distinction between an attribute and a method can become a little
blurred. For instance, the following is an attribute:
lDesc = ‘hello’
The value of lDesc has been initialised
as a single-quoted string. While the next example is a method:
lDesc = { “This is a method.”; }
But what about this?
lDesc = “%You% see%s% nothing unusual about
<<self.theDesc>>.”
Assigning a double-quoted
string appears to put the property into the attribute category, but
double-quoted strings aren’t values in TADS, instead the string is evaluated,
and the output is sent to the text formatter. This means that double-quoted
strings are shorthand for code blocks that in essence say:
lDesc = {
say(‘%You% sees%s% nothing unusual about ‘);
self.theDesc;
say( ‘.’ );
}
Alt adopts Java-style naming rules for classes, attributes,
methods and functions. See Coding
Conventions.
The parser is sending
messages to objects all the time. But besides messages sent from the parser,
objects can send messages to each other. This can be very useful when you wish
to associate one event with another. For example, somewhere in our forest
clearing there is an ominous-looking raven perched on the limb of a gnarled old
tree.
gnarledTree: FixedItem
location
= forestClearing
noun =
'tree'
adjective
= 'gnarled' 'old' 'very'
sDesc =
"gnarled old tree"
lDesc = {
"It looks as though it's been there for hundreds of years. ";
if
(raven.isIn(self))
"\^<<raven.aDesc>> <<raven.isDesc>>
<<raven.posture.desc>>
high up in one of its branches.";
}
initial =
"At the edge of the clearing stands a very old gnarled
tree.
"
;
The tree isn’t really a
Surface or a Container, it’s merely a FixedItem, which means it can’t be taken,
moved, thrown, or put anywhere. Additionally it doesn’t appear in room
descriptions, but the initial attribute is used to display a suitable
description. When the player examines the tree closer using <<look
at>> or <<examine>> they will find that a raven is perching
in one of its branches.
lDesc = {
"It looks as though it's been there for hundreds of years. ";
if
(raven.isIn(self))
"\^<<raven.aDesc>> <<raven.isDesc>>
<<raven.posture.desc>>
high up in one of its branches.";
}
Our raven is described using
the Alt description attributes, aDesc, isDesc, and posture.desc. These allow an
author to generate generalised descriptions for classes of objects.
Now for our raven:
raven: Animate
location
= gnarledTree
noun =
'raven'
adjective
= 'ominous' 'ominous-looking'
sDesc =
"raven"
lDesc =
"It's an ominous-looking raven! It seems to be watching
you!"
actorDesc
= "An ominous looking raven is here, watching you
intently."
squawk(utterance) = {
if
(self.isIn(gLocation(sound)))
"\bSquawk <<self.theDesc>>, \"<<utterance>>
<<utterance>>\"";
}
posture =
perching
;
The raven is an Animate
object, which means that it simulates behaviours of life. In addition to an
lDesc it possesses an actorDesc, which is displayed whenever the raven is directly
contained within the room. Because it’s location is the gnarledTree we won’t
see this description when we first encounter the bird, but it’s a good idea to
code an actorDesc anyway, to add atmosphere to the object.
Our raven overrides the
Animate posture attribute, which is standing by default. Since Alt
doesn’t define perching as a posture (see Object
Postures) so we must define it in our game code.
perching: Posture
desc =
"perching"
;
Notice too that our raven
defines a squawk() method, which is a new method defined to allow the bird to
make utterances from time to time.
We can then add something
like the following to our redBerry dobjPostAction rules:
case dropVerb:
"The berry drops to the ground, battered slightly. ";
raven.squawk('Nevermore!');
return true;
Now when we drop the berry
the raven utters its prophetic statement.
>drop berry
The berry drops to the ground, battered slightly.
Squawk the raven, "Nevermore! Nevermore!"
This same method can be sent
other single-quoted strings as arguments for generating responses to other
actions. Since only the raven defines a squawk() method it would be an error to
send a squawk message to any other game object. However, Alt’s class
inheritance mechanism allows an author to define a squawk() method at a class
level, which can then be inherited by instances of the class.
One extremely useful
property of classes is inheritance. TADS allows for multiple inheritance, and
the Alt library takes advantage of this feature. TADS inheritance mechanism
offers great flexibility and control in your object definitions.
To illustrate, suppose we
define a Bird class, from which raven is an instance.
class Bird: Animate
posture = perching
squawk(utterance) = {
if (self.isIn(gLocation(sound)))
“\^<<self.theDesc>>
squawks, \”<<utterance>> <<utterance>>\””;
}
;
Our raven could then be
defined as:
raven: Bird
location
= gnarledTree
noun =
'raven'
adjective
= 'ominous' 'ominous-looking'
sDesc =
"raven"
lDesc =
"It's an ominous-looking raven! It seems to be watching
you!"
actorDesc
= "An ominous looking raven is here, watching you
intently."
squawk(utterance) = {
if
(self.isIn(gLocation(sound)))
"\bSquawk <<self.theDesc>>,
\"<<utterance>> <<utterance>>\"";
}
;
There is no need to override
the posture attribute for raven, since it inherits a posture of perching
from Bird. We would, however, like to override the squawk() method to tailor it
to our raven.
TADS provides two very
useful means of passing control to superclasses. The first is the pass statement,
the second is the inherited statement. Alt library takes full advantage
of these two statements in its class definitions.
class Treasure
points = 10
dobjPostAction = {