X-Git-Url: https://git.distorted.org.uk/~mdw/sgt/puzzles/blobdiff_plain/1e3e152de7cc491b63c5e351b5f603d307182b07..871bf2946c5cbfdc7621c4b3d6850af0699297a0:/solo.c diff --git a/solo.c b/solo.c index 2f8ec23..b9510e2 100644 --- a/solo.c +++ b/solo.c @@ -113,7 +113,8 @@ typedef unsigned char digit; #define FLASH_TIME 0.4F -enum { SYMM_NONE, SYMM_ROT2, SYMM_ROT4, SYMM_REF4 }; +enum { SYMM_NONE, SYMM_ROT2, SYMM_ROT4, SYMM_REF2, SYMM_REF2D, SYMM_REF4, + SYMM_REF4D, SYMM_REF8 }; enum { DIFF_BLOCK, DIFF_SIMPLE, DIFF_INTERSECT, DIFF_SET, DIFF_RECURSIVE, DIFF_AMBIGUOUS, DIFF_IMPOSSIBLE }; @@ -203,12 +204,22 @@ static void decode_params(game_params *ret, char const *string) } while (*string) { if (*string == 'r' || *string == 'm' || *string == 'a') { - int sn, sc; + int sn, sc, sd; sc = *string++; + if (*string == 'd') { + sd = TRUE; + string++; + } else { + sd = FALSE; + } sn = atoi(string); while (*string && isdigit((unsigned char)*string)) string++; + if (sc == 'm' && sn == 8) + ret->symm = SYMM_REF8; if (sc == 'm' && sn == 4) - ret->symm = SYMM_REF4; + ret->symm = sd ? SYMM_REF4D : SYMM_REF4; + if (sc == 'm' && sn == 2) + ret->symm = sd ? SYMM_REF2D : SYMM_REF2; if (sc == 'r' && sn == 4) ret->symm = SYMM_ROT4; if (sc == 'r' && sn == 2) @@ -239,7 +250,11 @@ static char *encode_params(game_params *params, int full) sprintf(str, "%dx%d", params->c, params->r); if (full) { switch (params->symm) { + case SYMM_REF8: strcat(str, "m8"); break; case SYMM_REF4: strcat(str, "m4"); break; + case SYMM_REF4D: strcat(str, "md4"); break; + case SYMM_REF2: strcat(str, "m2"); break; + case SYMM_REF2D: strcat(str, "md2"); break; case SYMM_ROT4: strcat(str, "r4"); break; /* case SYMM_ROT2: strcat(str, "r2"); break; [default] */ case SYMM_NONE: strcat(str, "a"); break; @@ -276,7 +291,9 @@ static config_item *game_configure(game_params *params) ret[2].name = "Symmetry"; ret[2].type = C_CHOICES; - ret[2].sval = ":None:2-way rotation:4-way rotation:4-way mirror"; + ret[2].sval = ":None:2-way rotation:4-way rotation:2-way mirror:" + "2-way diagonal mirror:4-way mirror:4-way diagonal mirror:" + "8-way mirror"; ret[2].ival = params->symm; ret[3].name = "Difficulty"; @@ -1350,77 +1367,103 @@ static int check_valid(int c, int r, digit *grid) return TRUE; } -static void symmetry_limit(game_params *params, int *xlim, int *ylim, int s) +static int symmetries(game_params *params, int x, int y, int *output, int s) { int c = params->c, r = params->r, cr = c*r; + int i = 0; + +#define ADD(x,y) (*output++ = (x), *output++ = (y), i++) + + ADD(x, y); switch (s) { case SYMM_NONE: - *xlim = *ylim = cr; - break; + break; /* just x,y is all we need */ case SYMM_ROT2: - *xlim = (cr+1) / 2; - *ylim = cr; - break; - case SYMM_REF4: + ADD(cr - 1 - x, cr - 1 - y); + break; case SYMM_ROT4: - *xlim = *ylim = (cr+1) / 2; - break; + ADD(cr - 1 - y, x); + ADD(y, cr - 1 - x); + ADD(cr - 1 - x, cr - 1 - y); + break; + case SYMM_REF2: + ADD(cr - 1 - x, y); + break; + case SYMM_REF2D: + ADD(y, x); + break; + case SYMM_REF4: + ADD(cr - 1 - x, y); + ADD(x, cr - 1 - y); + ADD(cr - 1 - x, cr - 1 - y); + break; + case SYMM_REF4D: + ADD(y, x); + ADD(cr - 1 - x, cr - 1 - y); + ADD(cr - 1 - y, cr - 1 - x); + break; + case SYMM_REF8: + ADD(cr - 1 - x, y); + ADD(x, cr - 1 - y); + ADD(cr - 1 - x, cr - 1 - y); + ADD(y, x); + ADD(y, cr - 1 - x); + ADD(cr - 1 - y, x); + ADD(cr - 1 - y, cr - 1 - x); + break; } + +#undef ADD + + return i; } -static int symmetries(game_params *params, int x, int y, int *output, int s) +static char *encode_solve_move(int cr, digit *grid) { - int c = params->c, r = params->r, cr = c*r; - int i = 0; + int i, len; + char *ret, *p, *sep; - *output++ = x; - *output++ = y; - i++; + /* + * It's surprisingly easy to work out _exactly_ how long this + * string needs to be. To decimal-encode all the numbers from 1 + * to n: + * + * - every number has a units digit; total is n. + * - all numbers above 9 have a tens digit; total is max(n-9,0). + * - all numbers above 99 have a hundreds digit; total is max(n-99,0). + * - and so on. + */ + len = 0; + for (i = 1; i <= cr; i *= 10) + len += max(cr - i + 1, 0); + len += cr; /* don't forget the commas */ + len *= cr; /* there are cr rows of these */ - switch (s) { - case SYMM_NONE: - break; /* just x,y is all we need */ - case SYMM_REF4: - case SYMM_ROT4: - switch (s) { - case SYMM_REF4: - *output++ = cr - 1 - x; - *output++ = y; - i++; - - *output++ = x; - *output++ = cr - 1 - y; - i++; - break; - case SYMM_ROT4: - *output++ = cr - 1 - y; - *output++ = x; - i++; - - *output++ = y; - *output++ = cr - 1 - x; - i++; - break; - } - /* fall through */ - case SYMM_ROT2: - *output++ = cr - 1 - x; - *output++ = cr - 1 - y; - i++; - break; + /* + * Now len is one bigger than the total size of the + * comma-separated numbers (because we counted an + * additional leading comma). We need to have a leading S + * and a trailing NUL, so we're off by one in total. + */ + len++; + + ret = snewn(len, char); + p = ret; + *p++ = 'S'; + sep = ""; + for (i = 0; i < cr*cr; i++) { + p += sprintf(p, "%s%d", sep, grid[i]); + sep = ","; } + *p++ = '\0'; + assert(p - ret == len); - return i; + return ret; } -struct game_aux_info { - int c, r; - digit *grid; -}; - static char *new_game_desc(game_params *params, random_state *rs, - game_aux_info **aux, int interactive) + char **aux, int interactive) { int c = params->c, r = params->r, cr = c*r; int area = cr*cr; @@ -1430,7 +1473,7 @@ static char *new_game_desc(game_params *params, random_state *rs, int ret; char *desc; int coords[16], ncoords; - int xlim, ylim; + int *symmclasses, nsymmclasses; int maxdiff, recursing; /* @@ -1448,6 +1491,31 @@ static char *new_game_desc(game_params *params, random_state *rs, grid2 = snewn(area, digit); /* + * Find the set of equivalence classes of squares permitted + * by the selected symmetry. We do this by enumerating all + * the grid squares which have no symmetric companion + * sorting lower than themselves. + */ + nsymmclasses = 0; + symmclasses = snewn(cr * cr, int); + { + int x, y; + + for (y = 0; y < cr; y++) + for (x = 0; x < cr; x++) { + int i = y*cr+x; + int j; + + ncoords = symmetries(params, x, y, coords, params->symm); + for (j = 0; j < ncoords; j++) + if (coords[2*j+1]*cr+coords[2*j] < i) + break; + if (j == ncoords) + symmclasses[nsymmclasses++] = i; + } + } + + /* * Loop until we get a grid of the required difficulty. This is * nasty, but it seems to be unpleasantly hard to generate * difficult grids otherwise. @@ -1463,32 +1531,25 @@ static char *new_game_desc(game_params *params, random_state *rs, assert(check_valid(c, r, grid)); /* - * Save the solved grid in the aux_info. + * Save the solved grid in aux. */ { - game_aux_info *ai = snew(game_aux_info); - ai->c = c; - ai->r = r; - ai->grid = snewn(cr * cr, digit); - memcpy(ai->grid, grid, cr * cr * sizeof(digit)); /* * We might already have written *aux the last time we * went round this loop, in which case we should free - * the old aux_info before overwriting it with the new - * one. + * the old aux before overwriting it with the new one. */ if (*aux) { - sfree((*aux)->grid); sfree(*aux); } - *aux = ai; + + *aux = encode_solve_move(cr, grid); } /* * Now we have a solved grid, start removing things from it * while preserving solubility. */ - symmetry_limit(params, &xlim, &ylim, params->symm); recursing = FALSE; while (1) { int x, y, i, j; @@ -1499,13 +1560,15 @@ static char *new_game_desc(game_params *params, random_state *rs, */ nlocs = 0; - for (x = 0; x < xlim; x++) - for (y = 0; y < ylim; y++) - if (grid[y*cr+x]) { - locs[nlocs].x = x; - locs[nlocs].y = y; - nlocs++; - } + for (i = 0; i < nsymmclasses; i++) { + x = symmclasses[i] % cr; + y = symmclasses[i] / cr; + if (grid[y*cr+x]) { + locs[nlocs].x = x; + locs[nlocs].y = y; + nlocs++; + } + } /* * Now shuffle that list. @@ -1570,6 +1633,8 @@ static char *new_game_desc(game_params *params, random_state *rs, sfree(grid2); sfree(locs); + sfree(symmclasses); + /* * Now we have the grid as it will be presented to the user. * Encode it in a game desc. @@ -1619,12 +1684,6 @@ static char *new_game_desc(game_params *params, random_state *rs, return desc; } -static void game_free_aux_info(game_aux_info *aux) -{ - sfree(aux->grid); - sfree(aux); -} - static char *validate_desc(game_params *params, char *desc) { int area = params->r * params->r * params->c * params->c; @@ -1726,39 +1785,38 @@ static void free_game(game_state *state) sfree(state); } -static game_state *solve_game(game_state *state, game_aux_info *ai, - char **error) +static char *solve_game(game_state *state, game_state *currstate, + char *ai, char **error) { - game_state *ret; int c = state->c, r = state->r, cr = c*r; + char *ret; + digit *grid; int rsolve_ret; - ret = dup_game(state); - ret->completed = ret->cheated = TRUE; - /* - * If we already have the solution in the aux_info, save - * ourselves some time. + * If we already have the solution in ai, save ourselves some + * time. */ - if (ai) { + if (ai) + return dupstr(ai); - assert(c == ai->c); - assert(r == ai->r); - memcpy(ret->grid, ai->grid, cr * cr * sizeof(digit)); + grid = snewn(cr*cr, digit); + memcpy(grid, state->grid, cr*cr); + rsolve_ret = rsolve(c, r, grid, NULL, 2); - } else { - rsolve_ret = rsolve(c, r, ret->grid, NULL, 2); - - if (rsolve_ret != 1) { - free_game(ret); - if (rsolve_ret == 0) - *error = "No solution exists for this puzzle"; - else - *error = "Multiple solutions exist for this puzzle"; - return NULL; - } + if (rsolve_ret != 1) { + sfree(grid); + if (rsolve_ret == 0) + *error = "No solution exists for this puzzle"; + else + *error = "Multiple solutions exist for this puzzle"; + return NULL; } + ret = encode_solve_move(cr, grid); + + sfree(grid); + return ret; } @@ -1854,6 +1912,15 @@ static void free_ui(game_ui *ui) sfree(ui); } +static char *encode_ui(game_ui *ui) +{ + return NULL; +} + +static void decode_ui(game_ui *ui, char *encoding) +{ +} + static void game_changed_state(game_ui *ui, game_state *oldstate, game_state *newstate) { @@ -1881,12 +1948,12 @@ struct game_drawstate { int *entered_items; }; -static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds, - int x, int y, int button) +static char *interpret_move(game_state *state, game_ui *ui, game_drawstate *ds, + int x, int y, int button) { - int c = from->c, r = from->r, cr = c*r; + int c = state->c, r = state->r, cr = c*r; int tx, ty; - game_state *ret; + char buf[80]; button &= ~MOD_MASK; @@ -1895,7 +1962,7 @@ static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds, if (tx >= 0 && tx < cr && ty >= 0 && ty < cr) { if (button == LEFT_BUTTON) { - if (from->immutable[ty*cr+tx]) { + if (state->immutable[ty*cr+tx]) { ui->hx = ui->hy = -1; } else if (tx == ui->hx && ty == ui->hy && ui->hpencil == 0) { ui->hx = ui->hy = -1; @@ -1904,13 +1971,13 @@ static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds, ui->hy = ty; ui->hpencil = 0; } - return from; /* UI activity occurred */ + return ""; /* UI activity occurred */ } if (button == RIGHT_BUTTON) { /* * Pencil-mode highlighting for non filled squares. */ - if (from->grid[ty*cr+tx] == 0) { + if (state->grid[ty*cr+tx] == 0) { if (tx == ui->hx && ty == ui->hy && ui->hpencil) { ui->hx = ui->hy = -1; } else { @@ -1921,7 +1988,7 @@ static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds, } else { ui->hx = ui->hy = -1; } - return from; /* UI activity occurred */ + return ""; /* UI activity occurred */ } } @@ -1944,7 +2011,7 @@ static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds, * able to highlight the square, but it never hurts to be * careful. */ - if (from->immutable[ui->hy*cr+ui->hx]) + if (state->immutable[ui->hy*cr+ui->hx]) return NULL; /* @@ -1953,16 +2020,57 @@ static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds, * have even been able to pencil-highlight the square, but * it never hurts to be careful. */ - if (ui->hpencil && from->grid[ui->hy*cr+ui->hx]) + if (ui->hpencil && state->grid[ui->hy*cr+ui->hx]) return NULL; + sprintf(buf, "%c%d,%d,%d", + (char)(ui->hpencil && n > 0 ? 'P' : 'R'), ui->hx, ui->hy, n); + + ui->hx = ui->hy = -1; + + return dupstr(buf); + } + + return NULL; +} + +static game_state *execute_move(game_state *from, char *move) +{ + int c = from->c, r = from->r, cr = c*r; + game_state *ret; + int x, y, n; + + if (move[0] == 'S') { + char *p; + ret = dup_game(from); - if (ui->hpencil && n > 0) { - int index = (ui->hy*cr+ui->hx) * cr + (n-1); + ret->completed = ret->cheated = TRUE; + + p = move+1; + for (n = 0; n < cr*cr; n++) { + ret->grid[n] = atoi(p); + + if (!*p || ret->grid[n] < 1 || ret->grid[n] > cr) { + free_game(ret); + return NULL; + } + + while (*p && isdigit((unsigned char)*p)) p++; + if (*p == ',') p++; + } + + return ret; + } else if ((move[0] == 'P' || move[0] == 'R') && + sscanf(move+1, "%d,%d,%d", &x, &y, &n) == 3 && + x >= 0 && x < cr && y >= 0 && y < cr && n >= 0 && n <= cr) { + + ret = dup_game(from); + if (move[0] == 'P' && n > 0) { + int index = (y*cr+x) * cr + (n-1); ret->pencil[index] = !ret->pencil[index]; } else { - ret->grid[ui->hy*cr+ui->hx] = n; - memset(ret->pencil + (ui->hy*cr+ui->hx)*cr, 0, cr); + ret->grid[y*cr+x] = n; + memset(ret->pencil + (y*cr+x)*cr, 0, cr); /* * We've made a real change to the grid. Check to see @@ -1972,12 +2080,9 @@ static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds, ret->completed = TRUE; } } - ui->hx = ui->hy = -1; - - return ret; /* made a valid move */ - } - - return NULL; + return ret; + } else + return NULL; /* couldn't parse move string */ } /* ---------------------------------------------------------------------- @@ -1985,19 +2090,19 @@ static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds, */ #define SIZE(cr) ((cr) * TILE_SIZE + 2*BORDER + 1) -#define GETTILESIZE(cr, w) ( (w-1) / (cr+1) ) +#define GETTILESIZE(cr, w) ( (double)(w-1) / (double)(cr+1) ) static void game_size(game_params *params, game_drawstate *ds, int *x, int *y, int expand) { int c = params->c, r = params->r, cr = c*r; - int ts; + double ts; ts = min(GETTILESIZE(cr, *x), GETTILESIZE(cr, *y)); if (expand) - ds->tilesize = ts; + ds->tilesize = (int)(ts+0.5); else - ds->tilesize = min(ts, PREFERRED_TILE_SIZE); + ds->tilesize = min((int)ts, PREFERRED_TILE_SIZE); *x = SIZE(cr); *y = SIZE(cr); @@ -2238,9 +2343,9 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate, /* Mark obvious errors (ie, numbers which occur more than once * in a single row, column, or box). */ - if ((ds->entered_items[x*cr+d-1] & 2) || - (ds->entered_items[y*cr+d-1] & 8) || - (ds->entered_items[((x/r)+(y/c)*c)*cr+d-1] & 32)) + if (d && ((ds->entered_items[x*cr+d-1] & 2) || + (ds->entered_items[y*cr+d-1] & 8) || + (ds->entered_items[((x/r)+(y/c)*c)*cr+d-1] & 32))) highlight |= 16; draw_number(fe, ds, state, x, y, highlight); @@ -2296,7 +2401,6 @@ const struct game thegame = { TRUE, game_configure, custom_params, validate_params, new_game_desc, - game_free_aux_info, validate_desc, new_game, dup_game, @@ -2305,8 +2409,11 @@ const struct game thegame = { TRUE, game_text_format, new_ui, free_ui, + encode_ui, + decode_ui, game_changed_state, - make_move, + interpret_move, + execute_move, game_size, game_colours, game_new_drawstate,