This file is part of the TADS Author's Manual.
Copyright © 1987, 1996 by Michael J. Roberts. All rights reserved.

The manual was converted to HTML by N. K. Guy, tela design.


Chapter Six


Getting Started with TADS

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, it's often difficult when you're 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. We'll assume that you have a basic idea of how the TADS language works.


Designing and Implementing

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 wasn't 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 hadn't 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 didn't 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. We'd plan a little, implement it, then discover that the plan wasn't 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 couldn't 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 there's 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 it's stylistically consistent. The planet, on the other hand, has an empty, barren feel; it's spread out and there's 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.

I'm 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 it's 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 didn't 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 game's 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.


Designing a Game

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 we'll 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 don't 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.


Plot

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 game's plot isn't 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 character's 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 isn't 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 don't 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 you'll 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, aren't 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.


Setting

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, you'll 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, you'll probably find that you start to fill in the details of the plot at the same time as you're filling in the details of the setting. Many locations in the setting will offer natural puzzles: if you have a bank, you'll probably have a safe, for which you'll need to find the combination; maybe you'll need to figure out how to defeat the alarm system, or perhaps you'll need something to distract the teller; maybe you'll need to find the key to the safety deposit box. If you have an airport, you'll probably need to figure out how to get past the overly sensitive metal detectors, or you'll need to know someone's itinerary, or you'll need to find some airline tickets, or you'll need to find a badge or key or combination to get into a restricted area.

Whatever type of location you're designing, the location will often suggest a series of puzzles to be solved. As you design the solutions to the puzzles, you'll 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 you're 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 game's playability. Remember, you're 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.


Objects and Characters

You'll 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. You'll probably find it easiest to note objects and characters on the map, where they'll 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 don't 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 that's reasonable, and allowing the door to be broken down by other reasonable objects.


Implementation

Once you have the game fully designed, you're ready to start implementing. In this section, we'll 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, we'll implement a game that takes place in a small airport. Our airport will have a terminal area, a concourse, and a gate area. We'll 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. Here's the basic map we'll be implementing.


[ Map ]


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 you'd normally need a ticket to board a plane. Plus, the cockpit should be restricted to airline personnel.

Let's make the goal of the airport segment be getting out of the airport. The player will start off in the main terminal area, but won't be able to go outside the terminal - we'll 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, we'd probably have more game outside the airport, so we wouldn't 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, let's 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. (We'll implement this example up to the point that the player makes it into the cockpit; in a full game, we'd go on to let the player fly the plane somewhere else.) Now, only the pilot can go into the cockpit - the flight attendant wouldn't let a passenger into the cockpit. So, we'll 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, we'll require the player to find a pilot's uniform.

Where would the pilot's uniform be? The pilot's lounge would be a logical place; we'll put a suitcase in the pilot's 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 pilot's lounge, the player needs a magnetic ID card to put into a slot outside the security area.

We'll put the ID card out in the open, on the ticket counter in the terminal area. However, we'll 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, you'll 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. We'll leave the key with some other maintenance items in the plane's bathroom - we'll also leave a pail, sponge, and garbage bag, so that it's clear by association that the key is probably for the maintenance room.

To get into the plane's bathroom, you'll need a ticket to board the plane. We'll make the ticket fairly simple to find: we'll leave it hidden inside a newspaper in the snack bar. As soon as you pick up the newspaper, the ticket will fall out.

So, that's 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 plane's 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 pilot's lounge, get the pilot's 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.


[ Detailed Map ]


Implementing the Map

The first step is to convert the skeleton of your map into the beginnings of your game program. At this point, don't 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> 

Now, for each room in your game, make an entry that gives the room's name (sdesc, or short description) and long description (ldesc), and the other rooms that are connected to this room. Here's how to implement the first few rooms from the sample map above. For the time being, we won't 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, we're not worrying about the items contained in the rooms, or the other people around, or even the locked doors. We'll just implement all the rooms so we can walk through the map and try it out.


Implementing the items

The next step is to implement the basic objects that make up the game. As with the rooms, don't 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. It's 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.


Implementing Special Behavior

The next step is to implement all of the special behavior that really makes the game work. For example, let's 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 (we'll 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 hasn't already been found; if not, we display a message that it's been found, move the ticket into the game, and note that it's 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 it's 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, let's 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, we'll 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 can't be operated directly. The real work is done by the card slot; we'll 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 it's not already open.

Most of the remaining puzzles in this sample game are implemented in a similar fashion. You'll need to implement a couple of actors: one for the flight attendant, and one for the guard at the metal detector. In addition, you'll need a few more locked doors and special items.

We won't go any further into the other puzzles, because the next chapter describes in much greater detail how to implement these and much more.


Where to go from here

Before expanding this sample game by adding more areas, you could flesh out the game by adding lots of detail to what we've 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 you're 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 (you'll 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, you'll 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 thing's to begin; a grass-blade's no easier to make than an oak.
JAMES RUSSELL LOWELL, A Fable for Critics (1848)


Chapter Five Table of Contents Chapter Seven