X-Git-Url: https://git.distorted.org.uk/~mdw/sgt/puzzles/blobdiff_plain/08781119281c4c144ba167e87476429b0aae20b4..27a79972a58287f3f410ea528472f3994cb441b3:/mines.c diff --git a/mines.c b/mines.c index db98cc9..e0add2a 100644 --- a/mines.c +++ b/mines.c @@ -10,14 +10,6 @@ * That hook can talk to the game_ui and set the cheated flag, * and then make_move can avoid setting the `won' flag after that. * - * - delay game description generation until first click - * + do we actually _need_ to do this? Hmm. - * + it's a perfectly good puzzle game without - * + but it might be useful when we start timing, since it - * ensures the user is really paying attention. - * - * - timer - * * - question marks (arrgh, preferences?) * * - sensible parameter constraints @@ -60,10 +52,26 @@ struct game_params { int unique; }; +struct mine_layout { + /* + * This structure is shared between all the game_states for a + * given instance of the puzzle, so we reference-count it. + */ + int refcount; + char *mines; + /* + * If we haven't yet actually generated the mine layout, here's + * all the data we will need to do so. + */ + int n, unique; + random_state *rs; + midend_data *me; /* to give back the new game desc */ +}; + struct game_state { int w, h, n, dead, won; - char *mines; /* real mine positions */ - char *grid; /* player knowledge */ + struct mine_layout *layout; /* real mine positions */ + signed char *grid; /* player knowledge */ /* * Each item in the `grid' array is one of the following values: * @@ -247,6 +255,8 @@ static char *validate_params(game_params *params) return "Width must be greater than zero"; if (params->h <= 0) return "Height must be greater than zero"; + if (params->n > params->w * params->h - 9) + return "Too many mines for grid size"; /* * FIXME: Need more constraints here. Not sure what the @@ -550,7 +560,8 @@ static void std_add(struct squaretodo *std, int i) std->next[i] = -1; } -static void known_squares(int w, int h, struct squaretodo *std, char *grid, +static void known_squares(int w, int h, struct squaretodo *std, + signed char *grid, int (*open)(void *ctx, int x, int y), void *openctx, int x, int y, int mask, int mine) { @@ -617,9 +628,10 @@ struct perturbations { * steps were required; the exact return value is the number of * perturb calls. */ -static int minesolve(int w, int h, int n, char *grid, +static int minesolve(int w, int h, int n, signed char *grid, int (*open)(void *ctx, int x, int y), - struct perturbations *(*perturb)(void *ctx, char *grid, + struct perturbations *(*perturb)(void *ctx, + signed char *grid, int x, int y, int mask), void *ctx, random_state *rs) { @@ -1268,7 +1280,7 @@ static int minesolve(int w, int h, int n, char *grid, */ struct minectx { - char *grid; + signed char *grid; int w, h; int sx, sy; random_state *rs; @@ -1327,7 +1339,7 @@ static int squarecmp(const void *av, const void *bv) return 0; } -static struct perturbations *mineperturb(void *vctx, char *grid, +static struct perturbations *mineperturb(void *vctx, signed char *grid, int setx, int sety, int mask) { struct minectx *ctx = (struct minectx *)vctx; @@ -1647,7 +1659,7 @@ static char *minegen(int w, int h, int n, int x, int y, int unique, * We bypass this bit if we're not after a unique grid. */ if (unique) { - char *solvegrid = snewn(w*h, char); + signed char *solvegrid = snewn(w*h, char); struct minectx actx, *ctx = &actx; int solveret, prevret = -2; @@ -1790,52 +1802,77 @@ static void obfuscate_bitmap(unsigned char *bmp, int bits, int decode) } } -static char *new_game_desc(game_params *params, random_state *rs, - game_aux_info **aux) +static char *new_mine_layout(int w, int h, int n, int x, int y, int unique, + random_state *rs, char **game_desc) { - char *grid, *ret, *p; + signed char *grid, *ret, *p; unsigned char *bmp; - int x, y, i, area; + int i, area; - /* - * FIXME: allow user to specify initial open square. - */ - x = random_upto(rs, params->w); - y = random_upto(rs, params->h); + grid = minegen(w, h, n, x, y, unique, rs); - grid = minegen(params->w, params->h, params->n, x, y, params->unique, rs); + if (game_desc) { + /* + * Set up the mine bitmap and obfuscate it. + */ + area = w * h; + bmp = snewn((area + 7) / 8, unsigned char); + memset(bmp, 0, (area + 7) / 8); + for (i = 0; i < area; i++) { + if (grid[i]) + bmp[i / 8] |= 0x80 >> (i % 8); + } + obfuscate_bitmap(bmp, area, FALSE); - /* - * Set up the mine bitmap and obfuscate it. - */ - area = params->w * params->h; - bmp = snewn((area + 7) / 8, unsigned char); - memset(bmp, 0, (area + 7) / 8); - for (i = 0; i < area; i++) { - if (grid[i]) - bmp[i / 8] |= 0x80 >> (i % 8); - } - obfuscate_bitmap(bmp, area, FALSE); + /* + * Now encode the resulting bitmap in hex. We can work to + * nibble rather than byte granularity, since the obfuscation + * function guarantees to return a bit string of the same + * length as its input. + */ + ret = snewn((area+3)/4 + 100, char); + p = ret + sprintf(ret, "%d,%d,m", x, y); /* 'm' == masked */ + for (i = 0; i < (area+3)/4; i++) { + int v = bmp[i/2]; + if (i % 2 == 0) + v >>= 4; + *p++ = "0123456789abcdef"[v & 0xF]; + } + *p = '\0'; - /* - * Now encode the resulting bitmap in hex. We can work to - * nibble rather than byte granularity, since the obfuscation - * function guarantees to return a bit string of the same - * length as its input. - */ - ret = snewn((area+3)/4 + 100, char); - p = ret + sprintf(ret, "%d,%d,m", x, y); /* 'm' == masked */ - for (i = 0; i < (area+3)/4; i++) { - int v = bmp[i/2]; - if (i % 2 == 0) - v >>= 4; - *p++ = "0123456789abcdef"[v & 0xF]; - } - *p = '\0'; + sfree(bmp); - sfree(bmp); + *game_desc = ret; + } - return ret; + return grid; +} + +static char *new_game_desc(game_params *params, random_state *rs, + game_aux_info **aux, int interactive) +{ + if (!interactive) { + /* + * For batch-generated grids, pre-open one square. + */ + int x = random_upto(rs, params->w); + int y = random_upto(rs, params->h); + signed char *grid; + char *desc; + + grid = new_mine_layout(params->w, params->h, params->n, + x, y, params->unique, rs, &desc); + sfree(grid); + return desc; + } else { + char *rsdesc, *desc; + + rsdesc = random_state_encode(rs); + desc = snewn(strlen(rsdesc) + 100, char); + sprintf(desc, "r%d,%c,%s", params->n, params->unique ? 'u' : 'a', rsdesc); + sfree(rsdesc); + return desc; + } } static void game_free_aux_info(game_aux_info *aux) @@ -1848,32 +1885,48 @@ static char *validate_desc(game_params *params, char *desc) int wh = params->w * params->h; int x, y; - 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 == 'r') { + if (!*desc || !isdigit((unsigned char)*desc)) + return "No initial mine count in game description"; + while (*desc && isdigit((unsigned char)*desc)) + desc++; /* skip over mine count */ + if (*desc != ',') + return "No ',' after initial x-coordinate in game description"; desc++; - /* now just check length of remainder */ - if (strlen(desc) != (wh+3)/4) - return "Game description is wrong length"; + if (*desc != 'u' && *desc != 'a') + return "No uniqueness specifier in game description"; + desc++; + if (*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') + desc++; + /* now just check length of remainder */ + if (strlen(desc) != (wh+3)/4) + return "Game description is wrong length"; + } return NULL; } @@ -1883,7 +1936,24 @@ static int open_square(game_state *state, int x, int y) int w = state->w, h = state->h; int xx, yy, nmines, ncovered; - if (state->mines[y*w+x]) { + if (!state->layout->mines) { + /* + * We have a preliminary game in which the mine layout + * hasn't been generated yet. Generate it based on the + * initial click location. + */ + char *desc; + 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); + sfree(desc); + random_free(state->layout->rs); + state->layout->rs = NULL; + } + + if (state->layout->mines[y*w+x]) { /* * The player has landed on a mine. Bad luck. Expose all * the mines. @@ -1891,12 +1961,12 @@ static int open_square(game_state *state, int x, int y) state->dead = TRUE; for (yy = 0; yy < h; yy++) for (xx = 0; xx < w; xx++) { - if (state->mines[yy*w+xx] && + if (state->layout->mines[yy*w+xx] && (state->grid[yy*w+xx] == -2 || state->grid[yy*w+xx] == -3)) { state->grid[yy*w+xx] = 64; } - if (!state->mines[yy*w+xx] && + if (!state->layout->mines[yy*w+xx] && state->grid[yy*w+xx] == -1) { state->grid[yy*w+xx] = 66; } @@ -1927,7 +1997,7 @@ static int open_square(game_state *state, int x, int y) if (state->grid[yy*w+xx] == -10) { int dx, dy, v; - assert(!state->mines[yy*w+xx]); + assert(!state->layout->mines[yy*w+xx]); v = 0; @@ -1935,7 +2005,7 @@ static int open_square(game_state *state, int x, int y) for (dy = -1; dy <= +1; dy++) if (xx+dx >= 0 && xx+dx < state->w && yy+dy >= 0 && yy+dy < state->h && - state->mines[(yy+dy)*w+(xx+dx)]) + state->layout->mines[(yy+dy)*w+(xx+dx)]) v++; state->grid[yy*w+xx] = v; @@ -1966,7 +2036,7 @@ static int open_square(game_state *state, int x, int y) for (xx = 0; xx < w; xx++) { if (state->grid[yy*w+xx] < 0) ncovered++; - if (state->mines[yy*w+xx]) + if (state->layout->mines[yy*w+xx]) nmines++; } assert(ncovered >= nmines); @@ -1982,7 +2052,7 @@ static int open_square(game_state *state, int x, int y) return 0; } -static game_state *new_game(game_params *params, char *desc) +static game_state *new_game(midend_data *me, game_params *params, char *desc) { game_state *state = snew(game_state); int i, wh, x, y, ret, masked; @@ -1994,67 +2064,83 @@ static game_state *new_game(game_params *params, char *desc) state->dead = state->won = FALSE; wh = state->w * state->h; - state->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 == 'm') { - masked = TRUE; - desc++; - } else { - /* - * We permit game IDs to be entered by hand without the - * masking transformation. - */ - masked = FALSE; - } - bmp = snewn((wh + 7) / 8, unsigned char); - memset(bmp, 0, (wh + 7) / 8); - for (i = 0; i < (wh+3)/4; i++) { - int c = desc[i]; - int v; - - assert(c != 0); /* validate_desc should have caught */ - if (c >= '0' && c <= '9') - v = c - '0'; - else if (c >= 'a' && c <= 'f') - v = c - 'a' + 10; - else if (c >= 'A' && c <= 'F') - v = c - 'A' + 10; + state->layout = snew(struct mine_layout); + state->layout->refcount = 1; + + state->grid = snewn(wh, char); + memset(state->grid, -2, wh); + + if (*desc == 'r') { + desc++; + state->layout->n = atoi(desc); + while (*desc && isdigit((unsigned char)*desc)) + desc++; /* skip over mine count */ + if (*desc) desc++; /* eat comma */ + if (*desc == 'a') + state->layout->unique = FALSE; else - v = 0; + state->layout->unique = TRUE; + desc++; + if (*desc) desc++; /* eat comma */ - bmp[i / 2] |= v << (4 * (1 - (i % 2))); - } + state->layout->mines = NULL; + state->layout->rs = random_state_decode(desc); + state->layout->me = me; - if (masked) - obfuscate_bitmap(bmp, wh, TRUE); + } else { - memset(state->mines, 0, wh); - for (i = 0; i < wh; i++) { - if (bmp[i / 8] & (0x80 >> (i % 8))) - state->mines[i] = 1; - } + 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 == 'm') { + masked = TRUE; + desc++; + } else { + /* + * We permit game IDs to be entered by hand without the + * masking transformation. + */ + masked = FALSE; + } - state->grid = snewn(wh, char); - memset(state->grid, -2, wh); + bmp = snewn((wh + 7) / 8, unsigned char); + memset(bmp, 0, (wh + 7) / 8); + for (i = 0; i < (wh+3)/4; i++) { + int c = desc[i]; + int v; + + assert(c != 0); /* validate_desc should have caught */ + if (c >= '0' && c <= '9') + v = c - '0'; + else if (c >= 'a' && c <= 'f') + v = c - 'a' + 10; + else if (c >= 'A' && c <= 'F') + v = c - 'A' + 10; + else + v = 0; + + bmp[i / 2] |= v << (4 * (1 - (i % 2))); + } - ret = open_square(state, x, y); - /* - * FIXME: This shouldn't be an assert. Perhaps we actually - * ought to check it in validate_params! Alternatively, we can - * remove the assert completely and actually permit a game - * description to start you off dead. - */ - assert(ret != -1); + if (masked) + obfuscate_bitmap(bmp, wh, TRUE); + + memset(state->layout->mines, 0, wh); + for (i = 0; i < wh; i++) { + if (bmp[i / 8] & (0x80 >> (i % 8))) + state->layout->mines[i] = 1; + } + + ret = open_square(state, x, y); + } return state; } @@ -2068,8 +2154,8 @@ static game_state *dup_game(game_state *state) ret->n = state->n; ret->dead = state->dead; ret->won = state->won; - ret->mines = snewn(ret->w * ret->h, char); - memcpy(ret->mines, state->mines, ret->w * ret->h); + ret->layout = state->layout; + ret->layout->refcount++; ret->grid = snewn(ret->w * ret->h, char); memcpy(ret->grid, state->grid, ret->w * ret->h); @@ -2078,7 +2164,12 @@ static game_state *dup_game(game_state *state) static void free_game(game_state *state) { - sfree(state->mines); + if (--state->layout->refcount <= 0) { + sfree(state->layout->mines); + if (state->layout->rs) + random_free(state->layout->rs); + sfree(state->layout); + } sfree(state->grid); sfree(state); } @@ -2091,7 +2182,30 @@ static game_state *solve_game(game_state *state, game_aux_info *aux, static char *game_text_format(game_state *state) { - return NULL; + char *ret; + int x, y; + + ret = snewn((state->w + 1) * state->h + 1, char); + for (y = 0; y < state->h; y++) { + for (x = 0; x < state->w; x++) { + int v = state->grid[y*state->w+x]; + if (v == 0) + v = '-'; + else if (v >= 1 && v <= 8) + v = '0' + v; + else if (v == -1) + v = '*'; + else if (v == -2 || v == -3) + v = '?'; + else if (v >= 64) + v = '!'; + ret[y * (state->w+1) + x] = v; + } + ret[y * (state->w+1) + state->w] = '\n'; + } + ret[(state->w + 1) * state->h] = '\0'; + + return ret; } struct game_ui { @@ -2225,7 +2339,7 @@ static game_state *make_move(game_state *from, game_ui *ui, int x, int y, struct game_drawstate { int w, h, started; - char *grid; + signed char *grid; /* * Items in this `grid' array have all the same values as in * the game_state grid, and in addition: @@ -2558,7 +2672,7 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate, if (v == -1) markers++; - if (state->mines[y*ds->w+x]) + if (state->layout->mines && state->layout->mines[y*ds->w+x]) mines++; if ((v == -2 || v == -3) && @@ -2571,6 +2685,9 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate, } } + if (!state->layout->mines) + mines = state->layout->n; + /* * Update the status bar. */ @@ -2614,6 +2731,13 @@ static int game_wants_statusbar(void) return TRUE; } +static int game_timing_state(game_state *state) +{ + if (state->dead || state->won || !state->layout->mines) + return FALSE; + return TRUE; +} + #ifdef COMBINED #define thegame mines #endif @@ -2635,7 +2759,7 @@ const struct game thegame = { dup_game, free_game, FALSE, solve_game, - FALSE, game_text_format, + TRUE, game_text_format, new_ui, free_ui, make_move, @@ -2647,4 +2771,5 @@ const struct game thegame = { game_anim_length, game_flash_length, game_wants_statusbar, + TRUE, game_timing_state, };