home *** CD-ROM | disk | FTP | other *** search
Text File | 1994-04-01 | 13.0 KB | 458 lines | [TEXT/R*ch] |
- An Adventure Program
-
- Steven Pemberton
- CWI, Amsterdam
-
- Copyright (c) Steven Pemberton, CWI, Amsterdam, 1991
-
- Adventure style games are very popular in computing circles, and I'm
- going to develop a small one here. Because of space I will have to
- leave out many of the advanced features of most adventure games, but
- it will give you an idea of how it looks in ABC. And of course it
- will be obvious how the bells and whistles can quickly be added on.
-
- As I'm sure you know, a (textual) adventure program works by
- describing a scene. You then give instructions on where to go, or
- what to do, and it responds by telling you what happened as a result.
- For instance, if it says
-
- You are standing by a building at the end of a road.
- A spring flows from the building.
-
- and you reply
-
- > enter building
-
- it might reply
-
- You are inside a building, a well house for a spring.
- There is a bottle here.
- There are some keys here.
-
- after which the dialogue might proceed as follows:
-
- > take keys
- > leave the building
- Please use 1 or 2 word sentences.
- > leave
- You are outside the building.
- > go west
- You are standing by a stream.
- > go south
- You are at a small slit that the stream runs down.
- A dry river bed carries on ahead.
- > go down
- You don't seem to be able to go that way.
- > south
- You have found a metal grate fixed into the ground.
- > down
- Sorry, you can't do that.
- > open grate
- The grate is open.
- You are at a hole in the ground.
- There is a metal grate lying on the ground.
- > down
- You are in a dim chamber.
- A hole in the ceiling shows the sky above.
-
- and so on.
-
- The main program making up this adventure looks like this:
-
- HOW TO ADVENTURE:
- START
- GET command
- WHILE command <> "quit":
- OBEY command
- GET command
- FINISH
-
- START will initialise some variables, like the place where the player
- is, and what the player is holding. FINISH will print out the score
- and so on. GET will print the prompt, read a line, strip off spaces,
- and reduce it to lower-case:
-
- HOW TO GET command:
- GET LINE
- WHILE command = "": GET LINE
- GET LINE:
- WRITE "> "
- READ command RAW
- PUT lower stripped command IN command
-
- OBEY has to split a command into its constituent words, and then
- decide what action needs to be taken for that command:
-
- HOW TO OBEY command:
- SPLIT command INTO verb AND object
- SELECT:
- verb = "": PASS
- special command: TRY TO MOVE command
- verb = "move": TRY TO MOVE object
- verb = "take": TRY TO TAKE object
- verb = "drop": TRY TO DROP object
- verb = "kill": TRY TO KILL object
- verb = "what": INVENTORY
- ELSE: CAN'T DO verb, object
-
- SPLIT does what its name suggests: splits the command into its
- constituent words, and makes sure it only consists of one or two
- words:
-
- HOW TO SPLIT command INTO verb AND object:
- PUT split command IN words
- SELECT:
- #words = 1: PUT words item 1, "" IN verb, object
- #words = 2: PUT words item 1, words item 2 IN verb, object
- ELSE:
- WRITE "Please use 1 or 2 word sentences." /
- PUT "", "" IN verb, object
-
- A nice feature is to allow synonyms for commands, to allow ``go west''
- and ``proceed west'' and ``move west'' all to mean the same thing. We
- can do that by having a table of synonyms:
-
- >>> WRITE synonyms["move"]
- {"go"; "proceed"}
-
- and then adding in SPLIT:
-
- SHARE synonyms
- ...
- IF SOME word IN keys synonyms HAS verb in synonyms[word]:
- PUT word IN verb
-
-
- Moving places
-
- In this adventure, each place has a name, which is a short description
- you get each time you visit it after the first time (``You are x'',
- such as ``outside the building'' above). Then, each place has a long
- description used for describing it the first time you go there. Such
- a description is stored as a table of lines, for instance
-
- >>> WRITE description["inside large hall"]
- {[1]: "This is a large hall."; [2]: "There is an exit to the west."}
-
- To display such a message neatly, we can define the following command:
-
- HOW TO DISPLAY message:
- FOR line IN message:
- WRITE line /
-
-
- >>> DISPLAY description["inside large hall"]
- This is a large hall.
- There is an exit to the west.
-
- Then there is a map of all locations, which gives for each location a
- table of directions that the player can go in, and where that
- direction leads to.
-
- >>> WRITE map["inside the building"]
- {["out"]: "outside the building"}
- >>> WRITE map["outside the building"]
- {["in"]: "inside the building"; ["south"]: "standing by the stream";
- ["west"]: "in the forest"}
-
- We can play a nasty trick on the player:
-
- >>> WRITE map["in the forest"]
- {["east"]: "in the forest"; ["north"]: "in the forest";
- ["south"]: "in the forest"; ["west"]: "standing by the stream"}
-
- Moving is attempted by means of the command TRY TO MOVE. All commands
- beginning TRY TO first check that the conditions for the action are
- acceptable, and only then do the action. The current location is held
- in place: TRY TO MOVE checks that the direction asked for is in the
- map for the current place:
-
- HOW TO TRY TO MOVE direction:
- SHARE map, place
- SELECT:
- direction = "":
- WRITE "Where to?" /
- direction in keys map[place]:
- MOVE TO map[place][direction]
- ELSE:
- WRITE "You don't seem to be able to go that way" /
-
- MOVE TO does the actual moving. For now here is a simple version, but
- it will get more involved later.
-
- HOW TO MOVE TO there:
- SHARE place
- PUT there IN place
- DESCRIBE place
-
- (DESCRIBE describes a place and the objects to be found there; you'll
- see it shortly.)
-
- In OBEY, you will have noticed the lines
-
- SELECT:
- special command: TRY TO MOVE command
-
- This is to allow the player to say south instead of go south, by
- seeing if the command is already in the map for the current place:
-
- HOW TO REPORT special command:
- SHARE map, place
- REPORT command in keys map[place]
-
- Notice that it also allows you to use commands instead of directions
- in the map. For instance, when at the grate, you can open the grate
- by having two places, an open grate and a closed grate:
-
- >>> WRITE map["at closed grate"]
- {["north"]: "at slit"; ["open grate"]: "at open grate"}
-
-
- Taking and dropping objects
-
- Different objects are left lying about at various places. These are
- recorded in a table objects. Just as with places, each object has a
- simple name, to be used when the player wants to know what is being
- carried, and a longer description when an object is first found.
-
- >>> WRITE objects["inside the building"]
- {"bottle"; "keys"}
- >>> WRITE description["keys"]
- There are some keys here.
-
- Now I can show you DESCRIBE. It remembers which places have already
- been described (and therefore visited), and only gives the long
- description the first time:
-
- HOW TO DESCRIBE place:
- SHARE description, objects, visited
- SELECT:
- place in visited:
- WRITE "You are ", place /
- ELSE:
- DISPLAY description[place]
- INSERT place IN visited
- FOR object IN objects for place:
- DISPLAY description[object]
-
- Notice here the line ``FOR object IN objects for place:''. Not every
- place may be recorded in the objects table, so it is a shorthand to
- save repeated checks to see if it is:
-
- HOW TO RETURN property for thing:
- SELECT:
- thing in keys property: RETURN property[thing]
- ELSE: RETURN {}
-
- You'll find it used again later on.
-
- Then there is a list of what the player is carrying, called holding,
- which is initially empty. To find out what is being carried, the
- player can ask for an inventory:
-
- HOW TO INVENTORY:
- SHARE holding
- SELECT:
- holding = {}:
- WRITE "You aren't carrying anything" /
- ELSE:
- WRITE "You are carrying: "
- LIST holding
-
- This uses a useful command to neatly print a list of objects:
-
- HOW TO LIST things:
- PUT "" IN separator
- FOR object IN things:
- WRITE separator, object
- PUT ", " IN separator
- WRITE /
-
-
- >>> LIST objects["inside the building"]
- bottle, keys
-
- Another useful tool is a test to see if an object is currently being
- carried:
-
- HOW TO REPORT carrying object:
- SHARE holding
- REPORT object in holding
-
- and another to test if an object is present:
-
- HOW TO REPORT present object:
- SHARE objects, place
- REPORT object in objects for place
-
- TRY TO TAKE can now check that the object is present, that it's not
- already being carried and so on, before actually taking it:
-
- HOW TO TRY TO TAKE object:
- SHARE holding
- SELECT:
- object = "":
- WRITE "Which object?" /
- carrying object:
- WRITE "You're already carrying it!" /
- NOT present object:
- WRITE "I see no `object`." /
- #holding > 6:
- WRITE "You can't carry any more." /
- ELSE:
- TAKE object
-
- TAKE looks like this, again a simple version for now:
-
- HOW TO TAKE object:
- SHARE holding, objects, place
- REMOVE object FROM objects[place]
- INSERT object IN holding
-
-
- TRY TO DROP is similar:
-
- HOW TO TRY TO DROP object:
- SELECT:
- object = "": WRITE "Which object?" /
- NOT carrying object: WRITE "You're not holding it!" /
- ELSE: DROP object
-
-
- HOW TO DROP object:
- SHARE holding, objects, place
- REMOVE object FROM holding
- INCLUDE object IN objects FOR place
-
- The command INCLUDE adds an item to a table:
-
- HOW TO INCLUDE object IN property FOR thing:
- IF thing not.in keys property:
- PUT {} IN property[thing]
- INSERT object IN property[thing]
-
-
- Conditions and side effects
-
- One of the tricks of adventure games is that certain actions are not
- possible unless you are at a certain place, or you are carrying a
- certain thing, and some actions have unexpected side-effects.
-
- For instance, you shouldn't be able to open the grate if you aren't
- carrying the keys. So we can alter MOVE TO to check for this:
-
- HOW TO MOVE TO there:
- SHARE place
- SELECT:
- opening.grate AND NOT carrying "keys":
- WRITE "I don't seem able to open the grate" /
- ELSE:
- PUT there IN place
- DESCRIBE place
- opening.grate:
- REPORT (place, there) = ("at closed grate", "at open grate")
-
- Similarly, somewhere in the cave there is a bird, but you can only
- catch it if you're carrying the cage. Furthermore, the jangling of
- the keys frightens it. So we can alter TAKE to do this:
-
- HOW TO TAKE object:
- SHARE holding, objects, place
- SELECT:
- object = "bird" AND carrying "keys":
- WRITE "The bird flutters off in fright." /
- object = "bird" AND NOT carrying "cage":
- WRITE "You don't seem able to catch the bird." /
- ELSE:
- REMOVE object FROM objects[place]
- INSERT object IN holding
-
- An example of a side-effect is that dropping the bird is the only way
- to scare off the snake (should you meet it):
-
- HOW TO DROP object:
- SHARE holding, objects, place
- IF object = "bird" AND present "snake":
- WRITE "With a great flurry the bird attacks the snake." /
- WRITE "The snake flees into the darkness." /
- REMOVE "snake" FROM objects[place]
- REMOVE object FROM holding
- INCLUDE object IN objects FOR place
-
- (Obviously, TAKE should also be changed to prevent you from trying to
- take the snake.)
-
- Removing objects with extreme prejudice
-
- Now you've seen that there are living creatures in the cave. Certain
- of them are undesirable to the player's well-being and score, and in
- the brutal tradition of adventure games must be eliminated. Of course
- some are harmless, but computers only do what they are told...
-
- HOW TO TRY TO KILL object:
- SELECT:
- object = "":
- WRITE "Which object?" /
- (NOT present object) AND (NOT carrying object):
- WRITE "I see no `object`" /
- ELSE:
- KILL object
-
-
- HOW TO KILL object:
- SHARE holding, objects, place
- SELECT:
- object = "bird":
- WRITE "How cruel! The poor bird dies with a mournful peep." /
- ELIMINATE
- INCLUDE "dead bird" IN objects FOR place
- object = "snake":
- WRITE "Attacking the snake is both dangerous and ineffective." /
- ELSE: It's not a living creature
- WRITE "It's already dead!" /
- ELIMINATE:
- SELECT:
- carrying object: REMOVE object FROM holding
- present object: REMOVE object FROM objects[place]
-
-
- Odds and ends
-
- Well, that's the body of the adventure. Of course, lots of extra
- places, objects, beings and commands must be added, but that's just a
- case of more of the same.
-
- In OBEY, if it can't obey your command, it invokes CAN'T DO. As a
- nicety this prints funny remarks for certain commands. For instance,
- if you're at the stream, you might try ``swim'':
-
- >>> DISPLAY funny["swim"]
- The water would get into my circuits.
-
-
- HOW TO CAN'T DO verb:
- SHARE funny
- SELECT:
- verb in keys funny: DISPLAY funny[verb]
- ELSE:
- WRITE "Sorry, you can't do that" /
-
- As a final touch, you might want to add the commands ``save'' and
- ``restore'' to OBEY, so you can save a game, and come back later to it
- (or so you can try something, and if it fails restore it and try
- something else).
-
- This is remarkably easy. Since the state of the game is reflected by
- a small number of variables, you can just put them in another
- variable:
-
- HOW TO SAVE:
- SHARE saved, holding, objects, place
- PUT holding, objects, place IN saved
-
-
- HOW TO RESTORE:
- SHARE saved, holding, objects, place
- PUT saved IN holding, objects, place
- DESCRIBE place
-