chiark / gitweb /
Forbid undo of new-game if it would change the params.
[sgt-puzzles.git] / midend.c
index 5dd896f5b66fb9a162c186660e1248754eb69133..2f09bb528c7a9a1fb38e4262f21867b5f782326d 100644 (file)
--- a/midend.c
+++ b/midend.c
@@ -542,6 +542,62 @@ static int newgame_undo_deserialise_read(void *ctx, void *buf, int len)
     return use;
 }
 
+struct newgame_undo_deserialise_check_ctx {
+    int refused;
+};
+
+static char *newgame_undo_deserialise_check(
+    void *vctx, midend *me, const struct deserialise_data *data)
+{
+    struct newgame_undo_deserialise_check_ctx *ctx =
+        (struct newgame_undo_deserialise_check_ctx *)vctx;
+    char *old, *new;
+
+    /*
+     * Undoing a New Game operation is only permitted if it doesn't
+     * change the game parameters. The point of having the ability at
+     * all is to recover from the momentary finger error of having hit
+     * the 'n' key (perhaps in place of some other nearby key), or hit
+     * the New Game menu item by mistake when aiming for the adjacent
+     * Restart; in both those situations, the game params are the same
+     * before and after the new-game operation.
+     *
+     * In principle, we could generalise this so that _any_ call to
+     * midend_new_game could be undone, but that would need all front
+     * ends to be alert to the possibility that any keystroke passed
+     * to midend_process_key might (if it turns out to have been one
+     * of the synonyms for undo, which the frontend doesn't
+     * necessarily check for) have various knock-on effects like
+     * needing to select a different preset in the game type menu, or
+     * even resizing the window. At least for the moment, it's easier
+     * not to do that, and to simply disallow any newgame-undo that is
+     * disruptive in either of those ways.
+     *
+     * We check both params and cparams, to be as safe as possible.
+     */
+
+    old = me->ourgame->encode_params(me->params, TRUE);
+    new = me->ourgame->encode_params(data->params, TRUE);
+    if (strcmp(old, new)) {
+        /* Set a flag to distinguish this deserialise failure
+         * from one due to faulty decoding */
+        ctx->refused = TRUE;
+        return "Undoing this new-game operation would change params";
+    }
+
+    old = me->ourgame->encode_params(me->curparams, TRUE);
+    new = me->ourgame->encode_params(data->cparams, TRUE);
+    if (strcmp(old, new)) {
+        ctx->refused = TRUE;
+        return "Undoing this new-game operation would change params";
+    }
+
+    /*
+     * Otherwise, fine, go ahead.
+     */
+    return NULL;
+}
+
 static int midend_undo(midend *me)
 {
     char *deserialise_error;
@@ -557,13 +613,33 @@ static int midend_undo(midend *me)
     } 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;
        rctx.me = me;
        rctx.len = me->newgame_undo_len; /* copy for reentrancy safety */
        rctx.pos = 0;
-        deserialise_error =
-           midend_deserialise(me, newgame_undo_deserialise_read, &rctx);
-       assert(!deserialise_error);
-       return 1;
+        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.)
+             */
+            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);
+            return 1;
+        }
     } else
         return 0;
 }