From Inform 7 to TADS 3

From time to time, an author who has been writing interactive fiction in Inform 7 gets curious about how easy or difficult it would be to switch to TADS. Eric Eve (who has used both systems) wrote a good comparison of the two; you’ll find links at the end of this article; but by now that article is a bit outdated. For one thing, Eric himself later created the adv3Lite library for TADS 3, and the view of TADS in that article doesn’t include the new features. In any case, that article is a feature comparison; it’s not a guide for those who might like to switch.

So I made a list of a baker’s-dozen things that folks who already know Inform might want to know about what to expect when they try TADS. There’s far more to the story than I have room for here, but I’ll hit the high spots. Hopefully this quick summary will get you on the road without too many speed bumps.

And by the way, all of the references to “Inform” in this guide are to Inform 7. Inform 6 is a different beast altogether; I’d have to brush up on it big-time before I’d dare write about it. Same deal with TADS 2. It still exists, but I won’t be referring to it here.

I highly recommend using adv3Lite in TADS 3 as opposed to the default adv3 library. The “Lite” part is a bit of a misnomer. It will do everything you might want, and with less overhead in terms of learning new stuff. Plus, it has a few handy features that Eric cribbed from Inform, notably scenes, regions, and test scripts. These are not available in the standard adv3 library, but in adv3Lite they’re ready to go.

If you’re working in a Windows PC, you’ll naturally want to download Workbench as part of your TADS installation. It’s a nice enough IDE, though it’s not as nice as the Inform IDE. If you’re on a Mac or a Linux machine, you have two choices. You can run Workbench in a Windows emulator, such as Crossover, or you can use the free VS Code editor with the TADS 3 plug-in. I’m on Windows, so I can’t give specifics on the latter possibilities, but I’m told they’re quite manageable.

In no particular order, then, here’s the stuff you might like to know:

1. Object-oriented programming. Inform is rules-based, but TADS is object-oriented. Everything you’ll create will either be an object or embedded in an object. This helps keep the code tidy, and it’s also a way of insuring that the compiler and parser know what’s what. (See section 2.2 of the “Learning” document.)

2. C-style code. If you’re ever done any programming in a C-type language, you’ll soon feel right at home in TADS. If not, you’ll be facing a forest of curly braces and semicolons. Every program statement ends with a semicolon, except for the statements in object declarations. That is, when you write a new object, you’re likely to be adding code to it like this:

bulk = 25
isPowered = nil
isFragileMsg = 'Careful. This is fragile. '

These lines are property declarations. They don’t end with semicolons. See section 2.2 of “Learning” for more on this.

And by the way, this example is a hint that you can (and should) give readable names to your own names for objects, properties, and methods.

3. Case-sensitive. TADS code is case-sensitive. That is, if you create an object called apple and later refer to it as Apple, the compiler will stop you. But if it’s a property declaration, the compiler won’t care, and your code may not work as expected. For instance, the library code defines isFixed as a property of what Inform authors would call scenery. If you add a property declaration to your own in-game object like this:

isfixed = nil

…the compiler will just assume it’s a new and different property. This results in hard-to-find bugs.

Oh, and by the way, “nil” is a reserved word in TADS. It functions as false or zero.

TADS authors normally use camel case. The first letter is lower-case, and then capitals are used where needed inside the term. However, if you create a class, it’s usual to capitalize the first word. This is purely to make it easier to see what’s what. It’s not a requirement.

4. Indents are for convenience. In Inform, unless you’re using the obsolete syntax with “begin;”, indentation matters. In TADS code, indentation is purely for convenience. But it’s a really good idea to use it, as it makes your code more readable.

5. Templates. You will be making extensive use of the library’s templates. (You’ll probably never need to create one of your own.) A template is a way to speed up your writing. It’s a convention that the compiler understands. Here’s a simple object that is written using the template for the Thing class:

rubberBall: Thing 'rubber ball; big red round; sphere' @playground
"The ball is big, red, and round. Perfect for kicking. "
;

This code seems to be just a bunch of stuff spewed into the file, so at first it may be bewildering, but in fact the stuff is in a specific order (which you can’t change) and uses quotation marks, single and double, in a specific way (which you also can’t change). The template allows you to define the vocab property using single quotes, the location using the at-sign, and the desc (description) property using double-quotes. Again, this is explained in more detail in section 2.2 of “Learning TADS 3 with Adv3Lite.” Also, you’ll notice that the object declaration ends with a semicolon.

6. The containment structure. As in Inform, every object in your game will either be a top-level object (that is, either a room or nowhere), or else it will be contained in another object (such as a container or a surface). There are two ways of doing this. You can use the at-sign, as shown above, or you can use the symbols +, ++, +++, and so on. When an object declaration is preceded by a +, it will start the game in the nearest object above it in the code that has no +. Objects whose declarations are preceded by ++ will start the game in the most recent object with a +. And so on. See section 5.1.3 of “Learning” for more on this topic.

