The Imitable Process of Ryan Veeder: Basic Autosaving in Inform 7

Ryan Veeder’s Authentic Fly Fishing autosaves your progress! This is kind of noteworthy, at least in the realm of text adventures. I don’t think Ryan Veeder’s Authentic Fly Fishing is the first text adventure to autosave your progress, but it might be the first game to do it in Inform 7.

Why bother with this feature for this particular game? Well, it was kind of necessary to make another feature work: Ryan Veeder’s Authentic Fly Fishing also uses the real-world date for certain “real-time” elements. The game can’t really keep track of these elements (say, counting how many different days you’ve played) if players are able to “change history” by loading outdated save files.

But there’s another reason, one which may more likely be applicable to your own personal design interests: Ryan Veeder’s Authentic Fly Fishing is supposed to be an extremely casual experience. There is no urgency for the player to reach the ending. (There is no ending.) It’s not supposed to be a battle of wits between the author and the player. You’re meant to visit the world of Ryan Veeder’s Authentic Fly Fishing for however long you want, whenever you feel like it, and maybe you’ll make some “progress” by finding something new, or maybe you won’t. It’s supposed to be very chill.

Requiring players to manually save their state at the end of a play session and then restore their state the next time they show up (being careful not to restore an old file and lose progress!) would detract from the casual experience I wanted to create. So it was doubly important for me to take the burden of progress management off the players’ shoulders.

Maybe you’d like to do something similar with your own Inform 7 project. Here’s how you can do that!

The basic principle is pretty simple: Although mostly self-contained, games made in Inform 7 are able to create, write to, and read from external files, albeit in a somewhat limited fashion. We just use one or more of those files to track the player’s state, writing a new version of a file whenever the player does something noteworthy. Then, when the player starts up the game again, we have the game read the file(s) and recreate the player’s state.

Maybe this is too obvious to point out, but this type of autosaving, auto-restoring paradigm doesn’t pair well with tricksy old-school text adventure puzzle design where, you know, the player gets a cookie in Act 1, and then a critical path puzzle in Act 3 requires the cookie, so if you eat the cookie in Act 2 you’ve soft-locked the game/put it in an unwinnable state. If you did that kind of thing in an autosaving game, the only way for the player to beat the game would be to start all over!

Speaking of which, allowing the player to start all over requires some extra steps. Remind me to cover that after we’ve managed to make the base case work.

I mentioned there are some limitations on how Inform 7 games can interact with external files. Specifically, a file must be in one of a few different formats: A text string (which isn’t very helpful for our purposes), a binary (which, um, I don’t know what to do with), or an Inform 7 table. And you can only put certain things in these tables! I’ll quote Writing with Inform, §23.13:

One unfortunate restriction must be kept in mind. Some of what is stored in tables is solid information whose meaning never changes: the number 342, for instance, means the same to everyone. But other information depends entirely on the current location of certain structures in memory – for instance, a rule is internally referred to by its memory location. This potentially changes each time Go or Replay is clicked, and so it is not safe to pass it from one copy to another, or from one project to another. The only tables which Inform allows us to write into files are those containing “safe” data: numbers, units, times of day and kinds of value with named alternatives. Scenes, rules or rulebooks, in particular, are not allowed.

So we’re going to have to express our autosave data as a table, and express it with “safe” data types. It turns out the most useful safe data types are numbers and snippets of text (in quotation marks). (We won’t be using any such text in the examples in this post, though.)

At this juncture, we have to define “progress” for the purposes of a given project, so we can format our tables appropriately. Different games measure save-worthy progress as very different types and amounts of data:

  • In Super Mario Brothers, it’s just a matter of which level you’ve reached (so, a single numerical value).
  • In Super Mario World, it’s a matter of which level exits you’ve found (so, 96 binary values).
  • In Super Mario RPG, it’s a combination of the experience levels of all your party members, what items they have equipped, what other inventory items you have, how many coins (and Frog Coins) you have, which quests and sidequests you’ve completed, your high scores in various minigames, whether or not you’ve opened each of the various treasure boxes, and other stuff I forgot or don’t know about (so, a lot of different kinds of values).

I’ve written several very linear text adventures where the only critical data worth saving between play sessions is which stage of the story the player has reached. In Winter Storm Draco, for example, you can act freely within each “scene” of the game, but there’s only one way to move on to the next scene, and there are very few things you can do in early scenes that will affect later scenes in any fiddly, hard-to-track ways. It’s also impossible to go back to an earlier game state: Once you find your way out of the woods near the beginning, you can’t go back and get lost again. For a game like this, we can track progress with a single number.

