X-Git-Url: https://git.distorted.org.uk/~mdw/sgt/puzzles/blobdiff_plain/0c5f131395979c85963f77271c04058bca749e65..HEAD:/midend.c diff --git a/midend.c b/midend.c index 05ea663..53dca21 100644 --- a/midend.c +++ b/midend.c @@ -106,6 +106,22 @@ midend *midend_new(frontend *fe, const game *ourgame, me->nstates = me->statesize = me->statepos = 0; me->states = NULL; me->params = ourgame->default_params(); + /* + * Allow environment-based changing of the default settings by + * defining a variable along the lines of `NET_DEFAULT=25x25w' + * in which the value is an encoded parameter string. + */ + { + char buf[80], *e; + int j, k; + sprintf(buf, "%s_DEFAULT", me->ourgame->name); + for (j = k = 0; buf[j]; j++) + if (!isspace((unsigned char)buf[j])) + buf[k++] = toupper((unsigned char)buf[j]); + buf[k] = '\0'; + if ((e = getenv(buf)) != NULL) + me->ourgame->decode_params(me->params, e); + } me->curparams = NULL; me->desc = me->privdesc = NULL; me->seedstr = NULL; @@ -156,6 +172,11 @@ midend *midend_new(frontend *fe, const game *ourgame, return me; } +const game *midend_which_game(midend *me) +{ + return me->ourgame; +} + static void midend_purge_states(midend *me) { while (me->nstates > me->statepos) { @@ -413,6 +434,55 @@ void midend_new_game(midend *me) sfree(movestr); } + /* + * Soak test, enabled by setting _TESTSOLVE in the + * environment. This causes an immediate attempt to re-solve the + * game without benefit of aux_info. The effect is that (at least + * on Unix) you can run 'FOO_TESTSOLVE=1 foo --generate 10000 + * #12345' and it will generate a lot of game ids and + * instantly pass each one back to the solver. + * + * (It's worth putting in an explicit seed in any such test, so + * you can repeat it to diagnose a problem if one comes up!) + */ + { + char buf[80]; + int j, k; + static int doing_test_solve = -1; + if (doing_test_solve < 0) { + sprintf(buf, "%s_TESTSOLVE", me->ourgame->name); + for (j = k = 0; buf[j]; j++) + if (!isspace((unsigned char)buf[j])) + buf[k++] = toupper((unsigned char)buf[j]); + buf[k] = '\0'; + if (getenv(buf)) { + /* + * Since this is used for correctness testing, it's + * helpful to have a visual acknowledgment that the + * user hasn't mistyped the environment variable name. + */ + fprintf(stderr, "Running solver soak tests\n"); + doing_test_solve = TRUE; + } else { + doing_test_solve = FALSE; + } + } + if (doing_test_solve) { + game_state *s; + char *msg, *movestr; + + msg = NULL; + movestr = me->ourgame->solve(me->states[0].state, + me->states[0].state, + NULL, &msg); + assert(movestr && !msg); + s = me->ourgame->execute_move(me->states[0].state, movestr); + assert(s); + me->ourgame->free_game(s); + sfree(movestr); + } + } + me->states[me->nstates].movestr = NULL; me->states[me->nstates].movetype = NEWGAME; me->nstates++; @@ -428,6 +498,16 @@ void midend_new_game(midend *me) me->pressed_mouse_button = 0; } +int midend_can_undo(midend *me) +{ + return (me->statepos > 1); +} + +int midend_can_redo(midend *me) +{ + return (me->statepos < me->nstates); +} + static int midend_undo(midend *me) { if (me->statepos > 1) { @@ -1321,11 +1401,29 @@ char *midend_solve(midend *me) me->anim_time = 0.0; midend_finish_move(me); } - midend_redraw(me); + if (me->drawing) + midend_redraw(me); midend_set_timer(me); return NULL; } +int midend_status(midend *me) +{ + /* + * We should probably never be called when the state stack has no + * states on it at all - ideally, midends should never be left in + * that state for long enough to get put down and forgotten about. + * But if we are, I think we return _true_ - pedantically speaking + * a midend in that state is 'vacuously solved', and more + * practically, a user whose midend has been left in that state + * probably _does_ want the 'new game' option to be prominent. + */ + if (me->statepos == 0) + return +1; + + return me->ourgame->status(me->states[me->statepos-1].state); +} + char *midend_rewrite_statusbar(midend *me, char *text) { /* @@ -1856,6 +1954,111 @@ char *midend_deserialise(midend *me, return ret; } +/* + * This function examines a saved game file just far enough to + * determine which game type it contains. It returns NULL on success + * and the game name string in 'name' (which will be dynamically + * allocated and should be caller-freed), or an error message on + * failure. + */ +char *identify_game(char **name, int (*read)(void *ctx, void *buf, int len), + void *rctx) +{ + int nstates = 0, statepos = -1, gotstates = 0; + int started = FALSE; + + char *val = NULL; + /* Initially all errors give the same report */ + char *ret = "Data does not appear to be a saved game file"; + + *name = NULL; + + /* + * Loop round and round reading one key/value pair at a time from + * the serialised stream, until we've found the game name. + */ + while (nstates <= 0 || statepos < 0 || gotstates < nstates-1) { + char key[9], c; + int len; + + do { + if (!read(rctx, key, 1)) { + /* unexpected EOF */ + goto cleanup; + } + } while (key[0] == '\r' || key[0] == '\n'); + + if (!read(rctx, key+1, 8)) { + /* unexpected EOF */ + goto cleanup; + } + + if (key[8] != ':') { + if (started) + ret = "Data was incorrectly formatted for a saved game file"; + goto cleanup; + } + len = strcspn(key, ": "); + assert(len <= 8); + key[len] = '\0'; + + len = 0; + while (1) { + if (!read(rctx, &c, 1)) { + /* unexpected EOF */ + goto cleanup; + } + + if (c == ':') { + break; + } else if (c >= '0' && c <= '9') { + len = (len * 10) + (c - '0'); + } else { + if (started) + ret = "Data was incorrectly formatted for a" + " saved game file"; + goto cleanup; + } + } + + val = snewn(len+1, char); + if (!read(rctx, val, len)) { + if (started) + goto cleanup; + } + val[len] = '\0'; + + if (!started) { + if (strcmp(key, "SAVEFILE") || strcmp(val, SERIALISE_MAGIC)) { + /* ret already has the right message in it */ + goto cleanup; + } + /* Now most errors are this one, unless otherwise specified */ + ret = "Saved data ended unexpectedly"; + started = TRUE; + } else { + if (!strcmp(key, "VERSION")) { + if (strcmp(val, SERIALISE_VERSION)) { + ret = "Cannot handle this version of the saved game" + " file format"; + goto cleanup; + } + } else if (!strcmp(key, "GAME")) { + *name = dupstr(val); + ret = NULL; + goto cleanup; + } + } + + sfree(val); + val = NULL; + } + + cleanup: + sfree(val); + return ret; +} + char *midend_print_puzzle(midend *me, document *doc, int with_soln) { game_state *soln = NULL;