Goal-oriented behavior in TADS (LONG)

5 views
Skip to first unread message

Ron Hale-Evans

unread,
Jun 14, 1993, 2:27:00 PM6/14/93
to
Hello, Sailor!

This is my first real post to rec.arts.int-fiction. I'm a registered
and devoted TADS user who has been working on his own text game for
about two months. The game is called "Mad Venture," and is belongs to
the genre of what I call "interactive autobiography." It details my
own experiences with schizophrenia. I will be more than happy to post
details of the implementation as it develops. I've hesitated to post
so long because I really wanted to have something to contribute before
I posted. Now (I hope) I do.

In this article I will briefly show how to implement goal-oriented
behavior in TADS. Rather than redefine "goal-oriented behavior" I will
quote David Graves's excellent paper, "Second Generation Adventure
Games." (For information on how to obtain this and other papers of Mr.
Graves, consult the r.a.i-f FAQ.) NOTE: I am posting this snippet
without Mr. Graves's permission, but since he makes his articles
freely available, I thought it would be OK to treat them as net text a
la Usenet articles. Sorry if this assumption is unwarranted or
offensive.

BEGIN QUOTE:

GOALS AND SUB-GOALS

A standard feature in many adventure games is the requirement
that the player remember long sequences of actions in minute
detail and type these sequences when needed. Forgetting to
perform one of the steps in a sequence results in an error
message. For example, if the player sees a bottle of beer on the
table and says "Drink the beer," he is likely to get an error
message like "You aren't holding the beer" or "The beer isn't
open". Memorizing detailed sequences of instructions is not what
most people would call fun. Wouldn't it be much nicer if the
computer could just "understand" the player's intent?

It turns out that it is relatively easy to create a system where
the programmer defines a corrective action for such an error,
instead of giving an error message. By defining the prerequisite
state attributes for any action, the system can automatically
break a goal into sub-goals. Thus, when the Drink routine finds
that the player is not holding the beer, a new sub-goal is set:
get the beer. Upon resolving that sub-goal, the Drink routine is
re-entered. It then checks to see if this container is open and
if not, it sets a new goal: open the container. In the end the
player is told of how all these events proceeded with a message
like: "You take the beer from the table, open it, and drink from
it".

Filling in the implied prerequisite actions is a simple form of
planning, called backwards chaining. Any new goals must be
stored on a stack rather than in a static structure, since any
goal can create new sub-goals, which may create others. Each
goal must be resolved in order, the latest goal first. The game
program simply attempts the action on the top of the stack, pops
it off if successful, or pushes new sub-goals if needed. It is
important to note that only errors concerning variable attributes
are correctable. Errors caused by conflicts in fixed attributes
(such as attempting to set fire to a stone) are not correctable
by creating a new sub-goal. If a non-correctable error occurs,
the player is informed and his goal stack is cleared.

Note that once all of the verb routine have all their
prerequisite states defined as sub-goals, it becomes very easy to
simulate intelligent behavior by other actors in the story. For
example, if the player asks another character to "Go out, get the
rope and return," the character appears to make intelligent
decisions like opening the door before attempting to exit, and
untying the rope from a tree before attempting to walk away with
it. The pace of the game remains brisk since the player need not
specify each detailed step in a process. The software
"understands" what is implied by the player.

END QUOTE.

To demonstrate an easy way to implement goal-oriented behaviour in
TADS, I am including a little "adventure" written under TADS 2.1. If
you have TADS 2.1 and strip the code out of this message, you should
be able to run it as-is. Note that this code will not run under
versions of TADS lower than 2.1 because of my use of the "modify"
command.

What this code does, in a nutshell, is modify the "fooditem" class so
that any adventure using the code will treat food a little more
intelligently, to wit: when you give the command EAT SANDWICH (or
whatever), TADS will not respond with a message to the effect that you
don't have the sandwich, but will GET the sandwich for you, then EAT
it. Of course, this can be extended to other actors as well, as Mr.
Graves points out, so that they will seem to behave intelligently too.
With a little skull sweat, goal orientation can be made more general.

(TO MICHAEL J. ROBERTS: Mike, in the documentation to the TADS
package, you call for adventure authors to contribute code
improvements to future versions of adv.t. Are you at all interested in
including whatever I come up with?)

Before I include the code, here's a sample session with the game:

**********************************************

A TADS Adventure
Developed with TADS, the Text Adventure Development System.
Your room
You are stuck in a room. Good luck, chump.
You see a Cheez(tm) sandwich, a dill pickle, a pancake, a
McIntosh apple, some lutefisk, and a Macintosh computer
here.

