Last active
July 3, 2021 05:20
-
-
Save PleasingFungus/bbed8db838210a18ed7eb04f6814584f to your computer and use it in GitHub Desktop.
Undo implementation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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