The two methods are equivalent. The + method is easier if you’re adding scenery to a room, for instance, because you only have to type one character. Also, if you later change the code name of the containing object, the contained object will ride along. The tricky bit is, if you should later do any block-copying of code, you can easily end up with the wrong number of + signs. This can either stop the compiler or result in hard-to-find bugs.

7. The vocabulary. In the example above, the vocab property was declared using a single-quoted string with a semicolon in it. This is the vocab property, and it has a specific form. The first bit (before the first semicolon) is the name the parser will use when printing messages that the player can read. The second bit is a list of alternate adjectives that the player can use; the third bit is a list of alternate nouns that the player can use. If the object is plural, you park another semicolon near the end and add the word them. This tells the compiler and parser all they need to know.

The vocab works very much like an Understand line in Inform. The main difference is that the printed name (in this case, “rubber ball”) has nothing to do with the code name of the object. The object could be called smizzleBoggy in the code. The compiler wouldn’t care, and the words “smizzle” and “boggy” would not be understood by the parser. For more on vocab, see section 3.1 of “Learning.”

8. Room exits and doors. Room exits are declared in a room object (that is, an object of the Room class) like this:

north = patio
east = diningRoom
south = masterBedroom

These are one-way connections. TADS does not attempt (as Inform does) to guess what the reverse connection is. Let’s say this code is in a Room called frontHall. In order to allow the player to get back to frontHall from masterBedroom, you would have to include

north = frontHall

…in the masterBedroom declaration. At first this may seem a bit of extra work, but the advantage is, you can design your map however you like without having to hold the compiler’s hand so it won’t create room connections that it thinks you intended.

Doors work the same way. You have to have two separate doors, one in each room. They’re connected with a property called otherSide. (And they’re entirely movable, by the way. They’re just objects.) Not surprisingly, if there’s a door between the two rooms mentioned above, you would do something like this:

north = frontHallSouthDoorBackside

There’s a lot more to room connections than this, and it’s well documented. This is just a quick overview to help you understand something you’ll be doing a lot. Speaking of which….

9. The documentation. At first glance, the documentation (which will be downloaded along with adv3Lite) may be intimidating. Why are there four manuals?

Start by reading the Quick Start Guide. Then go through the Tutorial. Don’t try to get creative with the simple game described in the Tutorial; you’re not ready for that yet. (I’m speaking from experience here. My first venture into TADS, many years ago, was painful because I started trying to add features to the tutorial code.)

Learning TADS 3 with Adv3Lite is a good guide. You can read a chapter two at a time, and learn all sorts of stuff. Once you’re starting to get comfortable with the system, you’ll be able to use the Find command to search Learning for stuff you want to know. But it doesn’t cover everything. the Library Manual goes into more depth, but its table of contents branches to different pages, so it’s not searchable as a whole. To learn about creating new Actions, for instance, you have to start with the “Action Results” page.

The System Manual is about the TADS programming language itself, not about the adv3Lite library. It’s useful if you want to look up something about string manipulation, for instance, but it’s not something you’ll need too often.

The Library Reference Manual is, yet again, a different kettle of fish. It’s a massively cross-linked reference to all of the code in the adv3Lite library. When you’re first learning TADS, you can safely ignore this, but as you become more proficient, you’ll find it quite valuable. This is not the place for a discussion of how to use it.

10. Setting up test scripts. This important feature works almost identically to how it works in Inform. Here’s a simple example:

#ifdef __DEBUG
Test 'stabbing'
[
'gonear jerry', 'purloin pitchfork', 'stab jerry'
]
;
#endif

The #ifdef and #endif lines are not strictly necessary. They’re instructions to the compiler, telling it that it’s to include this code only in a work-in-progress, not in a release build. But it’s good practice to include them. For one thing, if you don’t, when you try to do a release build the compiler won’t cooperate, because the Test object is not included in the release build, so you’ll have to go through your whole game and either add that code or delete the test scripts.

You’ll note that the debugging verbs gonear and purloin are implemented. TADS lacks the handy commands showme and actions, unfortunately. That’s pretty much the only downside I’ve found so far.

11. Organizing your work in multiple files. In Inform, the game you write will all be in one massive file of code (unless you’re using Extensions, but normally you wouldn’t edit the code in an Extension). In TADS, it’s normal and indeed good practice to separate your game into multiple source code files. You might have one file for the rooms and scenery, another for the movable objects, another for your built-in hints, another for your newly defined Actions, and so on.

This makes it a bit easier to find stuff when you’re jumping around working on a specific part of the game, but it also has a practical advantage: TADS will compile your work-in-progress much faster than Inform will, because TADS only needs to recompile the code files that have changed since the last time you compiled. Inform has to compile the whole thing from the ground up every time.

12. Creating new actions. The library contains quite a variety of verbs that the player can use, but there will come a time when you’ll need to add a few of your own. Part IV of the Library Manual is your go-to for an exhaustive discussion of how to do it, but let’s take a quick look. The syntax is a bit snarly, so we’ll look at a quick example and then take it apart. Here’s a new Action I created for a game I’m working on:

