X-Git-Url: https://git.distorted.org.uk/~mdw/sgt/puzzles/blobdiff_plain/6193da8dac4ee67f89c5686c3c8e8ee253f07a0e..6c42c563460ffaa9fd04b7e3f336646065f33992:/loopy.c diff --git a/loopy.c b/loopy.c index 2b77798..71aaa31 100644 --- a/loopy.c +++ b/loopy.c @@ -42,6 +42,16 @@ * (Of course the algorithm would have to fail an assertion * if you tried to tell it two things it already knew to be * opposite were equal, or vice versa!) + * This data structure would also be useful in the + * graph-theoretic part of the solver, where it could be used + * for storing information about which lines are known-identical + * or known-opposite. (For example if two lines bordering a 3 + * are known-identical they must both be LINE_YES, and if they + * are known-opposite, the *other* two lines bordering that clue + * must be LINE_YES, etc). This may duplicate some + * functionality already present in the solver but it is more + * general and we could remove the old code, so that's no bad + * thing. */ #include @@ -59,7 +69,7 @@ #define LINEWIDTH TILE_SIZE / 16 #define BORDER (TILE_SIZE / 2) -#define FLASH_TIME 0.4F +#define FLASH_TIME 0.5F #define HL_COUNT(state) ((state)->w * ((state)->h + 1)) #define VL_COUNT(state) (((state)->w + 1) * (state)->h) @@ -108,21 +118,47 @@ #define OPP(dir) (dir == LINE_UNKNOWN ? LINE_UNKNOWN : \ dir == LINE_YES ? LINE_NO : LINE_YES) +#define BIT_SET(field, bit) ((field) & (1<<(bit))) + +#define SET_BIT(field, bit) (BIT_SET(field, bit) ? FALSE : \ + ((field) |= (1<<(bit)), TRUE)) + +#define CLEAR_BIT(field, bit) (BIT_SET(field, bit) ? \ + ((field) &= ~(1<<(bit)), TRUE) : FALSE) + static char *game_text_format(game_state *state); enum { COL_BACKGROUND, COL_FOREGROUND, COL_HIGHLIGHT, + COL_MISTAKE, NCOLOURS }; -enum line_state { LINE_UNKNOWN, LINE_YES, LINE_NO }; +/* + * Difficulty levels. I do some macro ickery here to ensure that my + * enum and the various forms of my name list always match up. + */ +#define DIFFLIST(A) \ + A(EASY,Easy,e) \ + A(NORMAL,Normal,n) +#define ENUM(upper,title,lower) DIFF_ ## upper, +#define TITLE(upper,title,lower) #title, +#define ENCODE(upper,title,lower) #lower +#define CONFIG(upper,title,lower) ":" #title +enum { DIFFLIST(ENUM) DIFFCOUNT }; +static char const *const loopy_diffnames[] = { DIFFLIST(TITLE) }; +static char const loopy_diffchars[] = DIFFLIST(ENCODE); +#define DIFFCONFIG DIFFLIST(CONFIG) + +/* LINE_YES_ERROR is only used in the drawing routine */ +enum line_state { LINE_UNKNOWN, LINE_YES, LINE_NO /*, LINE_YES_ERROR*/ }; enum direction { UP, DOWN, LEFT, RIGHT }; struct game_params { - int w, h, rec; + int w, h, diff, rec; }; struct game_state { @@ -182,13 +218,13 @@ enum solver_status { typedef struct solver_state { game_state *state; - /* XXX dot_atleastone[i,j, dline] is equivalent to */ - /* dot_atmostone[i,j,OPP_DLINE(dline)] */ char *dot_atleastone; char *dot_atmostone; /* char *dline_identical; */ int recursion_remaining; enum solver_status solver_status; + /* NB looplen is the number of dots that are joined together at a point, ie a + * looplen of 1 means there are no lines to a particular dot */ int *dotdsf, *looplen; } solver_state; @@ -209,7 +245,7 @@ static solver_state *new_solver_state(game_state *state) { #endif ret->recursion_remaining = state->recursion_depth; - ret->solver_status = SOLVER_INCOMPLETE; /* XXX This may be a lie */ + ret->solver_status = SOLVER_INCOMPLETE; ret->dotdsf = snewn(DOT_COUNT(state), int); ret->looplen = snewn(DOT_COUNT(state), int); @@ -227,15 +263,18 @@ static void free_solver_state(solver_state *sstate) { sfree(sstate->dot_atleastone); sfree(sstate->dot_atmostone); /* sfree(sstate->dline_identical); */ + sfree(sstate->dotdsf); + sfree(sstate->looplen); + sfree(sstate); } } static solver_state *dup_solver_state(solver_state *sstate) { - game_state *state = dup_game(sstate->state); + game_state *state; solver_state *ret = snew(solver_state); - ret->state = dup_game(state); + ret->state = state = dup_game(sstate->state); ret->dot_atmostone = snewn(DOT_COUNT(state), char); memcpy(ret->dot_atmostone, sstate->dot_atmostone, DOT_COUNT(state)); @@ -264,8 +303,10 @@ static solver_state *dup_solver_state(solver_state *sstate) { * Merge two dots due to the existence of an edge between them. * Updates the dsf tracking equivalence classes, and keeps track of * the length of path each dot is currently a part of. + * Returns TRUE if the dots were already linked, ie if they are part of a + * closed loop, and false otherwise. */ -static void merge_dots(solver_state *sstate, int x1, int y1, int x2, int y2) +static int merge_dots(solver_state *sstate, int x1, int y1, int x2, int y2) { int i, j, len; @@ -275,11 +316,14 @@ static void merge_dots(solver_state *sstate, int x1, int y1, int x2, int y2) i = dsf_canonify(sstate->dotdsf, i); j = dsf_canonify(sstate->dotdsf, j); - if (i != j) { + if (i == j) { + return TRUE; + } else { len = sstate->looplen[i] + sstate->looplen[j]; dsf_merge(sstate->dotdsf, i, j); i = dsf_canonify(sstate->dotdsf, i); sstate->looplen[i] = len; + return FALSE; } } @@ -338,19 +382,36 @@ static int square_order(const game_state* state, int i, int j, char line_type) return n; } -/* Set all lines bordering a dot of type old_type to type new_type */ -static void dot_setall(game_state *state, int i, int j, +/* Set all lines bordering a dot of type old_type to type new_type + * Return value tells caller whether this function actually did anything */ +static int dot_setall(game_state *state, int i, int j, char old_type, char new_type) { -/* printf("dot_setall([%d,%d], %d, %d)\n", i, j, old_type, new_type); */ - if (i > 0 && LEFTOF_DOT(state, i, j) == old_type) + int retval = FALSE; + if (old_type == new_type) + return FALSE; + + if (i > 0 && LEFTOF_DOT(state, i, j) == old_type) { LV_LEFTOF_DOT(state, i, j) = new_type; - if (i < state->w && RIGHTOF_DOT(state, i, j) == old_type) + retval = TRUE; + } + + if (i < state->w && RIGHTOF_DOT(state, i, j) == old_type) { LV_RIGHTOF_DOT(state, i, j) = new_type; - if (j > 0 && ABOVE_DOT(state, i, j) == old_type) + retval = TRUE; + } + + if (j > 0 && ABOVE_DOT(state, i, j) == old_type) { LV_ABOVE_DOT(state, i, j) = new_type; - if (j < state->h && BELOW_DOT(state, i, j) == old_type) + retval = TRUE; + } + + if (j < state->h && BELOW_DOT(state, i, j) == old_type) { LV_BELOW_DOT(state, i, j) = new_type; + retval = TRUE; + } + + return retval; } /* Set all lines bordering a square of type old_type to type new_type */ static void square_setall(game_state *state, int i, int j, @@ -370,9 +431,15 @@ static game_params *default_params(void) { game_params *ret = snew(game_params); +#ifdef SLOW_SYSTEM + ret->h = 4; + ret->w = 4; +#else ret->h = 10; ret->w = 10; - ret->rec = 0; +#endif + ret->diff = DIFF_EASY; + ret->rec = 0; return ret; } @@ -388,14 +455,18 @@ static const struct { char *desc; game_params params; } loopy_presets[] = { - { "4x4 Easy", { 4, 4, 0 } }, - { "4x4 Hard", { 4, 4, 2 } }, - { "7x7 Easy", { 7, 7, 0 } }, - { "7x7 Hard", { 7, 7, 0 } }, - { "10x10 Easy", { 10, 10, 0 } }, - { "10x10 Hard", { 10, 10, 2 } }, - { "15x15 Easy", { 15, 15, 0 } }, - { "20x30 Easy", { 20, 30, 0 } } + { "4x4 Easy", { 4, 4, DIFF_EASY, 0 } }, + { "4x4 Normal", { 4, 4, DIFF_NORMAL, 0 } }, + { "7x7 Easy", { 7, 7, DIFF_EASY, 0 } }, + { "7x7 Normal", { 7, 7, DIFF_NORMAL, 0 } }, + { "10x10 Easy", { 10, 10, DIFF_EASY, 0 } }, + { "10x10 Normal", { 10, 10, DIFF_NORMAL, 0 } }, +#ifndef SLOW_SYSTEM + { "15x15 Easy", { 15, 15, DIFF_EASY, 0 } }, + { "15x15 Normal", { 15, 15, DIFF_NORMAL, 0 } }, + { "30x20 Easy", { 30, 20, DIFF_EASY, 0 } }, + { "30x20 Normal", { 30, 20, DIFF_NORMAL, 0 } } +#endif }; static int game_fetch_preset(int i, char **name, game_params **params) @@ -421,6 +492,7 @@ static void decode_params(game_params *params, char const *string) { params->h = params->w = atoi(string); params->rec = 0; + params->diff = DIFF_EASY; while (*string && isdigit((unsigned char)*string)) string++; if (*string == 'x') { string++; @@ -432,6 +504,15 @@ static void decode_params(game_params *params, char const *string) params->rec = atoi(string); while (*string && isdigit((unsigned char)*string)) string++; } + if (*string == 'd') { + int i; + + string++; + for (i = 0; i < DIFFCOUNT; i++) + if (*string == loopy_diffchars[i]) + params->diff = i; + if (*string) string++; + } } static char *encode_params(game_params *params, int full) @@ -439,7 +520,8 @@ static char *encode_params(game_params *params, int full) char str[80]; sprintf(str, "%dx%d", params->w, params->h); if (full) - sprintf(str + strlen(str), "r%d", params->rec); + sprintf(str + strlen(str), "r%dd%c", params->rec, + loopy_diffchars[params->diff]); return dupstr(str); } @@ -462,11 +544,10 @@ static config_item *game_configure(game_params *params) ret[1].sval = dupstr(buf); ret[1].ival = 0; - ret[2].name = "Recursion depth"; - ret[2].type = C_STRING; - sprintf(buf, "%d", params->rec); - ret[2].sval = dupstr(buf); - ret[2].ival = 0; + ret[2].name = "Difficulty"; + ret[2].type = C_CHOICES; + ret[2].sval = DIFFCONFIG; + ret[2].ival = params->diff; ret[3].name = NULL; ret[3].type = C_END; @@ -482,7 +563,8 @@ static game_params *custom_params(config_item *cfg) ret->w = atoi(cfg[0].sval); ret->h = atoi(cfg[1].sval); - ret->rec = atoi(cfg[2].sval); + ret->rec = 0; + ret->diff = cfg[2].ival; return ret; } @@ -493,6 +575,14 @@ static char *validate_params(game_params *params, int full) return "Width and height must both be at least 4"; if (params->rec < 0) return "Recursion depth can't be negative"; + + /* + * This shouldn't be able to happen at all, since decode_params + * and custom_params will never generate anything that isn't + * within range. + */ + assert(params->diff >= 0 && params->diff < DIFFCOUNT); + return NULL; } @@ -503,7 +593,7 @@ static char *validate_params(game_params *params, int full) * light towards those with high scores */ struct square { int score; - int random; + unsigned long random; int x, y; }; @@ -535,10 +625,10 @@ static int square_sort_cmpfn(void *v1, void *v2) return r; } - r = s1->random - s2->random; - if (r) { - return r; - } + if (s1->random < s2->random) + return -1; + else if (s1->random > s2->random) + return 1; /* * It's _just_ possible that two squares might have been given @@ -754,7 +844,16 @@ static char *new_fullyclued_board(game_params *params, random_state *rs) square = (struct square *)index234(lightable_squares_sorted, 0); assert(square); - if (square->score <= 0) + /* + * We never want to _decrease_ the loop's perimeter. Making + * moves that leave the perimeter the same is occasionally + * useful: if it were _never_ done then the user would be + * able to deduce illicitly that any degree-zero vertex was + * on the outside of the loop. So we do it sometimes but + * not always. + */ + if (square->score < 0 || (square->score == 0 && + random_upto(rs, 2) == 0)) break; print_tree(lightable_squares_sorted); @@ -811,6 +910,7 @@ static char *new_fullyclued_board(game_params *params, random_state *rs) } } } + sfree(square); /* printf("\n\n"); */ } @@ -835,15 +935,15 @@ static char *new_fullyclued_board(game_params *params, random_state *rs) return clues; } -static solver_state *solve_game_rec(const solver_state *sstate); +static solver_state *solve_game_rec(const solver_state *sstate, int diff); -static int game_has_unique_soln(const game_state *state) +static int game_has_unique_soln(const game_state *state, int diff) { int ret; solver_state *sstate_new; solver_state *sstate = new_solver_state((game_state *)state); - sstate_new = solve_game_rec(sstate); + sstate_new = solve_game_rec(sstate, diff); ret = (sstate_new->solver_status == SOLVER_SOLVED); @@ -854,7 +954,7 @@ static int game_has_unique_soln(const game_state *state) } /* Remove clues one at a time at random. */ -static game_state *remove_clues(game_state *state, random_state *rs) +static game_state *remove_clues(game_state *state, random_state *rs, int diff) { int *square_list, squares; game_state *ret = dup_game(state), *saved_ret; @@ -876,13 +976,14 @@ static game_state *remove_clues(game_state *state, random_state *rs) saved_ret = dup_game(ret); LV_CLUE_AT(ret, square_list[n] % state->w, square_list[n] / state->w) = ' '; - if (game_has_unique_soln(ret)) { + if (game_has_unique_soln(ret, diff)) { free_game(saved_ret); } else { free_game(ret); ret = saved_ret; } } + sfree(square_list); return ret; } @@ -905,6 +1006,8 @@ static char *new_game_desc(game_params *params, random_state *rs, state->hl = snewn(HL_COUNT(params), char); state->vl = snewn(VL_COUNT(params), char); + +newboard_please: memset(state->hl, LINE_UNKNOWN, HL_COUNT(params)); memset(state->vl, LINE_UNKNOWN, VL_COUNT(params)); @@ -914,36 +1017,42 @@ static char *new_game_desc(game_params *params, random_state *rs, /* Get a new random solvable board with all its clues filled in. Yes, this * can loop for ever if the params are suitably unfavourable, but * preventing games smaller than 4x4 seems to stop this happening */ + do { state->clues = new_fullyclued_board(params, rs); - } while (!game_has_unique_soln(state)); + } while (!game_has_unique_soln(state, params->diff)); - state_new = remove_clues(state, rs); + state_new = remove_clues(state, rs, params->diff); free_game(state); state = state_new; + if (params->diff > 0 && game_has_unique_soln(state, params->diff-1)) { + /* Board is too easy */ + goto newboard_please; + } + empty_count = 0; for (j = 0; j < params->h; ++j) { for (i = 0; i < params->w; ++i) { if (CLUE_AT(state, i, j) == ' ') { if (empty_count > 25) { - dp += sprintf(dp, "%c", empty_count + 'a' - 1); + dp += sprintf(dp, "%c", (int)(empty_count + 'a' - 1)); empty_count = 0; } empty_count++; } else { if (empty_count) { - dp += sprintf(dp, "%c", empty_count + 'a' - 1); + dp += sprintf(dp, "%c", (int)(empty_count + 'a' - 1)); empty_count = 0; } - dp += sprintf(dp, "%c", CLUE_AT(state, i, j)); + dp += sprintf(dp, "%c", (int)(CLUE_AT(state, i, j))); } } } if (empty_count) - dp += sprintf(dp, "%c", empty_count + 'a' - 1); + dp += sprintf(dp, "%c", (int)(empty_count + 'a' - 1)); - sfree(state); + free_game(state); retval = dupstr(description); sfree(description); @@ -986,7 +1095,7 @@ static game_state *new_game(midend *me, game_params *params, char *desc) int n; const char *dp = desc; - state->recursion_depth = params->rec; + state->recursion_depth = 0; /* XXX pending removal, probably */ state->h = params->h; state->w = params->w; @@ -1027,142 +1136,9 @@ static game_state *new_game(midend *me, game_params *params, char *desc) enum { LOOP_NONE=0, LOOP_SOLN, LOOP_NOT_SOLN }; -/* Starting at dot [i,j] moves around 'state' removing lines until it's clear - * whether or not the starting dot was on a loop. Returns boolean specifying - * whether a loop was found. loop_status calls this and assumes that if state - * has any lines set, this function will always remove at least one. */ -static int destructively_find_loop(game_state *state) -{ - int a, b, i, j, new_i, new_j, n; - char *lp; - - lp = (char *)memchr(state->hl, LINE_YES, HL_COUNT(state)); - if (!lp) { - /* We know we're going to return false but we have to fulfil our - * contract */ - lp = (char *)memchr(state->vl, LINE_YES, VL_COUNT(state)); - if (lp) - *lp = LINE_NO; - - return FALSE; - } - - n = lp - state->hl; - - i = n % state->w; - j = n / state->w; - - assert(i + j * state->w == n); /* because I'm feeling stupid */ - /* Save start position */ - a = i; - b = j; - - /* Delete one line from the potential loop */ - if (LEFTOF_DOT(state, i, j) == LINE_YES) { - LV_LEFTOF_DOT(state, i, j) = LINE_NO; - i--; - } else if (ABOVE_DOT(state, i, j) == LINE_YES) { - LV_ABOVE_DOT(state, i, j) = LINE_NO; - j--; - } else if (RIGHTOF_DOT(state, i, j) == LINE_YES) { - LV_RIGHTOF_DOT(state, i, j) = LINE_NO; - i++; - } else if (BELOW_DOT(state, i, j) == LINE_YES) { - LV_BELOW_DOT(state, i, j) = LINE_NO; - j++; - } else { - return FALSE; - } - - do { - /* From the current position of [i,j] there needs to be exactly one - * line */ - new_i = new_j = -1; - -#define HANDLE_DIR(dir_dot, x, y) \ - if (dir_dot(state, i, j) == LINE_YES) { \ - if (new_i != -1 || new_j != -1) \ - return FALSE; \ - new_i = (i)+(x); \ - new_j = (j)+(y); \ - LV_##dir_dot(state, i, j) = LINE_NO; \ - } - HANDLE_DIR(ABOVE_DOT, 0, -1); - HANDLE_DIR(BELOW_DOT, 0, +1); - HANDLE_DIR(LEFTOF_DOT, -1, 0); - HANDLE_DIR(RIGHTOF_DOT, +1, 0); -#undef HANDLE_DIR - if (new_i == -1 || new_j == -1) { - return FALSE; - } - - i = new_i; - j = new_j; - } while (i != a || j != b); - - return TRUE; -} - -static int loop_status(game_state *state) -{ - int i, j, n; - game_state *tmpstate; - int loop_found = FALSE, non_loop_found = FALSE, any_lines_found = FALSE; - -#define BAD_LOOP_FOUND \ - do { free_game(tmpstate); return LOOP_NOT_SOLN; } while(0) - - /* Repeatedly look for loops until we either run out of lines to consider - * or discover for sure that the board fails on the grounds of having no - * loop */ - tmpstate = dup_game(state); - - while (TRUE) { - if (!memchr(tmpstate->hl, LINE_YES, HL_COUNT(tmpstate)) && - !memchr(tmpstate->vl, LINE_YES, VL_COUNT(tmpstate))) { - break; - } - any_lines_found = TRUE; - - if (loop_found) - BAD_LOOP_FOUND; - if (destructively_find_loop(tmpstate)) { - loop_found = TRUE; - if (non_loop_found) - BAD_LOOP_FOUND; - } else { - non_loop_found = TRUE; - } - } - - free_game(tmpstate); - - if (!any_lines_found) - return LOOP_NONE; - - if (non_loop_found) { - assert(!loop_found); /* should have dealt with this already */ - return LOOP_NONE; - } - - /* Check that every clue is satisfied */ - for (j = 0; j < state->h; ++j) { - for (i = 0; i < state->w; ++i) { - n = CLUE_AT(state, i, j); - if (n != ' ') { - if (square_order(state, i, j, LINE_YES) != n - '0') { - return LOOP_NOT_SOLN; - } - } - } - } - - return LOOP_SOLN; -} - /* Sums the lengths of the numbers in range [0,n) */ /* See equivalent function in solo.c for justification of this. */ -int len_0_to_n(int n) +static int len_0_to_n(int n) { int len = 1; /* Counting 0 as a bit of a special case */ int i; @@ -1235,8 +1211,8 @@ static char *encode_solve_move(const game_state *state) } /* No point in doing sums like that if they're going to be wrong */ - assert(strlen(ret) <= (size_t)len); - return dupstr(ret); + assert(strlen(ret) == (size_t)len); + return ret; } /* BEGIN SOLVER IMPLEMENTATION */ @@ -1285,84 +1261,37 @@ static void array_setall(char *array, char from, char to, int len) } } - -static int game_states_equal(const game_state *state1, - const game_state *state2) -{ - /* This deliberately doesn't check _all_ fields, just the ones that make a - * game state 'interesting' from the POV of the solver */ - /* XXX review this */ - if (state1 == state2) - return 1; - - if (!state1 || !state2) - return 0; - - if (state1->w != state2->w || state1->h != state2->h) - return 0; - - if (memcmp(state1->hl, state2->hl, HL_COUNT(state1))) - return 0; - - if (memcmp(state1->vl, state2->vl, VL_COUNT(state1))) - return 0; - - return 1; -} - -static int solver_states_equal(const solver_state *sstate1, - const solver_state *sstate2) -{ - if (!sstate1) { - if (!sstate2) - return TRUE; - else - return FALSE; - } - - if (!game_states_equal(sstate1->state, sstate2->state)) { - return 0; - } - - /* XXX fields missing, needs review */ - /* XXX we're deliberately not looking at solver_state as it's only a cache */ - - if (memcmp(sstate1->dot_atleastone, sstate2->dot_atleastone, - DOT_COUNT(sstate1->state))) { - return 0; - } - - if (memcmp(sstate1->dot_atmostone, sstate2->dot_atmostone, - DOT_COUNT(sstate1->state))) { - return 0; - } - - /* handle dline_identical here */ - - return 1; -} - -static void dot_setall_dlines(solver_state *sstate, enum dline dl, int i, int j, - enum line_state line_old, enum line_state line_new) +static int dot_setall_dlines(solver_state *sstate, enum dline dl, int i, int j, + enum line_state line_old, enum line_state line_new) { game_state *state = sstate->state; + int retval = FALSE; + + if (line_old == line_new) + return FALSE; /* First line in dline */ switch (dl) { case DLINE_UL: case DLINE_UR: case DLINE_VERT: - if (j > 0 && ABOVE_DOT(state, i, j) == line_old) + if (j > 0 && ABOVE_DOT(state, i, j) == line_old) { LV_ABOVE_DOT(state, i, j) = line_new; + retval = TRUE; + } break; case DLINE_DL: case DLINE_DR: - if (j <= (state)->h && BELOW_DOT(state, i, j) == line_old) + if (j <= (state)->h && BELOW_DOT(state, i, j) == line_old) { LV_BELOW_DOT(state, i, j) = line_new; + retval = TRUE; + } break; case DLINE_HORIZ: - if (i > 0 && LEFTOF_DOT(state, i, j) == line_old) + if (i > 0 && LEFTOF_DOT(state, i, j) == line_old) { LV_LEFTOF_DOT(state, i, j) = line_new; + retval = TRUE; + } break; } @@ -1370,52 +1299,71 @@ static void dot_setall_dlines(solver_state *sstate, enum dline dl, int i, int j, switch (dl) { case DLINE_UL: case DLINE_DL: - if (i > 0 && LEFTOF_DOT(state, i, j) == line_old) + if (i > 0 && LEFTOF_DOT(state, i, j) == line_old) { LV_LEFTOF_DOT(state, i, j) = line_new; + retval = TRUE; + } break; case DLINE_UR: case DLINE_DR: case DLINE_HORIZ: - if (i <= (state)->w && RIGHTOF_DOT(state, i, j) == line_old) + if (i <= (state)->w && RIGHTOF_DOT(state, i, j) == line_old) { LV_RIGHTOF_DOT(state, i, j) = line_new; + retval = TRUE; + } break; case DLINE_VERT: - if (j <= (state)->h && BELOW_DOT(state, i, j) == line_old) + if (j <= (state)->h && BELOW_DOT(state, i, j) == line_old) { LV_BELOW_DOT(state, i, j) = line_new; + retval = TRUE; + } break; } + + return retval; } -static void update_solver_status(solver_state *sstate) +#if 0 +/* This will fail an assertion if {dx,dy} are anything other than {-1,0}, {1,0} + * {0,-1} or {0,1} */ +static int line_status_from_point(const game_state *state, + int x, int y, int dx, int dy) { - if (sstate->solver_status == SOLVER_INCOMPLETE) { - switch (loop_status(sstate->state)) { - case LOOP_NONE: - sstate->solver_status = SOLVER_INCOMPLETE; - break; - case LOOP_SOLN: - if (sstate->solver_status != SOLVER_AMBIGUOUS) - sstate->solver_status = SOLVER_SOLVED; - break; - case LOOP_NOT_SOLN: - sstate->solver_status = SOLVER_MISTAKE; - break; - } - } + if (dx == -1 && dy == 0) + return LEFTOF_DOT(state, x, y); + if (dx == 1 && dy == 0) + return RIGHTOF_DOT(state, x, y); + if (dx == 0 && dy == -1) + return ABOVE_DOT(state, x, y); + if (dx == 0 && dy == 1) + return BELOW_DOT(state, x, y); + + assert(!"Illegal dx or dy in line_status_from_point"); + return 0; } - +#endif /* This will return a dynamically allocated solver_state containing the (more) * solved grid */ -static solver_state *solve_game_rec(const solver_state *sstate_start) +static solver_state *solve_game_rec(const solver_state *sstate_start, int diff) { - int i, j; + int i, j, w, h; int current_yes, current_no, desired; solver_state *sstate, *sstate_saved, *sstate_tmp; int t; -/* char *text; */ solver_state *sstate_rec_solved; int recursive_soln_count; + char *square_solved; + char *dot_solved; + int solver_progress; + + h = sstate_start->state->h; + w = sstate_start->state->w; + + dot_solved = snewn(DOT_COUNT(sstate_start->state), char); + square_solved = snewn(SQUARE_COUNT(sstate_start->state), char); + memset(dot_solved, FALSE, DOT_COUNT(sstate_start->state)); + memset(square_solved, FALSE, SQUARE_COUNT(sstate_start->state)); #if 0 printf("solve_game_rec: recursion_remaining = %d\n", @@ -1424,62 +1372,78 @@ static solver_state *solve_game_rec(const solver_state *sstate_start) sstate = dup_solver_state((solver_state *)sstate_start); -#if 0 - text = game_text_format(sstate->state); - printf("%s\n", text); - sfree(text); -#endif - -#define RETURN_IF_SOLVED \ +#define FOUND_MISTAKE \ do { \ - update_solver_status(sstate); \ - if (sstate->solver_status != SOLVER_INCOMPLETE) { \ - free_solver_state(sstate_saved); \ - return sstate; \ - } \ + sstate->solver_status = SOLVER_MISTAKE; \ + sfree(dot_solved); sfree(square_solved); \ + free_solver_state(sstate_saved); \ + return sstate; \ } while (0) sstate_saved = NULL; - RETURN_IF_SOLVED; nonrecursive_solver: while (1) { - sstate_saved = dup_solver_state(sstate); + solver_progress = FALSE; /* First we do the 'easy' work, that might cause concrete results */ /* Per-square deductions */ - for (j = 0; j < sstate->state->h; ++j) { - for (i = 0; i < sstate->state->w; ++i) { + for (j = 0; j < h; ++j) { + for (i = 0; i < w; ++i) { /* Begin rules that look at the clue (if there is one) */ + if (square_solved[i + j*w]) + continue; + desired = CLUE_AT(sstate->state, i, j); if (desired == ' ') continue; + desired = desired - '0'; current_yes = square_order(sstate->state, i, j, LINE_YES); current_no = square_order(sstate->state, i, j, LINE_NO); - if (desired <= current_yes) { + if (current_yes + current_no == 4) { + square_solved[i + j*w] = TRUE; + continue; + } + + if (desired < current_yes) + FOUND_MISTAKE; + if (desired == current_yes) { square_setall(sstate->state, i, j, LINE_UNKNOWN, LINE_NO); + square_solved[i + j*w] = TRUE; + solver_progress = TRUE; continue; } - if (4 - desired <= current_no) { + if (4 - desired < current_no) + FOUND_MISTAKE; + if (4 - desired == current_no) { square_setall(sstate->state, i, j, LINE_UNKNOWN, LINE_YES); + square_solved[i + j*w] = TRUE; + solver_progress = TRUE; } } } - RETURN_IF_SOLVED; - /* Per-dot deductions */ - for (j = 0; j < sstate->state->h + 1; ++j) { - for (i = 0; i < sstate->state->w + 1; ++i) { + for (j = 0; j < h + 1; ++j) { + for (i = 0; i < w + 1; ++i) { + if (dot_solved[i + j*(w+1)]) + continue; + switch (dot_order(sstate->state, i, j, LINE_YES)) { case 0: - if (dot_order(sstate->state, i, j, LINE_NO) == 3) { - dot_setall(sstate->state, i, j, LINE_UNKNOWN, LINE_NO); + switch (dot_order(sstate->state, i, j, LINE_NO)) { + case 3: + dot_setall(sstate->state, i, j, LINE_UNKNOWN, LINE_NO); + solver_progress = TRUE; + /* fall through */ + case 4: + dot_solved[i + j*(w+1)] = TRUE; + break; } break; case 1: @@ -1487,141 +1451,184 @@ nonrecursive_solver: #define H1(dline, dir1_dot, dir2_dot, dot_howmany) \ if (dir1_dot(sstate->state, i, j) == LINE_UNKNOWN) { \ if (dir2_dot(sstate->state, i, j) == LINE_UNKNOWN){ \ - sstate->dot_howmany \ - [i + (sstate->state->w + 1) * j] |= 1<dot_howmany[i + (w + 1) * j], \ + dline); \ } \ } case 1: + if (diff > DIFF_EASY) { #define HANDLE_DLINE(dline, dir1_dot, dir2_dot) \ H1(dline, dir1_dot, dir2_dot, dot_atleastone) - /* 1 yes, 1 no, so exactly one of unknowns is yes */ - DOT_DLINES; + /* 1 yes, 1 no, so exactly one of unknowns is + * yes */ + DOT_DLINES; #undef HANDLE_DLINE + } /* fall through */ case 0: + if (diff > DIFF_EASY) { #define HANDLE_DLINE(dline, dir1_dot, dir2_dot) \ H1(dline, dir1_dot, dir2_dot, dot_atmostone) - /* 1 yes, fewer than 2 no, so at most one of - * unknowns is yes */ - DOT_DLINES; + /* 1 yes, fewer than 2 no, so at most one of + * unknowns is yes */ + DOT_DLINES; #undef HANDLE_DLINE + } #undef H1 break; case 2: /* 1 yes, 2 no */ dot_setall(sstate->state, i, j, LINE_UNKNOWN, LINE_YES); + dot_solved[i + j*(w+1)] = TRUE; + solver_progress = TRUE; + break; + case 3: /* 1 yes, 3 no */ + FOUND_MISTAKE; break; } break; case 2: + if (dot_setall(sstate->state, i, j, LINE_UNKNOWN, LINE_NO)) { + solver_progress = TRUE; + } + dot_solved[i + j*(w+1)] = TRUE; + break; case 3: - dot_setall(sstate->state, i, j, LINE_UNKNOWN, LINE_NO); + case 4: + FOUND_MISTAKE; + break; } + if (diff > DIFF_EASY) { #define HANDLE_DLINE(dline, dir1_dot, dir2_dot) \ - if (sstate->dot_atleastone \ - [i + (sstate->state->w + 1) * j] & 1<dot_atmostone \ - [i + (sstate->state->w + 1) * j] |= 1<dot_atleastone[i + (w + 1) * j], dline)) { \ + solver_progress |= \ + SET_BIT(sstate->dot_atmostone[i + (w + 1) * j], \ + OPP_DLINE(dline)); \ + } + /* If at least one of a dline in a dot is YES, at most one + * of the opposite dline to that dot must be YES. */ + DOT_DLINES; } - /* If at least one of a dline in a dot is YES, at most one of - * the opposite dline to that dot must be YES. */ - DOT_DLINES; #undef HANDLE_DLINE - } - } - - /* More obscure per-square operations */ - for (j = 0; j < sstate->state->h; ++j) { - for (i = 0; i < sstate->state->w; ++i) { -#define H1(dline, dir1_sq, dir2_sq, a, b, dot_howmany, line_query, line_set) \ - if (sstate->dot_howmany[i+a + (sstate->state->w + 1) * (j+b)] &\ - 1<dot_howmany[i + (w+1) * j], dline)) { \ t = dir1_sq(sstate->state, i, j); \ - if (t == line_query) \ - dir2_sq(sstate->state, i, j) = line_set; \ - else { \ + if (t == line_query) { \ + if (dir2_sq(sstate->state, i, j) != line_set) { \ + LV_##dir2_sq(sstate->state, i, j) = line_set; \ + solver_progress = TRUE; \ + } \ + } else { \ t = dir2_sq(sstate->state, i, j); \ - if (t == line_query) \ - dir1_sq(sstate->state, i, j) = line_set; \ + if (t == line_query) { \ + if (dir1_sq(sstate->state, i, j) != line_set) { \ + LV_##dir1_sq(sstate->state, i, j) = line_set; \ + solver_progress = TRUE; \ + } \ + } \ } \ } -#define HANDLE_DLINE(dline, dir1_sq, dir2_sq, a, b) \ - H1(dline, dir1_sq, dir2_sq, a, b, dot_atmostone, \ - LINE_YES, LINE_NO) - /* If at most one of the DLINE is on, and one is definitely on, - * set the other to definitely off */ - SQUARE_DLINES; + if (diff > DIFF_EASY) { +#define HANDLE_DLINE(dline, dir1_sq, dir2_sq) \ + H1(dline, dir1_sq, dir2_sq, dot_atmostone, LINE_YES, LINE_NO) + /* If at most one of the DLINE is on, and one is definitely + * on, set the other to definitely off */ + DOT_DLINES; #undef HANDLE_DLINE + } -#define HANDLE_DLINE(dline, dir1_sq, dir2_sq, a, b) \ - H1(dline, dir1_sq, dir2_sq, a, b, dot_atleastone, \ - LINE_NO, LINE_YES) - /* If at least one of the DLINE is on, and one is definitely - * off, set the other to definitely on */ - SQUARE_DLINES; + if (diff > DIFF_EASY) { +#define HANDLE_DLINE(dline, dir1_sq, dir2_sq) \ + H1(dline, dir1_sq, dir2_sq, dot_atleastone, LINE_NO, LINE_YES) + /* If at least one of the DLINE is on, and one is definitely + * off, set the other to definitely on */ + DOT_DLINES; #undef HANDLE_DLINE + } #undef H1 + } + } + + /* More obscure per-square operations */ + for (j = 0; j < h; ++j) { + for (i = 0; i < w; ++i) { + if (square_solved[i + j*w]) + continue; + switch (CLUE_AT(sstate->state, i, j)) { - case '0': case '1': + if (diff > DIFF_EASY) { #define HANDLE_DLINE(dline, dir1_sq, dir2_sq, a, b) \ /* At most one of any DLINE can be set */ \ - sstate->dot_atmostone \ - [i+a + (sstate->state->w + 1) * (j+b)] |= 1<dot_atmostone[i+a + (w + 1) * (j+b)], \ + dline); \ /* This DLINE provides enough YESes to solve the clue */\ - if (sstate->dot_atleastone \ - [i+a + (sstate->state->w + 1) * (j+b)] & \ - 1<dot_atleastone \ + [i+a + (w + 1) * (j+b)], \ + dline)) { \ + solver_progress |= \ + dot_setall_dlines(sstate, OPP_DLINE(dline), \ + i+(1-a), j+(1-b), \ + LINE_UNKNOWN, LINE_NO); \ } - SQUARE_DLINES; + SQUARE_DLINES; #undef HANDLE_DLINE + } break; case '2': + if (diff > DIFF_EASY) { #define H1(dline, dot_at1one, dot_at2one, a, b) \ - if (sstate->dot_at1one \ - [i+a + (sstate->state->w + 1) * (j+b)] & \ - 1<dot_at2one \ - [i+(1-a) + (sstate->state->w + 1) * (j+(1-b))] |= \ - 1<dot_at1one \ + [i+a + (w+1) * (j+b)], dline)) { \ + solver_progress |= \ + SET_BIT(sstate->dot_at2one \ + [i+(1-a) + (w+1) * (j+(1-b))], \ + OPP_DLINE(dline)); \ } #define HANDLE_DLINE(dline, dir1_sq, dir2_sq, a, b) \ H1(dline, dot_atleastone, dot_atmostone, a, b); \ H1(dline, dot_atmostone, dot_atleastone, a, b); - /* If at least one of one DLINE is set, at most one of - * the opposing one is and vice versa */ - SQUARE_DLINES; + /* If at least one of one DLINE is set, at most one + * of the opposing one is and vice versa */ + SQUARE_DLINES; + } #undef HANDLE_DLINE #undef H1 break; case '3': - case '4': + if (diff > DIFF_EASY) { #define HANDLE_DLINE(dline, dir1_sq, dir2_sq, a, b) \ /* At least one of any DLINE can be set */ \ - sstate->dot_atleastone \ - [i+a + (sstate->state->w + 1) * (j+b)] |= 1<dot_atleastone \ + [i+a + (w + 1) * (j+b)], \ + dline); \ /* This DLINE provides enough NOs to solve the clue */ \ - if (sstate->dot_atmostone \ - [i+a + (sstate->state->w + 1) * (j+b)] & \ - 1<dot_atmostone \ + [i+a + (w + 1) * (j+b)], \ + dline)) { \ + solver_progress |= \ + dot_setall_dlines(sstate, OPP_DLINE(dline), \ + i+(1-a), j+(1-b), \ + LINE_UNKNOWN, LINE_YES); \ } - SQUARE_DLINES; + SQUARE_DLINES; #undef HANDLE_DLINE + } break; } } } - - if (solver_states_equal(sstate, sstate_saved)) { + + if (!solver_progress) { int edgecount = 0, clues = 0, satclues = 0, sm1clues = 0; + int shortest_chainlen = DOT_COUNT(sstate->state); + int loop_found = FALSE; int d; + int dots_connected; /* * Go through the grid and update for all the new edges. @@ -1632,14 +1639,14 @@ nonrecursive_solver: * clues, count the satisfied clues, and count the * satisfied-minus-one clues. */ - for (j = 0; j <= sstate->state->h; ++j) { - for (i = 0; i <= sstate->state->w; ++i) { + for (j = 0; j < h+1; ++j) { + for (i = 0; i < w+1; ++i) { if (RIGHTOF_DOT(sstate->state, i, j) == LINE_YES) { - merge_dots(sstate, i, j, i+1, j); + loop_found |= merge_dots(sstate, i, j, i+1, j); edgecount++; } if (BELOW_DOT(sstate->state, i, j) == LINE_YES) { - merge_dots(sstate, i, j, i, j+1); + loop_found |= merge_dots(sstate, i, j, i, j+1); edgecount++; } @@ -1655,14 +1662,30 @@ nonrecursive_solver: } } + for (i = 0; i < DOT_COUNT(sstate->state); ++i) { + dots_connected = sstate->looplen[dsf_canonify(sstate->dotdsf,i)]; + if (dots_connected > 1) + shortest_chainlen = min(shortest_chainlen, dots_connected); + } + + assert(sstate->solver_status == SOLVER_INCOMPLETE); + + if (satclues == clues && shortest_chainlen == edgecount) { + sstate->solver_status = SOLVER_SOLVED; + /* This discovery clearly counts as progress, even if we haven't + * just added any lines or anything */ + solver_progress = TRUE; + goto finished_loop_checking; + } + /* * Now go through looking for LINE_UNKNOWN edges which * connect two dots that are already in the same * equivalence class. If we find one, test to see if the * loop it would create is a solution. */ - for (j = 0; j <= sstate->state->h; ++j) { - for (i = 0; i <= sstate->state->w; ++i) { + for (j = 0; j <= h; ++j) { + for (i = 0; i <= w; ++i) { for (d = 0; d < 2; d++) { int i2, j2, eqclass, val; @@ -1680,11 +1703,9 @@ nonrecursive_solver: j2 = j+1; } - eqclass = dsf_canonify(sstate->dotdsf, - j * (sstate->state->w+1) + i); + eqclass = dsf_canonify(sstate->dotdsf, j * (w+1) + i); if (eqclass != dsf_canonify(sstate->dotdsf, - j2 * (sstate->state->w+1) + - i2)) + j2 * (w+1) + i2)) continue; val = LINE_NO; /* loop is bad until proven otherwise */ @@ -1750,10 +1771,13 @@ nonrecursive_solver: * a reasonable deduction for the user to * make. */ - if (d == 0) + if (d == 0) { LV_RIGHTOF_DOT(sstate->state, i, j) = val; - else + solver_progress = TRUE; + } else { LV_BELOW_DOT(sstate->state, i, j) = val; + solver_progress = TRUE; + } if (val == LINE_YES) { sstate->solver_status = SOLVER_AMBIGUOUS; goto finished_loop_checking; @@ -1764,18 +1788,16 @@ nonrecursive_solver: finished_loop_checking: - RETURN_IF_SOLVED; - } - - if (solver_states_equal(sstate, sstate_saved)) { - /* Solver has stopped making progress so we terminate */ - free_solver_state(sstate_saved); - break; + if (!solver_progress || + sstate->solver_status == SOLVER_SOLVED || + sstate->solver_status == SOLVER_AMBIGUOUS) { + break; + } } - - free_solver_state(sstate_saved); } + sfree(dot_solved); sfree(square_solved); + if (sstate->solver_status == SOLVER_SOLVED || sstate->solver_status == SOLVER_AMBIGUOUS) { /* s/LINE_UNKNOWN/LINE_NO/g */ @@ -1788,10 +1810,10 @@ nonrecursive_solver: /* Perform recursive calls */ if (sstate->recursion_remaining) { - sstate->recursion_remaining--; - sstate_saved = dup_solver_state(sstate); + sstate->recursion_remaining--; + recursive_soln_count = 0; sstate_rec_solved = NULL; @@ -1816,7 +1838,7 @@ nonrecursive_solver: if (dir_dot(sstate->state, i, j) == LINE_UNKNOWN) { \ debug(("Trying " #dir_dot " at [%d,%d]\n", i, j)); \ LV_##dir_dot(sstate->state, i, j) = LINE_YES; \ - sstate_tmp = solve_game_rec(sstate); \ + sstate_tmp = solve_game_rec(sstate, diff); \ switch (sstate_tmp->solver_status) { \ case SOLVER_AMBIGUOUS: \ debug(("Solver ambiguous, returning\n")); \ @@ -1853,18 +1875,14 @@ nonrecursive_solver: sstate = dup_solver_state(sstate_saved); \ } - for (j = 0; j < sstate->state->h + 1; ++j) { - for (i = 0; i < sstate->state->w + 1; ++i) { + for (j = 0; j < h + 1; ++j) { + for (i = 0; i < w + 1; ++i) { /* Only perform recursive calls on 'loose ends' */ if (dot_order(sstate->state, i, j, LINE_YES) == 1) { - if (LEFTOF_DOT(sstate->state, i, j) == LINE_UNKNOWN) - DO_RECURSIVE_CALL(LEFTOF_DOT); - if (RIGHTOF_DOT(sstate->state, i, j) == LINE_UNKNOWN) - DO_RECURSIVE_CALL(RIGHTOF_DOT); - if (ABOVE_DOT(sstate->state, i, j) == LINE_UNKNOWN) - DO_RECURSIVE_CALL(ABOVE_DOT); - if (BELOW_DOT(sstate->state, i, j) == LINE_UNKNOWN) - DO_RECURSIVE_CALL(BELOW_DOT); + DO_RECURSIVE_CALL(LEFTOF_DOT); + DO_RECURSIVE_CALL(RIGHTOF_DOT); + DO_RECURSIVE_CALL(ABOVE_DOT); + DO_RECURSIVE_CALL(BELOW_DOT); } } } @@ -1957,7 +1975,7 @@ static char *solve_game(game_state *state, game_state *currstate, solver_state *sstate, *new_sstate; sstate = new_solver_state(state); - new_sstate = solve_game_rec(sstate); + new_sstate = solve_game_rec(sstate, DIFFCOUNT); if (new_sstate->solver_status == SOLVER_SOLVED) { soln = encode_solve_move(new_sstate->state); @@ -2021,7 +2039,7 @@ static char *game_text_format(game_state *state) rp += sprintf(rp, " \n"); for (i = 0; i < state->w; ++i) { DRAW_VL; - rp += sprintf(rp, "%c", CLUE_AT(state, i, j)); + rp += sprintf(rp, "%c", (int)(CLUE_AT(state, i, j))); } DRAW_VL; rp += sprintf(rp, "\n"); @@ -2063,6 +2081,7 @@ struct game_drawstate { int tilesize; int flashing; char *hl, *vl; + char *clue_error; }; static char *interpret_move(game_state *state, game_ui *ui, game_drawstate *ds, @@ -2160,7 +2179,7 @@ static char *interpret_move(game_state *state, game_ui *ui, game_drawstate *ds, } - sprintf(buf, "%d,%d%c%c", i, j, hl_selected ? 'h' : 'v', button_char); + sprintf(buf, "%d,%d%c%c", i, j, (int)(hl_selected ? 'h' : 'v'), (int)button_char); ret = dupstr(buf); return ret; @@ -2226,6 +2245,7 @@ static game_state *execute_move(game_state *state, char *move) /* * Check for completion. */ + i = 0; /* placate optimiser */ for (j = 0; j <= newstate->h; j++) { for (i = 0; i < newstate->w; i++) if (LV_RIGHTOF_DOT(newstate, i, j) == LINE_YES) @@ -2356,6 +2376,10 @@ static float *game_colours(frontend *fe, game_state *state, int *ncolours) ret[COL_HIGHLIGHT * 3 + 1] = 1.0F; ret[COL_HIGHLIGHT * 3 + 2] = 1.0F; + ret[COL_MISTAKE * 3 + 0] = 1.0F; + ret[COL_MISTAKE * 3 + 1] = 0.0F; + ret[COL_MISTAKE * 3 + 2] = 0.0F; + *ncolours = NCOLOURS; return ret; } @@ -2368,16 +2392,19 @@ static game_drawstate *game_new_drawstate(drawing *dr, game_state *state) ds->started = 0; ds->hl = snewn(HL_COUNT(state), char); ds->vl = snewn(VL_COUNT(state), char); + ds->clue_error = snewn(SQUARE_COUNT(state), char); ds->flashing = 0; memset(ds->hl, LINE_UNKNOWN, HL_COUNT(state)); memset(ds->vl, LINE_UNKNOWN, VL_COUNT(state)); + memset(ds->clue_error, 0, SQUARE_COUNT(state)); return ds; } static void game_free_drawstate(drawing *dr, game_drawstate *ds) { + sfree(ds->clue_error); sfree(ds->hl); sfree(ds->vl); sfree(ds); @@ -2387,10 +2414,11 @@ static void game_redraw(drawing *dr, game_drawstate *ds, game_state *oldstate, game_state *state, int dir, game_ui *ui, float animtime, float flashtime) { - int i, j; + int i, j, n; int w = state->w, h = state->h; char c[2]; int line_colour, flash_changed; + int clue_mistake; if (!ds->started) { /* @@ -2443,10 +2471,48 @@ static void game_redraw(drawing *dr, game_drawstate *ds, game_state *oldstate, #define CROSS_SIZE (3 * LINEWIDTH / 2) + /* Redraw clue colours if necessary */ + for (j = 0; j < h; ++j) { + for (i = 0; i < w; ++i) { + c[0] = CLUE_AT(state, i, j); + c[1] = '\0'; + if (c[0] == ' ') + continue; + + n = c[0] - '0'; + assert(n >= 0 && n <= 4); + + clue_mistake = (square_order(state, i, j, LINE_YES) > n || + square_order(state, i, j, LINE_NO ) > (4-n)); + + if (clue_mistake != ds->clue_error[j * w + i]) { + draw_rect(dr, + BORDER + i * TILE_SIZE + CROSS_SIZE, + BORDER + j * TILE_SIZE + CROSS_SIZE, + TILE_SIZE - CROSS_SIZE * 2, TILE_SIZE - CROSS_SIZE * 2, + COL_BACKGROUND); + draw_text(dr, + BORDER + i * TILE_SIZE + TILE_SIZE/2, + BORDER + j * TILE_SIZE + TILE_SIZE/2, + FONT_VARIABLE, TILE_SIZE/2, + ALIGN_VCENTRE | ALIGN_HCENTRE, + clue_mistake ? COL_MISTAKE : COL_FOREGROUND, c); + draw_update(dr, i * TILE_SIZE + BORDER, j * TILE_SIZE + BORDER, + TILE_SIZE, TILE_SIZE); + + ds->clue_error[j * w + i] = clue_mistake; + } + } + } + + /* I've also had a request to colour lines red if they make a non-solution + * loop, or if more than two lines go into any point. I think that would + * be good some time. */ + #define CLEAR_VL(i, j) do { \ draw_rect(dr, \ BORDER + i * TILE_SIZE - CROSS_SIZE, \ - BORDER + j * TILE_SIZE + LINEWIDTH/2, \ + BORDER + j * TILE_SIZE + LINEWIDTH - LINEWIDTH/2, \ CROSS_SIZE * 2, \ TILE_SIZE - LINEWIDTH, \ COL_BACKGROUND); \ @@ -2459,7 +2525,7 @@ static void game_redraw(drawing *dr, game_drawstate *ds, game_state *oldstate, #define CLEAR_HL(i, j) do { \ draw_rect(dr, \ - BORDER + i * TILE_SIZE + LINEWIDTH/2, \ + BORDER + i * TILE_SIZE + LINEWIDTH - LINEWIDTH/2, \ BORDER + j * TILE_SIZE - CROSS_SIZE, \ TILE_SIZE - LINEWIDTH, \ CROSS_SIZE * 2, \ @@ -2486,7 +2552,7 @@ static void game_redraw(drawing *dr, game_drawstate *ds, game_state *oldstate, CLEAR_VL(i, j); draw_rect(dr, BORDER + i * TILE_SIZE - LINEWIDTH/2, - BORDER + j * TILE_SIZE + LINEWIDTH/2, + BORDER + j * TILE_SIZE + LINEWIDTH - LINEWIDTH/2, LINEWIDTH, TILE_SIZE - LINEWIDTH, line_colour); } @@ -2527,7 +2593,7 @@ static void game_redraw(drawing *dr, game_drawstate *ds, game_state *oldstate, flash_changed) { CLEAR_HL(i, j); draw_rect(dr, - BORDER + i * TILE_SIZE + LINEWIDTH/2, + BORDER + i * TILE_SIZE + LINEWIDTH - LINEWIDTH/2, BORDER + j * TILE_SIZE - LINEWIDTH/2, TILE_SIZE - LINEWIDTH, LINEWIDTH, line_colour);