>look at sandwich
It's a Cheez(tm) sandwich, which, as we know, is better than
eternal life, because nothing is better than eternal life
and a Cheez(tm) sandwich is better than nothing.

>eat macintosh
The Macintosh computer doesn't appear appetizing.

>eat mcintosh
Taken. That was delicious!

>eat sandwich
Taken. That was delicious!

>eat pancake
Taken. That was delicious!

>quit
In a total of 5 turns, you have achieved a score of 0 points
out of a possible 100.

Do you really want to quit? (YES or NO) >y


*******************************

And now... the album of the soundtrack of the trailer of the film of
the snippet of the code of... testGoals.t !!!

*** cut here ***

/*
* testGoals.t
* by Ron Hale-Evans
* a boring li'l adventure to demonstrate goal-oriented behavior in TADS
* this file requires TADS >= 2.1
*
* Prototyped by the TADS Template Stack v0.9 -- ©1993 by Jared L. Reisinger
*
* File: Severian:TADS Template:testGoals.t
* Date: Monday, June 14, 1993
* Time: 12:50:18 PM
*
*/

#include <adv.t>
#include <std.t>

modify fooditem
doEat(actor) =
{
if (self.location <> actor)
self.doTake(actor);
pass doEat;
}
;

startroom: room
sdesc = "Your room"
ldesc = "You are stuck in a room. Good luck, chump."
;

sandwich: fooditem
sdesc = "Cheez(tm) sandwich"
ldesc = "It's a Cheez(tm) sandwich, which, as we know, is better than
eternal life, because nothing is better than eternal life
and a Cheez(tm) sandwich is better than nothing."
noun = 'sandwich'
adjective = 'cheez' 'cheez(tm)' '(tm)'
location = startroom
;

pickle: fooditem
sdesc = "dill pickle"
ldesc = "It's the extra-crunchy, half-sour kosher kind."
noun = 'pickle'
adjective = 'dill' 'kosher' 'crunchy' 'extra' 'extra-crunchy'
'half' 'sour' 'half-sour'
location = startroom
;

pancake: fooditem
sdesc = "pancake"
ldesc = "Yum! It's a delicious blueberry flapjack!"
noun = 'pancake' 'flapjack'
adjective = 'blueberry'
location = startroom
;

apple: fooditem
sdesc = "McIntosh apple"
ldesc = "It's a McIntosh, not to be confused with a Macintosh."
noun = 'apple' 'mcintosh'
adjective = 'mcintosh'
location = startroom
;

lutefisk: fooditem
sdesc = "lutefisk"
adesc = "some lutefisk"
ldesc = "Yuk. It's *lutefisk*!"
noun = 'lutefisk'
location = startroom
;

macintosh: item
sdesc = "Macintosh computer"
ldesc = "It's a Macintosh, not to be confused with a McIntosh."
noun = 'mac' 'macintosh' 'computer'
adjective = 'macintosh'
location = startroom
;

*** cut here ***

That's all, folx. Let me know if you have any new ideas.

"The Sea refuses no river; remember that when a beggar buys a round."
--Pete Townshend * * * * * Ron Hale-Evans ev...@binah.cc.brandeis.edu
PGP 2 public key: finger ev...@binah.cc.brandeis.edu

Lars Joedal

unread,
Jun 17, 1993, 3:38:48 AM6/17/93
to
ev...@binah.cc.brandeis.edu (Ron Hale-Evans) writes:

> [...]


> What this code does, in a nutshell, is modify the "fooditem" class so
> that any adventure using the code will treat food a little more
> intelligently, to wit: when you give the command EAT SANDWICH (or
> whatever), TADS will not respond with a message to the effect that you
> don't have the sandwich, but will GET the sandwich for you, then EAT
> it. Of course, this can be extended to other actors as well, as Mr.
> Graves points out, so that they will seem to behave intelligently too.
> With a little skull sweat, goal orientation can be made more general.

