Revert to Past Game State

I didn’t quite know how to word the subject of this post. What I’m wondering is whether it’s possible in Inform 7 to do something equivalent to what AutoSave does in TADS 3. The description of that extension is that is “allows the player to restore the game to an earlier state determined by the game itself, like ‘undo’, except that the story is brought back to a specific point rather than one turn backwards.”

That’s going to be a key element of the work I’m doing. Essentially I need for the player to be able to turn on a certain device in the game. The player can then perform a series of actions, just as they normally would. If the player so chooses, they can “revert” all the actions by turning off the device. It’s almost as if the player went back in time and prevented themselves from taking the actions, leaving the world in the state it was at that point in time.

The key thing, of course, is that the game state would have to be brought back to the state it was in prior to the device being turned on. There can be a varying number of moves taken between when the player turns the device on and off. Also note that the player can leave the device on, which means the “reversion of history” never happens and thus the actions are committed to the game world for good.

I’m just on the start of investigating this but I figured I’d post the question while I do my investigations in case anyone has already trod this particular path. I suppose one thing I could do is hook into Save and Restore but, of course, I wouldn’t want the output to be the same as if the player was doing Save and Restore.

You’re the second person to ask about this here in the last week or so, believe it or not!

You can do indeed do this by hooking into save/restore, but there’s a catch: The technique is to stop saving the undo game state immediately after the turn you want to return to, meaning that the player will not be able to UNDO within the sequence of actions that postdate the last saved state; any UNDO will take the player back to your saved state. If that’s compatible with your desired player experience, then you might be interested in the new (still unreleased, any comments or bug reports welcome) version of the Undo Output Control extension, which provides the capability to change the game state, as well as to change the output selectively as needed:

http://dl.dropbox.com/u/947038/Undo%20Output%20Control.i7x

The third example in the extension shows how to do suspend the saving of undo states.

If you want your variation of UNDO to have its own command, e.g. REVERT, there is another difficulty. Undo/Restore is actually handled before the command is parsed (all we know at this early stage of parsing is how many words the command contains), so you can’t productively refer to the “player’s command” in I7 at this point. To make it work, you’d need to do two things: change one of the undo word constants (UNDO1__WD, UNDO2__WD, or UNDO3__WD) to “revert”, and check to see whether that word was typed before allowing UNDO in your special situation. I think this would let you distinguish REVERT from UNDO:

To decide whether the player typed REVERT: (- table-->1 == UNDO3_WD -)

We’re only checking the first word of the command, so “REVERT”, “REVERT THE GAME” and “REVERT TO MADNESS” would all work. Anyway, the idea is that you would use this test in a before undoing an action rule (as provided by Undo Output Control) to control whether reversion to the last saved state should occur or not.

–Erik

Oops, I was wrong about this. Undo only functions if you type a single word, so unless you hack the single-word requirement out of the library (a simple one-line change), your command will also need to be just a single word.

–Erik

I’m interested in thinking out an extension to Glulx’s undo mechanism that would allow the game to offer this sort of thing more flexibly. Maybe a game should be able to tag undo snapshots, and roll back to a specific one instead of just to the previous one?

I see two counterarguments:

  • The game can’t control when snapshots are thrown away, so in low-memory conditions, you could lose a critical game feature. (Sure, you could put in the spec that tagged snapshots aren’t discarded automatically. Result: in low-memory situations, the interpreter crashes. That’s not an improvement.)

  • If the game wants to do this, it should just save to a named file. That’s no problem in Glulx. Arguably this is the best solution.

Writing to an external file makes sense to me. I suspect that the most common use for this feature will be to allow for specific save points, and in that situation authors might very well want to have a number of them. (An autosave before every combat in an RPG-type game, for example).

–Erik

If we take the external file approach, then how do you handle this in Quixe? I’d hate to have a feature like this that cannot be used in browser-based terps.

Quixe uses localStorage for “external” files.

Thanks, Erik. I’m trying out the Undo Output Control as we speak. I played with the third example you mentioned and that certainly seems to do the basics of what I’m trying to achieve. I haven’t tried changing any of those UNDO constants to use a REVERT command. Maybe you answered this in your reply, but rather than changing what seems to be part of Inform, couldn’t I just make an action that behind the scenes tries an UNDO command?

In my case, the player can’t have multiple states they can revert to. Basically they’ll be utilizing a science-fiction like mechanism that allows them to experience one possible future. The “revert” (“undo”) will then take them back to the present – but with the understanding of what that future would have entailed, given certain actions.

Jeff, that question brings me a little out of my depth, so maybe someone else can provide a more definitive answer. But I’ll give it a shot first: I do think that you could implement your own undo action, but it might get a bit complex. Currently, undo is not an action–it is handled before the player’s command is parsed, and detected by comparing an array of the words the player typed directly to the word “undo” (UNDO1_WD etc). I suspect–but am not certain–that it was handled this way because performing an undo and saving the undo state are intertwined. Basically, the flow of events in the library goes like this:

  1. Print the command prompt and get player input.
  2. Check to see if the player typed UNDO. If so, restore the game state.
  3. Save the UNDO state. If the VM was just restored (i.e. by UNDO), return a special value.
  4. If the special value was returned, we need to print text to indicate that the game has been restored: print the appropriate output for restoring the game and restart the input loop (back to step 1). We will now be awaiting the player’s input with the game state restored to the last saved point.
  5. Otherwise, continue on, parsing the player’s command and resolving it to an action.

