AAL (Adventure Authoring Language) is something I designed and wrote
over the course of about a month. It is definitely "alpha-test"
software. It has never been used to write a serious game, and is
currently being used by, as far as I know, nobody. Not even me: I'm
more interested in language design than adventure games, and this was
just a project to explore some issues, which I'll discuss in more
depth later. AAL consists of a couple of thousand (roughly) lines of
Common Lisp. A description of the language can be found in the April,
1988 issue of _Dr. Dobb's_, but I'll provide a brief summary here.
AAL is sort of a cross between Lisp, Prolog and Smalltalk. The whole
thing is written in Lisp and is effectively a superset of Lisp; in
other words, you can escape to the Lisp interpreter at any time to do
anything you want. The central mechanism of AAL is like Prolog's:
deductive retrieval, that is, doing pattern-matching against a
database of facts. The database contains the state of the game, and
to write a game you write rules that manipulate the database, deleting
some facts and adding others. You also describe the objects of the
game. Each object can have any number of features, like whether the
object is fixed in position, counts as treasure (and how many points
it's worth), and so on. Features are user-definable and may
themselves have features. This provides a mechanism like
inheritance in object-oriented languages.
Here's an example game, with my annotations preceeded by semicolons.
You could actually cut this out and feed it to AAL, if you had a copy.
=========================================================
(loc the-first-room "Room #1" ; define a location called the-first-room
; internally, or "Room #1" to the player
"You are in a small, gloomy room lit by an unseen source above you.
The walls and floor are smooth, hard and dark, like obsidian. Exits
lead west and south."
(contains whistle) ; the whistle is here at beginning of game
(exits
(w the-second-room) ; if the player goes west, ends up in the-second-room
(s "You have wandered around and wound up back where you started")))
; if south, this message is printed and
; nothing else happens
(loc the-second-room
"You are in a vast chamber of ice and rock. Fiery torches in the walls
provide an eerie light. There is a passageway south and another exit to
the north."
(contains monster)
(exits
(s "The passageway is blocked by rubble." the-second-room)
(n (((alive monster) -> "The monster won't let you pass.")
the-first-room))))
; Here's a "rule list"--like a nested if-then statement.
; If the monster is alive, the message is printed. If the monster
; is not alive, player is transferred to the-first-room. Note
; that (alive monster) is a database query.
(obj monster fixed ; define an object, the monster, who is not moveable
(action throw *obj ; if the monster is the object of a throw command...
("The monster destroys the ~a" *instr) ;...the thing thrown
(not (exists *instr))) ; is destroyed
;; Note: symbols beginning with * are variables.
(obj whistle
(action blow *obj ; if you blow the whistle...
(-> "The whistle emits a piercing screech." ;...this is printed,
((here monster) -> ; and if the monster is in this room,
"The monster's eyes bug out--wider--wider--and then,~
finally, close forever."
(dead monster))))) ;...he dies
(action throw (hurl chuck) ; definition of an action--it has 2 synonyms
(throw *instr at *obj) ; here's the syntax
(requires (carrying player *instr) ; requirements before the action
(here *obj)) ; is done
("Nothing happens.")) ; default effect
(action blow
(blow *obj)
(requires ((carrying *obj) "You don't have ~a" *obj))
; if this requirement fails, the associated
; message is displayed
("You can't blow that!"))
================================================================
This example doesn't show all the features--for example, you can have
both backward- and forward-chaining rules, you can iterate over all
the objects that satisfy a query, features can have arguments, you can
set alarms to go off after a certain number of turns have elapsed, and
maybe some other things I forget--but it illustrates the basic flavor.
Let me just describe the flow of control when the user enters an
action, which I think is somewhat interesting and makes for nice
modularization.
First, the command is parsed according to the template supplied with
the action verb. (I basically punted on parsing--I was interested in
other things.) For example, "throw whistle at monster" is parsed,
resulting in *instr being bound to whistle and *obj being bound to
monster. *obj stands for "object," of course, and *instr for
"instrument." Also, *agent is bound to the player (though it may vary
if there are other player-controllable agents in the game, like the
robot Floyd in _Planetfall_).
Next, the requirements are checked. Requirements for an action may
reside on the action description, or on any object or location (we
don't have an example of the latter above, but for instance, if you
have a high-gravity room, it could contain a requirement for "throw"
that insists that the object be very light). The requirements on the
action are checked first, and if they succeed, the requirements on
*agent, *obj, *instr and *loc (bound to the current location) are
checked in that order. If a requirement doesn't succeed, its
associated error message (if any) is displayed and the action is
aborted. This is considerably more complicated than it may appear,
because backtracking may occur, and it's not always obvious which
requirement should really be blamed. (This issue is discussed a bit
in the DDJ article.)
If all the requirements succeed, the action is performed. The code
for doing the action may also reside anywhere. In this case, first
*agent is examined, then *obj, *instr, *loc and finally the action
itself. The first one to contain code for doing the action is
executed, and the action stops there. This should also remind you of
inheritance, though there is no obvious hierarchy--rather, I chose
that order based on what seemed most reasonable to me. In particular,
putting the action last means it gets to express the default behavior,
which seems right to me. Both the requirements order and the action
order can be overridden for any action.
Note how this works for "throw whistle at monster": the requirements
on the action are checked, and since they are the only ones, if they
succeed then AAL looks for code to execute the action. The first code
it finds is on the monster, which is *obj, and the effect is the
destruction of the whistle. If there had been nothing which handled
the throw action, then the default behavior, residing on the action
itself, would have been performed. (In this case, "Nothing happens"
would have been printed.)
What I very much like about this whole hairy thing is that it lets you
put actions and requirements exactly where they belong, and lets you
add them incrementally. I suspect that in other languages, you have
to put all this functionality in one place. But I'm really not very
familiar with ADL, AdvSys or the others.
One other nice thing, and then I'll shut up (almost): the Prolog-style
deductive retrieval paradigm really buys you a lot of power. Example:
say a requirement for lighting the lamp is that it contain a non-dead
battery, and assume also that the lamp could contain anything at all,
perhaps several things, each of which itself could be a container. In
a "normal" language, to check the requirement you'd have to iterate
and/or recurse over all the things in the lamp, looking for a live
battery. In AAL, it's much simpler. First, say that the "in"
predicate means "immediately inside", and assertions like (in lamp
battery-17) are stored in the database. Then we could write two
backward-chaining rules to define "contains," the transitive closure of
"in", as follows:
((contains *x *y) <- (in *y *x))
"X contains Y if Y is in X".
((contains *x *y) <- (in *z *x) (contains *z *y))
"X contains Y if Z is in X and Z contains Y".
You'd only have to write these rules once, of course; in fact, they're
probably a good thing to have in every adventure you write. Now to
implement the requirement, you need only write
((contains lamp *x) (battery *x) (not (dead *x)))
This will take care of checking everything that the lamp contains for
being a non-dead battery.
OK, some concluding remarks, mainly to address questions and concerns
of Steve:
If you don't have Common Lisp, you can get by with Scheme or, to a
lesser extent, XLisp, but you will have to translate. Not too
difficult, I think, though if you don't understand the code, bugs can
be hard to track down.
Implementing AAL in C (or any other standard language) is very hard
for two reasons: first, AAL uses streams, which you can't really
implement in C. You have to fake it, and this means you really have
to know what you're doing. It's hard. I don't know how to do it
offhand. (I don't mean Common Lisp I/O streams, I mean the
lazy-evaluation data structure described in the Abelson and Sussman
Scheme book.)
The second reason is that AAL is a lot of code as it is, and it
doesn't even implement a lot of things (like arithmetic, for example)
because the underlying Lisp takes care of that. To write AAL in C
means translating the existing code AND adding code for all the usual
programming language things like arithmetic, conditionals, loops,
arrays, etc. Whoosh, not your two-week project. I'd recommend
learning Lisp--it'll be more fun and take about as much time.
I know people in this newsgroup are interested in multi-player
adventure games. AAL has no facilities for that, and I have no ideas
about it. But I'm sure it could be added.
Speed: It's my impression that for most commands, most adventure games
need to do very little computation. I feel that, with a compiled Lisp
on a modern workstation (Mac II, Sun-3 or HP 350, for example), game
response would be instantaneous nearly all the time. I may even be
conservative here--perhaps even on something like a Mac Plus it would
be very fast. No one really knows. Anyway, there's plenty of room
for efficiency hacks.
Availability: I can post the sources (seven or eight files, avg. size
15K) to the appropriate group (rec.games.sources?) if there's enough
interest. It's totally free, public domain stuff, but it is
copyrighted by me.
Me: I'd be happy to discuss the issues at great length, and even to
discuss specific porting problems and bugs that crop up, but I am not
currently supporting or maintaining AAL in any way.
Jonathan Amsterdam
j...@wheaties.ai.mit.edu
What a pleasant surprise! I'd nearly concluded that my letter had gotten
lost in the busy life of a graduate student.
I'd certainly like to have a copy of AAL source. If you had a fairly
rigorous game written in AAL I'd strongly encourage you to post to
comp.sources.games; in the absence of a challenging "plug and play" game,
though, it becomes harder to call. You could post AAL directly to
rec.games.programmer but I don't know what percentage of the readers have
access to a lisp. The advantage of posting to a normal source forum,
of course, is that your source would get archived for future generations. :-)
Perhaps alt.sources is an appropriate alternative forum; that group is
unmoderated and I've seen some sources aimed at relatively restricted
audiences go through there.
As a final thought, if you could make the time to hack AAL to run under
xlisp (I know that is probably asking too much) I'd say it should *certainly*
be publicly posted--xlisp is distributed through one of the comp.sources
groups and there are doubtless a lot of people who would relish a real
xlisp "application" to entice them to learn lisp; I know that I would. For
that matter, if necessary I would probably break down and by Texas
Instrument's PC-Scheme for MS-DOS but I must confess that I much rather
be able to use it with xlisp so that I could stick with UNIX. If you *are*
able/willing to do a port of AAL to xlisp then I would be interested in
trying to write a "substantial" adventure game which could be distributed
with your package.
--------
I've decided to post this letter to the rec.games.programmer group to
hopefully encourage a little more discussion.
--
----------
Steve Rudek {ucbvax!ucdavis!csusac OR ames!pacbell!sactoh0} !tree!stever
--
----------
Steve Rudek {ucbvax!ucdavis!csusac OR ames!pacbell!sactoh0} !tree!stever
And now on to our regularly scheduled program.
--
Dave Brown ...!uunet!microsoft!davidbro
Systems Division, Microsoft Corp., Redmond Wa.
...ni ssendriew eht tel eW
"If you've got a black list, I want to be on it"