A TADS Tip Sheet, version 0.3b - Part 2 Concluded from Issue 77 CHECK OUT THE TADS DEBUGGER. One of the coolest things about TADS is that it has a fabulous source code debugger. This was one of the major perks for buying the shareware version of TADS back when it was shareware. And now that TADS has gone freeware the debugger is freely available at your disposal. At least, if you use Macintosh or MS-DOS computers it is. At time of writing the debugger wasn't available for any other platforms, but since the source code is available I'm sure busy hands are at work as we speak, porting the program to every platform under the sun. So be sure to check the thing out if you can get your hands on it. It lets you run a game and step and trace through the source, making it really easy to find bugs. You can even go in and change variables and stuff, making it quite simple to test your code. The Mac version is particularly nice, as it uses multiple windows to display the game, the command line for the debugger and the source code. In fact, at time of writing the Macintosh is the best platform for writing TADS games with (in my opinion) for this very reason. DON'T USE "THING". Never create an object of type "thing". You'll get a runtime error if the player tries to pick it up. Always create an object of type "item" if you want something small and takeable. An item with "thing" as its class is, of course, just fine. LOCAL STATEMENTS. Local statements must be the first statements to appear in a block of code. Otherwise you get a "general syntax error." As mentioned in the manual on page 71, the only kind of code that can precede a local statement is another local statement. Thus, this next code segment won't compile because its local statement is preceded by something else: superFunction: function { "Hello there! "; local burp := true; } This (admittedly rather pointless) function will work flawlessly if you put the local statement at the top, before any other statements within the brackets. WHY THE HECK WON'T FLOATING ITEMS, HIDDEN ITEMS OR OBSTACLES WORK? In all likelihood you've defined something in the game as being a floating item, hidden item or obstacle without also defining it as being a member of whatever class it needs to be. In other words, this won't work: tree: floatingItem but this will: tree: fixeditem, floatingItem (of course, it may sound like a bit of a contradiction in terms to have a tree that's both fixed and floating, but there you go.) If you look in adv.t you'll notice that floatingItems and hiddenItems and obstacles are all defined as objects rather than items. That means they contain no code of any kind. The class is being used as a marker class rather than one from which code is inherited. Note that if you've coded your stuff correctly and it still doesn't work then it's possible that you neglected to include the preinit() code that sets up the list of all floating and hidden items in the game. WHENCE DOUBLE-QUOTED STRINGS? By "double-quoted" I mean strings of text that are delineated by quotation marks rather than apostrophes. There's a crucial difference. TADS displays the former whenever it sees them but won't display the latter unless you explicitly use the say() function. In other words, this won't work: if ( self.colour = "red" ) self.colour; The runtime will automatically display the word "red" whenever it evaluates this piece of code. You probably don't want that. Instead you should do this: if ( self.colour = 'red' ) say( self.colour ); Why does TADS have this unusual double-quoted string concept? Well, because it's very useful. Adventure games are constantly displaying text. It'd be extremely tedious to have to say something like "printf( 'blah blah' );" or whatever all the time just to get words on the screen. Double-quoted TADS strings are a useful shortcut. This is explained on page 21 of the printed manual. "OF" IS A SPECIAL WORD IN OLDER TADS VERSIONS. As described on page 32 of the manual, the word "of" is a special word that gets removed by the parser in older versions of TADS. If you want to use it in some other context (say, "accuse Ronald of murder") then be sure to upgrade to version 2.2, which lets you use "of" as a preposition and removes the previously hardcoded restriction. PLURAL AND SINGULAR PROBLEMS. Let's say you have an object somewhere with a noun of "blinds". Let's say you also have another object elsewhere with a plural of "blinds". Now you won't be able to refer to the item with only a noun set to "blinds" - you'll get the "I don't see that here" message. Both have to be either noun only or plural only. CONDITIONAL VOCABULARY. TADS doesn't permit conditional vocabulary in noun and adjective definitions. It sets up a table of vocabulary words at compile time. So something like this is not legal: noun = { if ( self.isBig ) return( 'big' ); else return( 'small' ); } However, starting with version 2.2 you can add and delete words at will using the addword() and delword() built-in functions. These don't let you set up conditional code like the example above, but do let you add and delete vocabulary words at runtime. Very handy feature! (note however that there is a bug in the Macintosh version of the TADS runtime at time of writing. If you add a word using addword() and then undo that move then the word is not removed as it should be.) SPACES AFTER STRINGS. It's usually good form to add an extra space after a string of text, thus: ldesc = "It's an ordinary turnip. " instead of: ldesc = "It's an ordinary turnip." Why? Well you never know what text might be displayed next. If, say, a daemon displays some text without first printing a "\b"; sequence to add a blank line you'll get your sentences running together. Adding a blank space prevents this from happening. TADS IS OBJECT-ORIENTED. It really is! Don't code up objects with endless case statements and if-else statements when you can inherit properties and methods from classes. You'll find the code is much more elegant and easy to understand and often takes up less room as well. Thus, this following piece of code is a Really Clunky Way to do things: bananaTree: fixeditem, floatingItem sdesc = { switch( Me.location ) { case Forest: "big"; break; case Greenhouse: "small"; break; case Jungle: "enormous"; break; } " banana tree"; } [ etc etc ] locationOK = true location = { if ( Me.location = Forest or Me.location = Greenhouse or Me.location = Jungle ) return( Me.location ); } ; This is a somewhat better way to do the same sort of thing: bananaTree: fixeditem, floatingItem sdesc = { Me.location.bananaTreeString; " banana tree"; } [ etc etc ] locationOK = true location = { if ( Me.location.hasBananaTree ) return( Me.location ); } ; Then you could set the hasBananaTree property to nil in the "room" class, so that all other rooms would inherit this nil value. Special locations could have the property set to true. Likewise, those special locations could have their bananaTreeString properties set to display the appropriate message. What's the advantage of doing it this way? Well, putting special case code into special rooms means you don't have to hardcode a whole pile of unwieldy conditional coding into the floating item. Also, special case stuff (ie: the forest having a tree or whatever) is associated with special case locations. (note: this isn't the world's greatest piece of sample code as it has one major inconsistency in it. That is, unless you've also modified the chair item class in adv.t to pass the hasBananaTree value through, the tree will mysteriously vanish if you sit down on anything. Just something to keep in mind!) BREAKING OUT OF THE RUNTIME. Sometimes TADS gets stuck and only displays an error message instead of accepting input. Other times you type something but absolutely nothing is displayed - the game just returns you to the > prompt. These usually indicate a bug in your TADS code. In either case typing the special command $$ABEND in the runtime window should force the runtime to quit. BEWARE CONTROL CHARACTERS. It seems that accidentally embedding a control character in your TADS source will often cause the compiler to bomb. If you're getting mysterious compiler errors from a piece of code that looks perfectly legitimate, you might have accidentally typed an invisible control character into your code - maybe your finger slipped off the shift key or something. In cases like this it's a good idea to run your source through a filter program to eliminate any possible control characters before trying to figure out why an otherwise reasonable-looking piece of code isn't working. For example, Macintosh users of BBEdit can use that text editor's "Zap Gremlins" feature to toast all control characters. BEWARE OF #pragma C+ Remember that TADS supports two different styles of operators - its own style, and the style used in C. There are some similarities and some differences between these two styles. TADS lets you choose which style of operator you want. If you include the header "#pragma C+" at the start of each source code file then the compiler will use C style operators. If you don't include this header or include the header "#pragma C-" then TADS will use its default style. The thing to beware of is that some examples of TADS coding out there use the traditional style and some the C style. The vast majority of the code I've seen uses the traditional style, but I've noticed a handful of code examples (usually written by Mike Roberts in his documentation) that use the C style. This can lead to problems if you copy a chunk of code that uses one style operator and paste it into another file that uses the other style. So, let's say you see some code that includes this line: if ( v == inspectVerb ) That was written in the C style. The traditional TADS operator style would have read like this: if ( v = inspectVerb ) Likewise, you might see some code that looks like this: if ( self.value != nil ) The traditional TADS way to code that is: if ( self.value <> nil ) The other common problem operator is the assignment operator. In the C style it's this: trombone.noiseValue = true; but in the traditional TADS style it's this: trombone.noiseValue := true; So be sure to check which operator method the code you're using is written in. If the code contains the line #pragma C+ at the start then you know for sure it's written in the C style. However if it doesn't have anything then you're best off checking the code carefully to make sure that it is, in fact, written in the traditional TADS style. Some of the operators are compatible between styles - but others are not and will cause the compiler to choke. The C style operators are described in detail in the TADS 2.2 Release Notes. ADD IN THE DEBUG FUNCTIONS The standard debugTrace() function simply lets you enter the debugger's command line mode when you're running it. It's useful, but recent versions of TADS support a much niftier extension to the function. This extension turns on a diagnostic mode when you're playing a game. Even more usefully, this diagnostic mode works with the standard runtime as well as with the debugger program. The diagnostic mode works like this. Let's say it's turned on and you type the command: >examine the iron bench The runtime then displays the following extra information: . Checking words: ... examine (verb) ... the (article) ... iron (adj) ... bench (noun) . Checking for actor . Reading noun phrase ... iron (treating as adjective) ... bench (treating as noun) ... found objects matching vocabulary: ..... white wooden bench ..... cast iron bench . executing verb: examine .. setting it: cast iron bench The runtime then displays the normal response to the command; in this case: You're looking at the cold cast iron frame of an old-fashioned park bench. It seems to have been painted recently with glossy black enamel. Note all the useful information. The game breaks down the command, telling you which words it's treating as nouns or adjectives, then lists all the objects in the game that match the supplied input. In this case my game contains two items that have 'bench' as a noun, but the game disambiguates further and only selects the cast iron one. It also performs a setit() on the bench item. To add this handy feature to your game just add the following verbs: debugonVerb: sysverb verb = 'debugon' action( actor ) = { if ( debugTrace( 1, true ) ) "Debug diagnostic mode engaged. "; abort; } ; debugoffVerb: sysverb verb = 'debugoff' action( actor ) = { if ( debugTrace( 1, nil ) ) "Debug diagnostic mode disengaged. "; abort; } ; This problem with setit() is due to be fixed in the next version of the TADS runtime. "it" IS A PROBLEM WITH numObj AND strObj OBJECTS Here's an obscure one. Let's say you have a method in a numObj or strObj object. Here's a silly example of this: narfVerb: deepverb verb = 'narf' sdesc = "narf" doAction = 'Narf' ; numObj: basicNumObj; verDoNarf( actor ) = {} doNarf( actor ) = { local i, len; len := self.value; if ( len = 0 ) "No narf. "; else { "\b\t"; for ( i := 1 ; i <= len ; ++i ) "Narf! "; } } This pointless piece of code works like this: >narf 4 Narf! Narf! Narf! Narf! The problem is that TADS will then set the value of "it" to numObj. Which means that if the player types, say, "examine it" after issuing the "narf" command, nothing will happen: >narf 3 Narf! Narf! Narf! >examine it > This occurs because the numObj object hasn't got any verb methods defined for any common verbs. Exactly the same thing will happen with strObj objects, thus: >say "hello" You say "hello." >eat it > The easiest way to deal with this problem is to put the line setit( nil ); into your doNarf method. This resets the value of "it" to nil, safely avoiding the whole mess. A more complex solution is to create a universally accessible floating item that has no vocabulary words associated with it, then setting 'it' to be that object. This allows for fancy tricks like this: >say "hello" Okay. You say "hello." >x them I'm not sure what you're referring to by "them," because the last thing you referred to was the text "hello." WHERE IS THE NUMBEREDOBJECT CODE? The code for handling numbered objects seems to be missing from the 2.2.1 adv.t distribution. Here it is. /* * numbered_cleanup: function * This function is used as a fuse to delete objects created by * the "numberedObject" class in reponse to calls to its * newNumbered method. Whenever that method creates a new * object, it sets up a fuse call to this function to delete * the object at the end of the turn in which it created the * object. */ numbered_cleanup: function( obj ) { delete obj; } /* * numberedObject: object * This class can be added to a class list for an object to allow * it to be used as a generic numbered object. You can create * a single object with this class, and then the player can * refer to that object with any number. For example, you can * create a single "button" object that the player can refer to * with "button 100'' or "button 1000'' or any other number. If * you want to limit the range of acceptable numbers, override * the "num_is_valid" method so that it displays an appropriate * error message and returns "nil" for invalid numbers. If you * want to use a separate object to handle references to the * object with a plural ("look at buttons"), override * "newNumberedPlural" to return the object to handle these * references; by default, the original object is used to handle * plurals. */ class numberedObject: object adjective = '#' anyvalue( n ) = { return n; } clean_up = { delete self; } newNumberedPlural( a, v ) = { return self; } newNumbered( a, v, n ) = { local obj; if ( n = nil ) return self.newNumberedPlural( a, v ); if ( not self.num_is_valid( n ) ) return nil; obj := new self; obj.value := n; setfuse( numbered_cleanup, 0, obj ); return obj; } num_is_valid( n ) = { if ( n = 0 ) { "There aren't zero "; self.pluraldesc; " here! "; return nil; } else if ( n > self.maxNum ) { "There aren't that many "; self.pluraldesc; "! "; return nil; } else return true; } dobjGen( a, v, i, p ) = { if ( self.value = nil ) { "You'll have to be more specific about which one you mean. "; exit; } } iobjGen( a, v, d, p ) = { self.dobjGen( a, v, d, p ); } maxNum = 10 ; Note that the HTML version of this file normally lives at: http://www.tela.bc.ca/tela/tads/authoring/tads-tip-sheet.html - o -