The difficulty (again, as I see it–I could be wrong) is in separating these two. Because you need to call VM_SaveUndo to see whether the last VM_RestoreUndo succeeded, it is difficult to split the two routines up by placing one in an action and the other at the beginning of the turn. It could probably be done, but I think if I were doing it (I have no programming background), it would probably involve a lot of trial and error coding…

Back to the topic of the undo word constants (i.e., UNDO1_WD, UNDO2_WD, and UNDO3_WD): These are actually intended to be changed, though there still isn’t any way to do so at the Inform 7 level. Out of the box, all three of these constants are assigned to “undo”, with the idea that authors can replace one or two of the redundant constants as desired.

–Erik

Okay, I tried the “REVERT” thing. In case anyone follows along with this approach, I found the area you were talking about in terms of UNDO3__x. So I added this:

Include (-
  Constant UNDO3__WD = 'REVERT';
-) instead of "Vocabulary" in "Language.i6t".

That got me a slew of errors and a “Translating the Source Failed” message. The errors started like this:

auto.inf(5072): Error: No such constant as “THEN1__WD”
auto.inf(5252): Error: No such constant as “OOPS1__WD”
auto.inf(5252): Error: No such constant as “OOPS2__WD”
auto.inf(5252): Error: No such constant as “OOPS3__WD”

I thought I might know the answer to this but I was hoping it wasn’t the case. You have to include every constant in your own include code block. If you do that, it all works. (It’s terribly ugly from a design pattern approach but at least it works.)

Thanks again for the extension because I’d be lost without it at this point.

Actually, from a design standpoint that makes perfect sense and works just as it ought to: You’re replacing a named section of source code, so it stands to reason that you need to replace the whole section. You wouldn’t want the compiler (in any language) to guess what you’re after when you say “replace this entire section of source code with this code snippet”. How would it know that you meant “replace just this one constant but keep everything else” vs. “replace this section entirely with this new code I included”?

What is bad from a design standpoint is that there is no way to reassign these constants at the I7 level. (If there is no way to replace a single constant at the I6 level either, that is also a bad thing–but I’m not sure whether that’s the case or not.) In fact, I7 doesn’t have constants at all, so it probably makes the most sense to simply replace these with variables (though that’s a bit of extra memory usage for the z-machine). I could do that for the UNDO and OOPS constants in Undo Output Control; maybe I’ll give that some thought.

You bet!

–Erik

Well, in TADS 3 I’m used to the distinction between ‘modify’ and ‘replace’ which lets me be a little more selective in some cases. I’m also used to being a Ruby coder, where you have “open classes” that you can selectively modify on demand, either by replacing or modifying only one element – say a given method – without having to type everything else again.

It’s been awhile since I’ve worked with Inform 7 and I just finished a slew of classes with TADS 3 so I’m having to reorient myself again to Inform 7’s approach. One of the things I did with the TADS 3 class was show how various design patterns common in languages like Java, Python, Ruby, and C# can be applied to TADS code. I’m finding that’s not always the case with Inform. That being said, what is interesting in Inform is trying to show design pattern concepts for a rules-based language.

Even this example of hooking into the undo/save/restore stuff in Inform 7 is very different from the “autosave” approach in TADS 3. It’s a really good comparison in terms of different approaches based on language implementations.

Yeah, what you trying to do with that library replacement was really the equivalent of this (fake I6 code inclusion):

(- Replace Constant UNDO3__WD = “REVERT”; -)

That doesn’t work, but you can replace functions using this syntax. (There may well be an alternate means of replacing a constant, but I’m not familiar enough with I6 to say.)

I agree, the difference between Inform and TADS handling of game state is interesting. Inform’s implementation of UNDO/game states is very likely influenced by the fact that, given the Z-machine’s limited memory, you could only have one saved game state in memory at a time. TADS 3, of course, never faced those restrictions and winds up with a more robust implementation.

–Erik

EDIT: Now that I think about it, it may be only stubs that can be replaced using the I6 “Replace” directive; again, I’ve never actually programmed anything in I6, so I don’t know the language well enough to say!

Jeff, you might want to download the extension again (same URL): I’ve added an easier way of changing the word constants. It’s a bit awkward–there is no direct I7 equivalent type for the “character” type (single-quoted string) in I6–but it’s less annoying than replacing that whole section of Language.i6t. Here’s how it’s done:

[code]Include Undo Output Control by Erik Temple.

To decide which value is undo word #3:
(- ‘revert’ -)[/code]

You’re overloading a phrase that’s already defined in the extension, so your custom phrase definition must come after the line that includes the extension.

Also note that these tokens cannot be printed–really, they can’t be used for anything at all–at the I7 level.

–Erik

Just tried the updated extension and it looks to work just fine. Much thanks for the work on this. This extension is absolutely just what I needed.

Jeff, I was preparing the extension for submission to the library when I realized that there is a potential problem with all this. Maybe you’ve already thought of it, but just in case:

When the game is saved, no undo states will be saved with it. So if the player saves and restores the game while in your possible future, there will be no returning to the present–the REVERT command will only go back as far as the first move after the restore. For that reason, you might want to disable saving while the player is exploring the “future”. This is easy to do:

Check saving the game when the player is in a possible future: say "You'll need to REVERT to the present before saving the game."; rule fails.

–Erik

You should probably warn the player about that when you first press the button, too.