This file is part of the TADS Authors Manual.
Copyright © 1987 - 2000 by
Michael J. Roberts. All rights reserved.
The manual was converted to HTML and edited by N. K. Guy, tela design.
Chapter Nine
The previous chapters have covered a lot of ground: the language, the parser interface, and even the general principles of object-oriented programming. Because TADS provides such a rich set of options, its often difficult when youre first getting started with TADS to figure out the best way to do something.
This chapter is intended to help you through the process of writing a game with TADS. Well assume that you have a basic idea of how the TADS language works.
Your author has been involved in the design and implementation of several text adventure games. The game development process presented here reflects the experience gained from those projects.
The first step is designing the game. Using a system like TADS, which makes it very easy to get started with a game, you may be tempted to jump right in and start programming your game, but you should instead turn off your computer and get out a pad of paper.
We did not follow this advice with Deep Space Drifter. After formulating the basic plot of the game, and mapping out the portion that takes place on the space station (roughly the first half of the game), we started implementation. We had a basic idea of the second half of the game, but it wasnt even mapped.
Implementation went well for a while, but as we got further along, we started to run into details in the first half of the game that were dependent upon details from the undesigned second half. We improvised some details, and left others for later. As we did this, a strange thing happened: we started to realize that there were holes in the plot, and weird little inconsistencies that hadnt occurred to us until we needed to think about details. As a result, we started to change our basic ideas about the second half, which led to even more inconsistencies and plot holes. It was like digging in sand, and before long we decided to throw out the entire original plan for the second half and start over.
However, we had so much time and effort invested in the space station that we didnt want to throw it away. Instead, we tried to design a new second half that fit in with the existing first half. From this point on, the battle was lost. We went through a series of essentially unrelated plots for the game, trying to fit each new plot to an even larger set of existing implementation. Wed plan a little, implement it, then discover that the plan wasnt working and would have to go - but the programming work we did would have to stay. The swamp, the cave maze, and the shuttle represented so much work that we couldnt contemplate throwing them away, so whatever we came up with had to include them somehow; for a brief time, we were actually going to make the swamp a Swamp Simulator because it was the only way we could make it fit.
In the end, we were totally sick of writing Deep Space Drifter, but refused to let the project die before it was complete for psychological reasons. To me, this attitude shows through in the last half of the game; I think theres a room on the planet whose description is something like this: This room is very boring; you can leave to the north. In fact, I think the entire game reflects its history: the space station is full of things to do, it has some nice running jokes, and its stylistically consistent. The planet, on the other hand, has an empty, barren feel; its spread out and theres not much to do. The only parts that are interesting are essentially unrelated to each other and to the story in general, a reflection of having been forced into the game whether they belonged or not.
Im not saying that Deep Space Drifter is a bad game - I like the space station a lot, and the puzzles on the planet are very elaborate and elegant. But the game has some serious flaws, most of which I attribute to the long, chaotic process of design and implementation.
Ditch Day Drifter, on the other hand, went through a much shorter and smoother design and implementation process, and I think a better game resulted. Admittedly, Ditch is a much smaller game than Deep, but its clear to me that the same design process could have been applied Deep, and that doing so would have resulted in a much better game.
Before any programming work started on Ditch, I had a complete map of the game and descriptions of all the objects and chracters in the game, including the key elements of their behavior. This made implementation very smooth and easy, because I could simply go through the lists of rooms and objects and implement each. I didnt have to make any design decisions during implementation, which eliminated the desire to change design decisions that had already been made. The game took only a few weeks to implement, and came out fairly well.
If you resist the temptation to start programming before the design is complete, your games implementation will go much faster, the game will turn out much better, and most importantly, the chances that you complete your game will be much higher.
Before you start implementing your game, you should have a list of all of the locations, objects, and characters in the game. For each location, object, and character in the game (to which well refer collectively as game elements henceforth), you should have a description of the item and its key behavior. Basically, you should know enough about your game that you can go through the game and play it completely through, command by command. You dont need to know all of the details of the game at this point, but you should know all of the details along the minimal path through the game (the series of commands that a player would type to go through the game from start to finish, if no mistakes are made and the player never needs to back up). The remaining details do not affect the plot, so you will be able to make these up as you go without creating plot holes or inconsistencies.
The complete map and object list is the result of the design process. To get there, you will probably go through many steps, adding detail as you go, until you have a complete design. The process detailed here is a good way to design a game, but you may find other ways that suit you better.
Most adventure games have a basic plot framework that controls the overall flow of the game. The plot is a good place to start your design.
An adventure games plot isnt usually as elaborate as the plot of a non-interactive form of fiction, such as a book or a movie, because you have much less control over the details of the main characters actions. This makes plot design much more difficult, but you should give the player as much control as you can, since adventure games are always more satisfying when they give players a greater sense of control. Adventure game plots, therefore, should simply be a framework that provides the general direction of action in the game, and specifies only the important events.
You should start off with a basic background and goal. Where does the game take place? When? Who is the main character? What must be accomplished by the time the game is over?
For example, in Ditch Day Drifter, the location is the Caltech campus (or perhaps Caltech in some parallel universe, since the game isnt a strictly accurate simulation of the real Caltech), and the time is the present, generally, and Ditch Day, in particular. The main character is a Caltech underclassman. The goal is to solve the Ditch Day stack that the senior across the hall has created.
You can continue by identifying the major sub-goals that must be reached in order to reach the overall goal of the game. In Ditch, the major sub-goals are to find each treasure listed in the note that describes the stack.
Filling in the details of the plot can proceed by working backwards from the overall goal to the major sub-goals, then backwards to the smaller goals that must be reached for each sub-goal, and so on.
I would advise against designing a game with major sub-goals such as the player must find the five rings of power. When the sub-goals are abstract like this, they dont suggest anything about what you might need to do to accomplish them. On the other hand, if a sub-goal is something concrete and specific like the player must destroy the radio broadcasting tower, the minor sub-goals come almost automatically: you need to break into the compound where the broadcasting tower is located, and youll need some sort of explosive, and you may need to turn off power to the tower to avoid being electrocuted. But if you can turn off the power, why do you need to blow it up? Maybe because an attendant will come and turn the power back on after a few turns. Now we have another puzzle: dealing with the attendant. Sub-goals that are abstract, such as finding five rings of power, arent nearly as helpful in getting to the next level of detail. Concrete sub-goals can often provide a seed that your imagination can almost automatically expand into a full set of plot elements.
The basic plot will provide an overall setting for the game, and the plot elements will usually suggest a specific set of large-scale locations. So, youll start off with a low-resolution map simply by designing the basic plot.
Next, you can start filling in the specific rooms. At this point, youll probably find that you start to fill in the details of the plot at the same time as youre filling in the details of the setting. Many locations in the setting will offer natural puzzles: if you have a bank, youll probably have a safe, for which youll need to find the combination; maybe youll need to figure out how to defeat the alarm system, or perhaps youll need something to distract the teller; maybe youll need to find the key to the safety deposit box. If you have an airport, youll probably need to figure out how to get past the overly sensitive metal detectors, or youll need to know someones itinerary, or youll need to find some airline tickets, or youll need to find a badge or key or combination to get into a restricted area.
Whatever type of location youre designing, the location will often suggest a series of puzzles to be solved. As you design the solutions to the puzzles, youll be adding detail to the plot, which will in turn give you new locations to develop.
As you flesh out the setting, you may be tempted to add enormous amounts of space to your locations. For example, if youre building an airport, you may find yourself putting in dozens of gates, each pretty much the same as all the others. While the added space may make the game setting more like a real airport, it can often harm the games playability. Remember, youre designing a game, not an airport - the most important thing is making the game fun to play, not real.
The main problem with adding lots of essentially unused space to a game is that it tends to make the level of detail throughout the game less consistent. You should make an effort to keep the level of detail as consistent as possible throughout the game, to avoid annoying and confusing the player. If you have a few rooms with a great deal of detail (such as lots of objects that can be examined and manipulated), the player will come to expect that level of detail in other rooms. The dozens of almost identical airport gates will not have anything interesting to do.
Youll probably end up designing most of the game in the course of mapping out the setting and plot. As you develop plot elements, you should make notes of the objects and characters that are involved. Youll probably find it easiest to note objects and characters on the map, where theyll be placed.
For each object and character, you should make notes about its relevance to the game, and what it does. You should think carefully about what other objects might be used for the same purpose, and what other uses an object might have. For example, if you have a door that you need to break down, and you intend to provide an axe for this purpose, you should consider two things: What else might the axe reasonably be expected to destroy? And what else might reasonably be expected to be able to break down the door?
Because a player could reasonably expect an axe to have many uses, and because many objects should be able to break down a door, you should be very careful about creating puzzles that involve brute force and powerful tools of general utility. Explosives, flammable liquids, weapons, and heavy tools are all likely to frustrate the player when they dont remove just about any physical obstacle. On the other hand, it would be very satisfying if you took the trouble to fully realize the axe and the door by allowing the axe to break down everything thats reasonable, and allowing the door to be broken down by other reasonable objects.
Once you have the game fully designed, youre ready to start implementing. In this section, well show how to implement the basic types of objects.
Start off by drawing the map, and making annotations on the map describing the essential details of the plot. This should include placement of the major objects in the game, and descriptions of the important actions the player must perform.
As an example, well implement a game that takes place in a small airport. Our airport will have a terminal area, a concourse, and a gate area. Well also have a plane parked at one of the gates. The terminal area will have a ticket counter, and a metal detector leading into the concourse. In the concourse, there will be a snack bar, and a locked door leading off into a security area. The gate area will have a couple of gates, plus a locked maintenance room. Heres the basic map well be implementing.
This map conveniently has a couple of locked areas, which we can use for puzzles. In addition, we can probably find some use for the metal detector, since it will prevent the player from carrying any objects through it. The plane can also be a puzzle, since youd normally need a ticket to board a plane. Plus, the cockpit should be restricted to airline personnel.
Lets make the goal of the airport segment be getting out of the airport. The player will start off in the main terminal area, but wont be able to go outside the terminal - well make up some excuse, such as heavy traffic that always pushes the player back into the terminal, to prevent exiting that way. (If this were an actual game, wed probably have more game outside the airport, so we wouldnt use such an artificial boundary as having heavy traffic that pushes the player back in. For this example, though, we want to keep things fairly small.) The only other obvious way to get out of the airport is to fly out on a plane; so, lets make the goal be to fly the plane.
To take the plane out of the airport, the player will have to get into the cockpit. (Well implement this example up to the point that the player makes it into the cockpit; in a full game, wed go on to let the player fly the plane somewhere else.) Now, only the pilot can go into the cockpit - the flight attendant wouldnt let a passenger into the cockpit. So, well need some way to get past the flight attendant. One way would be to create a diversion that distracts the flight attendant long enough to slip by; for this example, though, well require the player to find a pilots uniform.
Where would the pilots uniform be? The pilots lounge would be a logical place; well put a suitcase in the pilots lounge that contains a uniform. Fortunately, the lounge is behind a locked door, which creates a secondary puzzle. To get into the security area that contains the pilots lounge, the player needs a magnetic ID card to put into a slot outside the security area.
Well put the ID card out in the open, on the ticket counter in the terminal area. However, well make it impossible to carry the ID card past the metal detector - the card will set off the metal detector, and the security guard will confiscate it (and place it back on the counter so that the player can try again). To get the card by the metal detector, youll have to turn off the power to the metal detector.
The power switch will be in the maintenance room, which is locked with a key. Well leave the key with some other maintenance items in the planes bathroom - well also leave a pail, sponge, and garbage bag, so that its clear by association that the key is probably for the maintenance room.
To get into the planes bathroom, youll need a ticket to board the plane. Well make the ticket fairly simple to find: well leave it hidden inside a newspaper in the snack bar. As soon as you pick up the newspaper, the ticket will fall out.
So, thats about the whole game: you go to the snack bar, pick up the newspaper, and find the ticket. You take the ticket and board the plane, then go to the planes bathroom and get the key. Take the key to the maintenance room, unlock the door, enter, and turn off the power to the metal detector. Go back to the ticket counter, pick up the ID card, go to the security door, put the magnetic card in the slot, and enter the security area. Go to the pilots lounge, get the pilots uniform out of the suitcase, and wear it. Go to the plane, and stroll right past the flight attendant and into the cockpit.
We should draw a new map now, which includes annotations for the main objects and actions that make up the game.
The first step is to convert the skeleton of your map into the beginnings of your game program. At this point, dont worry about entering all of the detailed behavior of your map, but just build the basic rooms and connections between rooms. This will allow you to get a prototype of the game running quickly, so you can walk around it and see how it feels.
Start off by creating a source file for your game, and including the base definition files:
#include <adv.t> #include <std.t>Remember, as always, to be be absolutely certain that the #include command has no space characters in front of it. The # must be the first symbol on the line.
Now, for each room in your game, make an entry that gives the rooms name (sdesc, or short description) and long description (ldesc), and the other rooms that are connected to this room. Heres how to implement the first few rooms from the sample map above. For the time being, we wont worry too much about making the long descriptions complete; we can always flesh those out later.
terminal: room sdesc = "Terminal" ldesc = "You are in the airport's main terminal. To the east, you see some ticket counters; to the north is the main concourse. " east = ticketCounter north = securityGate ; ticketCounter: room sdesc = "Ticket Counter" ldesc = "You are in the ticket counter area. Ticket counters line the north wall; so many people are waiting in line that you're sure you'll never manage to get to an agent. The main terminal is back to the west. " west = terminal ; securityGate: room sdesc = "Security Gate" ldesc = "You are at the security gate leading into the main concourse and boarding gate areas. The concourse lies to the north, through a metal detector. The terminal is back to the south. " north = concourse south = terminal ; concourse: room sdesc = "Concourse" ldesc = "You are in a long hallway connecting the terminal building (which lies to the south) to the boarding gates (which are to the north). To the east is a snack bar, and a door leads west. Next to the door on the west in a small slot that looks like it accepts magnetic ID cards to operate the door lock. " north = gateArea south = securityGate east = snackBar west = securityArea ;The other rooms are implemented in the same manner. For now, were not worrying about the items contained in the rooms, or the other people around, or even the locked doors. Well just implement all the rooms so we can walk through the map and try it out.
The next step is to implement the basic objects that make up the game. As with the rooms, dont worry about the complex behavior of some of the objects at this point; just go through and write basic object definitions for the main items in the game.
Items have different properties than rooms. The basic properties of an item are its name (sdesc), long description (ldesc), vocabulary words (noun and possibly adjective), and container (location, which may be either a room or another item). If the item can be carried by the player, the object will be of class item; if not, it will be of class fixeditem. Some items may be of different classes; for example, if you want to make an object that can contain other objects (such as the pail), make it a container. If you want to be able to put objects on top of another object, use a surface object. You can make something both a container or surface and a fixeditem if you want.
Here are some of the basic object definitions for our sample game.
counter: fixeditem, surface location = ticketCounter noun = 'counter' adjective = 'ticket' sdesc = "ticket counter" ; IDcard: item location = counter noun = 'card' adjective = 'id' 'identification' sdesc = "ID card" adesc = "an ID card" ; newspaper: readable location = snackBar noun = 'newspaper' 'paper' adjective = 'news' sdesc = "newspaper" ldesc = "It's today's copy of USA YESTERDAY. " readdesc = "You read a few articles, and promptly become depressed. The federal deficit just went up by another twenty billion dollars, but it's all \"off budget,\" so it doesn't really count. There's another White House scandal involving illegal arms sales, money laundering through Italian banks, Congressional Pages; several high-ranking federal arts critics have already resigned in disgrace. The economy had yet another downturn, but the President says he's confident that the recovery is \"just around the corner and picking up steam.\" " ; cardslot: fixeditem location = concourse noun = 'slot' adjective = 'card' sdesc = "card slot" ldesc = "The slot appears to accept special ID cards with magnetic encoding. If you had an appropriate ID card, you could put it in the slot to open the door. " ; suitcase: openable isopen = nil location = pilotsLounge noun = 'suitcase' sdesc = "suitcase" ; uniform: clothingItem location = suitcase noun = 'uniform' adjective = 'pilot' 'pilot\'s' sdesc = "pilot's uniform" ldesc = "It's a uniform for an Untied Airlines pilot. It's a little large for you, but you could probably wear it. " ;The rest of the objects are implemented in much the same way. In implementing the basic objects, the main properties you need to fill in are location, so the object appears somewhere in the game; noun, so the player can refer to the object; and sdesc, so the system knows what to call the object when it mentions the object in messages. Its also important to choose the appropriate class for each item, so that you can get the correct basic behavior of the object without any additional work. At some point, you should go through adv.t and familiarize yourself with the classes defined there, so you know what you can get from adv.t classes without any work. See Appendix A for full details on adv.t.
The next step is to implement all of the special behavior that really makes the game work. For example, lets implement the simple mechanism that lets the player find the airline ticket upon picking up the newspaper. To do this, all we need to do is add a doTake method to the newspaper object (well just show the new doTake method below; the rest of the object is the same as shown above):
doTake( actor ) = { if ( not self.foundTicket ) { "As you pick up the paper, an airline ticket that was inside falls to the floor. " ; ticket.moveInto( actor.location ); self.foundTicket := true; } pass doTake; } ;This method runs whenever the player takes the newspaper. The first thing we do is check to make sure that the airline ticket hasnt already been found; if not, we display a message that its been found, move the ticket into the game, and note that its been found. Finally, we continue with the default doTake action that the newspaper object inherited from its superclass (in this case, readable) by using the pass statement.
Note that the airline ticket itself should be defined with no location, because its not anywhere at all when the game first starts:
ticket: item noun = 'ticket' adjective = 'airline' sdesc = "airline ticket" ldesc = "It's a one-way ticket to New York, in class C (the \"C\" probably stands for \"Cattle\"). " ;As another example, lets implement the puzzle the keeps the player out of the security area until the ID card is used to unlock the door. First, we must prevent movement from the concourse into the security area. For this, well change the concourse room definition, and add a door object.
concourse: room sdesc = "Concourse" ldesc = "You are in a long hallway connecting the terminal building (which lies to the south) to the boarding gates (which are to the north). To the east is a snack bar, and a door leads west. Next to the door on the west is a small slot that looks like it accepts magnetic ID cards to operate the door lock. " north = gateArea south = securityGate east = snackBar west = { if ( securityDoor.isopen ) return( securityArea ); else { "The door is closed and locked. "; return( nil ); } } ; securityDoor: fixeditem location = concourse noun = 'door' sdesc = "door" isopen = nil ldesc = { "The door has a label reading SECURITY AREA-AUTHORIZED PERSONNEL ONLY. "; if ( self.isopen ) "The door is open, which isn't very secure. "; else "The door is securely closed. "; } verDoOpen( actor ) = { "The door is securely locked. "; } verDoUnlock( actor ) = { "You should examine the slot if you want to unlock the door. "; }The door is really just a decoration item; the various methods we implement are just to inform the user that the door cant be operated directly. The real work is done by the card slot; well add some new behavior to that object now.
cardslot: fixeditem location = concourse noun = 'slot' adjective = 'card' sdesc = "card slot" ldesc = "The slot appears to accept special ID cards with magnetic encoding. If you had an appropriate ID card, you could put it in the slot to open the door. " verIoPutIn( actor ) = {} ioPutIn( actor, dobj ) = { if ( dobj = IDcard ) { if ( securityDoor.isopen ) "You put the card in the slot; nothing happens, so you remove it. "; else { "You put the card in the slot. There's a click, and the security door pops open! You remove the card. "; securityDoor.isopen := true; } } else "That doesn't seem to fit in the slot. "; } ;The new behavior is that the player can put the ID card in the slot. When this is done, the ioPutIn method runs, with the ID card as the direct object (the dobj parameter). This method opens the door, if its not already open.
Most of the remaining puzzles in this sample game are implemented in a similar fashion. Youll need to implement a couple of actors: one for the flight attendant, and one for the guard at the metal detector. In addition, youll need a few more locked doors and special items.
We wont go any further into the other puzzles, because the next chapter describes in much greater detail how to implement these and much more.
Before expanding this sample game by adding more areas, you could flesh out the game by adding lots of detail to what weve implemented so far. Most of the added material would probably be irrelevant to the plot, but it could make the game more interesting and more fun to play. Keep in mind that youre writing a game, not a real-world simulator; try to concentrate on things that make the game more fun to play.
Some items that would enhance the airport: Add lots of incredibly overpriced and extremely dubious snack items at the snack bar. Generate random messages over the public address system once in a while, asking various people to pick up the white courtesy telephone and warning travellers not to park in the red zone. Implement other effects for other power switches in the maintenance room: what happens when you turn off power to the snack bar, or the ticket counter, or the PA system, or the automatic doors? Add lots of strange people milling about in the airport; make some of them actively bother the player, such as various fringe religious and political groups trying to hand out literature (youll want to write some wacky literature for them to distribute).
Alternatively, you could expand this game by providing some place to fly to. The player could go on to crash the plane into a remote mountain or desert island, and explore that. Or, you could take the plane to several other cities and explore.
Of course, youll probably have the most fun if you start with your own ideas and write a whole new game. The hard part is always finding the right idea; once you have the premise for your game, you will probably be surprised at how quickly you can build a map and start implementing. Refer to the examples in this chapter to help you get started. As your game starts to take shape, and you want to add more ambitious features, the examples of advanced TADS programming techniques shown in the next chapter should be helpful.
In creating, the only hard things to begin; a grass-blades no easier to make than an oak.
JAMES RUSSELL LOWELL, A Fable for Critics (1848)
Chapter Eight | Table of Contents | Chapter Ten |