So, now that we’ve looked at the surviving Wander games, let’s take a glance at the Wander language. (I’m not sure “language” is entirely the right word… data file format? But I’ll call it a “language” for purposes of brevity.) I’m sure I’ll have more to say about it as I actually start using it to make a game, but for now let’s just see what we can get from the documentation.
The data from each Wander game is split into two files: essentially, one that includes the overall game info (and which has the extension “.misc”) and one that has the information on specific locations (and which has the extension “.wrld”). We’ll look at them in that order.
The .misc file starts with an introductory message. This is just what it sounds like: a message that is printed when the game starts. After that is a section on “word definitions”—defining the game’s vocabulary. It’s not actually necessary to define here every vocabulary word used in the game; words can (and indeed in the code to the surviving games often are) defined “on the fly” in actions (about which more below). However, if any words are to have synonyms, those must be defined here. (Actually, even that isn’t strictly true, but if they’re not defined here the synonyms would have to be explicitly handled in every action in which the words appear.) Furthermore, any objects that have an initial location have to be defined here as well. For the code in the surviving games, the word definition lists are divided into separate sections for verbs and objects, but according to the documentation that’s not strictly necessary; verbs and objects can be intermingled arbitrarily, and the separation is only done to make the file easier to read.
Each word in the word definition list can optionally be followed by up to three flags. The first flag just handles synonyms: if it’s not zero, regardless of the exact value, then the word is a synonym of the previous word. You can give a word as many synonyms as you want by just following it with multiple words with a nonzero first flag. For example, here’s the beginning of the word definition list in a3.misc:
ask question 1 attack hit 1 beat 2 kick 3 kill 4 buy purchase 1 steal borrow 1 purloin 2
Note that the exact value of the flag doesn’t matter, just whether or not it’s zero. (And zero is the default value, so an omitted flag can be assumed to be zero.) Thus the following code would work exactly the same:
ask 0 question 13 attack 0 hit 100 beat 99 kick 3 kill 45 buy 0 purchase 2 steal borrow 20 purloin 10
The first flag is the only one that matters for verbs, but for objects there are a few more possible flags. The second flag states the object’s starting location. 0 means the object effectively does not exist at the beginning of the game until brought into existence by some action; -1 means it starts out in the player’s possession. (Containers are not implemented, at least by default, though it may be possible to use actions and dummy locations to kludge them together in some limited form.) Finally, the third flag is five binary flags in one, each represented by one bit of the value:
- The lowest bit states that the word is singular even though it ends in an s; this is to prevent the game from interpreting objects like “princess” as plural and telling the player that “there are some princess here” (this is the example used in the documentation).
- The next bit is just the reverse: telling the game that a word that doesn’t end in an s is plural. This didn’t need to be a separate bit, and could have just been combined with the previous, but… well, it wasn’t.
- The third bit states that the listed word doesn’t need an article added. The documentation only says that this is to be used if the word “already contains an article”, but I suppose this would also be used for proper nouns. How could a single word already contain an article, anyway? Well, one thing I haven’t mentioned yet is that a “word” in the word definition list can actually be a multi-word phrase, as long as each space in the phrase is preceded by a backslash. For instance, the very first word defined in the castle.misc word list is a “channel selector knob”, defined by the line:
channel\ selector\ knob 0 -1
The 0 indicates that this is not a synonym of the previous word (it does have its own synonym, “knob”, but that’s defined on the next line); the -1 indicates that it starts in the player’s possession. (The knob is not, however, referenced in any actions or anywhere else outside the word list, so it has no actual use in the game. Oh well.)
- The fourth bit flags the word as the object’s complete description, so it’s not necessary to embed it in a sentence.
- Finally, the fifth bit states that this form of the word is to be used to describe it only when the object is in a location, not when it’s being carried or the subject of an action.
It may be worth noting that none of the surviving Wander games use the third flag at all, at least in the forms currently present; it was presumably a late addition to the language.
After the word definitions come the actions, again divided into two parts but this time mandatorily separated: pre-actions and post-actions. The actions are the core of the gameplay, defining what happens when the player types in particular commands. Since actions also come into play in the .wrld file, though, I’ll hold off on discussing them until the end. The difference between pre-actions and post-actions is that the pre-actions are matched before the local actions associated with the player’s location; the post-actions are matched after them (and therefore only come into play if none of the local actions match, or if any matching actions are set to fall through).
Finally, the last section of the .misc file contains the variable definitions. A Wander game can use up to 128 variables, numbered 0 to 127, although the top 28 of those are reserved so in practice only 100 custom variables can be defined. For each variable, the file defines its initial values and a mnemonic to refer to it by. The reserved variables already have present mnemomics (except the last three variables, reserved for future use); for instance, variable 100 is “CUR_LOC”, and holds the player’s current location.
Which brings us to the world file. The world file details all of the game’s locations. Each location entry starts with the location number (preceded by an octothorpe (#)), followed by the location’s short name, then its long description (used on the player’s first visit or after a LOOK command, or after a certain number of turns), followed by any local actions specific to that location.
(I mentioned in my previous post, by the way, that I suspected the INVENTORY command was a late, post-Adventure addition to the Wander language. Now that I think of it, the same is probably true of the LOOK command; it’s another case that it seems unlikely two people happened to come up with the same command for that purpose independently. At this point I’m pretty sure the existing documentation postdates all of the surviving games themselves—the fact that none of the existing games use that third flag in their word definitions is one hint in that direction. Another thing I just noticed is that one of the built-in commands listed in the Wander documentation, INIT, which is supposed to load a new Wander world, isn’t actually implemented in the existing Wander code, suggesting that it, too, is from earlier than the documentation.)
One of the most interesting things about the way Wander treats locations is that each location has a state. This is essentially a special variable associated with that location, with an initial value of zero. What’s especially useful about the states, however, is that a location at a particular state can be given an entry just like a new location, with its own short and long description and its own actions. Anything not implemented for that specific state falls back to the stateless location entry as a default.
The other thing I found interesting about the Wander code was the format for actions, which is what I’ll get to next. Again, actions are the directives for response to the player’s inputs. Each action, therefore, starts with the input that produces that action. Wander doesn’t have much of a parser to speak of; it does seem to drop certain filler words like “the”, and it does recognize synonyms, but other than that the command has to pretty much be entered verbatim as listed in the action. However, alternate commands for the same action can be given if separated by vertical bars (|). For instance, here’s an action in the first room of a3:
north|east|ne|nw|se|sw m="zzzZZZAAAAPPPP! OUCH!"
The “m=” part just makes the game output the given message. So if the player tries to go any compass direction from that room except south or west, the game gives that response. (The location is described as being surrounded by an “electrified chain link fence”.) Note that “n” is already defined by the game code as a synonym of “north”, “northeast” as a synonym of “ne”, and so on.
As in the word list, a command with multiple words can be specified by putting backslashes before each space. So, for instance, here’s an action from the first location of castle.msc:
follow\ road|climb|cross m="In what direction?"
So if the player enters “follow road” (or “follow the road”, or “climb”, or “cross”) the game asks in what direction.
Well, you wouldn’t have much of a game if actions could only produce messages and not change the game state. So of course there’s more to the actions than just the commands and the message. If the command is followed by a number, then the player is moved to that location number if the command matches. For instance, here are some more actions from the first location of Castle:
n 2 s 3 e 4 w 5
So if the player goes north from that location, the player ends up in location 2; south leads to location 3; and so on. It’s possible to include both a destination and a message, as in this location from the first room of library:
up|north 2 m="You climb up the steps and into the building."
Still, though, there’s more to an adventure game than just moving around. And that’s where things get interesting. For all Wander’s limitations—and it’s certainly got a lot of limitations compared to more modern adventure game languages—I kind of like the syntax of its actions. It’s a bit weird at first, but it’s easy to get used to, and it seems to me to have a certain element of charm. I don’t know of any other language similar to its syntax, but then again I’m hardly an expert on programming languages, and it’s entirely possible it is closely based on a language I’m unaware of. A commenter on Anthony Hope’s post about Wander’s rediscovery remarks that the “syntax slightly resembles META II system”, but after some cursory research on META II, I really don’t see much resemblance. (I did ask Langston if the action syntax was inspired by any computer language in particular, but I didn’t get an answer… I hope it’s not because I’d asked him too many questions and bothered him, but if that’s the case, then Mr. Langston, in the unlikely circumstance that you’re reading this, I apologize.) Essentially, after the commands (and the optional destination number) an action can include a number of fields, with their own quirky syntax. These can be test fields that test conditions that must be true for the command to match, and result fields that change the game state in various ways. All of the fields include of a letter that specifies what’s being changed or tested (“v” for a variable, “o” for an object’s location, “s” for the state of the location, “t” (for “tool”) for whether an object is in the player’s possession, etc.), followed by a symbol (?, ~, <, >, =, +, -, *, or /) specifying what kind of comparison or operation to perform, and a number specifying which object, location, etc. to use. Depending on the comparison or operation involved, many fields then follow this by a second symbol (@ or .) and then a second number specifying what to compare against, a second value for the operation, etc. The numbers in an action can be replaced by object or variable names, which are replaced by the number of the corresponding word or variable, or by a variable name surrounded by percent signs, which is replaced when interpreted by the value of the variable.
It’s easier to see how this works from an example. Here’s another action from the first location of a3.wrld:
take\ card o?card t+card m="\ Done Your account has 50 credits left. (You can type "balance" any time to find current status)."
So, the command to be matched is “take card”. “o?card” tests whether the card is in the current location; if it’s not, this action isn’t matched and the execution falls through to the default “take” handling. If it is, then the action is carried out. “t+card” adds the card to the player’s inventory, and then the message is displayed. In fact, the whole point of this action is to display the message; if this action didn’t exist, the card if present would be added to the player’s inventory anyway by the built-in handling for the “take” verb. In fact, by default, all objects are takeable. This can be overridden by pre- or post-actions, but if it’s not… well, it may lead to unintended behavior. In the a3 game, for example, are some ruffians who steal your credit card if your account balance is over a certain amount. (How they know the balance in your credit card account is a mystery.) In fact, we may as well take a look at the code behind these ruffians’ theft, because it’s a good example of a more complicated action. This code occurs in location 3 of the game:
north 5 v>6.30 t?card o+ruffians@4 o+card@4 v-6.20 m=\ "As you step into the alley two ruffians bump into you & make profuse apologies... (better check your pockets)." north 5
So, the first line in this code matches the input string “north”, but only if variable 6 (the player’s credit card balance) is more than 30 (“v>6.30”), and the card is in the player’s possession (“t?card”). If all this is true, then the ruffians and the card are both moved to location 4 (“o+ruffians@4”, “o+card@4”), and the player’s credit card balance is reduced by 20 (“v-6.20”). Also, the player is moved to location 5. The next line ensures that “north” still takes the player to location 5 even if the ruffians’ theft action doesn’t trigger.
Anyway, location 4 is the spaceport bar, and the player can find the ruffians and the card there. But if the player tries to take the card… well…
take\ card o?ruffians o?card t~card m="\ As you reach for the card the smaller of the two ruffians quickly steps on it, winking at his buddy and feigning nonchalance."
So if you enter “take card” while the ruffians are present (“o?ruffians”), and while the card is present (“o?card”) but not in the player’s inventory (“t~card”), then this message is displayed… and the card is not taken, since this action overrides the default “take” handling. The intended solution to this predicament is for the player to attack the ruffians:
attack\ ruffians o-ruffians t+card t+tokens v+2.2 m="\ \"My! What lovely sandals!\" You exclaim as you pick up the small thug by all six pseudopods and dangle him over the pitcher of foul smelling brew at their table. \"B..But I thought you Terries were pacifists! Let go of me!\" he squeals. \"Oh, you're quite right; I beg your pardon\", you agree, dropping him into the pitcher eyestalks first. As the other ruffian starts to get up from the booth to attack, you flick the pitcher containing his cohort into his lap and the two of them disappear under the table in a mass of writhing pseudopods and eyestalks. Finally extricating themselves the two ruffians escape out the door as you pocket their valuables."
So if the player types “attack ruffians”, then the ruffians are removed from the location “o-ruffians”, the card and subway tokens are added to the player’s inventory (“t+card”, “t+tokens”), and the number of subway tokens in the player’s inventory (variable 2) is increased by 2 (“v+2.2”). I do wonder about the size of the aliens… they’re small enough to fit in a pitcher? But the bartender, who I think is supposed to be of the same alien race, doesn’t seem so small, according to some of the actions in the bar…
hit\ bartender m=\ "No way! You may be big and strong, but with those six huge manipulative members he could turn you into a bloody mary!" south|out m=\ "With an amazingly graceful movement for someone his size, the bartender leaps over the bar and blocks your exit while pointing at the sign!"
(Apparently an earlier version of the game, preserved in the Wander documentation, had the bartender clubbing you to death if you tried to leave without performing the required action, rather than just blocking your exit…)
So… are the ruffians of a different species than the bartender? Or do the native Aldebaranians come in a wide range of sizes? Or are they very compressible, so despite their size they can still fit in a pitcher? Or do the bars on Aldebaran III just use really big pitchers? Who knows?
Anyway, as I was saying, attacking the ruffians is the intended solution to the problem. But because the default take action isn’t overridden for them, there’s another, unintended solution. You can just take the ruffians, go drop them off in another location, and then go back and pick up the card at your leisure. Of course, then you miss out on those two free subway tokens. But that’s less of an issue than it may seem. For one thing, there’s a place later where you can get subway tokens cheaply. But even more importantly, if you’re exploiting bugs in the code anyway, because of another oversight in the ruffian code you can get as many subway tokens as you could ever want. Note that the “attack ruffians” action never actually checks if the ruffians are present. It could, very easily, by just including the field “o?ruffians”… but it doesn’t. That means you can attack the ruffians even if they’re not in the bar… and in fact even if you haven’t met them! As long as you’re in that location, you can go ahead and type “attack ruffians” as many times as you want, getting two free subway tokens every time you do.
There’s another thing you may be wondering about the subway tokens. If there’s a variable to indicate how many tokens are in your possession, then what happens if you drop the tokens somewhere? What’s the value of the variable, when you’re not carrying the tokens? Let’s say you had three subway tokens, you dropped them and left them somewhere else, and then you attacked the ruffians and got two tokens. What is variable 2 set to? Are the three subway tokens you dropped teleported back into your possession? The answer is that Langston avoids having to deal with subway tokens in multiple places by just having them stolen if they’re not in the player’s possession. And that brings up a special field, or two special fields: “,,,” or “…”. Both of these fields allow the action to pass through; the action (if matched) is carried out, but then the interpreter goes on to match any further actions as well. The difference between the two is that “,,,” requires at least one later action to be matched in order to avoid an error message; “…” does not. So to see how the tokens are dealt with, let’s look at two relevant lines in the a3.misc file:
* t?tokens v?2.0 t-tokens ,,, * o~tokens v>2.0 v=2.0 o+tokens@0 ,,, m="\ I understand that theft is not uncommon on Aldebaran III."
The “*” is a wildcard command; this means these actions will fire if their test fields are met regardless of the player’s command. The first line is a housekeeping line ensuring that if the player is carrying the tokens (“t?tokens”) but the number of tokens is zero (“v?2.0”), then the tokens are removed from the player’s inventory (“t-tokens”)… which makes sense; there’s no need for the player’s inventory to include zero tokens. The second line is the one that handles abandoned tokens: if the number of tokens is greater than zero (“v>2.0”), but the tokens are not in the player’s location (“o~tokens”), then the number of tokens is set to zero (“v=2.0”), and the tokens object is set to zero (“o+tokens@0”), effectively removing it from the game. And the player is given a message about theft to explain the tokens’ disappearance. The “,,,” ensures that this action doesn’t prevent the player’s input from being processed as usual. (There is still one glitchy edge case Langston overlooked though… if you drop the tokens and then acquire more tokens without leaving the location, the tokens you dropped are returned to your inventory. Oh well.)
For one last example, I guess it’s about time to take a look at that bugged line in Castle that I referred to. As I mentioned in my last post, Jason Dyer encountered a bug in his playthrough of Castle that prevented him from getting two of the three endings. He was able to trace where in the source code the crash occurred; the responsible line was the following:
drop t?%INP_OBJ% s=44.1 o+mace o+%INP_OBJ% m=\ “clank … … K A B O O M ! ! The slight shock seems to have detonated some plastic explosive! “
A commenter suggested fixing this line by removing the “t?%INP_OBJ” and “o+%INP_OBJ”. That does prevent the crash, but at the cost of adding some unintended behavior… with those fields removed, the “dropped” object isn’t dropped but remains where it was, and, more importantly, doesn’t have to be in the player’s inventory in the first place! The player can trigger this command by dropping anything, whether it’s in the player’s inventory or not—or even by just typing “drop” without an object!
So let’s take a look at how that command is supposed to have worked. Again, a variable name surrounded by percent signs references the value of that variable, so “%INP_OBJ%” represents the value of the variable with the mnemonic INP_OBJ—which obviously must be the object referenced in the input. So if the player is holding the object (“t?%INP_OBJ%”), then the state of room 44 is set to 1 (“s=44.1”), the mace is moved into the room (“o+mace”), and the dropped object is also moved into the room (“o+%INP_OBJ%”).
Seems straightforward enough… so why does it crash the game? Well, I said that INP_OBJ obviously must be the object referenced in the input—but in fact no such variable is defined in the code! So when the Wander interpreter runs across this line, it tries to dereference this variable name, but it can’t find a variable with that mnemonic, so it throws an error and exits. It was this that at first led me to conclude that Castle must be the newest of the surviving games: clearly it would be useful to have a means to reference the object in the command, and if such a variable were implemented there wouldn’t be any reason to remove it from the system, so this must represent a recently added variable, recently enough that it wasn’t in the surviving documentation—or the surviving interpreter code!
So what’s wrong with this reasoning, and why do I no longer think this? Well, because I glanced over the Library code again and noticed the first action in the post-actions section of library.misc:
kill\ * o~%INP_W2% m="I don't see any %INP_W2% here"
This gives an appropriate message if the player tries to kill something that’s not in the room… but it doesn’t use INP_OBJ; it uses a different variable, INP_W2, which matches the second recognized word from the input… which, for a command with a one-word verb, is just going to be the object. Which means that INP_OBJ would be redundant, and presumably was removed from the variable list once the more versatile INP_W2 was added, along with the corresponding INP_W1, INP_W3, INP_W4, and INP_W5. (And indeed, replacing “INP_OBJ” with “INP_W2” in the offending command makes it work as intended.) Which means I was wrong about Castle being the latest of the surviving games… it was indeed almost certainly the earliest of the surviving games in their current forms, since both Library and a3 use the newer INP_W? variables. (Well, Tut could be earlier, but it’s barely a game.) I’m still convinced Jason Dyer errs in dating it to 1974—it’s clearly influenced by Adventure, which puts it at 1977 at the earliest—but it’s less of an error than I’d initially thought.
Anyway, that’s an overview of how the Wander code works. Again, it’s not as powerful as more modern adventure game languages, but it should be possible to create a decent game with it nevertheless. And I may have more to say about the language after I try to do just that…