Let’s imagine an incredibly simple story along these lines: The player character is a cat burglar. You need to find a blunt instrument, bash a glass case open, grab a ruby, and escape. We are deciding that these events cannot happen in any other order, they are not reversible (well, mostly), and the player cannot subvert our story by making the thief decide to steal a painting instead or anything like that. (Our project doesn’t sound very exciting when I describe it that way, but super-linear structures like these rest at the heart of some excellent games! Or, I like to think so, anyway…)

Let’s make this into a conventional, non-autosaving Inform 7 game.

"The Nicest Ruby"

Hall of Jewels is a room. "An incredible collection of gems surrounds you."

Section - The Display Case

The display case is a closed openable transparent container in Hall of Jewels. "In pride of place is a [if the display case is open]broken [end if]display case[if the Nicest Ruby is in the display case] containing the Nicest Ruby[end if]." The display case is fixed in place.

Check opening the display case:
if the display case is open:
say "That's already open, so to speak." instead;
if the player does not carry the heavy candlestick:
say "You can't break the glass with your bare hands. You need a blunt instrument." instead.

After opening the display case:
say "You smash the candlestick into the glass, smashing it open!"

Instead of attacking the display case:
try opening the display case. [This isn't necessary for the example. It just seems like a good idea.]

Instead of closing the display case:
if the display case is open:
say "That would require special glue.";
otherwise:
say "It's closed right now."

Section - The Nicest Ruby

The Nicest Ruby is in the display case. The Nicest Ruby is proper-named. Description of the Nicest Ruby is "Words cannot convey the extent of its sparkliness."

Section - The Air Duct

The air duct is in Hall of Jewels. "The nearby air duct, your means of entry, will also be your means of egress." The air duct is fixed in place.

Instead of entering the air duct:
if the player carries the Nicest Ruby:
say "You slither into the ducts and escape with the ruby! Gosh. You're the best jewel thief I ever saw.";
end the story;
otherwise:
say "You can't leave yet! You haven't stolen the Nicest Ruby!"

Section - The Heavy Candlestick

The heavy candlestick is in Hall of Jewels. "A heavy candlestick is conveniently nearby."

Quite thrilling, despite its linearity! Let’s map out the number of states in the critical path, along with the commands that will move the story from one state to the next.

  • 0 – The game has just started. The player hasn’t done anything important yet.
    • TAKE CANDLESTICK
  • 1 – Now the player has the candlestick.
    • OPEN DISPLAY CASE (or BREAK DISPLAY CASE)
  • 2 – The display case has been busted open!
    • TAKE RUBY
  • 3 – Now the player has the ruby.
    • ENTER DUCT
  • 4 – We don’t strictly need a 4, because once the player enters the duct, the game ends! But I thought of a clever way to use this 4, so let’s hold on to it…

To prepare for the game to automatically save the player’s position on this storyline, we’ll add a number variable and make it change at the right times.

Progress is a number that varies. Progress is 0.

After taking the heavy candlestick while progress is less than 1: [Saying "while progress is less than 1" prevents this from triggering another time if we drop the candlestick and pick it up again.]
say "Taken.";
now progress is 1.

After opening the display case: [We have to replace the original "after opening the display case rule" with this one!]
say "You smash the candlestick into the glass, smashing it open!";
now progress is 2.

After taking the Nicest Ruby while progress is less than 3:
say "Taken.";
now progress is 3.

Instead of entering the air duct: [Again, this replaces the original version of this rule.]
if the player carries the Nicest Ruby:
now progress is 4;
say "You slither into the ducts and escape with the ruby! Gosh. You're the best jewel thief I ever saw.";
end the story;
otherwise:
say "You can't leave yet! You haven't stolen the Nicest Ruby!"

This is all standard stuff, the kind of stuff I’ve used in many non-autosaving projects. A progress variable can be very useful for stuff like hint systems (e.g. If progress is 0, say “Are there any blunt instruments around? Maybe something heavy?”) or NPCs whose behaviors change as you move through the story.

But now we’ll get into non-standard stuff by using an external file and a table. Maybe we should describe the table first.

Table of Progression
content
0

If you’re not familiar with how Inform 7 structures tables, what we’re looking at is a table (named “Table of Progression”) with one column (prosaically named “content,” out of habit I guess) and one row. Not much of a table! But we only need one cell for this particular game.

Next we need that external file.

The File of Progression is called "rubyprogression".

The name “rubyprogression” is not especially important for our purposes; we’ll only be referring to it as the “File of Progression.” And this declaration doesn’t create the file itself; that only happens when we “write the File of Progression.” So let’s do that!

We might do it like this:

After taking the heavy candlestick while progress is less than 1:
say "Taken.";
now progress is 1;
choose row 1 in Table of Progression;
now content entry is 1;
write File of Progression from Table of Progression.

But we can make things a little more elegant with a general rule:

To update progression:
choose row 1 in Table of Progression;
now content entry is progress;
write File of Progression from Table of Progression.

Then refer to that general rule in any appropriate context:

After taking the heavy candlestick while progress is less than 1:
say "Taken.";
now progress is 1;
update progression;

After opening the display case:
say "You smash the candlestick into the glass, smashing it open!";
now progress is 2;
update progression;

After taking the Nicest Ruby while progress is less than 3:
say "Taken.";
now progress is 3;
update progression;

Instead of entering the air duct:
if the player carries the Nicest Ruby:
now progress is 4;
update progression;
say "You slither into the ducts and escape with the ruby! Gosh. You're the best jewel thief I ever saw.";
end the story;
otherwise:
say "You can't leave yet! You haven't stolen the Nicest Ruby!"

Now the game remembers how far you’ve progressed! Great!

The other half of the equation is restoring that progress when you open the game again. One important thing to bear in mind is that we can’t simply check what’s written in the File of Progression. We have to read it into a game-internal table and then read the table. Also, we have to make sure the file exists first.

When play begins:
if File of Progression exists:
read File of Progression into Table of Progression;
choose row 1 in Table of Progression;
now progress is content entry;

Let’s pause here and appreciate how a variable from an earlier playthrough has made its way into the current playthrough. That’s the key to this whole idea! Everything that comes afterward kind of feels like busy work.

But we’ve gotta do it!

When play begins:
if File of Progression exists:
read File of Progression into Table of Progression;
choose row 1 in Table of Progression;
now progress is content entry;
if progress is greater than 0: [i.e., if it's 1 or greater]
now player carries the heavy candlestick;
if progress is greater than 1:
now the display case is open;
if progress is greater than 2:
now player carries the Nicest Ruby.

Note that I say “if progress is greater than…” so that having a progress of 3 will also get you the benefits of having a progress of 2 or 1. This is the kind of thing that I tend to mess up!

(A fiddly detail: The way we’re modeling progress, acquiring the candlestick is treated as a step forward that you can’t step backward from. If you TAKE CANDLESTICK and then DROP CANDLESTICK and then RESTART, you’ll find the candlestick back in your inventory. This is perhaps unrealistic, but we’re specifically tracking the player’s progress, not the player’s unproductive or counterproductive incidental actions.)

Now, if we grab the ruby and “restart,” we’ll find ourselves right where we left off! Excellent!

The problem is, if we enter the duct and win the game, when we try to restart, we’ll find ourselves at the same point—candlestick and ruby in hand, display case open, ready to enter the duct and win the game again. We can’t start the game over again.

Well, players who know where the external file is can just delete it, and make things go back to the 0 state that way. But we can make things a little easier:

When play begins:
if File of Progression exists:
read File of Progression into Table of Progression;
choose row 1 in Table of Progression;
now progress is content entry;
if progress is 4:
now progress is 0;
update progression;
if progress is more than 0: [i.e., if it's 1 or greater]
now player carries the heavy candlestick; [replacing TAKE CANDLESTICK]
if progress is more than 1:
now the display case is open; [replacing a successful OPEN CASE]
if progress is more than 2:
now player carries the Nicest Ruby. [replacing a successful TAKE RUBY]

“If progress is 4” detects the state of someone who beat the game on their last playthrough, so we know to start things over. How elegant! I am impressed.

Letting the player decide to start things over in mid-game requires a little more work. Ryan Veeder’s Authentic Fly Fishing uses the command “RESTART COMPLETELY” for this decision. For our project, we can implement it like this:

Completely restarting is an action out of world. Understand "restart completely" as completely restarting.

Carry out completely restarting:
say "Do you want me to erase all your progress?[paragraph break]>";
if player consents:
say "Are you sure?[paragraph break]>";
if player consents:
now progress is 0;
update progression;
end the story saying "Done! Choose RESTART to start again.";
otherwise:
say "All right, then I won't.";
otherwise:
say "All right, then I won't.";

So we save progress automatically, we restore progress automatically, and we can erase progress if the player wishes. Everything works! You can see how this model could be expanded for more complicated (but still linear) games.

You can also see how this model doesn’t save any other kind of information! So if you have a rule like

Instead of jumping the first time: Say "You jump, having never jumped before. This is the very first time you have ever jumped!"

The Inform 7 compiler is not aware of our crafty autosaving technique, so a restored version of the game will treat “for the first time” as meaning “for the first time in this play session,” and won’t remember if a player jumped any number of times in previous playthroughs. So the message won’t make sense! We have to be a little careful how we write text that “interacts” with “plot events” that don’t get saved across playthroughs, but when you start writing in this mode you find that these things are easy to keep in mind.

We can auto-save other types of data, though. We can remember particular actions of the player. We can restore the player’s inventory in more nuanced ways. We can autosave a nonlinear game like Ryan Veeder’s Authentic Fly Fishing! But maybe we should save the complicated stuff for another post.