Skip to content

Instantly share code, notes, and snippets.

@PleasingFungus
Last active July 3, 2021 05:20
Show Gist options
  • Save PleasingFungus/bbed8db838210a18ed7eb04f6814584f to your computer and use it in GitHub Desktop.
Save PleasingFungus/bbed8db838210a18ed7eb04f6814584f to your computer and use it in GitHub Desktop.
Undo implementation
// Ways to implement Undo
// The way to Undo described in http://temporenup.fuyu.gs/project.php
// is cool, but it looks like it has some downsides.
//
// (A) It is easy to cause subtle bugs. For example, imagine this
// error was introduced in redo:
// if (Undolog[0][changevaluename] == "HB") { <- should be "HP"
// HP = Undolog[0][afterchange];
// }
//
// Since people don't redo as often as they undo, if this was a rarely
// changed stat such as XP multiplier, this bug might not be discovered
// for a while.
//
// (B) There will be many long if-else statements. This makes the
// code longer and hard to read.
// Below, I have a few ideas on how Undo can work, based on my own
// games. Of course, you don't have to use any of these ideas yourself,
// but perhaps they could be helpful, maybe even in a future project.
// ---------------------------------------------------------------
// (1) Like before, we can store each stat that changed,
// To avoid mistakes like in (A) above, we can define string constants:
static const string HP_KEY = "HP";
static const string ATK_KEY = "Attack";
static const string DEF_KEY = "Defense";
// ..and so on. Then we can write this:
void GetDropOfDreamOcean()
{
// Instead of writing down the original values,
// let's just store the change.
Undolog[0][ATK_KEY] = 5;
Undolog[0][DEF_KEY] = 5;
Undolog[0][HP_KEY] = 9999 * HP_MODIFIER;
// If we typo 'HP_KEY' to 'HD_KEY' here, then our
// compiler will complain, or the game will crash,
// instead of just quietly putting the number in the
// wrong place. That is much better than if we wrote ["HP"]!
}
void Undo()
{
// Let's go through all of the different things that changed
// in the last undo step, and reverse each of them.
foreach (var kv in Undolog[0])
{
// If your language has switch-case statements, they're
// a good way to avoid long if-else chains.
//
// Some languages don't allow switch-case for strings,
// only for integers. In this case, instead of using string
// constants as I recommended above, you can use integer
// enums. ( https://ja.wikipedia.org/wiki/%E5%88%97%E6%8C%99%E5%9E%8B )
//
// When saving data to files on disk, you may want to turn
// enums into strings, so that it is easier to debug files when
// something goes wrong.
switch (kv.Key)
{
case ATK_KEY:
Attack -= kv.Value;
break;
case DEF_KEY:
Defense -= kv.Value;
break;
case HP_KEY:
HP -= kv.Value;
break;
// ...
}
}
}
// ---------------------------------------------------------------
// (2) Like the above, but instead, we store what we *did* and the
// bare minimum of the old state. We rely on the game to do everything
// else needed.
//
// Wikipedia calls this the 'command pattern'. See:
// https://en.wikipedia.org/wiki/Undo#Undo_implementation
const string LEVEL_ATK = "Attack";
const string LEVEL_DEF = "Defense";
const string LEVEL_RED_KEY = "Red Key";
// ... and other level up options
struct LevelUpAction
{
string LevelUpChoice;
void Do() // same as redo
{
switch (LevelUpChoice)
{
case LEVEL_ATK:
Attack += Level + 4;
break;
case LEVEL_RED_KEY:
RedKeys++;
break;
// and so on...
}
Level++;
}
void Undo()
{
// reverse this
}
}
// And then likewise for other actions:
// using an orb, taking an item, popping a floor,
// fighting an enemy, changing floors... etc
//
// This is a bit harder to marshal to disk than (1), and in the past I've found
// subtle bugs created by differences between Do and Undo, but it's kind of classy.
// ---------------------------------------------------------------
// (3) Store the whole old state.
// That is, store all HP, items, enemies, and so on, with a separate copy for each
// entry in the undo log.
// Since Tactical Nexus is a big game, and changes only happen on one floor at a time
// (and usually only in one tile at a time), I think this does not make much sense.
// However, for other games, it is a much simpler way to store changes, and can avoid
// strange bugs.
//
// Wikipedia calls this the 'memento pattern'. See:
// https://en.wikipedia.org/wiki/Undo#Undo_implementation
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment