COL_INK,
COL_SLANT1,
COL_SLANT2,
+ COL_ERROR,
NCOLOURS
};
typedef struct game_clues {
int w, h;
signed char *clues;
- int *dsf; /* scratch space for completion check */
+ signed char *tmpsoln;
int refcount;
} game_clues;
+#define ERR_VERTEX 1
+#define ERR_SQUARE 2
+
struct game_state {
struct game_params p;
game_clues *clues;
signed char *soln;
+ unsigned char *errors;
int completed;
int used_solve; /* used to suppress completion flash */
};
config_item *ret;
char buf[80];
- ret = snewn(2, config_item);
+ ret = snewn(4, config_item);
ret[0].name = "Width";
ret[0].type = C_STRING;
return NULL;
}
-static game_state *new_game(midend_data *me, game_params *params, char *desc)
+static game_state *new_game(midend *me, game_params *params, char *desc)
{
int w = params->w, h = params->h, W = w+1, H = h+1;
game_state *state = snew(game_state);
state->soln = snewn(w*h, signed char);
memset(state->soln, 0, w*h);
state->completed = state->used_solve = FALSE;
+ state->errors = snewn(W*H, unsigned char);
+ memset(state->errors, 0, W*H);
state->clues = snew(game_clues);
state->clues->w = w;
state->clues->h = h;
state->clues->clues = snewn(W*H, signed char);
state->clues->refcount = 1;
- state->clues->dsf = snewn(W*H, int);
+ state->clues->tmpsoln = snewn(w*h, signed char);
memset(state->clues->clues, -1, W*H);
while (*desc) {
int n = *desc++;
static game_state *dup_game(game_state *state)
{
- int w = state->p.w, h = state->p.h;
+ int w = state->p.w, h = state->p.h, W = w+1, H = h+1;
game_state *ret = snew(game_state);
ret->p = state->p;
ret->soln = snewn(w*h, signed char);
memcpy(ret->soln, state->soln, w*h);
+ ret->errors = snewn(W*H, unsigned char);
+ memcpy(ret->errors, state->errors, W*H);
+
return ret;
}
static void free_game(game_state *state)
{
+ sfree(state->errors);
sfree(state->soln);
assert(state->clues);
if (--state->clues->refcount <= 0) {
sfree(state->clues->clues);
- sfree(state->clues->dsf);
+ sfree(state->clues->tmpsoln);
sfree(state->clues);
}
sfree(state);
}
+/*
+ * Utility function to return the current degree of a vertex. If
+ * `anti' is set, it returns the number of filled-in edges
+ * surrounding the point which _don't_ connect to it; thus 4 minus
+ * its anti-degree is the maximum degree it could have if all the
+ * empty spaces around it were filled in.
+ *
+ * (Yes, _4_ minus its anti-degree even if it's a border vertex.)
+ *
+ * If ret > 0, *sx and *sy are set to the coordinates of one of the
+ * squares that contributed to it.
+ */
+static int vertex_degree(int w, int h, signed char *soln, int x, int y,
+ int anti, int *sx, int *sy)
+{
+ int ret = 0;
+
+ assert(x >= 0 && x <= w && y >= 0 && y <= h);
+ if (x > 0 && y > 0 && soln[(y-1)*w+(x-1)] - anti < 0) {
+ if (sx) *sx = x-1;
+ if (sy) *sy = y-1;
+ ret++;
+ }
+ if (x > 0 && y < h && soln[y*w+(x-1)] + anti > 0) {
+ if (sx) *sx = x-1;
+ if (sy) *sy = y;
+ ret++;
+ }
+ if (x < w && y > 0 && soln[(y-1)*w+x] + anti > 0) {
+ if (sx) *sx = x;
+ if (sy) *sy = y-1;
+ ret++;
+ }
+ if (x < w && y < h && soln[y*w+x] - anti < 0) {
+ if (sx) *sx = x;
+ if (sy) *sy = y;
+ ret++;
+ }
+
+ return anti ? 4 - ret : ret;
+}
+
static int check_completion(game_state *state)
{
int w = state->p.w, h = state->p.h, W = w+1, H = h+1;
- int i, x, y;
+ int x, y, err = FALSE;
+ signed char *ts;
+
+ memset(state->errors, 0, W*H);
/*
- * Establish a disjoint set forest for tracking connectedness
- * between grid points. Use the dsf scratch space in the shared
- * clues structure, to avoid mallocing too often.
+ * An easy way to do loop checking would be by means of the
+ * same dsf technique we've used elsewhere (loop over all edges
+ * in the grid, joining vertices together into equivalence
+ * classes when connected by an edge, and raise the alarm when
+ * an edge joins two already-equivalent vertices). However, a
+ * better approach is to repeatedly remove the single edge
+ * connecting to any degree-1 vertex, and then see if there are
+ * any edges left over; if so, precisely those edges are part
+ * of loops, which means we can highlight them as errors for
+ * the user.
+ *
+ * We use the `tmpsoln' scratch space in the shared clues
+ * structure, to avoid mallocing too often.
*/
- for (i = 0; i < W*H; i++)
- state->clues->dsf[i] = i; /* initially all distinct */
+ ts = state->clues->tmpsoln;
+ memcpy(ts, state->soln, w*h);
+ for (y = 0; y < H; y++)
+ for (x = 0; x < W; x++) {
+ int vx = x, vy = y;
+ int sx, sy;
+ /*
+ * Every time we disconnect a vertex like this, there
+ * is precisely one other vertex which might have
+ * become degree 1; so we follow the trail as far as it
+ * leads. This ensures that we don't have to make more
+ * than one loop over the grid, because whenever a
+ * degree-1 vertex comes into existence somewhere we've
+ * already looked, we immediately remove it again.
+ * Hence one loop over the grid is adequate; and
+ * moreover, this algorithm visits every vertex at most
+ * twice (once in the loop and possibly once more as a
+ * result of following a trail) so it has linear time
+ * in the area of the grid.
+ */
+ while (vertex_degree(w, h, ts, vx, vy, FALSE, &sx, &sy) == 1) {
+ ts[sy*w+sx] = 0;
+ vx = vx + 1 + (sx - vx) * 2;
+ vy = vy + 1 + (sy - vy) * 2;
+ }
+ }
/*
- * Now go through the grid checking connectedness. While we're
- * here, also check that everything is filled in.
+ * Now mark any remaining edges with ERR_SQUARE.
*/
for (y = 0; y < h; y++)
- for (x = 0; x < w; x++) {
- int i1, i2;
-
- if (state->soln[y*w+x] == 0)
- return FALSE;
- if (state->soln[y*w+x] < 0) {
- i1 = y*W+x;
- i2 = (y+1)*W+(x+1);
- } else {
- i1 = (y+1)*W+x;
- i2 = y*W+(x+1);
- }
-
- /*
- * Our edge connects i1 with i2. If they're already
- * connected, return failure. Otherwise, link them.
- */
- if (dsf_canonify(state->clues->dsf, i1) ==
- dsf_canonify(state->clues->dsf, i2))
- return FALSE;
- else
- dsf_merge(state->clues->dsf, i1, i2);
- }
+ for (x = 0; x < w; x++)
+ if (ts[y*w+x]) {
+ state->errors[y*W+x] |= ERR_SQUARE;
+ err = TRUE;
+ }
/*
- * The grid is _a_ valid grid; let's see if it matches the
- * clues.
+ * Now go through and check the degree of each clue vertex, and
+ * mark it with ERR_VERTEX if it cannot be fulfilled.
*/
for (y = 0; y < H; y++)
- for (x = 0; x < W; x++) {
- int v, c;
+ for (x = 0; x < W; x++) {
+ int c;
if ((c = state->clues->clues[y*W+x]) < 0)
continue;
- v = 0;
+ /*
+ * Check to see if there are too many connections to
+ * this vertex _or_ too many non-connections. Either is
+ * grounds for marking the vertex as erroneous.
+ */
+ if (vertex_degree(w, h, state->soln, x, y,
+ FALSE, NULL, NULL) > c ||
+ vertex_degree(w, h, state->soln, x, y,
+ TRUE, NULL, NULL) > 4-c) {
+ state->errors[y*W+x] |= ERR_VERTEX;
+ err = TRUE;
+ }
+ }
- if (x > 0 && y > 0 && state->soln[(y-1)*w+(x-1)] == -1) v++;
- if (x > 0 && y < h && state->soln[y*w+(x-1)] == +1) v++;
- if (x < w && y > 0 && state->soln[(y-1)*w+x] == +1) v++;
- if (x < w && y < h && state->soln[y*w+x] == -1) v++;
+ /*
+ * Now our actual victory condition is that (a) none of the
+ * above code marked anything as erroneous, and (b) every
+ * square has an edge in it.
+ */
- if (c != v)
+ if (err)
+ return FALSE;
+
+ for (y = 0; y < h; y++)
+ for (x = 0; x < w; x++)
+ if (state->soln[y*w+x] == 0)
return FALSE;
- }
return TRUE;
}
/*
* Bit fields in the `grid' and `todraw' elements of the drawstate.
*/
-#define BACKSLASH 0x0001
-#define FORWSLASH 0x0002
-#define L_T 0x0004
-#define L_B 0x0008
-#define T_L 0x0010
-#define T_R 0x0020
-#define R_T 0x0040
-#define R_B 0x0080
-#define B_L 0x0100
-#define B_R 0x0200
-#define C_TL 0x0400
-#define C_TR 0x0800
-#define C_BL 0x1000
-#define C_BR 0x2000
-#define FLASH 0x4000
+#define BACKSLASH 0x00000001L
+#define FORWSLASH 0x00000002L
+#define L_T 0x00000004L
+#define ERR_L_T 0x00000008L
+#define L_B 0x00000010L
+#define ERR_L_B 0x00000020L
+#define T_L 0x00000040L
+#define ERR_T_L 0x00000080L
+#define T_R 0x00000100L
+#define ERR_T_R 0x00000200L
+#define C_TL 0x00000400L
+#define ERR_C_TL 0x00000800L
+#define FLASH 0x00001000L
+#define ERRSLASH 0x00002000L
+#define ERR_TL 0x00004000L
+#define ERR_TR 0x00008000L
+#define ERR_BL 0x00010000L
+#define ERR_BR 0x00020000L
struct game_drawstate {
int tilesize;
int started;
- int *grid;
- int *todraw;
+ long *grid;
+ long *todraw;
};
static char *interpret_move(game_state *state, game_ui *ui, game_drawstate *ds,
int v;
char buf[80];
+ /*
+ * This is an utterly awful hack which I should really sort out
+ * by means of a proper configuration mechanism. One Slant
+ * player has observed that they prefer the mouse buttons to
+ * function exactly the opposite way round, so here's a
+ * mechanism for environment-based configuration. I cache the
+ * result in a global variable - yuck! - to avoid repeated
+ * lookups.
+ */
+ {
+ static int swap_buttons = -1;
+ if (swap_buttons < 0) {
+ char *env = getenv("SLANT_SWAP_BUTTONS");
+ swap_buttons = (env && (env[0] == 'y' || env[0] == 'Y'));
+ }
+ if (swap_buttons) {
+ if (button == LEFT_BUTTON)
+ button = RIGHT_BUTTON;
+ else
+ button = LEFT_BUTTON;
+ }
+ }
+
x = FROMCOORD(x);
y = FROMCOORD(y);
if (x < 0 || y < 0 || x >= w || y >= h)
}
}
- if (!ret->completed)
- ret->completed = check_completion(ret);
+ /*
+ * We never clear the `completed' flag, but we must always
+ * re-run the completion check because it also highlights
+ * errors in the grid.
+ */
+ ret->completed = check_completion(ret) || ret->completed;
return ret;
}
*y = 2 * BORDER + params->h * TILESIZE + 1;
}
-static void game_set_size(game_drawstate *ds, game_params *params,
- int tilesize)
+static void game_set_size(drawing *dr, game_drawstate *ds,
+ game_params *params, int tilesize)
{
ds->tilesize = tilesize;
}
ret[COL_SLANT2 * 3 + 1] = 0.0F;
ret[COL_SLANT2 * 3 + 2] = 0.0F;
+ ret[COL_ERROR * 3 + 0] = 1.0F;
+ ret[COL_ERROR * 3 + 1] = 0.0F;
+ ret[COL_ERROR * 3 + 2] = 0.0F;
+
*ncolours = NCOLOURS;
return ret;
}
-static game_drawstate *game_new_drawstate(game_state *state)
+static game_drawstate *game_new_drawstate(drawing *dr, game_state *state)
{
int w = state->p.w, h = state->p.h;
int i;
ds->tilesize = 0;
ds->started = FALSE;
- ds->grid = snewn(w*h, int);
- ds->todraw = snewn(w*h, int);
- for (i = 0; i < w*h; i++)
+ ds->grid = snewn((w+2)*(h+2), long);
+ ds->todraw = snewn((w+2)*(h+2), long);
+ for (i = 0; i < (w+2)*(h+2); i++)
ds->grid[i] = ds->todraw[i] = -1;
return ds;
}
-static void game_free_drawstate(game_drawstate *ds)
+static void game_free_drawstate(drawing *dr, game_drawstate *ds)
{
sfree(ds->todraw);
sfree(ds->grid);
sfree(ds);
}
-static void draw_clue(frontend *fe, game_drawstate *ds,
- int x, int y, int v)
+static void draw_clue(drawing *dr, game_drawstate *ds,
+ int x, int y, long v, long err, int bg, int colour)
{
char p[2];
- int col = ((x ^ y) & 1) ? COL_SLANT1 : COL_SLANT2;
+ int ccol = colour >= 0 ? colour : ((x ^ y) & 1) ? COL_SLANT1 : COL_SLANT2;
+ int tcol = colour >= 0 ? colour : err ? COL_ERROR : COL_INK;
if (v < 0)
return;
p[0] = v + '0';
p[1] = '\0';
- draw_circle(fe, COORD(x), COORD(y), CLUE_RADIUS, COL_BACKGROUND, col);
- draw_text(fe, COORD(x), COORD(y), FONT_VARIABLE,
- CLUE_TEXTSIZE, ALIGN_VCENTRE|ALIGN_HCENTRE,
- COL_INK, p);
+ draw_circle(dr, COORD(x), COORD(y), CLUE_RADIUS,
+ bg >= 0 ? bg : COL_BACKGROUND, ccol);
+ draw_text(dr, COORD(x), COORD(y), FONT_VARIABLE,
+ CLUE_TEXTSIZE, ALIGN_VCENTRE|ALIGN_HCENTRE, tcol, p);
}
-static void draw_tile(frontend *fe, game_drawstate *ds, game_clues *clues,
- int x, int y, int v)
+static void draw_tile(drawing *dr, game_drawstate *ds, game_clues *clues,
+ int x, int y, long v)
{
- int w = clues->w /*, h = clues->h*/, W = w+1 /*, H = h+1 */;
- int xx, yy;
+ int w = clues->w, h = clues->h, W = w+1 /*, H = h+1 */;
int chesscolour = (x ^ y) & 1;
int fscol = chesscolour ? COL_SLANT2 : COL_SLANT1;
int bscol = chesscolour ? COL_SLANT1 : COL_SLANT2;
- clip(fe, COORD(x), COORD(y), TILESIZE+1, TILESIZE+1);
+ clip(dr, COORD(x), COORD(y), TILESIZE, TILESIZE);
- draw_rect(fe, COORD(x), COORD(y), TILESIZE, TILESIZE,
+ draw_rect(dr, COORD(x), COORD(y), TILESIZE, TILESIZE,
(v & FLASH) ? COL_GRID : COL_BACKGROUND);
/*
* Draw the grid lines.
*/
- draw_line(fe, COORD(x), COORD(y), COORD(x+1), COORD(y), COL_GRID);
- draw_line(fe, COORD(x), COORD(y+1), COORD(x+1), COORD(y+1), COL_GRID);
- draw_line(fe, COORD(x), COORD(y), COORD(x), COORD(y+1), COL_GRID);
- draw_line(fe, COORD(x+1), COORD(y), COORD(x+1), COORD(y+1), COL_GRID);
+ if (x >= 0 && x < w && y >= 0)
+ draw_rect(dr, COORD(x), COORD(y), TILESIZE+1, 1, COL_GRID);
+ if (x >= 0 && x < w && y < h)
+ draw_rect(dr, COORD(x), COORD(y+1), TILESIZE+1, 1, COL_GRID);
+ if (y >= 0 && y < h && x >= 0)
+ draw_rect(dr, COORD(x), COORD(y), 1, TILESIZE+1, COL_GRID);
+ if (y >= 0 && y < h && x < w)
+ draw_rect(dr, COORD(x+1), COORD(y), 1, TILESIZE+1, COL_GRID);
+ if (x == -1 && y == -1)
+ draw_rect(dr, COORD(x+1), COORD(y+1), 1, 1, COL_GRID);
+ if (x == -1 && y == h)
+ draw_rect(dr, COORD(x+1), COORD(y), 1, 1, COL_GRID);
+ if (x == w && y == -1)
+ draw_rect(dr, COORD(x), COORD(y+1), 1, 1, COL_GRID);
+ if (x == w && y == h)
+ draw_rect(dr, COORD(x), COORD(y), 1, 1, COL_GRID);
/*
* Draw the slash.
*/
if (v & BACKSLASH) {
- draw_line(fe, COORD(x), COORD(y), COORD(x+1), COORD(y+1), bscol);
- draw_line(fe, COORD(x)+1, COORD(y), COORD(x+1), COORD(y+1)-1,
- bscol);
- draw_line(fe, COORD(x), COORD(y)+1, COORD(x+1)-1, COORD(y+1),
- bscol);
+ int scol = (v & ERRSLASH) ? COL_ERROR : bscol;
+ draw_line(dr, COORD(x), COORD(y), COORD(x+1), COORD(y+1), scol);
+ draw_line(dr, COORD(x)+1, COORD(y), COORD(x+1), COORD(y+1)-1,
+ scol);
+ draw_line(dr, COORD(x), COORD(y)+1, COORD(x+1)-1, COORD(y+1),
+ scol);
} else if (v & FORWSLASH) {
- draw_line(fe, COORD(x+1), COORD(y), COORD(x), COORD(y+1), fscol);
- draw_line(fe, COORD(x+1)-1, COORD(y), COORD(x), COORD(y+1)-1,
- fscol);
- draw_line(fe, COORD(x+1), COORD(y)+1, COORD(x)+1, COORD(y+1),
- fscol);
+ int scol = (v & ERRSLASH) ? COL_ERROR : fscol;
+ draw_line(dr, COORD(x+1), COORD(y), COORD(x), COORD(y+1), scol);
+ draw_line(dr, COORD(x+1)-1, COORD(y), COORD(x), COORD(y+1)-1,
+ scol);
+ draw_line(dr, COORD(x+1), COORD(y)+1, COORD(x)+1, COORD(y+1),
+ scol);
}
/*
* Draw dots on the grid corners that appear if a slash is in a
* neighbouring cell.
*/
- if (v & L_T)
- draw_rect(fe, COORD(x), COORD(y)+1, 1, 1, bscol);
- if (v & L_B)
- draw_rect(fe, COORD(x), COORD(y+1)-1, 1, 1, fscol);
- if (v & R_T)
- draw_rect(fe, COORD(x+1), COORD(y)+1, 1, 1, fscol);
- if (v & R_B)
- draw_rect(fe, COORD(x+1), COORD(y+1)-1, 1, 1, bscol);
- if (v & T_L)
- draw_rect(fe, COORD(x)+1, COORD(y), 1, 1, bscol);
- if (v & T_R)
- draw_rect(fe, COORD(x+1)-1, COORD(y), 1, 1, fscol);
- if (v & B_L)
- draw_rect(fe, COORD(x)+1, COORD(y+1), 1, 1, fscol);
- if (v & B_R)
- draw_rect(fe, COORD(x+1)-1, COORD(y+1), 1, 1, bscol);
- if (v & C_TL)
- draw_rect(fe, COORD(x), COORD(y), 1, 1, bscol);
- if (v & C_TR)
- draw_rect(fe, COORD(x+1), COORD(y), 1, 1, fscol);
- if (v & C_BL)
- draw_rect(fe, COORD(x), COORD(y+1), 1, 1, fscol);
- if (v & C_BR)
- draw_rect(fe, COORD(x+1), COORD(y+1), 1, 1, bscol);
+ if (v & (L_T | BACKSLASH))
+ draw_rect(dr, COORD(x), COORD(y)+1, 1, 1,
+ (v & ERR_L_T ? COL_ERROR : bscol));
+ if (v & (L_B | FORWSLASH))
+ draw_rect(dr, COORD(x), COORD(y+1)-1, 1, 1,
+ (v & ERR_L_B ? COL_ERROR : fscol));
+ if (v & (T_L | BACKSLASH))
+ draw_rect(dr, COORD(x)+1, COORD(y), 1, 1,
+ (v & ERR_T_L ? COL_ERROR : bscol));
+ if (v & (T_R | FORWSLASH))
+ draw_rect(dr, COORD(x+1)-1, COORD(y), 1, 1,
+ (v & ERR_T_R ? COL_ERROR : fscol));
+ if (v & (C_TL | BACKSLASH))
+ draw_rect(dr, COORD(x), COORD(y), 1, 1,
+ (v & ERR_C_TL ? COL_ERROR : bscol));
/*
* And finally the clues at the corners.
*/
- for (xx = x; xx <= x+1; xx++)
- for (yy = y; yy <= y+1; yy++)
- draw_clue(fe, ds, xx, yy, clues->clues[yy*W+xx]);
-
- unclip(fe);
- draw_update(fe, COORD(x), COORD(y), TILESIZE+1, TILESIZE+1);
+ if (x >= 0 && y >= 0)
+ draw_clue(dr, ds, x, y, clues->clues[y*W+x], v & ERR_TL, -1, -1);
+ if (x < w && y >= 0)
+ draw_clue(dr, ds, x+1, y, clues->clues[y*W+(x+1)], v & ERR_TR, -1, -1);
+ if (x >= 0 && y < h)
+ draw_clue(dr, ds, x, y+1, clues->clues[(y+1)*W+x], v & ERR_BL, -1, -1);
+ if (x < w && y < h)
+ draw_clue(dr, ds, x+1, y+1, clues->clues[(y+1)*W+(x+1)], v & ERR_BR,
+ -1, -1);
+
+ unclip(dr);
+ draw_update(dr, COORD(x), COORD(y), TILESIZE, TILESIZE);
}
-static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
+static void game_redraw(drawing *dr, game_drawstate *ds, game_state *oldstate,
game_state *state, int dir, game_ui *ui,
float animtime, float flashtime)
{
if (!ds->started) {
int ww, wh;
game_compute_size(&state->p, TILESIZE, &ww, &wh);
- draw_rect(fe, 0, 0, ww, wh, COL_BACKGROUND);
- draw_update(fe, 0, 0, ww, wh);
-
- /*
- * Draw any clues on the very edges (since normal tile
- * redraw won't draw the bits outside the grid boundary).
- */
- for (y = 0; y < H; y++) {
- draw_clue(fe, ds, 0, y, state->clues->clues[y*W+0]);
- draw_clue(fe, ds, w, y, state->clues->clues[y*W+w]);
- }
- for (x = 0; x < W; x++) {
- draw_clue(fe, ds, x, 0, state->clues->clues[0*W+x]);
- draw_clue(fe, ds, x, h, state->clues->clues[h*W+x]);
- }
-
+ draw_rect(dr, 0, 0, ww, wh, COL_BACKGROUND);
+ draw_update(dr, 0, 0, ww, wh);
ds->started = TRUE;
}
* We need to do this because a slash in one square affects the
* drawing of the next one along.
*/
- for (y = 0; y < h; y++)
- for (x = 0; x < w; x++)
- ds->todraw[y*w+x] = flashing ? FLASH : 0;
+ for (y = -1; y <= h; y++)
+ for (x = -1; x <= w; x++) {
+ if (x >= 0 && x < w && y >= 0 && y < h)
+ ds->todraw[(y+1)*(w+2)+(x+1)] = flashing ? FLASH : 0;
+ else
+ ds->todraw[(y+1)*(w+2)+(x+1)] = 0;
+ }
for (y = 0; y < h; y++) {
for (x = 0; x < w; x++) {
+ int err = state->errors[y*W+x] & ERR_SQUARE;
+
if (state->soln[y*w+x] < 0) {
- ds->todraw[y*w+x] |= BACKSLASH;
- if (x > 0)
- ds->todraw[y*w+(x-1)] |= R_T | C_TR;
- if (x+1 < w)
- ds->todraw[y*w+(x+1)] |= L_B | C_BL;
- if (y > 0)
- ds->todraw[(y-1)*w+x] |= B_L | C_BL;
- if (y+1 < h)
- ds->todraw[(y+1)*w+x] |= T_R | C_TR;
- if (x > 0 && y > 0)
- ds->todraw[(y-1)*w+(x-1)] |= C_BR;
- if (x+1 < w && y+1 < h)
- ds->todraw[(y+1)*w+(x+1)] |= C_TL;
+ ds->todraw[(y+1)*(w+2)+(x+1)] |= BACKSLASH;
+ ds->todraw[(y+2)*(w+2)+(x+1)] |= T_R;
+ ds->todraw[(y+1)*(w+2)+(x+2)] |= L_B;
+ ds->todraw[(y+2)*(w+2)+(x+2)] |= C_TL;
+ if (err) {
+ ds->todraw[(y+1)*(w+2)+(x+1)] |= ERRSLASH |
+ ERR_T_L | ERR_L_T | ERR_C_TL;
+ ds->todraw[(y+2)*(w+2)+(x+1)] |= ERR_T_R;
+ ds->todraw[(y+1)*(w+2)+(x+2)] |= ERR_L_B;
+ ds->todraw[(y+2)*(w+2)+(x+2)] |= ERR_C_TL;
+ }
} else if (state->soln[y*w+x] > 0) {
- ds->todraw[y*w+x] |= FORWSLASH;
- if (x > 0)
- ds->todraw[y*w+(x-1)] |= R_B | C_BR;
- if (x+1 < w)
- ds->todraw[y*w+(x+1)] |= L_T | C_TL;
- if (y > 0)
- ds->todraw[(y-1)*w+x] |= B_R | C_BR;
- if (y+1 < h)
- ds->todraw[(y+1)*w+x] |= T_L | C_TL;
- if (x > 0 && y+1 < h)
- ds->todraw[(y+1)*w+(x-1)] |= C_TR;
- if (x+1 < w && y > 0)
- ds->todraw[(y-1)*w+(x+1)] |= C_BL;
+ ds->todraw[(y+1)*(w+2)+(x+1)] |= FORWSLASH;
+ ds->todraw[(y+1)*(w+2)+(x+2)] |= L_T | C_TL;
+ ds->todraw[(y+2)*(w+2)+(x+1)] |= T_L | C_TL;
+ if (err) {
+ ds->todraw[(y+1)*(w+2)+(x+1)] |= ERRSLASH |
+ ERR_L_B | ERR_T_R;
+ ds->todraw[(y+1)*(w+2)+(x+2)] |= ERR_L_T | ERR_C_TL;
+ ds->todraw[(y+2)*(w+2)+(x+1)] |= ERR_T_L | ERR_C_TL;
+ }
}
}
}
+ for (y = 0; y < H; y++)
+ for (x = 0; x < W; x++)
+ if (state->errors[y*W+x] & ERR_VERTEX) {
+ ds->todraw[y*(w+2)+x] |= ERR_BR;
+ ds->todraw[y*(w+2)+(x+1)] |= ERR_BL;
+ ds->todraw[(y+1)*(w+2)+x] |= ERR_TR;
+ ds->todraw[(y+1)*(w+2)+(x+1)] |= ERR_TL;
+ }
+
/*
* Now go through and draw the grid squares.
*/
- for (y = 0; y < h; y++) {
- for (x = 0; x < w; x++) {
- if (ds->todraw[y*w+x] != ds->grid[y*w+x]) {
- draw_tile(fe, ds, state->clues, x, y, ds->todraw[y*w+x]);
- ds->grid[y*w+x] = ds->todraw[y*w+x];
+ for (y = -1; y <= h; y++) {
+ for (x = -1; x <= w; x++) {
+ if (ds->todraw[(y+1)*(w+2)+(x+1)] != ds->grid[(y+1)*(w+2)+(x+1)]) {
+ draw_tile(dr, ds, state->clues, x, y,
+ ds->todraw[(y+1)*(w+2)+(x+1)]);
+ ds->grid[(y+1)*(w+2)+(x+1)] = ds->todraw[(y+1)*(w+2)+(x+1)];
}
}
}
return TRUE;
}
+static void game_print_size(game_params *params, float *x, float *y)
+{
+ int pw, ph;
+
+ /*
+ * I'll use 6mm squares by default.
+ */
+ game_compute_size(params, 600, &pw, &ph);
+ *x = pw / 100.0;
+ *y = ph / 100.0;
+}
+
+static void game_print(drawing *dr, game_state *state, int tilesize)
+{
+ int w = state->p.w, h = state->p.h, W = w+1;
+ int ink = print_mono_colour(dr, 0);
+ int paper = print_mono_colour(dr, 1);
+ int x, y;
+
+ /* Ick: fake up `ds->tilesize' for macro expansion purposes */
+ game_drawstate ads, *ds = &ads;
+ ads.tilesize = tilesize;
+
+ /*
+ * Border.
+ */
+ print_line_width(dr, TILESIZE / 16);
+ draw_rect_outline(dr, COORD(0), COORD(0), w*TILESIZE, h*TILESIZE, ink);
+
+ /*
+ * Grid.
+ */
+ print_line_width(dr, TILESIZE / 24);
+ for (x = 1; x < w; x++)
+ draw_line(dr, COORD(x), COORD(0), COORD(x), COORD(h), ink);
+ for (y = 1; y < h; y++)
+ draw_line(dr, COORD(0), COORD(y), COORD(w), COORD(y), ink);
+
+ /*
+ * Solution.
+ */
+ print_line_width(dr, TILESIZE / 12);
+ for (y = 0; y < h; y++)
+ for (x = 0; x < w; x++)
+ if (state->soln[y*w+x]) {
+ int ly, ry;
+ /*
+ * To prevent nasty line-ending artefacts at
+ * corners, I'll do something slightly cunning
+ * here.
+ */
+ clip(dr, COORD(x), COORD(y), TILESIZE, TILESIZE);
+ if (state->soln[y*w+x] < 0)
+ ly = y-1, ry = y+2;
+ else
+ ry = y-1, ly = y+2;
+ draw_line(dr, COORD(x-1), COORD(ly), COORD(x+2), COORD(ry),
+ ink);
+ unclip(dr);
+ }
+
+ /*
+ * Clues.
+ */
+ print_line_width(dr, TILESIZE / 24);
+ for (y = 0; y <= h; y++)
+ for (x = 0; x <= w; x++)
+ draw_clue(dr, ds, x, y, state->clues->clues[y*W+x],
+ FALSE, paper, ink);
+}
+
#ifdef COMBINED
#define thegame slant
#endif
game_redraw,
game_anim_length,
game_flash_length,
+ TRUE, FALSE, game_print_size, game_print,
game_wants_statusbar,
FALSE, game_timing_state,
0, /* mouse_priorities */
*/
void frontend_default_colour(frontend *fe, float *output) {}
-void draw_text(frontend *fe, int x, int y, int fonttype, int fontsize,
+void draw_text(drawing *dr, int x, int y, int fonttype, int fontsize,
int align, int colour, char *text) {}
-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,
+void draw_rect(drawing *dr, int x, int y, int w, int h, int colour) {}
+void draw_rect_outline(drawing *dr, int x, int y, int w, int h, int colour) {}
+void draw_line(drawing *dr, int x1, int y1, int x2, int y2, int colour) {}
+void draw_polygon(drawing *dr, int *coords, int npoints,
int fillcolour, int outlinecolour) {}
-void draw_circle(frontend *fe, int cx, int cy, int radius,
+void draw_circle(drawing *dr, int cx, int cy, int radius,
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) {}
-void draw_update(frontend *fe, int x, int y, int w, int h) {}
-void end_draw(frontend *fe) {}
+void clip(drawing *dr, int x, int y, int w, int h) {}
+void unclip(drawing *dr) {}
+void start_draw(drawing *dr) {}
+void draw_update(drawing *dr, int x, int y, int w, int h) {}
+void end_draw(drawing *dr) {}
+int print_mono_colour(drawing *dr, int grey) { return 0; }
+void print_line_width(drawing *dr, int width) {}
unsigned long random_bits(random_state *state, int bits)
{ assert(!"Shouldn't get randomness"); return 0; }
unsigned long random_upto(random_state *state, unsigned long limit)
game_state *s;
char *id = NULL, *desc, *err;
int grade = FALSE;
- int ret;
+ int ret, diff, really_verbose = FALSE;
struct solver_scratch *sc;
while (--argc > 0) {
char *p = *++argv;
if (!strcmp(p, "-v")) {
- verbose = TRUE;
+ really_verbose = TRUE;
} else if (!strcmp(p, "-g")) {
grade = TRUE;
} else if (*p == '-') {
sc = new_scratch(p->w, p->h);
- if (grade) {
+ /*
+ * When solving an Easy puzzle, we don't want to bother the
+ * user with Hard-level deductions. For this reason, we grade
+ * the puzzle internally before doing anything else.
+ */
+ ret = -1; /* placate optimiser */
+ for (diff = 0; diff < DIFFCOUNT; diff++) {
ret = slant_solve(p->w, p->h, s->clues->clues,
- s->soln, sc, DIFF_EASY);
- if (ret == 0)
- printf("Difficulty rating: impossible (no solution exists)\n");
- else if (ret == 1)
- printf("Difficulty rating: Easy\n");
- else {
- ret = slant_solve(p->w, p->h, s->clues->clues,
- s->soln, sc, DIFF_HARD);
+ s->soln, sc, diff);
+ if (ret < 2)
+ break;
+ }
+
+ if (diff == DIFFCOUNT) {
+ if (grade)
+ printf("Difficulty rating: harder than Hard, or ambiguous\n");
+ else
+ printf("Unable to find a unique solution\n");
+ } else {
+ if (grade) {
if (ret == 0)
printf("Difficulty rating: impossible (no solution exists)\n");
else if (ret == 1)
- printf("Difficulty rating: Hard\n");
+ printf("Difficulty rating: %s\n", slant_diffnames[diff]);
+ } else {
+ verbose = really_verbose;
+ ret = slant_solve(p->w, p->h, s->clues->clues,
+ s->soln, sc, diff);
+ if (ret == 0)
+ printf("Puzzle is inconsistent\n");
else
- printf("Difficulty rating: harder than Hard, or ambiguous\n");
+ fputs(game_text_format(s), stdout);
}
- } else {
- ret = slant_solve(p->w, p->h, s->clues->clues,
- s->soln, sc, DIFF_HARD);
- if (ret == 0)
- printf("Puzzle is inconsistent\n");
- else if (ret > 1)
- printf("Unable to find a unique solution\n");
- else
- printf("%s\n", game_text_format(s));
}
return 0;