#include "puzzles.h"
-#define max(x,y) ((x)>(y)?(x):(y))
-
/*
* To save space, I store digits internally as unsigned char. This
* imposes a hard limit of 255 on the order of the puzzle. Since
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 };
COL_CLUE,
COL_USER,
COL_HIGHLIGHT,
+ COL_ERROR,
COL_PENCIL,
NCOLOURS
};
{ "3x3 Intermediate", { 3, 3, SYMM_ROT2, DIFF_INTERSECT } },
{ "3x3 Advanced", { 3, 3, SYMM_ROT2, DIFF_SET } },
{ "3x3 Unreasonable", { 3, 3, SYMM_ROT2, DIFF_RECURSIVE } },
+#ifndef SLOW_SYSTEM
{ "3x4 Basic", { 3, 4, SYMM_ROT2, DIFF_SIMPLE } },
{ "4x4 Basic", { 4, 4, SYMM_ROT2, DIFF_SIMPLE } },
+#endif
};
if (i < 0 || i >= lenof(presets))
}
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)
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;
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";
return ret;
}
+struct nsolve_scratch {
+ unsigned char *grid, *rowidx, *colidx, *set;
+};
+
static int nsolve_set(struct nsolve_usage *usage,
+ struct nsolve_scratch *scratch,
int start, int step1, int step2
#ifdef STANDALONE_SOLVER
, char *fmt, ...
{
int c = usage->c, r = usage->r, cr = c*r;
int i, j, n, count;
- unsigned char *grid = snewn(cr*cr, unsigned char);
- unsigned char *rowidx = snewn(cr, unsigned char);
- unsigned char *colidx = snewn(cr, unsigned char);
- unsigned char *set = snewn(cr, unsigned char);
+ unsigned char *grid = scratch->grid;
+ unsigned char *rowidx = scratch->rowidx;
+ unsigned char *colidx = scratch->colidx;
+ unsigned char *set = scratch->set;
/*
* We are passed a cr-by-cr matrix of booleans. Our first job
}
if (progress) {
- sfree(set);
- sfree(colidx);
- sfree(rowidx);
- sfree(grid);
return TRUE;
}
}
break; /* done */
}
- sfree(set);
- sfree(colidx);
- sfree(rowidx);
- sfree(grid);
-
return FALSE;
}
+static struct nsolve_scratch *nsolve_new_scratch(struct nsolve_usage *usage)
+{
+ struct nsolve_scratch *scratch = snew(struct nsolve_scratch);
+ int cr = usage->cr;
+ scratch->grid = snewn(cr*cr, unsigned char);
+ scratch->rowidx = snewn(cr, unsigned char);
+ scratch->colidx = snewn(cr, unsigned char);
+ scratch->set = snewn(cr, unsigned char);
+ return scratch;
+}
+
+static void nsolve_free_scratch(struct nsolve_scratch *scratch)
+{
+ sfree(scratch->set);
+ sfree(scratch->colidx);
+ sfree(scratch->rowidx);
+ sfree(scratch->grid);
+ sfree(scratch);
+}
+
static int nsolve(int c, int r, digit *grid)
{
struct nsolve_usage *usage;
+ struct nsolve_scratch *scratch;
int cr = c*r;
int x, y, n;
int diff = DIFF_BLOCK;
memset(usage->col, FALSE, cr * cr);
memset(usage->blk, FALSE, cr * cr);
+ scratch = nsolve_new_scratch(usage);
+
/*
* Place all the clue numbers we are given.
*/
*/
for (x = 0; x < cr; x += r)
for (y = 0; y < r; y++)
- if (nsolve_set(usage, cubepos(x,y,1), r*cr, 1
+ if (nsolve_set(usage, scratch, cubepos(x,y,1), r*cr, 1
#ifdef STANDALONE_SOLVER
, "set elimination, block (%d,%d)", 1+x/r, 1+y
#endif
* Row-wise set elimination.
*/
for (y = 0; y < cr; y++)
- if (nsolve_set(usage, cubepos(0,y,1), cr*cr, 1
+ if (nsolve_set(usage, scratch, cubepos(0,y,1), cr*cr, 1
#ifdef STANDALONE_SOLVER
, "set elimination, row %d", 1+YUNTRANS(y)
#endif
* Column-wise set elimination.
*/
for (x = 0; x < cr; x++)
- if (nsolve_set(usage, cubepos(x,0,1), cr, 1
+ if (nsolve_set(usage, scratch, cubepos(x,0,1), cr, 1
#ifdef STANDALONE_SOLVER
, "set elimination, column %d", 1+x
#endif
break;
}
+ nsolve_free_scratch(scratch);
+
sfree(usage->cube);
sfree(usage->row);
sfree(usage->col);
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;
}
int ret;
char *desc;
int coords[16], ncoords;
- int xlim, ylim;
+ int *symmclasses, nsymmclasses;
int maxdiff, recursing;
/*
locs = snewn(area, struct xy);
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
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.
+ */
+ if (*aux) {
+ sfree((*aux)->grid);
+ sfree(*aux);
+ }
*aux = ai;
}
* 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;
*/
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.
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.
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)
{
* Drawing routines.
*/
-struct game_drawstate {
- int started;
- int c, r, cr;
- digit *grid;
- unsigned char *pencil;
- unsigned char *hl;
-};
-
-#define XSIZE(cr) ((cr) * TILE_SIZE + 2*BORDER + 1)
-#define YSIZE(cr) ((cr) * TILE_SIZE + 2*BORDER + 1)
+#define SIZE(cr) ((cr) * TILE_SIZE + 2*BORDER + 1)
+#define GETTILESIZE(cr, w) ( (w-1) / (cr+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)
ret[COL_HIGHLIGHT * 3 + 1] = 0.85F * ret[COL_BACKGROUND * 3 + 1];
ret[COL_HIGHLIGHT * 3 + 2] = 0.85F * ret[COL_BACKGROUND * 3 + 2];
+ ret[COL_ERROR * 3 + 0] = 1.0F;
+ ret[COL_ERROR * 3 + 1] = 0.0F;
+ ret[COL_ERROR * 3 + 2] = 0.0F;
+
ret[COL_PENCIL * 3 + 0] = 0.5F * ret[COL_BACKGROUND * 3 + 0];
ret[COL_PENCIL * 3 + 1] = 0.5F * ret[COL_BACKGROUND * 3 + 1];
ret[COL_PENCIL * 3 + 2] = ret[COL_BACKGROUND * 3 + 2];
memset(ds->pencil, 0, cr*cr*cr);
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;
}
sfree(ds->hl);
sfree(ds->pencil);
sfree(ds->grid);
+ sfree(ds->entered_items);
sfree(ds);
}
clip(fe, cx, cy, cw, ch);
/* background needs erasing */
- draw_rect(fe, cx, cy, cw, ch, hl == 1 ? COL_HIGHLIGHT : COL_BACKGROUND);
+ draw_rect(fe, cx, cy, cw, ch, (hl & 15) == 1 ? COL_HIGHLIGHT : COL_BACKGROUND);
/* pencil-mode highlight */
- if (hl == 2) {
+ if ((hl & 15) == 2) {
int coords[6];
coords[0] = cx;
coords[1] = cy;
str[0] += 'a' - ('9'+1);
draw_text(fe, tx + TILE_SIZE/2, ty + TILE_SIZE/2,
FONT_VARIABLE, TILE_SIZE/2, ALIGN_VCENTRE | ALIGN_HCENTRE,
- state->immutable[y*cr+x] ? COL_CLUE : COL_USER, str);
+ state->immutable[y*cr+x] ? COL_CLUE : (hl & 16) ? COL_ERROR : COL_USER, str);
} else {
- /* pencil marks required? */
- int i, j;
+ int i, j, npencil;
+ int pw, ph, pmax, fontsize;
+
+ /* count the pencil marks required */
+ for (i = npencil = 0; i < cr; i++)
+ if (state->pencil[(y*cr+x)*cr+i])
+ npencil++;
+
+ /*
+ * It's not sensible to arrange pencil marks in the same
+ * layout as the squares within a block, because this leads
+ * to the font being too small. Instead, we arrange pencil
+ * marks in the nearest thing we can to a square layout,
+ * and we adjust the square layout depending on the number
+ * of pencil marks in the square.
+ */
+ for (pw = 1; pw * pw < npencil; pw++);
+ if (pw < 3) pw = 3; /* otherwise it just looks _silly_ */
+ ph = (npencil + pw - 1) / pw;
+ if (ph < 2) ph = 2; /* likewise */
+ pmax = max(pw, ph);
+ fontsize = TILE_SIZE/(pmax*(11-pmax)/8);
for (i = j = 0; i < cr; i++)
if (state->pencil[(y*cr+x)*cr+i]) {
- int dx = j % r, dy = j / r, crm = max(c, r);
+ int dx = j % pw, dy = j / pw;
+
str[1] = '\0';
str[0] = i + '1';
if (str[0] > '9')
str[0] += 'a' - ('9'+1);
- draw_text(fe, tx + (4*dx+3) * TILE_SIZE / (4*r+2),
- ty + (4*dy+3) * TILE_SIZE / (4*c+2),
- FONT_VARIABLE, TILE_SIZE/(crm*5/4),
+ draw_text(fe, tx + (4*dx+3) * TILE_SIZE / (4*pw+2),
+ ty + (4*dy+3) * TILE_SIZE / (4*ph+2),
+ FONT_VARIABLE, fontsize,
ALIGN_VCENTRE | ALIGN_HCENTRE, COL_PENCIL, str);
j++;
}
* 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.
}
}
+ /*
+ * This array is used to keep track of rows, columns and boxes
+ * which contain a number more than once.
+ */
+ for (x = 0; x < cr * cr; x++)
+ ds->entered_items[x] = 0;
+ for (x = 0; x < cr; x++)
+ for (y = 0; y < cr; y++) {
+ digit d = state->grid[y*cr+x];
+ if (d) {
+ int box = (x/r)+(y/c)*c;
+ ds->entered_items[x*cr+d-1] |= ((ds->entered_items[x*cr+d-1] & 1) << 1) | 1;
+ ds->entered_items[y*cr+d-1] |= ((ds->entered_items[y*cr+d-1] & 4) << 1) | 4;
+ ds->entered_items[box*cr+d-1] |= ((ds->entered_items[box*cr+d-1] & 16) << 1) | 16;
+ }
+ }
+
/*
* Draw any numbers which need redrawing.
*/
for (x = 0; x < cr; x++) {
for (y = 0; y < cr; y++) {
int highlight = 0;
+ digit d = state->grid[y*cr+x];
+
if (flashtime > 0 &&
(flashtime <= FLASH_TIME/3 ||
flashtime >= FLASH_TIME*2/3))
highlight = 1;
+
+ /* Highlight active input areas. */
if (x == ui->hx && y == ui->hy)
highlight = ui->hpencil ? 2 : 1;
+
+ /* Mark obvious errors (ie, numbers which occur more than once
+ * in a single row, column, or box). */
+ 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);
}
}
* 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;
}
}
TRUE, game_text_format,
new_ui,
free_ui,
+ game_changed_state,
make_move,
game_size,
game_colours,