After thinking a long, meandering thought process starting from Groovy Science, wandering through programming language design, and somehow following a segue into interactive stories again, a friend re-sparked my interest in IF (interactive fiction). I’ve gotten absorbed reading about IF theory and practice before, but that was about five years ago, and now I know much more about programming and a little bit more about writing. So I downloaded the TADS 3 and Inform 7 SDKs and some IF interpreters, and I got to work reading the developer reference material and actually playing some games, including Photopia, Violet, Glass, and Alabaster.
One of the first things I noticed about the IF languages was that superficially, TADS 3 the language is incredibly close to Groovy. It uses C-style imperative syntax, it has closures, it uses dynamic typing, it minimizes the difference between properties and methods, and it has what look like interpolated strings (which print automatically, meaning it takes a bit of output-rerouting to use them for anything else). Now, I’m not saying these are all great features, but I knew right away that if I was going to be comfortable enough in any IF language to implement the kinds of crazy things I find myself wanting to implement all the time, it was going to be TADS. I could port my general-purpose Groovy code to TADS as I needed to.
With that thought on my mind, I realized: Could I port the TADS IF libraries to Groovy? Maybe Groovy could provide the wonderful land of Java libraries and cross-platform-ness that it has, and maybe TADS could provide the wonderful parser it has, and maybe Inform could provide inspiration for some wonderful relational database approaches or something, and they could all come together in a sweet train wreck of paradigms. Sounds good to me… except that Groovy isn’t (in my typical experience) capable of all that TADS is as a language:
- Looping through every existing object is a built-in language feature of TADS, and it makes sense because a game in TADS is primarily composed of a whole lot of singleton objects that get reattached to each other in various ways over the course of a game session. (I suppose the same could be said of a lot of game engines.) It’s like having each superclass keep a list of weak references to its singleton subclass instances. Do draw another comparison, it’s like every class is an enum.
- TADS lets you view the stack trace. You can see which functions are being called, which arguments they’re being passed, where they are in the source code, and maybe a couple of other things (but conspicuously, you can’t see local variables). I think the primary use is for printing stack traces, but I get the impression that it’s also capable of the kind of “rule for printing the name of the lemon sherbet while listing contents” antics that Inform 7 uses (although I do suspect that usage would horribly confuse TADS programmers).
- The syntax for declaring a bunch of simple singletons in a hierarchical is-in relationship is pretty much ideal, in my opinion. It’s about as straightforward-looking as YAML, and yet it’s the same syntax that’s used for every object in the program.
- There’s a special syntax for specifying cross-referenced grammar rules for the purposes of parsing IF commands. I don’t think it’s enormously expressive—it’s BNF without frills like the Kleene star, and subclassing
GrammarProdto help with the odd cases looks to be tempting but impossible—but apparently it gets the job done. And with the help of the reflective
getGrammarInfo(), you can hijack the whole BNF-style syntax system and interpret it manually, confounding those poor traditional TADS programmers any way you see fit.
- TADS has undo! Sheesh! What programming languages can boast that? Hmmm… okay, anything with program-wide continuations or syntax-friendly monads can, but only sometimes.
With all this in TADS’s favor, is there any reason for me not to switch? Well, for my general programming purposes, there are a few that would come up every so often: lack of thread support, lack of operator overloading, lack of AST transformations, etc. But for the purposes of my IF writing, there were two excuses that nagged at me:
- I feel like I need namespaces. Eventually, I want to make or witness a gargantuan interactive story with, say, hundreds of hand-written multilinear conversation scripts. I don’t have any actual experience making that kind of story, but I’m pretty sure it’s essential to reuse the same names in similar situations. That way you can develop banter on the side in one big brainstorming pool and gradually slip them into bigger conversations like pieces of a jigsaw puzzle without having to worry about introducing name conflicts among subtly different versions of the same basic concept.
- I feel like I need better undo support. The comment on
undo()in the TADS system file tadsgen.h says that it’ll “undo all changes to persistent objects, up to the most recent savepoint,” but then it goes on to say that some early undo data might become unavailable because “The system automatically limits the UNDO log’s total memory consumption, according to local system parameters.” If my intuition is right all the UNDO log needs to know is the game transcript plus whatever hidden information was used to determine nondeterministic branching, then the UNDO log shouldn’t be nearly big enough to warrant being recycled for the sake of memory. In fact, I consider it to be the last resort; in case of a memory crisis, if at least the transcript can be dumped, then all the live data of the player’s session, complete with undo capability, can be recovered from that. I fear that what must be happening is that the TADS UNDO log keeps several snapshots of all the game’s memory, or at best keeps tabs on events like object creation and property modification.
Alas, these are only excuses. I’m pretty sure the namespaces I’m looking for can be implemented reasonably well using nested objects, and whatever undo functionality I could implement in Groovy, if it’s any improvement at all, could just be implemented in TADS in almost exactly the same way.
So why do I still feel like I’d be better off in Groovy? Existing Java graph theory libraries to mooch off of? Potential code reuse in story-intensive MU* servers? I don’t know. It’s just an irrational feeling, I think.