diff --git a/cube.c b/cube.c index 78d35c1..428569c 100644 --- a/cube.c +++ b/cube.c @@ -868,7 +868,7 @@ static char *validate_desc(game_params *params, char *desc) return NULL; } -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 area; diff --git a/fifteen.c b/fifteen.c index 5bcc87a..310e70a 100644 --- a/fifteen.c +++ b/fifteen.c @@ -322,7 +322,7 @@ static char *validate_desc(game_params *params, char *desc) return err; } -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; diff --git a/midend.c b/midend.c index 6ae10f9..e31ce26 100644 --- a/midend.c +++ b/midend.c @@ -165,7 +165,8 @@ void midend_new_game(midend_data *me) } ensure(me); - me->states[me->nstates].state = me->ourgame->new_game(me->params, me->desc); + me->states[me->nstates].state = + me->ourgame->new_game(me, me->params, me->desc); me->states[me->nstates].special = TRUE; me->nstates++; me->statepos = 1; @@ -495,7 +496,7 @@ float *midend_colours(midend_data *me, int *ncolours) if (me->nstates == 0) { game_aux_info *aux = NULL; char *desc = me->ourgame->new_desc(me->params, me->random, &aux); - state = me->ourgame->new_game(me->params, desc); + state = me->ourgame->new_game(me, me->params, desc); sfree(desc); if (aux) me->ourgame->free_aux_info(aux); @@ -626,6 +627,12 @@ int midend_wants_statusbar(midend_data *me) return me->ourgame->wants_statusbar(); } +void midend_supersede_game_desc(midend_data *me, char *desc) +{ + sfree(me->desc); + me->desc = dupstr(desc); +} + config_item *midend_get_config(midend_data *me, int which, char **wintitle) { char *titlebuf, *parstr; diff --git a/mines.c b/mines.c index db98cc9..4016b44 100644 --- a/mines.c +++ b/mines.c @@ -60,9 +60,25 @@ 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 */ + struct mine_layout *layout; /* real mine positions */ char *grid; /* player knowledge */ /* * Each item in the `grid' array is one of the following values: @@ -1790,52 +1806,71 @@ 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; 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) +{ +#ifdef PREOPENED + int x = random_upto(rs, params->w); + int y = random_upto(rs, params->h); + char *grid, *desc; + + grid = new_mine_layout(params->w, params->h, params->n, + x, y, params->unique, rs); +#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; +#endif } static void game_free_aux_info(game_aux_info *aux) @@ -1848,32 +1883,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 +1934,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 +1959,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 +1995,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 +2003,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 +2034,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 +2050,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 +2062,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; - else - v = 0; - - bmp[i / 2] |= v << (4 * (1 - (i % 2))); - } - - if (masked) - obfuscate_bitmap(bmp, wh, TRUE); - - memset(state->mines, 0, wh); - for (i = 0; i < wh; i++) { - if (bmp[i / 8] & (0x80 >> (i % 8))) - state->mines[i] = 1; - } + state->layout = snew(struct mine_layout); + state->layout->refcount = 1; state->grid = snewn(wh, char); memset(state->grid, -2, wh); - 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 (*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 + state->layout->unique = TRUE; + desc++; + if (*desc) desc++; /* eat comma */ + + state->layout->mines = NULL; + state->layout->rs = random_state_decode(desc); + state->layout->me = me; + + } else { + + 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; + } + + 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))); + } + + 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 +2152,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 +2162,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); } @@ -2558,7 +2647,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 +2660,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. */ diff --git a/net.c b/net.c index 0df502f..8793886 100644 --- a/net.c +++ b/net.c @@ -1558,7 +1558,7 @@ static char *validate_desc(game_params *params, char *desc) * Construct an initial game state, given a description and parameters. */ -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; int w, h, x, y; diff --git a/netslide.c b/netslide.c index 5e98222..5209842 100644 --- a/netslide.c +++ b/netslide.c @@ -738,7 +738,7 @@ static char *validate_desc(game_params *params, char *desc) * Construct an initial game state, given a description and parameters. */ -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; int w, h, x, y; diff --git a/nullgame.c b/nullgame.c index 8d08530..7e27c4a 100644 --- a/nullgame.c +++ b/nullgame.c @@ -99,7 +99,7 @@ static char *validate_desc(game_params *params, char *desc) return NULL; } -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); diff --git a/pattern.c b/pattern.c index 0d5f8a8..df67c83 100644 --- a/pattern.c +++ b/pattern.c @@ -601,7 +601,7 @@ static char *validate_desc(game_params *params, char *desc) return NULL; } -static game_state *new_game(game_params *params, char *desc) +static game_state *new_game(midend_data *me, game_params *params, char *desc) { int i; char *p; diff --git a/puzzles.h b/puzzles.h index d2a22a5..9eb991a 100644 --- a/puzzles.h +++ b/puzzles.h @@ -144,6 +144,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); /* * malloc.c @@ -176,6 +177,8 @@ random_state *random_init(char *seed, int len); unsigned long random_bits(random_state *state, int bits); unsigned long random_upto(random_state *state, unsigned long limit); void random_free(random_state *state); +char *random_state_encode(random_state *state); +random_state *random_state_decode(char *input); /* random.c also exports SHA, which occasionally comes in useful. */ typedef unsigned long uint32; typedef struct { @@ -213,7 +216,7 @@ struct game { game_aux_info **aux); void (*free_aux_info)(game_aux_info *aux); char *(*validate_desc)(game_params *params, char *desc); - game_state *(*new_game)(game_params *params, char *desc); + game_state *(*new_game)(midend_data *me, game_params *params, char *desc); game_state *(*dup_game)(game_state *state); void (*free_game)(game_state *state); int can_solve; diff --git a/random.c b/random.c index d70dd00..08bc608 100644 --- a/random.c +++ b/random.c @@ -12,6 +12,7 @@ #include #include +#include #include "puzzles.h" @@ -278,3 +279,63 @@ void random_free(random_state *state) { sfree(state); } + +char *random_state_encode(random_state *state) +{ + char retbuf[256]; + int len = 0, i; + + for (i = 0; i < lenof(state->seedbuf); i++) + len += sprintf(retbuf+len, "%02x", state->seedbuf[i]); + for (i = 0; i < lenof(state->databuf); i++) + len += sprintf(retbuf+len, "%02x", state->databuf[i]); + len += sprintf(retbuf+len, "%02x", state->pos); + + return dupstr(retbuf); +} + +random_state *random_state_decode(char *input) +{ + random_state *state; + int pos, byte, digits; + + state = snew(random_state); + + memset(state->seedbuf, 0, sizeof(state->seedbuf)); + memset(state->databuf, 0, sizeof(state->databuf)); + state->pos = 0; + + byte = digits = 0; + pos = 0; + while (*input) { + int v = *input++; + + if (v >= '0' && v <= '9') + v = v - '0'; + else if (v >= 'A' && v <= 'F') + v = v - 'A' + 10; + else if (v >= 'a' && v <= 'f') + v = v - 'a' + 10; + else + v = 0; + + byte = (byte << 4) | v; + digits++; + + if (digits == 2) { + /* + * We have a byte. Put it somewhere. + */ + if (pos < lenof(state->seedbuf)) + state->seedbuf[pos++] = byte; + else if (pos < lenof(state->seedbuf) + lenof(state->databuf)) + state->databuf[pos++ - lenof(state->seedbuf)] = byte; + else if (pos == lenof(state->seedbuf) + lenof(state->databuf) && + byte <= lenof(state->databuf)) + state->pos = byte; + byte = digits = 0; + } + } + + return state; +} diff --git a/rect.c b/rect.c index 4a88d3b..b0e0f9c 100644 --- a/rect.c +++ b/rect.c @@ -1691,7 +1691,7 @@ static char *validate_desc(game_params *params, char *desc) return NULL; } -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 x, y, i, area; diff --git a/sixteen.c b/sixteen.c index e7eb7bd..8f43b7a 100644 --- a/sixteen.c +++ b/sixteen.c @@ -442,7 +442,7 @@ static char *validate_desc(game_params *params, char *desc) return err; } -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; diff --git a/solo.c b/solo.c index 3272a56..9e350f6 100644 --- a/solo.c +++ b/solo.c @@ -1620,7 +1620,7 @@ static char *validate_desc(game_params *params, char *desc) return NULL; } -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 c = params->c, r = params->r, cr = c*r, area = cr * cr; diff --git a/twiddle.c b/twiddle.c index 02b7982..6434c64 100644 --- a/twiddle.c +++ b/twiddle.c @@ -428,7 +428,7 @@ static char *validate_desc(game_params *params, char *desc) return NULL; } -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 w = params->w, h = params->h, n = params->n, wh = w*h;