DefineTAction(Stab);
VerbRule(Stab)
'stab' singleDobj
: VerbProduction
action = Stab
verbPhrase = 'stab/stabbing (what)'
missingQ = 'what did you want to stab'
;
modify Thing
    dobjFor(Stab) {
        verify() {
            illogical ('{The subj dobj} {is} not something that\'s in need of stabbing. ');
        }
    }
;

There are three things going on here, each in a code block that ends with a semicolon. First, a one-line declaration of a T action. A T action takes one object. In this case, it would be something like “stab Jerry”. A TI action takes two objects (“stab Jerry with toothpick”). An I action, such as the library’s “look” action, has no object. This is all explained in great detail in the Library manual. I’m just shining a flashlight on it.

The VerbRule adds the vocabulary for the new action. It also refers back to the name of the newly defined action, in this case Stab. We find that the ‘stab’ command can take only a single direct object. (The letters Dobj show up a lot, sometimes as gDobj, sometimes as dobjFor, sometimes as just plain dobj. Get used to it.) The rest of the VerbRule is pretty much for the internal processes, but be careful: You’re defining an object here and declaring its properties. If you accidentally type “verbphrase” instead of “verbPhrase”, that’s a bug.

The third thing we do is use the modify keyword to add some new code to the Thing class. Every object in your game that derives from Thing (and that will be most of them) can now respond to the “stab” command. You can read up in the docs about how to define dobjFor blocks. All that need concern us here is that by default, stabbing something is illogical. If the player tries it, the parser will not allow the action, and will print the message. To allow the player to stab some particular thing, we would then add something like this to the object that’s in need of stabbing:

dobjFor(Stab) {
    verify() { logical; }
    check() {
        if (hasAlreadyBeenStabbed) "He's lying there bleeding. Why do it again? ";
    }
    action() {
        hasAlreadyBeenStabbed = true;
        "You stab Jerry repeatedly with the pitchfork. ";
    }
}

This is a poor example, because naturally the player would need something to stab with. That would require a different action (StabWith), which would be a TI action, and we would have to add code for the iobj (indirect object), which in this case would probably be the pitchfork. But that’s too deep for this quick overview, so we’ll move on.

13. Text substitutions. This very convenient feature for adding variation to the game output works very much the way it does in Inform. The main difference is, you’ll use pairs of angle brackets instead of square brackets. Here’s a totally spoilery example from the game I’m working on:

"The silver nightingale trills <<one of>>a winsome<<or>>a limpid<<or>>a lovely<<or>>an exotic<<at random>> melody. <<if (mynahBird.canSee(nightingale))>>The mynah bird gazes <<one of>>intently<<or>>fixedly<<or>>longingly<<or>>enraptured<<at random>> at the nightingale.<<end>> ";

There’s more to text substitutions than that. For instance, you’ll use <q> to add an opening quotation mark and </q> for a closing quote. In place of Inform’s [paragraph break], you’ll use <.p>. To learn the whole system, you’ll need to read the manual.

By the way, it’s good practice to end each of your texts with a space character. TADS is generally better than Inform about figuring out how to make the output to the player look nicer; it will deal with that space character as needed, so don’t worry, just do it.

14. Instead of Instead. Inform invites authors to use the Instead statement to define specific actions rather than tinkering with the Before and Check rules. In TADS, most of your action handling will be in the dobjFor() blocks in specific objects, but there are occasions when an Instead rule might come in handy. Adv3Lite provides a handy widget called a Doer, which works very much like an Instead rule. With a Doer, you can intercept an action and turn it into an entirely different action during a specific scene or when the player is in a specific room, for example. I’ll let you read up on Doers in the “Learning” manual.

Links. To learn more, you can visit Eric Eve’s comparison article in Brass Lantern. Eric discusses the adv3Lite library here. You can download TADS here, and adv3Lite here. The adv3Lite download includes a Quick Start Guide that will tell you how to install it.

What’s the point? The differences between Inform and TADS are not quite as large as may at first appear. When you’re writing a game in Inform, everything you write, except the stuff within quotation marks, is computer programming. It’s cleverly designed not to look like computer programming, but that’s what it is. My personal view is that Inform’s pitch is, “Hey, don’t be intimidated! It’s not like that awful programming stuff. It’s easy!” Whereas TADS’s pitch is, “You’re programming. Get used to it.”

The underlying difference is that Inform is rules-based while TADS is object-oriented. This is not the place for a full explanation of what that means, much less for a debate about which is better. The fact remains, most of what you’ll be doing, as an author of parser-based interactive fiction, is creating objects and defining what happens when your reader/player/user tries to examine or manipulate the objects. To me, the object-oriented paradigm is just easier and more natural. As long as you’re not intimidated by nested curly braces, you’ll be just fine.

This entry was posted in games, Interactive Fiction, technology, writing and tagged , , , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s