chiark / gitweb /
midend: Allow "new game" to be undone
[sgt-puzzles.git] / midend.c
index 53ee0a2338e42bfa7c1ce86c12865c5df69dcb26..03d6e98f8e0f10c88915f409be0fa3ea2685d0bf 100644 (file)
--- 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;