X-Git-Url: https://git.distorted.org.uk/~mdw/sgt/puzzles/blobdiff_plain/ccda7394b8c9e892b6da20eb028bd6dda25345e7..3c9388ef66ea155d1623d3d68d6733454edbe8b2:/slant.c diff --git a/slant.c b/slant.c index 1730409..c4f9a9f 100644 --- a/slant.c +++ b/slant.c @@ -37,6 +37,7 @@ enum { COL_INK, COL_SLANT1, COL_SLANT2, + COL_ERROR, NCOLOURS }; @@ -75,14 +76,19 @@ struct game_params { typedef struct game_clues { int w, h; signed char *clues; - int *dsf; /* scratch space for completion check */ + int *tmpdsf; int refcount; } game_clues; +#define ERR_VERTEX 1 +#define ERR_SQUARE 2 +#define ERR_SQUARE_TMP 4 + 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 */ }; @@ -1098,7 +1104,7 @@ static char *validate_desc(game_params *params, char *desc) 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); @@ -1109,13 +1115,15 @@ static game_state *new_game(midend_data *me, game_params *params, char *desc) 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->tmpdsf = snewn(W*H, int); memset(state->clues->clues, -1, W*H); while (*desc) { int n = *desc++; @@ -1133,7 +1141,7 @@ static game_state *new_game(midend_data *me, game_params *params, char *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; @@ -1145,84 +1153,264 @@ static game_state *dup_game(game_state *state) 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->tmpdsf); 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 i, x, y, err = FALSE; + int *dsf; - /* - * 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. - */ - for (i = 0; i < W*H; i++) - state->clues->dsf[i] = i; /* initially all distinct */ + memset(state->errors, 0, W*H); /* - * Now go through the grid checking connectedness. While we're - * here, also check that everything is filled in. + * To detect loops in the grid, we iterate through each edge + * building up a dsf of connected components, and raise the + * alarm whenever we find an edge that connects two + * already-connected vertices. + * + * We use the `tmpdsf' scratch space in the shared clues + * structure, to avoid mallocing too often. + * + * When we find such an edge, we then search around the grid to + * find the loop it is a part of, so that we can highlight it + * as an error for the user. We do this by the hand-on-one-wall + * technique: the search will follow branches off the inside of + * the loop, discover they're dead ends, and unhighlight them + * again when returning to the actual loop. + * + * This technique guarantees that every loop it tracks will + * surround a disjoint area of the grid (since if an existing + * loop appears on the boundary of a new one, so that there are + * multiple possible paths that would come back to the starting + * point, it will pick the one that allows it to turn right + * most sharply and hence the one that does not re-surround the + * area of the previous one). Thus, the total time taken in + * searching round loops is linear in the grid area since every + * edge is visited at most twice. */ + dsf = state->clues->tmpdsf; + for (i = 0; i < W*H; i++) + dsf[i] = i; /* initially all distinct */ for (y = 0; y < h; y++) - for (x = 0; x < w; x++) { - int i1, i2; + for (x = 0; x < w; x++) { + int i1, i2; + + if (state->soln[y*w+x] == 0) + continue; + if (state->soln[y*w+x] < 0) { + i1 = y*W+x; + i2 = (y+1)*W+(x+1); + } else { + i1 = y*W+(x+1); + i2 = (y+1)*W+x; + } - 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, flag an error. Otherwise, link them. + */ + if (dsf_canonify(dsf, i1) == dsf_canonify(dsf, i2)) { + int x1, y1, x2, y2, dx, dy, dt, pass; - /* - * 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); - } + err = TRUE; + + /* + * Now search around the boundary of the loop to + * highlight it. + * + * We have to do this in two passes. The first + * time, we toggle ERR_SQUARE_TMP on each edge; + * this pass terminates with ERR_SQUARE_TMP set on + * exactly the loop edges. In the second pass, we + * trace round that loop again and turn + * ERR_SQUARE_TMP into ERR_SQUARE. We have to do + * this because otherwise we might cancel part of a + * loop highlighted in a previous iteration of the + * outer loop. + */ + + for (pass = 0; pass < 2; pass++) { + + x1 = i1 % W; + y1 = i1 / W; + x2 = i2 % W; + y2 = i2 / W; + + do { + /* Mark this edge. */ + if (pass == 0) { + state->errors[min(y1,y2)*W+min(x1,x2)] ^= + ERR_SQUARE_TMP; + } else { + state->errors[min(y1,y2)*W+min(x1,x2)] |= + ERR_SQUARE; + state->errors[min(y1,y2)*W+min(x1,x2)] &= + ~ERR_SQUARE_TMP; + } + + /* + * Progress to the next edge by turning as + * sharply right as possible. In fact we do + * this by facing back along the edge and + * turning _left_ until we see an edge we + * can follow. + */ + dx = x1 - x2; + dy = y1 - y2; + + for (i = 0; i < 4; i++) { + /* + * Rotate (dx,dy) to the left. + */ + dt = dx; dx = dy; dy = -dt; + + /* + * See if (x2,y2) has an edge in direction + * (dx,dy). + */ + if (x2+dx < 0 || x2+dx >= W || + y2+dy < 0 || y2+dy >= H) + continue; /* off the side of the grid */ + /* In the second pass, ignore unmarked edges. */ + if (pass == 1 && + !(state->errors[(y2-(dy<0))*W+x2-(dx<0)] & + ERR_SQUARE_TMP)) + continue; + if (state->soln[(y2-(dy<0))*w+x2-(dx<0)] == + (dx==dy ? -1 : +1)) + break; + } + + /* + * In pass 0, we expect to have found + * _some_ edge we can follow, even if it + * was found by rotating all the way round + * and going back the way we came. + * + * In pass 1, because we're removing the + * mark on each edge that allows us to + * follow it, we expect to find _no_ edge + * we can follow when we've come all the + * way round the loop. + */ + if (pass == 1 && i == 4) + break; + assert(i < 4); + + /* + * Set x1,y1 to x2,y2, and x2,y2 to be the + * other end of the new edge. + */ + x1 = x2; + y1 = y2; + x2 += dx; + y2 += dy; + } while (y2*W+x2 != i2); + + } + + } else + dsf_merge(dsf, i1, i2); + } /* - * 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; + } + } + + /* + * 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 (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++; + if (err) + return FALSE; - if (c != v) + for (y = 0; y < h; y++) + for (x = 0; x < w; x++) + if (state->soln[y*w+x] == 0) return FALSE; - } return TRUE; } @@ -1370,27 +1558,30 @@ static void game_changed_state(game_ui *ui, game_state *oldstate, /* * 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, @@ -1486,8 +1677,12 @@ static game_state *execute_move(game_state *state, char *move) } } - 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; } @@ -1506,13 +1701,13 @@ static void game_compute_size(game_params *params, int tilesize, *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; } -static float *game_colours(frontend *fe, game_state *state, int *ncolours) +static float *game_colours(frontend *fe, int *ncolours) { float *ret = snewn(3 * NCOLOURS, float); @@ -1534,11 +1729,15 @@ static float *game_colours(frontend *fe, game_state *state, int *ncolours) 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; @@ -1546,118 +1745,129 @@ static game_drawstate *game_new_drawstate(game_state *state) 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) { @@ -1673,22 +1883,8 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate, 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; } @@ -1697,52 +1893,62 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate, * 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)]; } } } @@ -1764,14 +1970,80 @@ static float game_flash_length(game_state *oldstate, game_state *newstate, return 0.0F; } -static int game_wants_statusbar(void) +static int game_timing_state(game_state *state, game_ui *ui) { - return FALSE; + return TRUE; } -static int game_timing_state(game_state *state, game_ui *ui) +static void game_print_size(game_params *params, float *x, float *y) { - return TRUE; + 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; + game_set_size(dr, ds, NULL, 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 @@ -1809,54 +2081,16 @@ const struct game thegame = { game_redraw, game_anim_length, game_flash_length, - game_wants_statusbar, + TRUE, FALSE, game_print_size, game_print, + FALSE, /* wants_statusbar */ FALSE, game_timing_state, - 0, /* mouse_priorities */ + 0, /* flags */ }; #ifdef STANDALONE_SOLVER #include -/* - * gcc -DSTANDALONE_SOLVER -o slantsolver slant.c malloc.c - */ - -void frontend_default_colour(frontend *fe, float *output) {} -void draw_text(frontend *fe, 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, - int fillcolour, int outlinecolour) {} -void draw_circle(frontend *fe, 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) {} -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) -{ assert(!"Shouldn't get randomness"); return 0; } -void shuffle(void *array, int nelts, int eltsize, random_state *rs) -{ assert(!"Shouldn't get randomness"); } - -void fatal(char *fmt, ...) -{ - va_list ap; - - fprintf(stderr, "fatal error: "); - - va_start(ap, fmt); - vfprintf(stderr, fmt, ap); - va_end(ap); - - fprintf(stderr, "\n"); - exit(1); -} - int main(int argc, char **argv) { game_params *p; @@ -1908,6 +2142,7 @@ int main(int argc, char **argv) * 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);