X-Git-Url: https://git.distorted.org.uk/~mdw/sgt/puzzles/blobdiff_plain/edf63745d9f5a4432e05eb6cb27b9c69b7caf237..84cf18f701885475a025e3350492bbffcc201113:/solo.c diff --git a/solo.c b/solo.c index 97acf76..4255c02 100644 --- a/solo.c +++ b/solo.c @@ -107,12 +107,14 @@ int solver_show_working; typedef unsigned char digit; #define ORDER_MAX 255 -#define TILE_SIZE 32 -#define BORDER 18 +#define PREFERRED_TILE_SIZE 32 +#define TILE_SIZE (ds->tilesize) +#define BORDER (TILE_SIZE / 2) #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 }; @@ -202,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) @@ -238,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; @@ -275,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"; @@ -1349,67 +1367,55 @@ 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) -{ - int c = params->c, r = params->r, cr = c*r; - - switch (s) { - case SYMM_NONE: - *xlim = *ylim = cr; - break; - case SYMM_ROT2: - *xlim = (cr+1) / 2; - *ylim = cr; - break; - case SYMM_REF4: - case SYMM_ROT4: - *xlim = *ylim = (cr+1) / 2; - break; - } -} - 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; - *output++ = x; - *output++ = y; - i++; +#define ADD(x,y) (*output++ = (x), *output++ = (y), i++) + + ADD(x, y); 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; + ADD(cr - 1 - x, cr - 1 - y); + break; + case SYMM_ROT4: + 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; } @@ -1429,7 +1435,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; /* @@ -1447,6 +1453,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. @@ -1487,7 +1518,6 @@ static char *new_game_desc(game_params *params, random_state *rs, * 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; @@ -1498,13 +1528,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. @@ -1569,6 +1601,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. @@ -1725,8 +1759,8 @@ static void free_game(game_state *state) sfree(state); } -static game_state *solve_game(game_state *state, game_aux_info *ai, - char **error) +static game_state *solve_game(game_state *state, game_state *currstate, + game_aux_info *ai, char **error) { game_state *ret; int c = state->c, r = state->r, cr = c*r; @@ -1853,6 +1887,33 @@ static void free_ui(game_ui *ui) sfree(ui); } +static void game_changed_state(game_ui *ui, game_state *oldstate, + game_state *newstate) +{ + int c = newstate->c, r = newstate->r, cr = c*r; + /* + * We prevent pencil-mode highlighting of a filled square. So + * if the user has just filled in a square which we had a + * pencil-mode highlight in (by Undo, or by Redo, or by Solve), + * then we cancel the highlight. + */ + if (ui->hx >= 0 && ui->hy >= 0 && ui->hpencil && + newstate->grid[ui->hy * cr + ui->hx] != 0) { + ui->hx = ui->hy = -1; + } +} + +struct game_drawstate { + int started; + int c, r, cr; + int tilesize; + digit *grid; + unsigned char *pencil; + unsigned char *hl; + /* This is scratch space used within a single call to game_redraw. */ + int *entered_items; +}; + static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds, int x, int y, int button) { @@ -1956,25 +2017,23 @@ static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds, * Drawing routines. */ -struct game_drawstate { - int started; - int c, r, cr; - digit *grid; - unsigned char *pencil; - unsigned char *hl; - /* This is scratch space used within a single call to game_redraw. */ - int *entered_items; -}; +#define SIZE(cr) ((cr) * TILE_SIZE + 2*BORDER + 1) +#define GETTILESIZE(cr, w) ( (w-1) / (cr+1) ) -#define XSIZE(cr) ((cr) * TILE_SIZE + 2*BORDER + 1) -#define YSIZE(cr) ((cr) * TILE_SIZE + 2*BORDER + 1) - -static void game_size(game_params *params, int *x, int *y) +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; + + ts = min(GETTILESIZE(cr, *x), GETTILESIZE(cr, *y)); + if (expand) + ds->tilesize = ts; + else + ds->tilesize = min(ts, PREFERRED_TILE_SIZE); - *x = XSIZE(cr); - *y = YSIZE(cr); + *x = SIZE(cr); + *y = SIZE(cr); } static float *game_colours(frontend *fe, game_state *state, int *ncolours) @@ -2027,6 +2086,7 @@ static game_drawstate *game_new_drawstate(game_state *state) ds->hl = snewn(cr*cr, unsigned char); memset(ds->hl, 0, cr*cr); ds->entered_items = snewn(cr*cr, int); + ds->tilesize = 0; /* not decided yet */ return ds; } @@ -2158,7 +2218,7 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate, * all games should start by drawing a big * background-colour rectangle covering the whole window. */ - draw_rect(fe, 0, 0, XSIZE(cr), YSIZE(cr), COL_BACKGROUND); + draw_rect(fe, 0, 0, SIZE(cr), SIZE(cr), COL_BACKGROUND); /* * Draw the grid. @@ -2211,9 +2271,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); @@ -2224,7 +2284,7 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate, * Update the _entire_ grid if necessary. */ if (!ds->started) { - draw_update(fe, 0, 0, XSIZE(cr), YSIZE(cr)); + draw_update(fe, 0, 0, SIZE(cr), SIZE(cr)); ds->started = TRUE; } } @@ -2278,6 +2338,7 @@ const struct game thegame = { TRUE, game_text_format, new_ui, free_ui, + game_changed_state, make_move, game_size, game_colours,