From: simon Date: Tue, 28 Jun 2005 06:59:27 +0000 (+0000) Subject: Annoying special cases for Mines. X-Git-Url: https://git.distorted.org.uk/~mdw/sgt/puzzles/commitdiff_plain/0a6892dbcbd8ecb9be7e88a0368926fe521ae479 Annoying special cases for Mines. Firstly, the `Restart' function now reconstructs an initial game state from the game description rather than dup_game(states[0]). This means that Restart in a game of Mines restarts to just _after_ the initial click, so you can resume the puzzle-solving part without having to remember where you placed that click. Secondly, the midend now contains a second `private' game desc, which is guaranteed to actually reconstruct the initial game_state correctly (which Mines's publicly visible game descs tend not to, since they describe a state which has already had the first click). This should make serialising of Mines more sensible. git-svn-id: svn://svn.tartarus.org/sgt/puzzles@6025 cda61777-01e9-0310-a592-d414129be87e --- diff --git a/midend.c b/midend.c index 5f4e795..bcc842f 100644 --- a/midend.c +++ b/midend.c @@ -25,7 +25,29 @@ struct midend_data { random_state *random; const game *ourgame; - char *desc, *seedstr; + /* + * `desc' and `privdesc' deserve a comment. + * + * `desc' is the game description as presented to the user when + * they ask for Game -> Specific. `privdesc', if non-NULL, is a + * different game description used to reconstruct the initial + * game_state when de-serialising. If privdesc is NULL, `desc' + * is used for both. + * + * For almost all games, `privdesc' is NULL and never used. The + * exception (as usual) is Mines: the initial game state has no + * squares open at all, but after the first click `desc' is + * rewritten to describe a game state with an initial click and + * thus a bunch of squares open. If we used that desc to + * serialise and deserialise, then the initial game state after + * deserialisation would look unlike the initial game state + * beforehand, and worse still execute_move() might fail on the + * attempted first click. So `privdesc' is also used in this + * case, to provide a game description describing the same + * fixed mine layout _but_ no initial click. (These game IDs + * may also be typed directly into Mines if you like.) + */ + char *desc, *privdesc, *seedstr; game_aux_info *aux_info; enum { GOT_SEED, GOT_DESC, GOT_NOTHING } genmode; int nstates, statesize, statepos; @@ -75,7 +97,7 @@ midend_data *midend_new(frontend *fe, const game *ourgame) me->states = NULL; me->params = ourgame->default_params(); me->curparams = NULL; - me->desc = NULL; + me->desc = me->privdesc = NULL; me->seedstr = NULL; me->aux_info = NULL; me->genmode = GOT_NOTHING; @@ -212,6 +234,7 @@ void midend_new_game(midend_data *me) } sfree(me->desc); + sfree(me->privdesc); if (me->aux_info) me->ourgame->free_aux_info(me->aux_info); me->aux_info = NULL; @@ -219,6 +242,7 @@ void midend_new_game(midend_data *me) rs = random_init(me->seedstr, strlen(me->seedstr)); me->desc = me->ourgame->new_desc(me->curparams, rs, &me->aux_info, TRUE); + me->privdesc = NULL; random_free(rs); } @@ -317,7 +341,14 @@ void midend_restart_game(midend_data *me) if (me->statepos == 1) return; /* no point doing anything at all! */ - s = me->ourgame->dup_game(me->states[0].state); + /* + * During restart, we reconstruct the game from the (public) + * game description rather than from states[0], because that + * way Mines gets slightly more sensible behaviour (restart + * goes to _after_ the first click so you don't have to + * remember where you clicked). + */ + s = me->ourgame->new_game(me, me->params, me->desc); /* * Now enter the restarted state as the next move. @@ -737,10 +768,13 @@ int midend_wants_statusbar(midend_data *me) return me->ourgame->wants_statusbar(); } -void midend_supersede_game_desc(midend_data *me, char *desc) +void midend_supersede_game_desc(midend_data *me, char *desc, char *privdesc) { sfree(me->desc); + sfree(me->privdesc); +printf("%s\n%s\n", desc, privdesc); me->desc = dupstr(desc); + me->privdesc = privdesc ? dupstr(privdesc) : NULL; } config_item *midend_get_config(midend_data *me, int which, char **wintitle) @@ -880,7 +914,8 @@ static char *midend_game_id_int(midend_data *me, char *id, int defmode) } sfree(me->desc); - me->desc = NULL; + sfree(me->privdesc); + me->desc = me->privdesc = NULL; sfree(me->seedstr); me->seedstr = NULL; diff --git a/mines.c b/mines.c index 4fd0e21..6aee39f 100644 --- a/mines.c +++ b/mines.c @@ -1855,7 +1855,7 @@ static char *describe_layout(char *grid, int area, int x, int y, */ ret = snewn((area+3)/4 + 100, char); p = ret + sprintf(ret, "%d,%d,%s", x, y, - obfuscate ? "m" : ""); /* 'm' == masked */ + obfuscate ? "m" : "u"); /* 'm' == masked */ for (i = 0; i < (area+3)/4; i++) { int v = bmp[i/2]; if (i % 2 == 0) @@ -2009,28 +2009,28 @@ static char *validate_desc(game_params *params, char *desc) return "No ',' after uniqueness specifier in game description"; /* now ignore the rest */ } else { - if (!*desc || !isdigit((unsigned char)*desc)) - return "No initial x-coordinate in game description"; - x = atoi(desc); - if (x < 0 || x >= params->w) - return "Initial x-coordinate was out of range"; - while (*desc && isdigit((unsigned char)*desc)) - desc++; /* skip over x coordinate */ - if (*desc != ',') - return "No ',' after initial x-coordinate in game description"; - desc++; /* eat comma */ - if (!*desc || !isdigit((unsigned char)*desc)) - return "No initial y-coordinate in game description"; - y = atoi(desc); - if (y < 0 || y >= params->h) - return "Initial y-coordinate was out of range"; - while (*desc && isdigit((unsigned char)*desc)) - desc++; /* skip over y coordinate */ - if (*desc != ',') - return "No ',' after initial y-coordinate in game description"; - desc++; /* eat comma */ - /* eat `m', meaning `masked', if present */ - if (*desc == 'm') + if (*desc && isdigit((unsigned char)*desc)) { + x = atoi(desc); + if (x < 0 || x >= params->w) + return "Initial x-coordinate was out of range"; + while (*desc && isdigit((unsigned char)*desc)) + desc++; /* skip over x coordinate */ + if (*desc != ',') + return "No ',' after initial x-coordinate in game description"; + desc++; /* eat comma */ + if (!*desc || !isdigit((unsigned char)*desc)) + return "No initial y-coordinate in game description"; + y = atoi(desc); + if (y < 0 || y >= params->h) + return "Initial y-coordinate was out of range"; + while (*desc && isdigit((unsigned char)*desc)) + desc++; /* skip over y coordinate */ + if (*desc != ',') + return "No ',' after initial y-coordinate in game description"; + desc++; /* eat comma */ + } + /* eat `m' for `masked' or `u' for `unmasked', if present */ + if (*desc == 'm' || *desc == 'u') desc++; /* now just check length of remainder */ if (strlen(desc) != (wh+3)/4) @@ -2051,12 +2051,23 @@ static int open_square(game_state *state, int x, int y) * hasn't been generated yet. Generate it based on the * initial click location. */ - char *desc; + char *desc, *privdesc; state->layout->mines = new_mine_layout(w, h, state->layout->n, x, y, state->layout->unique, state->layout->rs, &desc); - midend_supersede_game_desc(state->layout->me, desc); + /* + * Find the trailing substring of the game description + * corresponding to just the mine layout; we will use this + * as our second `private' game ID for serialisation. + */ + privdesc = desc; + while (*privdesc && isdigit((unsigned char)*privdesc)) privdesc++; + if (*privdesc == ',') privdesc++; + while (*privdesc && isdigit((unsigned char)*privdesc)) privdesc++; + if (*privdesc == ',') privdesc++; + assert(*privdesc == 'm'); + midend_supersede_game_desc(state->layout->me, desc, privdesc); sfree(desc); random_free(state->layout->rs); state->layout->rs = NULL; @@ -2191,21 +2202,27 @@ static game_state *new_game(midend_data *me, game_params *params, char *desc) } else { state->layout->rs = NULL; state->layout->me = NULL; - state->layout->mines = snewn(wh, char); - x = atoi(desc); - while (*desc && isdigit((unsigned char)*desc)) - desc++; /* skip over x coordinate */ - if (*desc) desc++; /* eat comma */ - y = atoi(desc); - while (*desc && isdigit((unsigned char)*desc)) - desc++; /* skip over y coordinate */ - if (*desc) desc++; /* eat comma */ + + if (*desc && isdigit((unsigned char)*desc)) { + x = atoi(desc); + while (*desc && isdigit((unsigned char)*desc)) + desc++; /* skip over x coordinate */ + if (*desc) desc++; /* eat comma */ + y = atoi(desc); + while (*desc && isdigit((unsigned char)*desc)) + desc++; /* skip over y coordinate */ + if (*desc) desc++; /* eat comma */ + } else { + x = y = -1; + } if (*desc == 'm') { masked = TRUE; desc++; } else { + if (*desc == 'u') + desc++; /* * We permit game IDs to be entered by hand without the * masking transformation. @@ -2241,7 +2258,8 @@ static game_state *new_game(midend_data *me, game_params *params, char *desc) state->layout->mines[i] = 1; } - ret = open_square(state, x, y); + if (x >= 0 && y >= 0) + ret = open_square(state, x, y); sfree(bmp); } diff --git a/puzzles.h b/puzzles.h index 40c3916..92c5a10 100644 --- a/puzzles.h +++ b/puzzles.h @@ -185,7 +185,7 @@ char *midend_set_config(midend_data *me, int which, config_item *cfg); char *midend_game_id(midend_data *me, char *id); char *midend_text_format(midend_data *me); char *midend_solve(midend_data *me); -void midend_supersede_game_desc(midend_data *me, char *desc); +void midend_supersede_game_desc(midend_data *me, char *desc, char *privdesc); char *midend_rewrite_statusbar(midend_data *me, char *text); /*