aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIan Jackson <ijackson@chiark.greenend.org.uk>2017-09-30 19:50:49 +0100
committerSimon Tatham <anakin@pobox.com>2017-10-01 10:38:01 +0100
commitb9b73adb53b3ffb09a2e8c9682351bf892634470 (patch)
tree41f68c4abacf66ba55f86d7b8f05a6a900bfe2fe
parent9e62c710dfba0596eb355b95c46fd5153f5fe13d (diff)
downloadpuzzles-b9b73adb53b3ffb09a2e8c9682351bf892634470.zip
puzzles-b9b73adb53b3ffb09a2e8c9682351bf892634470.tar.gz
puzzles-b9b73adb53b3ffb09a2e8c9682351bf892634470.tar.bz2
puzzles-b9b73adb53b3ffb09a2e8c9682351bf892634470.tar.xz
midend: Allow "new game" to be undone
It is annoying when one intends to choose "restart" and chooses "new game" instead. Right now, the puzzle one wanted to try again is discarded. To fix this we are going to have to save a lot more information than a normal game state. Handily, we already have the serialise/deserialise machinery. The advantage of using this is that the previous game is easily saved in its entirety, including its own undo history, and also probably in a more compact format. The (de)serialisation interface is rather clunky for use with a memory target. Sadly none of the existing implementations of a resizing memory array have been conveniently brought out into puzzles.h, and I think that that's beyond the scope of what I wanted to do here. We don't serialise the new game undo serialisation data. So loading/saving doesn't preserve any "new game" undo, as does "new game" twice (and as does context switching on a Palm Pilot). Signed-off-by: Ian Jackson <ijackson@chiark.greenend.org.uk>
-rw-r--r--midend.c66
1 files changed, 65 insertions, 1 deletions
diff --git a/midend.c b/midend.c
index 53ee0a2..03d6e98 100644
--- a/midend.c
+++ b/midend.c
@@ -63,6 +63,9 @@ struct midend {
int nstates, statesize, statepos;
struct midend_state_entry *states;
+ void *newgame_undo;
+ int newgame_undo_avail, newgame_undo_used;
+
game_params *params, *curparams;
game_drawstate *drawstate;
game_ui *ui;
@@ -155,6 +158,8 @@ midend *midend_new(frontend *fe, const game *ourgame,
me->random = random_new(randseed, randseedsize);
me->nstates = me->statesize = me->statepos = 0;
me->states = NULL;
+ me->newgame_undo = 0;
+ me->newgame_undo_avail = me->newgame_undo_used = 0;
me->params = ourgame->default_params();
me->game_id_change_notify_function = NULL;
me->game_id_change_notify_ctx = NULL;
@@ -378,8 +383,28 @@ void midend_force_redraw(midend *me)
midend_redraw(me);
}
+static void newgame_serialise_write(void *ctx, void *buf, int len)
+{
+ midend *const me = ctx;
+ int new_used;
+
+ assert(len < INT_MAX - me->newgame_undo_used);
+ new_used = me->newgame_undo_used + len;
+ if (new_used > me-> newgame_undo_avail) {
+ me->newgame_undo_avail = max(me->newgame_undo_avail, new_used);
+ me->newgame_undo_avail *= 2;
+ me->newgame_undo = sresize(me->newgame_undo,
+ me->newgame_undo_avail, char);
+ }
+ memcpy(me->newgame_undo + me->newgame_undo_used, buf, len);
+ me->newgame_undo_used = new_used;
+}
+
void midend_new_game(midend *me)
{
+ me->newgame_undo_used = 0;
+ midend_serialise(me, newgame_serialise_write, me);
+
midend_stop_anim(me);
midend_free_game(me);
@@ -493,7 +518,7 @@ void midend_new_game(midend *me)
int midend_can_undo(midend *me)
{
- return (me->statepos > 1);
+ return (me->statepos > 1 || me->newgame_undo_used);
}
int midend_can_redo(midend *me)
@@ -501,8 +526,26 @@ int midend_can_redo(midend *me)
return (me->statepos < me->nstates);
}
+struct newgame_undo_deserialise_read_ctx {
+ midend *me;
+ int size, pos;
+};
+
+int newgame_undo_deserialise_read(void *ctx, void *buf, int len)
+{
+ struct newgame_undo_deserialise_read_ctx *const rctx = ctx;
+ midend *const me = rctx->me;
+
+ int use = min(len, rctx->size - rctx->pos);
+ memcpy(buf, me->newgame_undo + rctx->pos, use);
+ rctx->pos += use;
+ return use;
+}
+
static int midend_undo(midend *me)
{
+ char *deserialise_error;
+
if (me->statepos > 1) {
if (me->ui)
me->ourgame->changed_state(me->ui,
@@ -511,6 +554,18 @@ static int midend_undo(midend *me)
me->statepos--;
me->dir = -1;
return 1;
+ } else if (me->newgame_undo_used) {
+ /* This undo cannot be undone with redo */
+ struct newgame_undo_deserialise_read_ctx rctx;
+ rctx.me = me;
+ rctx.size = me->newgame_undo_used; /* copy for reentrancy safety */
+ rctx.pos = 0;
+ deserialise_error =
+ midend_deserialise(me, newgame_undo_deserialise_read, &rctx);
+ if (deserialise_error)
+ /* umm, better to carry on than to crash ? */
+ return 0;
+ return 1;
} else
return 0;
}
@@ -2068,6 +2123,15 @@ static char *midend_deserialise_internal(
}
me->statepos = data.statepos;
+ /*
+ * Don't save the "new game undo" state. So "new game" twice or
+ * (in some environments) switching away and back, will make a
+ * "new game" irreversible. Maybe in the future we will have a
+ * more sophisticated way to decide when to discard the previous
+ * game state.
+ */
+ me->newgame_undo_used = 0;
+
{
game_params *tmp;