Yes, this is really a nice idea. It is rather cumbersome to have to
be so specific when playing, and it is the sort of thing that might
give newcomers the idea that IF is boring (at an occasion I demonstated
a game to my cousin; when she tried it herself it only took a moment
before she said: "Hey, do I really have to take the toothbrush before I
can use it? How stupid!").

[most of a nice little game deleted]

> modify fooditem
> doEat(actor) =
> {
> if (self.location <> actor)
> self.doTake(actor);
> pass doEat;
> }
> ;

This will work in most cases. But it may give problems in cases where
the item is not easily taken. Example transcript:

>look
You're in a bakery. On a desk is lying stables of fresh-baked bread,
reminding you of how painfully hungry you are.

>take bread
As you reach for the bread the baker stops you. "Cash first!" he demands.

>eat bread
(Taking the bread first)
The baker slaps your hand. "I know your type," he says. "You ain't gonna
get no bread until I see money!"
That was delicious!

[end of sample transcript]

You see the problem? Even if taking the fooditem gives problems it is
eaten. I'm afraid some kind of checking is needed even though it means
more complicated code.
I've fought a bit with this problem myself and have come up with what I
thought to be a solution. Unfortunately it only worked to some extend
(more details later), but it might work with TADS 2.1! I don't know
since TADS 2.1 is not out for Atari ST yet.
I'll give you the code - take it for what it's worth.

This function is supposed to be part of the general class item:

checkPossess( actor ) =
{
/* This method returns true if the actor has or can easily get
this object.
*/
if ( actor.isCarrying(self) )
return (true);
/* The actor doesn't have the object, but maybe it can just be
taken. To check this we hide the output, run the verDoTake
method, and see if it gave any output. If there WAS output
the object cannot be taken.
*/
outhide(true);
self.verDoTake( actor );
return (not outhide(nil));
}

The function "checkPossess" is supposed to be called from ver-methods.
It returns true if the actor is already carrying this item (self) or
if it looks like this item can be easily taken.
"Easily taken" means that self.verDoTake(actor) outputs no text. This
is checked with the built-in function outhide(): outhide(true) suppresses
any output until outhide(nil) is called. outhide(nil) turns output on
again and returns true if there was any output, nil if there was no output.
Thus the effect is similar to the disambiguation mechanism in TADS.

possess( actor ) =
{
/* Get this object. It is assumed that the verDoTake method has been
called and gave no output (i.e. that checkPossess has been called).
If even calling doTake don't take the object (e.g. the actor may
have got his/her hands full) the current command is exit'ed.
*/
if ( not actor.isCarrying(self) )
{
"(Taking <<self.thedesc>> first)\n";
self.doTake( actor );
"\n";
/* Check that the actor actually got the object - perhaps s/he
couldn't carry any more stuff!
*/
if ( not actor.isCarrying(self) )
exit; // Stop the current command
}
}

"possess" is another function that is supposed to be part of "item".
It makes the actor possess this item (self) if possible. If this is
NOT possible the current command is aborted with exit.

This must be the time for an example. For a fooditem the general code
would be something like:

verDoEat(actor) =
{
/* Is this object at hand? If not give some kind of error message. */
if ( not self.checkPossess(actor) )
"You don't have <<self.thedesc>>! ";
}

doEat(actor) =
{
/* Since verDoEat HAS been called for this object we are 90% sure
the object is at hand. Get it.
*/
self.possess(actor);
/* At this point the actor has the object. Because, if it was not
available "possess" would have stopped command processing with
an exit command.
*/
// Here goes the code for eating. I just can't remember it in
// details!
}

That was it! The part with the baker will now be:

>eat bread
You don't have the bread!

if the baker is checked in the bread's verDoTake method. If the check is
in the doTake method the player will see:

>eat bread
(Taking the bread first)
The baker slaps your hand. "I know your type," he says. "You ain't gonna
get no bread until I see money!"

and the bread is not eaten, because the player still doesn't have the bread
after bread.possess have called bread.doTake.

So, what do you think of it?


Oh, the problem I mentioned? TADS 2.0 is confusing the user's hidden output
with its internal hidden output.
This means that if one ver-method hides the output and calls anothe ver-
method, then the output of the secon ver-method still counts as output for
the ver-mechanism. In the above case it is not a major problem: checkPossess
gives output if there was any hidden output. So whether the first (hidden)
output or the second output is detected is of little difference. But it is
left as an exercise for the reader to come up with a case where this does
casue problems.
Maybe it is fixed with TADS 2.1. At least I notified Michael J. Roberts
when I found the problem. (Any comments, Mike?)

+------------------------------------------------------------------------+
| Lars J|dal | Q: What's the difference between a quantum |
| email: joe...@dfi.aau.dk | mechanic and an auto mechanic? |
| Physics student at the | A: A quantum mechanic can get his car into |
| University of Aarhus | the garage without opening the door. |
| Denmark | -- David Kra |
+------------------------------------------------------------------------+

Reply all
Reply to author
Forward
0 new messages