int nstates, statesize, statepos;
struct midend_state_entry *states;
- struct midend_serialise_buf newgame_undo;
+ struct midend_serialise_buf newgame_undo, newgame_redo;
game_params *params, *curparams;
game_drawstate *drawstate;
me->states = NULL;
me->newgame_undo.buf = NULL;
me->newgame_undo.size = me->newgame_undo.len = 0;
+ me->newgame_redo.buf = NULL;
+ me->newgame_redo.size = me->newgame_redo.len = 0;
me->params = ourgame->default_params();
me->game_id_change_notify_function = NULL;
me->game_id_change_notify_ctx = NULL;
if (me->states[me->nstates].movestr)
sfree(me->states[me->nstates].movestr);
}
+ me->newgame_redo.len = 0;
}
static void midend_free_game(midend *me)
drawing_free(me->drawing);
random_free(me->random);
sfree(me->newgame_undo.buf);
+ sfree(me->newgame_redo.buf);
sfree(me->states);
sfree(me->desc);
sfree(me->privdesc);
* and just confuse us into thinking we had something to undo
* to.
*/
+ midend_purge_states(me);
midend_serialise(me, newgame_serialise_write, &me->newgame_undo);
}
int midend_can_redo(midend *me)
{
- return (me->statepos < me->nstates);
+ return (me->statepos < me->nstates || me->newgame_redo.len);
}
struct newgame_undo_deserialise_read_ctx {
me->dir = -1;
return 1;
} else if (me->newgame_undo.len) {
- /* This undo cannot be undone with redo */
struct newgame_undo_deserialise_read_ctx rctx;
struct newgame_undo_deserialise_check_ctx cctx;
+ struct midend_serialise_buf serbuf;
+
+ /*
+ * Serialise the current game so that you can later redo past
+ * this undo. Once we're committed to the undo actually
+ * happening, we'll copy this data into place.
+ */
+ serbuf.buf = NULL;
+ serbuf.len = serbuf.size = 0;
+ midend_serialise(me, newgame_serialise_write, &serbuf);
+
rctx.ser = &me->newgame_undo;
rctx.len = me->newgame_undo.len; /* copy for reentrancy safety */
rctx.pos = 0;
* contain the dummy error message generated by our check
* function, which we ignore.)
*/
+ sfree(serbuf.buf);
return 0;
} else {
/*
* replaced by the wrong file, etc., by user error.
*/
assert(!deserialise_error);
+
+ /*
+ * Clear the old newgame_undo serialisation, so that we
+ * don't try to undo past the beginning of the game we've
+ * just gone back to and end up at the front of it again.
+ */
+ me->newgame_undo.len = 0;
+
+ /*
+ * Copy the serialisation of the game we've just left into
+ * the midend so that we can redo back into it later.
+ */
+ me->newgame_redo.len = 0;
+ newgame_serialise_write(&me->newgame_redo, serbuf.buf, serbuf.len);
+
+ sfree(serbuf.buf);
return 1;
}
} else
static int midend_redo(midend *me)
{
+ const char *deserialise_error;
+
if (me->statepos < me->nstates) {
if (me->ui)
me->ourgame->changed_state(me->ui,
me->statepos++;
me->dir = +1;
return 1;
+ } else if (me->newgame_redo.len) {
+ struct newgame_undo_deserialise_read_ctx rctx;
+ struct newgame_undo_deserialise_check_ctx cctx;
+ struct midend_serialise_buf serbuf;
+
+ /*
+ * Serialise the current game so that you can later undo past
+ * this redo. Once we're committed to the undo actually
+ * happening, we'll copy this data into place.
+ */
+ serbuf.buf = NULL;
+ serbuf.len = serbuf.size = 0;
+ midend_serialise(me, newgame_serialise_write, &serbuf);
+
+ rctx.ser = &me->newgame_redo;
+ rctx.len = me->newgame_redo.len; /* copy for reentrancy safety */
+ rctx.pos = 0;
+ cctx.refused = FALSE;
+ deserialise_error = midend_deserialise_internal(
+ me, newgame_undo_deserialise_read, &rctx,
+ newgame_undo_deserialise_check, &cctx);
+ if (cctx.refused) {
+ /*
+ * Our post-deserialisation check shows that we can't use
+ * this saved game after all. (deserialise_error will
+ * contain the dummy error message generated by our check
+ * function, which we ignore.)
+ */
+ sfree(serbuf.buf);
+ return 0;
+ } else {
+ /*
+ * There should never be any _other_ deserialisation
+ * error, because this serialised data has been held in
+ * our memory since it was created, and hasn't had any
+ * opportunity to be corrupted on disk, accidentally
+ * replaced by the wrong file, etc., by user error.
+ */
+ assert(!deserialise_error);
+
+ /*
+ * Clear the old newgame_redo serialisation, so that we
+ * don't try to redo past the end of the game we've just
+ * come into and end up at the back of it again.
+ */
+ me->newgame_redo.len = 0;
+
+ /*
+ * Copy the serialisation of the game we've just left into
+ * the midend so that we can undo back into it later.
+ */
+ me->newgame_undo.len = 0;
+ newgame_serialise_write(&me->newgame_undo, serbuf.buf, serbuf.len);
+
+ sfree(serbuf.buf);
+ return 1;
+ }
} else
return 0;
}
me->statepos = data.statepos;
/*
- * Don't save the "new game undo" state. So "new game" twice or
+ * Don't save the "new game undo/redo" 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.len = 0;
+ me->newgame_redo.len = 0;
{
game_params *tmp;