#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 };
}
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 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;
int ret;
char *desc;
int coords[16], ncoords;
- int xlim, ylim;
+ int *symmclasses, nsymmclasses;
int maxdiff, recursing;
/*
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.
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;
*/
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.
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;
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;
}
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)
{
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;
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;
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 {
} else {
ui->hx = ui->hy = -1;
}
- return from; /* UI activity occurred */
+ return ""; /* UI activity occurred */
}
}
* 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;
/*
* 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);
+ 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 (ui->hpencil && n > 0) {
- int index = (ui->hy*cr+ui->hx) * cr + (n-1);
+ 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
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 */
}
/* ----------------------------------------------------------------------
*/
#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)
+static void game_compute_size(game_params *params, int tilesize,
+ int *x, int *y)
{
- int c = params->c, r = params->r, cr = c*r;
- int ts;
+ /* Ick: fake up `ds->tilesize' for macro expansion purposes */
+ struct { int tilesize; } ads, *ds = &ads;
+ ads.tilesize = tilesize;
- ts = min(GETTILESIZE(cr, *x), GETTILESIZE(cr, *y));
- if (expand)
- ds->tilesize = ts;
- else
- ds->tilesize = min(ts, PREFERRED_TILE_SIZE);
+ *x = SIZE(params->c * params->r);
+ *y = SIZE(params->c * params->r);
+}
- *x = SIZE(cr);
- *y = SIZE(cr);
+static void game_set_size(game_drawstate *ds, game_params *params,
+ int tilesize)
+{
+ ds->tilesize = tilesize;
}
static float *game_colours(frontend *fe, game_state *state, int *ncolours)
coords[3] = cy;
coords[4] = cx;
coords[5] = cy+ch/2;
- draw_polygon(fe, coords, 3, TRUE, COL_HIGHLIGHT);
+ draw_polygon(fe, coords, 3, COL_HIGHLIGHT, COL_HIGHLIGHT);
}
/* new number needs drawing? */
/* 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);
TRUE, game_configure, custom_params,
validate_params,
new_game_desc,
- game_free_aux_info,
validate_desc,
new_game,
dup_game,
TRUE, game_text_format,
new_ui,
free_ui,
+ encode_ui,
+ decode_ui,
game_changed_state,
- make_move,
- game_size,
+ interpret_move,
+ execute_move,
+ PREFERRED_TILE_SIZE, game_compute_size, game_set_size,
game_colours,
game_new_drawstate,
game_free_drawstate,
void draw_rect(frontend *fe, int x, int y, int w, int h, int colour) {}
void draw_line(frontend *fe, int x1, int y1, int x2, int y2, int colour) {}
void draw_polygon(frontend *fe, int *coords, int npoints,
- int fill, int colour) {}
+ int fillcolour, int outlinecolour) {}
void clip(frontend *fe, int x, int y, int w, int h) {}
void unclip(frontend *fe) {}
void start_draw(frontend *fe) {}