An Adventure walkthrough, with bugs

Saturday, July 1, 2023

Comments: 3   (latest October 21)

Tagged: inform, adventure, colossal cave, glulxe, testing, regtest, design, endings, xyzzy, plugh

Here's the least exciting IF news of the year: I just wrote a walkthrough for Adventure!

Yes, the original Colossal Cave game, yes, the one with hundreds of maps and hint files available. The IF Archive has an Adventure walkthrough dating to 1992. I have a walkthrough in a book (Schuette's Book of Adventure Games) that was published in 1984.

Did Adventure need another walkthrough? Obviously not. So what was I up to?

To answer this, let us swoosh over to the Inform 7 source repository. In particular, the very large set of unit tests which verify that nothing's broken this week.

Testing an IF game is easy... in principle. Shove a walkthrough in; make sure the right text comes out. I have a little Python script which I like to use for this purpose. Inform 7 has a tool called intest which does basically the same thing.

Now, when I say it's easy "in principle", what I mean is it's complicated. For example, some IF games accept keystroke input or timed input. Some display images or complicated diagrams in the status line. You want your test-tool to be able to check all that stuff, not just plain text.

Then there's random events. The thief in Zork; the pirate and dwarves in Adventure. They run around administering chaos. It's no good trying to verify a walkthrough if you can take a nasty knife to the knee at any moment.

To handle this, every major IF system provides a way to put the random-number generator in not-so-random mode. With this option, the dice come up the same way in every playthrough. So the thief always appears on turn 87 (or whatever) -- as long as you stick to the script in the first 86 moves.

In Inform, this feature is the RANDOM debug command:

>random [Random number generator now predictable.]

This invokes the @setrandom Glulx opcode, which calls the glulx_setrandom() interpreter routine, which calls the srandom() Unix library function, which... let's stop there. But you can see there's a whole chain of logic to support this apparently simple problem of "test the game".

A few days ago, David Kinder sent me a message asking if he could tweak the Glulxe interpreter a bit. See, he was working on the Windows version of the test suite -- which doesn't have srandom(). Well, that's no problem. Glulxe comes with a plug-in replacement, exactly for operating systems that don't have it. (When I originally wrote the interpreter, not even all Unixes had it. It was a long time ago.)

Even better, the plug-in is guaranteed to work the same on every machine. The deterministic numbers you get from srandom() will be the same on a given machine, but no bets when you compare Linux and MacOS.

So David posted a small change request; I merged it; problem solved.

(Well, maybe not entirely solved. My plug-in is better for testing but not ideal for normal play. It's not as random as the srandom() call, which is available on a lot of platforms even if not all of them. So we might make this more configurable in the future. Or maybe not. The quality of the RNG in your IF system is not anybody's first concern!)

So far so good. But this reminded me of another issue, which was that Inform's deterministic mode is a debug command. You can't type RANDOM in a released game; the debug commands have been switched off.

So what if you want to test a release version of your game, rather than a development version?

That may sound silly. If you're testing your game, it's still in development! But testing is complicated. Maybe you're not testing the game at all -- you're testing the interpreter, using the game as a test case. Or maybe you're testing the compiler. You want to test the compiler in both debug and non-debug mode, right? Your users (game authors) need both to be reliable!

As it happens, the I7 test framework doesn't use the RANDOM debug command. It compiles the "deterministic" switch into the game directly -- it's built into the I7 templates. But this is still a bit of a sin, right? You're not quite testing the release version of the game. It's a very minor flaw, but as a developer it makes me itch.

Besides, not everybody uses I7! I have a separate test suite for the I6 compiler. That's meant to cover all styles of I6 code: with and without debug commands, with and without I7 setup code, the lot. I have to be able to set deterministic mode with no help from the game at all.

How about a command-line option? I whipped one up, dropped it in, problem solved. Again.

As it happens, I had an I6 fix that was just itching for some tests. It was an incredibly minor optimization to the I6 print statement. Saves a byte here and there. I won't get into the details.

How do you test a change like this? Run a game with lots of output; save all the output; recompile; run it again; make sure the output hasn't changed.

And what's my favorite game to test with? Adventure, of course. Specifically, Graham Nelson's 1996 port of Adventure to Inform 6.

Of course no existing walkthrough would work perfectly. Even a step-by-step transcript would account for the pirate and dwarves only in general terms: "If a dwarf appears, THROW AXE AT DWARF." I needed a walkthrough which would give that command at exactly the right time, given the deterministic RNG set up by my new --rngseed option.

So I sat down and wrote that walkthrough. And there it is! The whole game, knives and axes on schedule, all commands and responses.

(Okay, not all the responses. The test script lets you trim it down to the crucial lines. Makes for a more readable test file.)

The funny thing is, my testing turned up some bugs.

Not in my I6 fix. That's fine. No, I found some bugs in Graham's 1996 Adventure port!

(That file has been updated a few times since 1996. Here's the most recent version from 2006. But the bugs I'm describing are still present.)

As Graham's introduction notes, his version of Adventure is a close port of the 1993 TADS version by David M. Baggett. It's simplified in a few respects, though. For example, the Inform version doesn't try to track the movements of five individual dwarves. It has just one that it pops on and off stage as needed.

(That's a design decision, not a bug. Graham was targeting the early-80s Z-machine, rather than the early-90s TADS2 VM or, you know, a DEC mainframe. It made sense to cut down the game logic.)

But some changes are clearly bugs. Witt's End is supposed to be a trap. The TADS version is clear about this. Moving in any direction has just a 5% chance of getting you out -- unless you go west, in which case there's a 100% chance of being blocked by a cave-in. (Implicitly the passage you came in by. Sorry! Enjoy flailing your way out! Could take a while!)

In the Inform version, the logic is backwards. Going west always gets you back out to the Anteroom. Not a trap at all! The "cave-in" message exists in the code but can never be printed.

Similarly, when you grab the last treasure, you hear a "Cave closing soon!" message. At that point all the cave exits are supposed to be locked down. The monsters vanish. The grate is shut; the keys are gone. Any attempt to leave the cave via magic word fails:

A mysterious recorded voice groans into life and announces, "This exit is closed. Please leave via main office."

The TADS source refers to this as self.panicked. The whole point is that you're trapped. You try to escape the underworld, failing again and again, until the cave closes and throws you into the endgame.

The Inform port sets all of this up, except that it fails to turn off the magic words! The "exit is closed" message is entirely absent. So you can pop up to the surface with XYZZY or PLUGH and wander around outside... until the cave closes and drags you back underground. Bit of a surprise if you're home having dinner with the wife and kids!

"But wait," you interrupt. "If the cave is locked down at closing time, and the cave closes you get the last treasure, how can you put it in the trophy case?!"

(Trick question! The trophy case is Zork, not Adventure. I'm sorry, I'm sorry.)

No, that's an excellent point. I didn't even think of that until I was halfway through writing this blog post! How is it possible to get your full 350 points?

The TADS source only deepens the intrigue!

bonustime = 20

// start endgame this many turns after the cave closes. In the original this was 50, but since we've change things slightly (the closing timer starts once the player deposits the last treasure in the building, not once he merely sees it), we've had to make this value smaller. If we were to leave it the way it was, it would be very hard to finish the game before the lamp batteries died.

We're now looking at three different conditions for Adventure's endgame. Remember, these are all ports of the "original" 350-point ADVENT.

  • Original ADVENT: Cave-closed announcement is 30 turns (clock1) after you see the last treasure, but the clock only runs while you're underground. Cave closes 50 turns (clock2) after announcement. (I verified this by looking at the C port, which is close to the Fortran version.)

  • TADS CCR port: Cave-closed announcement is 30 turns (closingtime) after you deposit the last treasure, but the clock only runs while you're underground. Cave closes 20 turns (bonustime) after announcement.

  • I6 port: Cave-closed announcement immediately when you pick up the last treasure. Cave closes 25 turns (endgame_timer) after announcement.

So in the original game, stashing the last treasure is a bit of a challenge. If you spot it, but dawdle on getting it above ground, you could be trapped in the closing cave with that last treasure unstashed. You can still win the endgame, but you won't get the maximum score.

The TADS port dispenses with this puzzle in favor of pure dramatic tension. The "closing" announcement only fires if you deposit all the treasures and then continue to explore underground. After the announcement, you're on rails, banging on the walls until the endgame begins.

(Presumably a sensible adventurer would deposit all the treasures and then go home! The game has no "home with family" ending, though. If you QUIT aboveground after depositing all the treasures, you just get an inferior score.)

The Inform port brings back the idea of a challenge, but it's a different challenge! The "closing" announcement fires when you pick up the last treasure (for the first time). You are of course underground at that point, and you're sort of trapped underground, except not really because the magic words still work. So you have to dash to Y2 (probably) and PLUGH out.

Unfortunately, by this point you've been trained to use magic words exclusively -- they're the fastest way out of the cave. So it's not much of a challenge! You'll probably never even notice that the grate is locked. The whole "panic trying to escape" scene is essentially erased.

(There's a secondary bug having to do with the crystal bridge. It's supposed to disappear at closing time, presumably to intensify the air of panic. You can't resummon the bridge either; the black rod no longer works. The Inform version copies this logic, except there's a missing line so the bridge silently reappears next turn. Oops.)

So what do we do with this?

Every version of Adventure -- 350-point and beyond -- is an adaptation, not a clone. An adaptation can faithfully follow the original, but it doesn't have to. It's silly to criticize it just for that.

Graham Nelson's Adventure contains many small improvements over the original, and over the TADS version. Graham added subtle hints in many places. He came up with a (canonically supportable!) use for the spectacular-but-useless hanging mirror.

On the other hand, some differences are clearly bugs. The logic for the crystal bridge (at closing time) and for Witt's End is almost the same as the TADS port. And each is one small change away from being the same as the TADS port. If you're fixing bugs, those are low-hanging fruit.

The ending, well, that's endlessly debatable. (I'd love to see an analysis of the endgame logic in other versions of Adventure!)

Design-wise, I think the TADS ending improves on the original. "Cave is closing" works better as a denouement, a disorienting phase change to the game. You shouldn't have to go back and futz with treasures after you hit it. If I were updating the Inform port, I'd bring it fully in line with that version.

I might even crank up the tension by forcing the player to try to exit. Have the lamp start to flicker (dramatically, not on a timer now). When you try XYZZY or PLUGH, the lamp flickers worse. And the upper rooms are dimmer, because the sun is setting, right? When you've tried both magic words and failed to open the locked grate, then the lamp dies entirely... it's pitch black... and poof, you are transported to the Repository! How's that?

Or, alternatively, you might quit and go home without ever returning underground! I rather like that as an optional ending. But of course "cave closing" is the only real signal that you've found all the treasures. Before that point, you might just have missed some.

Interesting trail down from the original test tweak, isn't it?

Comments from Mastodon

Comments from Andrew Plotkin