* (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 <stdio.h>
#define PREFERRED_TILE_SIZE 32
#define TILE_SIZE (ds->tilesize)
-#define LINEWIDTH TILE_SIZE / 16
+#define LINEWIDTH (ds->linewidth)
#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)
#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 {
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;
#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);
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));
* 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;
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;
}
}
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,
{
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;
}
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)
{
params->h = params->w = atoi(string);
params->rec = 0;
+ params->diff = DIFF_EASY;
while (*string && isdigit((unsigned char)*string)) string++;
if (*string == 'x') {
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)
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);
}
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;
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;
}
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;
}
* light towards those with high scores */
struct square {
int score;
- int random;
+ unsigned long random;
int x, y;
};
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
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);
}
}
}
+ sfree(square);
/* printf("\n\n"); */
}
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);
}
/* 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;
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;
}
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));
/* 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);
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;
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;
}
/* 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 */
}
}
-
-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;
}
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",
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:
#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<<dline; \
+ solver_progress |= \
+ SET_BIT(sstate->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<<dline) { \
- sstate->dot_atmostone \
- [i + (sstate->state->w + 1) * j] |= 1<<OPP_DLINE(dline); \
+ if (BIT_SET(sstate->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<<dline) { \
+
+#define H1(dline, dir1_sq, dir2_sq, dot_howmany, line_query, line_set) \
+ if (BIT_SET(sstate->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<<dline; \
+ SET_BIT(sstate->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<<dline) { \
- dot_setall_dlines(sstate, OPP_DLINE(dline), \
- i+(1-a), j+(1-b), \
- LINE_UNKNOWN, LINE_NO); \
+ if (BIT_SET(sstate->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<<dline) { \
- sstate->dot_at2one \
- [i+(1-a) + (sstate->state->w + 1) * (j+(1-b))] |= \
- 1<<OPP_DLINE(dline); \
+ if (BIT_SET(sstate->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<<dline; \
+ solver_progress |= \
+ SET_BIT(sstate->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<<dline) { \
- dot_setall_dlines(sstate, OPP_DLINE(dline), \
- i+(1-a), j+(1-b), \
- LINE_UNKNOWN, LINE_YES); \
+ if (BIT_SET(sstate->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.
* 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++;
}
}
}
+ 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;
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 */
* 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;
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 */
/* 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;
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")); \
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);
}
}
}
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);
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");
struct game_drawstate {
int started;
- int tilesize;
+ int tilesize, linewidth;
int flashing;
char *hl, *vl;
+ char *clue_error;
};
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;
/*
* 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)
game_params *params, int tilesize)
{
ds->tilesize = tilesize;
+ ds->linewidth = max(1,tilesize/16);
}
-static float *game_colours(frontend *fe, game_state *state, int *ncolours)
+static float *game_colours(frontend *fe, int *ncolours)
{
float *ret = snewn(4 * NCOLOURS, float);
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;
}
{
struct game_drawstate *ds = snew(struct game_drawstate);
- ds->tilesize = 0;
+ ds->tilesize = ds->linewidth = 0;
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);
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) {
/*
#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); \
#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, \
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);
}
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);
return 0.0F;
}
-static int game_wants_statusbar(void)
-{
- return FALSE;
-}
-
static int game_timing_state(game_state *state, game_ui *ui)
{
return TRUE;
int ink = print_mono_colour(dr, 0);
int x, y;
game_drawstate ads, *ds = &ads;
- ds->tilesize = tilesize;
+
+ game_set_size(dr, ds, NULL, tilesize);
/*
* Dots. I'll deliberately make the dots a bit wider than the
game_anim_length,
game_flash_length,
TRUE, FALSE, game_print_size, game_print,
- game_wants_statusbar,
+ FALSE, /* wants_statusbar */
FALSE, game_timing_state,
- 0, /* mouse_priorities */
+ 0, /* flags */
};