"Hold" function in Guess was completely broken.
[sgt/puzzles] / loopy.c
diff --git a/loopy.c b/loopy.c
index 4fa56e1..f70e5b1 100644 (file)
--- a/loopy.c
+++ b/loopy.c
  *      (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 {
@@ -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);
@@ -267,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;
 
@@ -278,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;
     }
 }
 
@@ -341,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,
@@ -380,6 +438,7 @@ static game_params *default_params(void)
     ret->h = 10;
     ret->w = 10;
 #endif
+    ret->diff = DIFF_EASY;
     ret->rec = 0;
 
     return ret;
@@ -396,15 +455,17 @@ 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, 2 } },
-    { "10x10 Easy", { 10, 10, 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
-    { "10x10 Hard", { 10, 10, 2 } },
-    { "15x15 Easy", { 15, 15, 0 } },
-    { "30x20 Easy", { 30, 20, 0 } }
+    { "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
 };
 
@@ -431,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++;
@@ -442,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)
@@ -449,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);
 }
 
@@ -472,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;
@@ -492,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;
 }
@@ -503,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;
 }
 
@@ -764,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);
@@ -846,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);
 
@@ -865,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;
@@ -887,7 +976,7 @@ 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);
@@ -917,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));
 
@@ -926,14 +1017,20 @@ 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) {
@@ -998,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;
@@ -1039,139 +1136,6 @@ 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. */
 static int len_0_to_n(int n)
@@ -1246,7 +1210,13 @@ static char *encode_solve_move(const game_state *state)
         }
     }
 
-    /* No point in doing sums like that if they're going to be wrong */
+    /*
+     * Ensure we haven't overrun the buffer we allocated (which we
+     * really shouldn't have, since we computed its maximum size).
+     * Note that this assert is <= rather than ==, because the
+     * solver is permitted to produce an incomplete solution in
+     * which case the buffer will be only partially used.
+     */
     assert(strlen(ret) <= (size_t)len);
     return ret;
 }
@@ -1297,84 +1267,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;                                                          
     }
 
@@ -1382,52 +1305,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", 
@@ -1436,62 +1378,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:
@@ -1499,141 +1457,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<<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.
@@ -1644,14 +1645,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++;
                   }
 
@@ -1667,14 +1668,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;
 
@@ -1692,11 +1709,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 */
@@ -1762,10 +1777,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;
@@ -1776,18 +1794,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 */
@@ -1800,10 +1816,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;
 
@@ -1828,7 +1844,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"));                \
@@ -1865,18 +1881,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);
                }
            }
        }
@@ -1969,7 +1981,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);
@@ -2072,9 +2084,10 @@ static void game_changed_state(game_ui *ui, game_state *oldstate,
 
 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,
@@ -2353,9 +2366,10 @@ static void game_set_size(drawing *dr, game_drawstate *ds,
                          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);
 
@@ -2369,6 +2383,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;
 }
@@ -2377,20 +2395,23 @@ static game_drawstate *game_new_drawstate(drawing *dr, game_state *state)
 {
     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);
@@ -2400,10 +2421,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) {
         /*
@@ -2456,10 +2478,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);                          \
@@ -2472,7 +2532,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,                           \
@@ -2499,7 +2559,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);
                     }
@@ -2540,7 +2600,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);
@@ -2586,11 +2646,6 @@ static float game_flash_length(game_state *oldstate, game_state *newstate,
     return 0.0F;
 }
 
-static int game_wants_statusbar(void)
-{
-    return FALSE;
-}
-
 static int game_timing_state(game_state *state, game_ui *ui)
 {
     return TRUE;
@@ -2614,7 +2669,8 @@ static void game_print(drawing *dr, game_state *state, int tilesize)
     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
@@ -2696,7 +2752,7 @@ const struct game thegame = {
     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 */
 };