TADS Tip No. 1

Proper Use of the "ver*" Methods

By: Mark J. Musante

Copyright 1998 by Mark J. Musante. All rights reserved.

One of the most common traps a beginner to TADS runs into is the misuse of the ver* methods. What are those? Well, each object that intends to respond to a particular verb supplies a pair of methods so that the TADS runtime (the program the user runs to play your game) can check to see if that object is a valid object for that verb.

For example, lets say you create a potion and you want your player to be able to drink it. Here's a simple definition of a potion:

potion: item
    noun = 'potion'
    sdesc = "potion"
    ldesc = "It's all bubbly. "
    verDoDrink( actor ) = {
       if ( not self.isIn( actor ) ) {
          "You have to be holding the potion in order to drink it! ";
       }
    }
    doDrink( actor ) = {
        "You drink the potion. Yum. ";
        self.moveInto( nil );
    }
;

Looks simple enough, right? We define a noun for the parser to recognize, we define an sdesc for the parser to print, we define an ldesc for the 'examine potion' command, and then we create a pair of methods which the parser uses to determine if this object is really drinkable.

In simplified terms, first the parser sees the command 'drink potion'. It then checks to see if the potion actually CAN be drunk by calling the verDoDrink() method. If that method prints out any text, then the parser concludes that the potion is not drinkable.

Our verDoDrink() method is simple enough: check to see if the player is holding the potion. Notice that all we do is print out a message if the player ISN'T holding the potion. The parser detects this condition and stops right there.

But let's say that our player did, in fact, pick up the potion before trying to drink it. The parser will notice that no text comes out of the verDoDrink() method and concludes that "drink" is a valid verb to apply to the potion. After reaching that conclusion, the parser calls the doDrink() method. The doDrink() goes ahead and performs the necessary actions: print a message telling the player what he/she has done, and move the potion somehwere inaccessible.

So, you might think, why bother having two functions? Why not write the verDoDrink() method to do all the dirty work?

The problem is that when the parser calls verDoDrink() the first time to see if it generates output (remember: if the verDoDrink() method generates output, then the parser assumes that the command was invalid, and stops), it calls it "silently". In other words, any text that was generated is NOT printed out. If text *is* generated, then the parser calls the verDoDrink() method a SECOND time so that the text is actually printed.

Now say we changed the potion definition above to remove the doDrink() method and change the verDoDrink() method to look like this:

verDoDrink( actor ) = {
   if ( not self.isIn( actor ) ) {
       "You have to be holding the potion in order to drink it! ";
   } else {
       "You drink the potion. Yum. ";
       self.moveInto( nil );
   }

What happens? If you run it, you'll notice that whether or not you pick up the potion, the game will tell you that you have to hold it! Why?

First the parser takes the players command ("drink potion"), and determines it has to run the verDoDrink() method on the potion. But, remember, it calls it "silently"; in other words, any output is hidden from the player.

Lets say that the player isn't holding the potion. In that case, the verDoDrink() method checks to see if the player is holding it, sees that he/she isn't, and prints out an error message. Since this message is hidden, the player won't see it. Next, the parser detects that output was generated by the verDoDrink() method, so it runs the method again, this time WITHOUT hiding the output. So we get the error message we expect because, again, the player isn't holding the potion.

Now lets say our intrepid player picks up the potion and then drinks it. Again, TADS's parser calls the verDoDrink() method silently. So the method determins that the player is holding it, it outputs the "yum" message, and moves the potion to 'nil'. The parser then says "a-ha! output was generated!" so it calls the method AGAIN, but this time with the output turned on. What happens? The "if ( not self.isIn( actor ) )" line executes and, since the potion was moved the first time this method was called, it figures out that the player is no longer holding it! It prints out the "You have to be holding..." message this time round.

The bottom line is: do not "change the game state" in the verDo* or verIo* methods. Changing the game state includes: modifying variables, moving objects, starting daemons, creating new objects, and so on. Just about all a verDo or verIo method should do is CHECK the variables/objects in a game and print out a message only if there's a problem.

You can take a look at sample code which demonstrates this problem if you like.