The TADS Alternate Library
Version 2.0
Getting Started
Copyright 2000 by Kevin Forchione.
This is part of the TADS Alternate Library Authors Manual.
Introduction and Table of Contents
Building A Sample Game
It has been said that the
best way to learn is by example. In this chapter we begin building a sample
game that will serve to illustrate several of the features of the Alt library.
First, open a text-editor
and copy the following, saving the file as ASCII text. Name the file venture.t
and place it in a folder named Venture.
#include <stdif.h>
modify story
startingLocation = forestClearing
title =
"VENTURE"
headline = "An Interactive Worked
Example\n
Copyright (c) 2000 by Kevin Forchione\n"
introduction = "\b\b\b\bDays of cutting through impenetrable
underbrush have brought you to the brink of exhaustion. At last
your efforts
bear fruit as you stumble upon the remnants of a
lost
civilisation."
;
forestClearing: Room
sDesc =
"Forest Clearing"
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."
;
Because Alt supports HTML
status and prompt by default there is no need to #define USE_HTML_STATUS and
USE_HTML_PROMPT at the beginning of your game file. If you wish to compile your
game with non-HTML support then you will need to modify story.initCommon() and
remove the #defines from the stdif header file.
Compile this file using the
TADS workbench. You will need to set the #include paths in the workbench: Build
-> settings, then choose the include tab. Using the Add button include the
following directories:
alt
alt\classes
alt\functions
alt\grammar
alt\objects
The full path names will
depend upon your system, and where you put the Alt library folder. Once you’ve
applied these additional include libraries then you’ll be ready to compile and
run.
Days of cutting through impenetrable underbrush have
brought you to the brink of exhaustion. At last your efforts bear fruit as you
stumble upon the remnants of a lost civilisation.
An Interactive Worked Example
Copyright (c) 2000 by Kevin Forchione
Release 1 / Serial number 000330
Alt Library Version 1.0.2
Forest Clearing
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.
>i
You are empty-handed.
>north
You can't go that way.
>wait
Time passes...
>quit
In a total of 15 minutes, you have achieved a score
of 0 points out of a possible 100.
Do you really want to quit? (YES or NO) > yes
The release number defaults
to 1, but is easily changed by modifying the story objects release attribute:
modify
story
release
= 2
;
Version information is automatically
generated at compile time and consists of the compilation date in the format
YYMMDD.
Next we’ll add an object to
our forestClearing simply by coding the following after the forestClearing
definition.
redBerry: FoodItem
location
= forestClearing
noun =
'berry'
adjective
= 'red'
sDesc =
"red berry"
;
The redBerry is an instance
of FoodItem class, which means that it inherits methods that allow an actor to
eat it. The berry has a location attribute that indicates that it is to be
found in the forestClearing; an sDesc (short description) that is used to
describe the berry in listings; as well as noun and adjective vocabulary
attributes, which allow the berry to be referenced by the parser as part of a
player command.
The room description now
displays the line “You see a red berry here.” after the room’s lDesc (long
description). We can embellish this by replacing the above with the following
definition.
redBerry: FoodItem
location
= forestClearing
noun =
'berry'
adjective
= 'red'
sDesc =
"red berry"
initial =
"A red berry hangs suspended from a vine
like
a single drop of blood."
;
Our redBerry definition now
includes an initial attribute. The initial attribute is displayed in room
descriptions before the object has been taken or moved. Our room description
now appears as follows:
Forest Clearing
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.
A red
berry hangs suspended from a vine like a single drop of blood.
But our berry simply displays
“It looks like an ordinary red berry to me.” when we examine or look at it. To
change this response we need to give our definition an lDesc (long
description).
redBerry: FoodItem
location
= forestClearing
noun =
'berry'
adjective
= 'red'
sDesc =
"red berry"
initial =
"A red berry hangs suspended from a vine
like
a single drop of blood."
lDesc =
"The berry gleams darkly ruby-red, leaving you
to
wonder what it is you seem to have forgotten about
this particular
fruit. "
;
Now our berry has taken on a
slightly sinister aspect. But FoodItem class objects allow an actor to eat an
instance of its class without reticence.
>take berry
Taken.
>x berry
The berry gleams darkly ruby-red, leaving you to
wonder what it is you seem to have forgotten about this species.
>eat berry
That was delicious!
Suppose we want to make
special comment concerning the taking of the berry, rather than the default
“Taken.” display. Alt implements postAction methods that allow you to replace
the default display without having to modify the class’ doEat() method. To
illustrate, we’ll vary the <<take berry>> and <<drop
berry>> messages using the dobjPostAction method, one of Alt’s object
reaction methods.
redBerry: FoodItem
location
= forestClearing
noun =
'berry'
adjective
= 'red'
sDesc =
"red berry"
initial =
"A red berry hangs suspended from a vine
like
a single drop of blood."
lDesc =
"The berry gleams darkly ruby-red, leaving you
to wonder
what it is you seem to have forgotten about
this
particular fruit. "
dobjPostAction = {
switch(gVerb()) {
case takeVerb:
if (self.hasMovedCount == 1)
"You pick the berry, neatly cleaving it from the
vine.
";
else
"You pick up the slowly-decaying berry.";
return true;
case dropVerb:
"The berry drops to the ground, battered slightly. ";
return true;
}
}
;
The first thing to notice
about dobjPostAction is its name. This method is called during the postAction()
stage of Command Execution and will be called for redBerry when it is the
direct object of a command and the preAction and action stages of Command
Execution have been successful.
The second thing to note is
that dobjPostAction takes no arguments. In order to limit the types of commands
we want dobjPostAction to consider we need to use the global command variable functions.
In this case we simply check gVerb() to see if the verb is takeVerb or
dropVerb.
There are two
special attributes that are automatically maintained each time an object is
moved: hasMoved and hasMovedCount. The first is a Boolean indicator of whether
the object has moved or not. The second counts the number of times an object
has moved. This allows an author to vary the message for <<take
berry>> based on whether it’s the first time or not.
Finally, dobjPostAction
returns true for both takeVerb and dropVerb, indicating that the displayed
message is to override any prcediing messages resulting from the take or drop
action. If dobjPostAction doesn’t return true then any default library messages
are first displayed, followed by any messages produced in dobjPostAction.
>take berry
You pick the berry, neatly cleaving it from the
vine.
>drop it
The berry drops to the ground, battered slightly.
Suppose we now wish to have
a response to the player command <<eat berry>>, making the berry
poisonous some percentage of the time and harmless the rest of the time. Again,
we could directly override the doEat() method on berry, or we can choose to
employ the object reaction’s dobjPreAction() method.
redBerry: FoodItem
location
= forestClearing
noun =
'berry'
adjective
= 'red'
sDesc =
"red berry"
initial =
"A red berry hangs suspended from a vine
like
a single drop of blood."
lDesc =
"The berry gleams darkly ruby-red, leaving you
to
wonder what it is you seem to have forgotten about
this
particular fruit. "
dobjPreAction = {
local
r;
randomize;
switch(gVerb()) {
case tasteVerb:
"You extend your tongue tentatively\n";
return nil;
case eatVerb:
r = _rand(100);
if (r <= 30) {
"The tinniest nibble is enough. It was a poisonous
berry after all. ";
story.deathMessage(nil);
} else
"You nibble at the berry, but the curious taste
repels you. ";
return true;
}
}
dobjPostAction = {
switch(gVerb()) {
case takeVerb:
if (self.hasMovedCount == 1)
"You pick the berry, neatly cleaving it from the vine. ";
else
"You pick up the slowly-decaying berry.";
return true;
case dropVerb:
"The berry drops to the ground, battered slightly. ";
return true;
}
}
;
The dobjPreAction
has a switch statement that checks for tasteVerb and eatVerb. Because the logic
for the taseVerb case returns nil preAction processing is not terminated, and
instead control continues to the action stage of Command Execution.
>taste berry
You extend your tongue tentatively
You taste nothing unexpected.
Eating the berry,
unfortunately, has fatal results.
>eat berry
The tinniest nibble is enough. It was a poisonous
berry after all.
*** You have died ***
In a total of 0 minutes, you have achieved a score
of 0 points out of a possible 100.
You may restore a saved game, start over, quit, or
undo the current command.
Please enter RESTORE, RESTART, QUIT, or UNDO: >
In fact, you’ll
notice, if you choose UNDO that eating the berry always results in death for
the player. This is because the random number generator hasn’t been properly
randomised. To do this we add the following code to our story object.
modify story
startingLocation = forestClearing
title =
"VENTURE"
headline
= "An Interactive Worked Example\n
Copyright (c) 2000 by Kevin Forchione\n"
introduction = "\b\b\b\bDays of cutting through impenetrable
underbrush have brought you to the brink of exhaustion. At last
your
efforts bear fruit as you stumble upon the remnants of a
lost
civilisation."
initDaemon = {
randomize;
notify(queue, &processQueue, 0);
setdaemon(turnCount, nil);
// start the turn counter daemon
setdaemon(sleepDaemon, nil); // start the sleep daemon
setdaemon(eatDaemon, nil);
// start the hunger daemon
gameClock.setClock('5:00 am', 5, 1887, 6, 17);
}
;
We’ve copied the
initDaemon() method from story object (story.t in the objects folder) and added
the randomise statement to the method. This method also sets the queue daemon
that produces aggregated display; the daemons for turn and time advancement
(turnCount), fatigue (sleepDaemon), hunger (eatDaemon); as well as setting the
gameClock to the default time of 5:00 a.m, June 17th 1887 (The
starting date and time of Infocom’s Sherlock: The Riddle of the Crown Jewels).
Now when we eat the berry we
get "The tinniest nibble is enough. It was a poisonous berry after all.
" displayed roughly 30% of the time and "You nibble at the berry, but
the curious taste repels you. " about 70% of the time. Because we return
in the latter case we skip the Action stage of Command Execution and the berry
remains in our inventory.
The next object in VENTURE
is a handy rucksack that is capable of tidying away objects automatically once
our own carrying capacity has been exceeded.
ruckSack: SackItem
location
= forestClearing
noun =
'sack' 'rucksack' 'bag'
adjective
= 'ruck'
sDesc =
"rucksack"
aDesc =
"your <<self.sDesc>>"
theDesc =
"your <<self.sDesc>>"
lDesc = "This capacious sack can hold a
surprising number of
objects."
whenOpen
= "\^<<self.theDesc>> is here, lying open."
;
The rucksack is a SackItem
class object, which has a default maxBulk of 100, more than enough for the
average adventurer. A SackItem object starts out open by default, and aside
from the usual sDesc and lDesc we’ve given it a whenOpen description attribute.
The whenOpen is displayed
when the object is displayed in room descriptions, but before the object has moved.
Once the object has moved the whenOpen, like the initial, is no longer
displayed.
Forest Clearing
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.
Your
rucksack is here, lying open.
A red
berry hangs suspended from a vine like a single drop of blood.
>take sack
Taken.
>drop it
Dropped.
>l
Forest Clearing
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.
A red
berry hangs suspended from a vine like a single drop of blood.
You can
also see your rucksack here.
The rucksack can be worn, like any ClothingItem class object
and by default must be worn in order to tidy away objects.
>put on rucksack
(First taking your rucksack)
Okay, you're now wearing your rucksack.
The “(Fist taking your
rucksack)” is the display of an implicit command, in this case an implicit
<<take rucksack>> has been done before the rucksack is worn. More
will be said about this later.
Another common object
definition follows.
stoneSteps: Passageway
location
= forestClearing
noun =
'steps'
adjective
= 'stone'
isThem =
true
sDesc = "stone
steps"
lDesc =
"Ancient steps extend down into shadows. Perhaps these steps
have
have lain untrodden for a thousand years."
doorDest
= stoneChamber
;
The Passageway class derives
from the more basic Obstacle class and defines a one-way passage between two
locations. The important elements of this definition are the isThem attribute,
which tells the library to refer to this object as a plural; and the doorDest,
which indicates where the actor will go when he passes through the obstacle.
We end the chapter with one
more Room definition, for completeness.
stoneChamber: Room
sDesc =
"Stone Chamber"
lDesc =
"The sunken chamber appears to be hewn from the living rock
itself. It forms a perfect square ten yards on each side.
Sunlight filters down from the steps above, casting the furthest
corners of the chamber into deepening shadow."
;