chiark / gitweb /
changelog: document last change
[sgt-puzzles.git] / rect.c
diff --git a/rect.c b/rect.c
index c0606dd3a1130404b4e8f2bdc09a495e5bc44fc4..465e1436fac50b2cfc8b125224011316f3b83178 100644 (file)
--- a/rect.c
+++ b/rect.c
@@ -9,25 +9,16 @@
 /*
  * TODO:
  * 
- *  - Improve on singleton removal by making an aesthetic choice
- *    about which of the options to take.
- * 
- *  - When doing the 3x3 trick in singleton removal, limit the size
- *    of the generated rectangles in accordance with the max
- *    rectangle size.
- * 
- *  - It might be interesting to deliberately try to place
- *    numbers so as to reduce alternative solution patterns. I
- *    doubt we can do a perfect job of this, but we can make a
- *    start by, for example, noticing pairs of 2-rects
- *    alongside one another and _not_ putting their numbers at
- *    opposite ends.
- *
- *  - If we start by sorting the rectlist in descending order
- *    of area, we might be able to bias our random number
- *    selection to produce a few large rectangles more often
- *    than oodles of small ones? Unsure, but might be worth a
- *    try.
+ *  - Improve singleton removal.
+ *     + It would be nice to limit the size of the generated
+ *       rectangles in accordance with existing constraints such as
+ *       the maximum rectangle size and the one about not
+ *       generating a rectangle the full width or height of the
+ *       grid.
+ *     + This could be achieved by making a less random choice
+ *       about which of the available options to use.
+ *     + Alternatively, we could create our rectangle and then
+ *       split it up.
  */
 
 #include <stdio.h>
@@ -45,13 +36,15 @@ enum {
     COL_LINE,
     COL_TEXT,
     COL_GRID,
-    COL_DRAG,
+    COL_DRAG, COL_DRAGERASE,
+    COL_CURSOR,
     NCOLOURS
 };
 
 struct game_params {
     int w, h;
     float expandfactor;
+    int unique;
 };
 
 #define INDEX(state, x, y)    (((y) * (state)->w) + (x))
@@ -66,8 +59,13 @@ struct game_params {
 #define HRANGE(state,x,y) CRANGE(state,x,y,0,1)
 #define VRANGE(state,x,y) CRANGE(state,x,y,1,0)
 
-#define TILE_SIZE 24
-#define BORDER 18
+#define PREFERRED_TILE_SIZE 24
+#define TILE_SIZE (ds->tilesize)
+#ifdef SMALL_SCREEN
+#define BORDER (2)
+#else
+#define BORDER (TILE_SIZE * 3 / 4)
+#endif
 
 #define CORNER_TOLERANCE 0.15F
 #define CENTRE_TOLERANCE 0.15F
@@ -82,7 +80,8 @@ struct game_state {
     int *grid;                        /* contains the numbers */
     unsigned char *vedge;             /* (w+1) x h */
     unsigned char *hedge;             /* w x (h+1) */
-    int completed;
+    int completed, cheated;
+    unsigned char *correct;
 };
 
 static game_params *default_params(void)
@@ -91,6 +90,7 @@ static game_params *default_params(void)
 
     ret->w = ret->h = 7;
     ret->expandfactor = 0.0F;
+    ret->unique = TRUE;
 
     return ret;
 }
@@ -103,9 +103,14 @@ static int game_fetch_preset(int i, char **name, game_params **params)
 
     switch (i) {
       case 0: w = 7, h = 7; break;
-      case 1: w = 11, h = 11; break;
-      case 2: w = 15, h = 15; break;
-      case 3: w = 19, h = 19; break;
+      case 1: w = 9, h = 9; break;
+      case 2: w = 11, h = 11; break;
+      case 3: w = 13, h = 13; break;
+      case 4: w = 15, h = 15; break;
+#ifndef SMALL_SCREEN
+      case 5: w = 17, h = 17; break;
+      case 6: w = 19, h = 19; break;
+#endif
       default: return FALSE;
     }
 
@@ -115,6 +120,7 @@ static int game_fetch_preset(int i, char **name, game_params **params)
     ret->w = w;
     ret->h = h;
     ret->expandfactor = 0.0F;
+    ret->unique = TRUE;
     return TRUE;
 }
 
@@ -123,19 +129,16 @@ static void free_params(game_params *params)
     sfree(params);
 }
 
-static game_params *dup_params(game_params *params)
+static game_params *dup_params(const game_params *params)
 {
     game_params *ret = snew(game_params);
     *ret = *params;                   /* structure copy */
     return ret;
 }
 
-static game_params *decode_params(char const *string)
+static void decode_params(game_params *ret, char const *string)
 {
-    game_params *ret = default_params();
-
     ret->w = ret->h = atoi(string);
-    ret->expandfactor = 0.0F;
     while (*string && isdigit((unsigned char)*string)) string++;
     if (*string == 'x') {
         string++;
@@ -144,22 +147,30 @@ static game_params *decode_params(char const *string)
     }
     if (*string == 'e') {
        string++;
-       ret->expandfactor = atof(string);
+       ret->expandfactor = (float)atof(string);
+       while (*string &&
+              (*string == '.' || isdigit((unsigned char)*string))) string++;
+    }
+    if (*string == 'a') {
+       string++;
+       ret->unique = FALSE;
     }
-
-    return ret;
 }
 
-static char *encode_params(game_params *params)
+static char *encode_params(const game_params *params, int full)
 {
     char data[256];
 
     sprintf(data, "%dx%d", params->w, params->h);
+    if (full && params->expandfactor)
+        sprintf(data + strlen(data), "e%g", params->expandfactor);
+    if (full && !params->unique)
+        strcat(data, "a");
 
     return dupstr(data);
 }
 
-static config_item *game_configure(game_params *params)
+static config_item *game_configure(const game_params *params)
 {
     config_item *ret;
     char buf[80];
@@ -184,36 +195,46 @@ static config_item *game_configure(game_params *params)
     ret[2].sval = dupstr(buf);
     ret[2].ival = 0;
 
-    ret[3].name = NULL;
-    ret[3].type = C_END;
+    ret[3].name = "Ensure unique solution";
+    ret[3].type = C_BOOLEAN;
     ret[3].sval = NULL;
-    ret[3].ival = 0;
+    ret[3].ival = params->unique;
+
+    ret[4].name = NULL;
+    ret[4].type = C_END;
+    ret[4].sval = NULL;
+    ret[4].ival = 0;
 
     return ret;
 }
 
-static game_params *custom_params(config_item *cfg)
+static game_params *custom_params(const config_item *cfg)
 {
     game_params *ret = snew(game_params);
 
     ret->w = atoi(cfg[0].sval);
     ret->h = atoi(cfg[1].sval);
-    ret->expandfactor = atof(cfg[2].sval);
+    ret->expandfactor = (float)atof(cfg[2].sval);
+    ret->unique = cfg[3].ival;
 
     return ret;
 }
 
-static char *validate_params(game_params *params)
+static char *validate_params(const game_params *params, int full)
 {
-    if (params->w <= 0 && params->h <= 0)
+    if (params->w <= 0 || params->h <= 0)
        return "Width and height must both be greater than zero";
-    if (params->w < 2 && params->h < 2)
+    if (params->w*params->h < 2)
        return "Grid area must be greater than one";
     if (params->expandfactor < 0.0F)
        return "Expansion factor may not be negative";
     return NULL;
 }
 
+struct point {
+    int x, y;
+};
+
 struct rect {
     int x, y;
     int w, h;
@@ -224,13 +245,698 @@ struct rectlist {
     int n;
 };
 
-static struct rectlist *get_rectlist(game_params *params, int *grid)
+struct numberdata {
+    int area;
+    int npoints;
+    struct point *points;
+};
+
+/* ----------------------------------------------------------------------
+ * Solver for Rectangles games.
+ * 
+ * This solver is souped up beyond the needs of actually _solving_
+ * a puzzle. It is also designed to cope with uncertainty about
+ * where the numbers have been placed. This is because I run it on
+ * my generated grids _before_ placing the numbers, and have it
+ * tell me where I need to place the numbers to ensure a unique
+ * solution.
+ */
+
+static void remove_rect_placement(int w, int h,
+                                  struct rectlist *rectpositions,
+                                  int *overlaps,
+                                  int rectnum, int placement)
 {
-    int rw, rh;
-    int x, y;
-    int maxarea;
-    struct rect *rects = NULL;
-    int nrects = 0, rectsize = 0;
+    int x, y, xx, yy;
+
+#ifdef SOLVER_DIAGNOSTICS
+    printf("ruling out rect %d placement at %d,%d w=%d h=%d\n", rectnum,
+           rectpositions[rectnum].rects[placement].x,
+           rectpositions[rectnum].rects[placement].y,
+           rectpositions[rectnum].rects[placement].w,
+           rectpositions[rectnum].rects[placement].h);
+#endif
+
+    /*
+     * Decrement each entry in the overlaps array to reflect the
+     * removal of this rectangle placement.
+     */
+    for (yy = 0; yy < rectpositions[rectnum].rects[placement].h; yy++) {
+        y = yy + rectpositions[rectnum].rects[placement].y;
+        for (xx = 0; xx < rectpositions[rectnum].rects[placement].w; xx++) {
+            x = xx + rectpositions[rectnum].rects[placement].x;
+
+            assert(overlaps[(rectnum * h + y) * w + x] != 0);
+
+            if (overlaps[(rectnum * h + y) * w + x] > 0)
+                overlaps[(rectnum * h + y) * w + x]--;
+        }
+    }
+
+    /*
+     * Remove the placement from the list of positions for that
+     * rectangle, by interchanging it with the one on the end.
+     */
+    if (placement < rectpositions[rectnum].n - 1) {
+        struct rect t;
+
+        t = rectpositions[rectnum].rects[rectpositions[rectnum].n - 1];
+        rectpositions[rectnum].rects[rectpositions[rectnum].n - 1] =
+            rectpositions[rectnum].rects[placement];
+        rectpositions[rectnum].rects[placement] = t;
+    }
+    rectpositions[rectnum].n--;
+}
+
+static void remove_number_placement(int w, int h, struct numberdata *number,
+                                    int index, int *rectbyplace)
+{
+    /*
+     * Remove the entry from the rectbyplace array.
+     */
+    rectbyplace[number->points[index].y * w + number->points[index].x] = -1;
+
+    /*
+     * Remove the placement from the list of candidates for that
+     * number, by interchanging it with the one on the end.
+     */
+    if (index < number->npoints - 1) {
+        struct point t;
+
+        t = number->points[number->npoints - 1];
+        number->points[number->npoints - 1] = number->points[index];
+        number->points[index] = t;
+    }
+    number->npoints--;
+}
+
+/*
+ * Returns 0 for failure to solve due to inconsistency; 1 for
+ * success; 2 for failure to complete a solution due to either
+ * ambiguity or it being too difficult.
+ */
+static int rect_solver(int w, int h, int nrects, struct numberdata *numbers,
+                       unsigned char *hedge, unsigned char *vedge,
+                      random_state *rs)
+{
+    struct rectlist *rectpositions;
+    int *overlaps, *rectbyplace, *workspace;
+    int i, ret;
+
+    /*
+     * Start by setting up a list of candidate positions for each
+     * rectangle.
+     */
+    rectpositions = snewn(nrects, struct rectlist);
+    for (i = 0; i < nrects; i++) {
+        int rw, rh, area = numbers[i].area;
+        int j, minx, miny, maxx, maxy;
+        struct rect *rlist;
+        int rlistn, rlistsize;
+
+        /*
+         * For each rectangle, begin by finding the bounding
+         * rectangle of its candidate number placements.
+         */
+        maxx = maxy = -1;
+        minx = w;
+        miny = h;
+        for (j = 0; j < numbers[i].npoints; j++) {
+            if (minx > numbers[i].points[j].x) minx = numbers[i].points[j].x;
+            if (miny > numbers[i].points[j].y) miny = numbers[i].points[j].y;
+            if (maxx < numbers[i].points[j].x) maxx = numbers[i].points[j].x;
+            if (maxy < numbers[i].points[j].y) maxy = numbers[i].points[j].y;
+        }
+
+        /*
+         * Now loop over all possible rectangle placements
+         * overlapping a point within that bounding rectangle;
+         * ensure each one actually contains a candidate number
+         * placement, and add it to the list.
+         */
+        rlist = NULL;
+        rlistn = rlistsize = 0;
+
+        for (rw = 1; rw <= area && rw <= w; rw++) {
+            int x, y;
+
+            if (area % rw)
+                continue;
+            rh = area / rw;
+            if (rh > h)
+                continue;
+
+            for (y = miny - rh + 1; y <= maxy; y++) {
+                if (y < 0 || y+rh > h)
+                    continue;
+
+                for (x = minx - rw + 1; x <= maxx; x++) {
+                    if (x < 0 || x+rw > w)
+                        continue;
+
+                    /*
+                     * See if we can find a candidate number
+                     * placement within this rectangle.
+                     */
+                    for (j = 0; j < numbers[i].npoints; j++)
+                        if (numbers[i].points[j].x >= x &&
+                            numbers[i].points[j].x < x+rw &&
+                            numbers[i].points[j].y >= y &&
+                            numbers[i].points[j].y < y+rh)
+                            break;
+
+                    if (j < numbers[i].npoints) {
+                        /*
+                         * Add this to the list of candidate
+                         * placements for this rectangle.
+                         */
+                        if (rlistn >= rlistsize) {
+                            rlistsize = rlistn + 32;
+                            rlist = sresize(rlist, rlistsize, struct rect);
+                        }
+                        rlist[rlistn].x = x;
+                        rlist[rlistn].y = y;
+                        rlist[rlistn].w = rw;
+                        rlist[rlistn].h = rh;
+#ifdef SOLVER_DIAGNOSTICS
+                        printf("rect %d [area %d]: candidate position at"
+                               " %d,%d w=%d h=%d\n",
+                               i, area, x, y, rw, rh);
+#endif
+                        rlistn++;
+                    }
+                }
+            }
+        }
+
+        rectpositions[i].rects = rlist;
+        rectpositions[i].n = rlistn;
+    }
+
+    /*
+     * Next, construct a multidimensional array tracking how many
+     * candidate positions for each rectangle overlap each square.
+     * 
+     * Indexing of this array is by the formula
+     * 
+     *   overlaps[(rectindex * h + y) * w + x]
+     * 
+     * A positive or zero value indicates what it sounds as if it
+     * should; -1 indicates that this square _cannot_ be part of
+     * this rectangle; and -2 indicates that it _definitely_ is
+     * (which is distinct from 1, because one might very well know
+     * that _if_ square S is part of rectangle R then it must be
+     * because R is placed in a certain position without knowing
+     * that it definitely _is_).
+     */
+    overlaps = snewn(nrects * w * h, int);
+    memset(overlaps, 0, nrects * w * h * sizeof(int));
+    for (i = 0; i < nrects; i++) {
+        int j;
+
+        for (j = 0; j < rectpositions[i].n; j++) {
+            int xx, yy;
+
+            for (yy = 0; yy < rectpositions[i].rects[j].h; yy++)
+                for (xx = 0; xx < rectpositions[i].rects[j].w; xx++)
+                    overlaps[(i * h + yy+rectpositions[i].rects[j].y) * w +
+                             xx+rectpositions[i].rects[j].x]++;
+        }
+    }
+
+    /*
+     * Also we want an array covering the grid once, to make it
+     * easy to figure out which squares are candidate number
+     * placements for which rectangles. (The existence of this
+     * single array assumes that no square starts off as a
+     * candidate number placement for more than one rectangle. This
+     * assumption is justified, because this solver is _either_
+     * used to solve real problems - in which case there is a
+     * single placement for every number - _or_ used to decide on
+     * number placements for a new puzzle, in which case each
+     * number's placements are confined to the intended position of
+     * the rectangle containing that number.)
+     */
+    rectbyplace = snewn(w * h, int);
+    for (i = 0; i < w*h; i++)
+        rectbyplace[i] = -1;
+
+    for (i = 0; i < nrects; i++) {
+        int j;
+
+        for (j = 0; j < numbers[i].npoints; j++) {
+            int x = numbers[i].points[j].x;
+            int y = numbers[i].points[j].y;
+
+            assert(rectbyplace[y * w + x] == -1);
+            rectbyplace[y * w + x] = i;
+        }
+    }
+
+    workspace = snewn(nrects, int);
+
+    /*
+     * Now run the actual deduction loop.
+     */
+    while (1) {
+        int done_something = FALSE;
+
+#ifdef SOLVER_DIAGNOSTICS
+        printf("starting deduction loop\n");
+
+        for (i = 0; i < nrects; i++) {
+            printf("rect %d overlaps:\n", i);
+            {
+                int x, y;
+                for (y = 0; y < h; y++) {
+                    for (x = 0; x < w; x++) {
+                        printf("%3d", overlaps[(i * h + y) * w + x]);
+                    }
+                    printf("\n");
+                }
+            }
+        }
+        printf("rectbyplace:\n");
+        {
+            int x, y;
+            for (y = 0; y < h; y++) {
+                for (x = 0; x < w; x++) {
+                    printf("%3d", rectbyplace[y * w + x]);
+                }
+                printf("\n");
+            }
+        }
+#endif
+
+        /*
+         * Housekeeping. Look for rectangles whose number has only
+         * one candidate position left, and mark that square as
+         * known if it isn't already.
+         */
+        for (i = 0; i < nrects; i++) {
+            if (numbers[i].npoints == 1) {
+                int x = numbers[i].points[0].x;
+                int y = numbers[i].points[0].y;
+                if (overlaps[(i * h + y) * w + x] >= -1) {
+                    int j;
+
+                    if (overlaps[(i * h + y) * w + x] <= 0) {
+                        ret = 0;       /* inconsistency */
+                        goto cleanup;
+                    }
+#ifdef SOLVER_DIAGNOSTICS
+                    printf("marking %d,%d as known for rect %d"
+                           " (sole remaining number position)\n", x, y, i);
+#endif
+
+                    for (j = 0; j < nrects; j++)
+                        overlaps[(j * h + y) * w + x] = -1;
+                    
+                    overlaps[(i * h + y) * w + x] = -2;
+                }
+            }
+        }
+
+        /*
+         * Now look at the intersection of all possible placements
+         * for each rectangle, and mark all squares in that
+         * intersection as known for that rectangle if they aren't
+         * already.
+         */
+        for (i = 0; i < nrects; i++) {
+            int minx, miny, maxx, maxy, xx, yy, j;
+
+            minx = miny = 0;
+            maxx = w;
+            maxy = h;
+
+            for (j = 0; j < rectpositions[i].n; j++) {
+                int x = rectpositions[i].rects[j].x;
+                int y = rectpositions[i].rects[j].y;
+                int w = rectpositions[i].rects[j].w;
+                int h = rectpositions[i].rects[j].h;
+
+                if (minx < x) minx = x;
+                if (miny < y) miny = y;
+                if (maxx > x+w) maxx = x+w;
+                if (maxy > y+h) maxy = y+h;
+            }
+
+            for (yy = miny; yy < maxy; yy++)
+                for (xx = minx; xx < maxx; xx++)
+                    if (overlaps[(i * h + yy) * w + xx] >= -1) {
+                        if (overlaps[(i * h + yy) * w + xx] <= 0) {
+                            ret = 0;   /* inconsistency */
+                            goto cleanup;
+                        }
+#ifdef SOLVER_DIAGNOSTICS
+                        printf("marking %d,%d as known for rect %d"
+                               " (intersection of all placements)\n",
+                               xx, yy, i);
+#endif
+
+                        for (j = 0; j < nrects; j++)
+                            overlaps[(j * h + yy) * w + xx] = -1;
+                    
+                        overlaps[(i * h + yy) * w + xx] = -2;
+                    }
+        }
+
+        /*
+         * Rectangle-focused deduction. Look at each rectangle in
+         * turn and try to rule out some of its candidate
+         * placements.
+         */
+        for (i = 0; i < nrects; i++) {
+            int j;
+
+            for (j = 0; j < rectpositions[i].n; j++) {
+                int xx, yy, k;
+                int del = FALSE;
+
+                for (k = 0; k < nrects; k++)
+                    workspace[k] = 0;
+
+                for (yy = 0; yy < rectpositions[i].rects[j].h; yy++) {
+                    int y = yy + rectpositions[i].rects[j].y;
+                    for (xx = 0; xx < rectpositions[i].rects[j].w; xx++) {
+                        int x = xx + rectpositions[i].rects[j].x;
+                        if (overlaps[(i * h + y) * w + x] == -1) {
+                            /*
+                             * This placement overlaps a square
+                             * which is _known_ to be part of
+                             * another rectangle. Therefore we must
+                             * rule it out.
+                             */
+#ifdef SOLVER_DIAGNOSTICS
+                            printf("rect %d placement at %d,%d w=%d h=%d "
+                                   "contains %d,%d which is known-other\n", i,
+                                   rectpositions[i].rects[j].x,
+                                   rectpositions[i].rects[j].y,
+                                   rectpositions[i].rects[j].w,
+                                   rectpositions[i].rects[j].h,
+                                   x, y);
+#endif
+                            del = TRUE;
+                        }
+
+                        if (rectbyplace[y * w + x] != -1) {
+                            /*
+                             * This placement overlaps one of the
+                             * candidate number placements for some
+                             * rectangle. Count it.
+                             */
+                            workspace[rectbyplace[y * w + x]]++;
+                        }
+                    }
+                }
+
+                if (!del) {
+                    /*
+                     * If we haven't ruled this placement out
+                     * already, see if it overlaps _all_ of the
+                     * candidate number placements for any
+                     * rectangle. If so, we can rule it out.
+                     */
+                    for (k = 0; k < nrects; k++)
+                        if (k != i && workspace[k] == numbers[k].npoints) {
+#ifdef SOLVER_DIAGNOSTICS
+                            printf("rect %d placement at %d,%d w=%d h=%d "
+                                   "contains all number points for rect %d\n",
+                                   i,
+                                   rectpositions[i].rects[j].x,
+                                   rectpositions[i].rects[j].y,
+                                   rectpositions[i].rects[j].w,
+                                   rectpositions[i].rects[j].h,
+                                   k);
+#endif
+                            del = TRUE;
+                            break;
+                        }
+
+                    /*
+                     * Failing that, see if it overlaps at least
+                     * one of the candidate number placements for
+                     * itself! (This might not be the case if one
+                     * of those number placements has been removed
+                     * recently.).
+                     */
+                    if (!del && workspace[i] == 0) {
+#ifdef SOLVER_DIAGNOSTICS
+                        printf("rect %d placement at %d,%d w=%d h=%d "
+                               "contains none of its own number points\n",
+                               i,
+                               rectpositions[i].rects[j].x,
+                               rectpositions[i].rects[j].y,
+                               rectpositions[i].rects[j].w,
+                               rectpositions[i].rects[j].h);
+#endif
+                        del = TRUE;
+                    }
+                }
+
+                if (del) {
+                    remove_rect_placement(w, h, rectpositions, overlaps, i, j);
+
+                    j--;               /* don't skip over next placement */
+
+                    done_something = TRUE;
+                }
+            }
+        }
+
+        /*
+         * Square-focused deduction. Look at each square not marked
+         * as known, and see if there are any which can only be
+         * part of a single rectangle.
+         */
+        {
+            int x, y, n, index;
+            for (y = 0; y < h; y++) for (x = 0; x < w; x++) {
+                /* Known squares are marked as <0 everywhere, so we only need
+                 * to check the overlaps entry for rect 0. */
+                if (overlaps[y * w + x] < 0)
+                    continue;          /* known already */
+
+                n = 0;
+                index = -1;
+                for (i = 0; i < nrects; i++)
+                    if (overlaps[(i * h + y) * w + x] > 0)
+                        n++, index = i;
+
+                if (n == 1) {
+                    int j;
+
+                    /*
+                     * Now we can rule out all placements for
+                     * rectangle `index' which _don't_ contain
+                     * square x,y.
+                     */
+#ifdef SOLVER_DIAGNOSTICS
+                    printf("square %d,%d can only be in rectangle %d\n",
+                           x, y, index);
+#endif
+                    for (j = 0; j < rectpositions[index].n; j++) {
+                        struct rect *r = &rectpositions[index].rects[j];
+                        if (x >= r->x && x < r->x + r->w &&
+                            y >= r->y && y < r->y + r->h)
+                            continue;  /* this one is OK */
+                        remove_rect_placement(w, h, rectpositions, overlaps,
+                                              index, j);
+                        j--;           /* don't skip over next placement */
+                        done_something = TRUE;
+                    }
+                }
+            }
+        }
+
+        /*
+         * If we've managed to deduce anything by normal means,
+         * loop round again and see if there's more to be done.
+         * Only if normal deduction has completely failed us should
+         * we now move on to narrowing down the possible number
+         * placements.
+         */
+        if (done_something)
+            continue;
+
+        /*
+         * Now we have done everything we can with the current set
+         * of number placements. So we need to winnow the number
+         * placements so as to narrow down the possibilities. We do
+         * this by searching for a candidate placement (of _any_
+         * rectangle) which overlaps a candidate placement of the
+         * number for some other rectangle.
+         */
+        if (rs) {
+            struct rpn {
+                int rect;
+                int placement;
+                int number;
+            } *rpns = NULL;
+            size_t nrpns = 0, rpnsize = 0;
+            int j;
+
+            for (i = 0; i < nrects; i++) {
+                for (j = 0; j < rectpositions[i].n; j++) {
+                    int xx, yy;
+
+                    for (yy = 0; yy < rectpositions[i].rects[j].h; yy++) {
+                        int y = yy + rectpositions[i].rects[j].y;
+                        for (xx = 0; xx < rectpositions[i].rects[j].w; xx++) {
+                            int x = xx + rectpositions[i].rects[j].x;
+
+                            if (rectbyplace[y * w + x] >= 0 &&
+                                rectbyplace[y * w + x] != i) {
+                                /*
+                                 * Add this to the list of
+                                 * winnowing possibilities.
+                                 */
+                                if (nrpns >= rpnsize) {
+                                    rpnsize = rpnsize * 3 / 2 + 32;
+                                    rpns = sresize(rpns, rpnsize, struct rpn);
+                                }
+                                rpns[nrpns].rect = i;
+                                rpns[nrpns].placement = j;
+                                rpns[nrpns].number = rectbyplace[y * w + x];
+                                nrpns++;
+                            }
+                        }
+                    }
+                }
+            }
+
+#ifdef SOLVER_DIAGNOSTICS
+            printf("%d candidate rect placements we could eliminate\n", nrpns);
+#endif
+            if (nrpns > 0) {
+                /*
+                 * Now choose one of these unwanted rectangle
+                 * placements, and eliminate it.
+                 */
+                int index = random_upto(rs, nrpns);
+                int k, m;
+                struct rpn rpn = rpns[index];
+                struct rect r;
+                sfree(rpns);
+
+                i = rpn.rect;
+                j = rpn.placement;
+                k = rpn.number;
+                r = rectpositions[i].rects[j];
+
+                /*
+                 * We rule out placement j of rectangle i by means
+                 * of removing all of rectangle k's candidate
+                 * number placements which do _not_ overlap it.
+                 * This will ensure that it is eliminated during
+                 * the next pass of rectangle-focused deduction.
+                 */
+#ifdef SOLVER_DIAGNOSTICS
+                printf("ensuring number for rect %d is within"
+                       " rect %d's placement at %d,%d w=%d h=%d\n",
+                       k, i, r.x, r.y, r.w, r.h);
+#endif
+
+                for (m = 0; m < numbers[k].npoints; m++) {
+                    int x = numbers[k].points[m].x;
+                    int y = numbers[k].points[m].y;
+
+                    if (x < r.x || x >= r.x + r.w ||
+                        y < r.y || y >= r.y + r.h) {
+#ifdef SOLVER_DIAGNOSTICS
+                        printf("eliminating number for rect %d at %d,%d\n",
+                               k, x, y);
+#endif
+                        remove_number_placement(w, h, &numbers[k],
+                                                m, rectbyplace);
+                        m--;           /* don't skip the next one */
+                        done_something = TRUE;
+                    }
+                }
+            }
+        }
+
+        if (!done_something) {
+#ifdef SOLVER_DIAGNOSTICS
+            printf("terminating deduction loop\n");
+#endif
+            break;
+        }
+    }
+
+    cleanup:
+    ret = 1;
+    for (i = 0; i < nrects; i++) {
+#ifdef SOLVER_DIAGNOSTICS
+        printf("rect %d has %d possible placements\n",
+               i, rectpositions[i].n);
+#endif
+        if (rectpositions[i].n <= 0) {
+            ret = 0;                   /* inconsistency */
+        } else if (rectpositions[i].n > 1) {
+            ret = 2;                   /* remaining uncertainty */
+        } else if (hedge && vedge) {
+            /*
+             * Place the rectangle in its only possible position.
+             */
+            int x, y;
+            struct rect *r = &rectpositions[i].rects[0];
+
+            for (y = 0; y < r->h; y++) {
+                if (r->x > 0)
+                   vedge[(r->y+y) * w + r->x] = 1;
+                if (r->x+r->w < w)
+                   vedge[(r->y+y) * w + r->x+r->w] = 1;
+            }
+            for (x = 0; x < r->w; x++) {
+                if (r->y > 0)
+                    hedge[r->y * w + r->x+x] = 1;
+                if (r->y+r->h < h)
+                    hedge[(r->y+r->h) * w + r->x+x] = 1;
+            }
+       }
+    }
+
+    /*
+     * Free up all allocated storage.
+     */
+    sfree(workspace);
+    sfree(rectbyplace);
+    sfree(overlaps);
+    for (i = 0; i < nrects; i++)
+        sfree(rectpositions[i].rects);
+    sfree(rectpositions);
+
+    return ret;
+}
+
+/* ----------------------------------------------------------------------
+ * Grid generation code.
+ */
+
+/*
+ * This function does one of two things. If passed r==NULL, it
+ * counts the number of possible rectangles which cover the given
+ * square, and returns it in *n. If passed r!=NULL then it _reads_
+ * *n to find an index, counts the possible rectangles until it
+ * reaches the nth, and writes it into r.
+ * 
+ * `scratch' is expected to point to an array of 2 * params->w
+ * ints, used internally as scratch space (and passed in like this
+ * to avoid re-allocating and re-freeing it every time round a
+ * tight loop).
+ */
+static void enum_rects(game_params *params, int *grid, struct rect *r, int *n,
+                       int sx, int sy, int *scratch)
+{
+    int rw, rh, mw, mh;
+    int x, y, dx, dy;
+    int maxarea, realmaxarea;
+    int index = 0;
+    int *top, *bottom;
 
     /*
      * Maximum rectangle area is 1/6 of total grid size, unless
@@ -241,43 +947,97 @@ static struct rectlist *get_rectlist(game_params *params, int *grid)
     if (maxarea < 2)
         maxarea = 2;
 
-    for (rw = 1; rw <= params->w; rw++)
-        for (rh = 1; rh <= params->h; rh++) {
-            if (rw * rh > maxarea)
+    /*
+     * Scan the grid to find the limits of the region within which
+     * any rectangle containing this point must fall. This will
+     * save us trawling the inside of every rectangle later on to
+     * see if it contains any used squares.
+     */
+    top = scratch;
+    bottom = scratch + params->w;
+    for (dy = -1; dy <= +1; dy += 2) {
+        int *array = (dy == -1 ? top : bottom);
+        for (dx = -1; dx <= +1; dx += 2) {
+            for (x = sx; x >= 0 && x < params->w; x += dx) {
+                array[x] = -2 * params->h * dy;
+                for (y = sy; y >= 0 && y < params->h; y += dy) {
+                    if (index(params, grid, x, y) == -1 &&
+                        (x == sx || dy*y <= dy*array[x-dx]))
+                        array[x] = y;
+                    else
+                        break;
+                }
+            }
+        }
+    }
+
+    /*
+     * Now scan again to work out the largest rectangles we can fit
+     * in the grid, so that we can terminate the following loops
+     * early once we get down to not having much space left in the
+     * grid.
+     */
+    realmaxarea = 0;
+    for (x = 0; x < params->w; x++) {
+        int x2;
+
+        rh = bottom[x] - top[x] + 1;
+        if (rh <= 0)
+            continue;                  /* no rectangles can start here */
+
+        dx = (x > sx ? -1 : +1);
+        for (x2 = x; x2 >= 0 && x2 < params->w; x2 += dx)
+            if (bottom[x2] < bottom[x] || top[x2] > top[x])
+                break;
+
+        rw = abs(x2 - x);
+        if (realmaxarea < rw * rh)
+            realmaxarea = rw * rh;
+    }
+
+    if (realmaxarea > maxarea)
+        realmaxarea = maxarea;
+
+    /*
+     * Rectangles which go right the way across the grid are
+     * boring, although they can't be helped in the case of
+     * extremely small grids. (Also they might be generated later
+     * on by the singleton-removal process; we can't help that.)
+     */
+    mw = params->w - 1;
+    if (mw < 3) mw++;
+    mh = params->h - 1;
+    if (mh < 3) mh++;
+
+    for (rw = 1; rw <= mw; rw++)
+        for (rh = 1; rh <= mh; rh++) {
+            if (rw * rh > realmaxarea)
                 continue;
             if (rw * rh == 1)
                 continue;
-            for (x = 0; x <= params->w - rw; x++)
-                for (y = 0; y <= params->h - rh; y++) {
-                    if (nrects >= rectsize) {
-                        rectsize = nrects + 256;
-                        rects = sresize(rects, rectsize, struct rect);
+            for (x = max(sx - rw + 1, 0); x <= min(sx, params->w - rw); x++)
+                for (y = max(sy - rh + 1, 0); y <= min(sy, params->h - rh);
+                     y++) {
+                    /*
+                     * Check this rectangle against the region we
+                     * defined above.
+                     */
+                    if (top[x] <= y && top[x+rw-1] <= y &&
+                        bottom[x] >= y+rh-1 && bottom[x+rw-1] >= y+rh-1) {
+                        if (r && index == *n) {
+                            r->x = x;
+                            r->y = y;
+                            r->w = rw;
+                            r->h = rh;
+                            return;
+                        }
+                        index++;
                     }
-
-                    rects[nrects].x = x;
-                    rects[nrects].y = y;
-                    rects[nrects].w = rw;
-                    rects[nrects].h = rh;
-                    nrects++;
                 }
         }
 
-    if (nrects > 0) {
-        struct rectlist *ret;
-        ret = snew(struct rectlist);
-        ret->rects = rects;
-        ret->n = nrects;
-        return ret;
-    } else {
-        assert(rects == NULL);         /* hence no need to free */
-        return NULL;
-    }
-}
-
-static void free_rectlist(struct rectlist *list)
-{
-    sfree(list->rects);
-    sfree(list);
+    assert(!r);
+    *n = index;
 }
 
 static void place_rect(game_params *params, int *grid, struct rect r)
@@ -386,482 +1146,602 @@ static void display_grid(game_params *params, int *grid, int *numbers, int all)
 }
 #endif
 
-static char *new_game_seed(game_params *params, random_state *rs,
-                          game_aux_info **aux)
+static char *new_game_desc(const game_params *params_in, random_state *rs,
+                          char **aux, int interactive)
 {
-    int *grid, *numbers;
-    struct rectlist *list;
-    int x, y, y2, y2last, yx, run, i;
-    char *seed, *p;
+    game_params params_copy = *params_in; /* structure copy */
+    game_params *params = &params_copy;
+    int *grid, *numbers = NULL;
+    int x, y, y2, y2last, yx, run, i, nsquares;
+    char *desc, *p;
+    int *enum_rects_scratch;
     game_params params2real, *params2 = &params2real;
 
-    /*
-     * Set up the smaller width and height which we will use to
-     * generate the base grid.
-     */
-    params2->w = params->w / (1.0F + params->expandfactor);
-    if (params2->w < 2 && params->w >= 2) params2->w = 2;
-    params2->h = params->h / (1.0F + params->expandfactor);
-    if (params2->h < 2 && params->h >= 2) params2->h = 2;
-
-    grid = snewn(params2->w * params2->h, int);
+    while (1) {
+        /*
+         * Set up the smaller width and height which we will use to
+         * generate the base grid.
+         */
+        params2->w = (int)((float)params->w / (1.0F + params->expandfactor));
+        if (params2->w < 2 && params->w >= 2) params2->w = 2;
+        params2->h = (int)((float)params->h / (1.0F + params->expandfactor));
+        if (params2->h < 2 && params->h >= 2) params2->h = 2;
 
-    for (y = 0; y < params2->h; y++)
-        for (x = 0; x < params2->w; x++) {
-            index(params2, grid, x, y) = -1;
-        }
+        grid = snewn(params2->w * params2->h, int);
 
-    list = get_rectlist(params2, grid);
-    assert(list != NULL);
+        enum_rects_scratch = snewn(2 * params2->w, int);
 
-    /*
-     * Place rectangles until we can't any more.
-     */
-    while (list->n > 0) {
-        int i, m;
-        struct rect r;
+        nsquares = 0;
+        for (y = 0; y < params2->h; y++)
+            for (x = 0; x < params2->w; x++) {
+                index(params2, grid, x, y) = -1;
+                nsquares++;
+            }
 
         /*
-         * Pick a random rectangle.
+         * Place rectangles until we can't any more. We do this by
+         * finding a square we haven't yet covered, and randomly
+         * choosing a rectangle to cover it.
          */
-        i = random_upto(rs, list->n);
-        r = list->rects[i];
+        
+        while (nsquares > 0) {
+            int square = random_upto(rs, nsquares);
+            int n;
+            struct rect r;
+
+            x = params2->w;
+            y = params2->h;
+            for (y = 0; y < params2->h; y++) {
+                for (x = 0; x < params2->w; x++) {
+                    if (index(params2, grid, x, y) == -1 && square-- == 0)
+                        break;
+                }
+                if (x < params2->w)
+                    break;
+            }
+            assert(x < params2->w && y < params2->h);
 
-        /*
-         * Place it.
-         */
-        place_rect(params2, grid, r);
+            /*
+             * Now see how many rectangles fit around this one.
+             */
+            enum_rects(params2, grid, NULL, &n, x, y, enum_rects_scratch);
 
-        /*
-         * Winnow the list by removing any rectangles which
-         * overlap this one.
-         */
-        m = 0;
-        for (i = 0; i < list->n; i++) {
-            struct rect s = list->rects[i];
-            if (s.x+s.w <= r.x || r.x+r.w <= s.x ||
-                s.y+s.h <= r.y || r.y+r.h <= s.y)
-                list->rects[m++] = s;
+            if (!n) {
+                /*
+                 * There are no possible rectangles covering this
+                 * square, meaning it must be a singleton. Mark it
+                 * -2 so we know not to keep trying.
+                 */
+                index(params2, grid, x, y) = -2;
+                nsquares--;
+            } else {
+                /*
+                 * Pick one at random.
+                 */
+                n = random_upto(rs, n);
+                enum_rects(params2, grid, &r, &n, x, y, enum_rects_scratch);
+
+                /*
+                 * Place it.
+                 */
+                place_rect(params2, grid, r);
+                nsquares -= r.w * r.h;
+            }
         }
-        list->n = m;
-    }
 
-    free_rectlist(list);
+        sfree(enum_rects_scratch);
 
-    /*
-     * Deal with singleton spaces remaining in the grid, one by
-     * one.
-     * 
-     * We do this by making a local change to the layout. There are
-     * several possibilities:
-     * 
-     *     +-----+-----+    Here, we can remove the singleton by
-     *     |     |     |    extending the 1x2 rectangle below it
-     *     +--+--+-----+    into a 1x3.
-     *     |  |  |     |
-     *     |  +--+     |
-     *     |  |  |     |
-     *     |  |  |     |
-     *     |  |  |     |
-     *     +--+--+-----+
-     * 
-     *     +--+--+--+       Here, that trick doesn't work: there's no
-     *     |     |  |       1 x n rectangle with the singleton at one
-     *     |     |  |       end. Instead, we extend a 1 x n rectangle
-     *     |     |  |       _out_ from the singleton, shaving a layer
-     *     +--+--+  |       off the end of another rectangle. So if we
-     *     |  |  |  |       extended up, we'd make our singleton part
-     *     |  +--+--+       of a 1x3 and generate a 1x2 where the 2x2
-     *     |  |     |       used to be; or we could extend right into
-     *     +--+-----+       a 2x1, turning the 1x3 into a 1x2.
-     * 
-     *     +-----+--+       Here, we can't even do _that_, since any
-     *     |     |  |       direction we choose to extend the singleton
-     *     +--+--+  |       will produce a new singleton as a result of
-     *     |  |  |  |       truncating one of the size-2 rectangles.
-     *     |  +--+--+       Fortunately, this case can _only_ occur when
-     *     |  |     |       a singleton is surrounded by four size-2s
-     *     +--+-----+       in this fashion; so instead we can simply
-     *                      replace the whole section with a single 3x3.
-     */
-    for (x = 0; x < params2->w; x++) {
-        for (y = 0; y < params2->h; y++) {
-            if (index(params2, grid, x, y) < 0) {
-                int dirs[4], ndirs;
+        /*
+         * Deal with singleton spaces remaining in the grid, one by
+         * one.
+         *
+         * We do this by making a local change to the layout. There are
+         * several possibilities:
+         *
+         *     +-----+-----+    Here, we can remove the singleton by
+         *     |     |     |    extending the 1x2 rectangle below it
+         *     +--+--+-----+    into a 1x3.
+         *     |  |  |     |
+         *     |  +--+     |
+         *     |  |  |     |
+         *     |  |  |     |
+         *     |  |  |     |
+         *     +--+--+-----+
+         *
+         *     +--+--+--+       Here, that trick doesn't work: there's no
+         *     |     |  |       1 x n rectangle with the singleton at one
+         *     |     |  |       end. Instead, we extend a 1 x n rectangle
+         *     |     |  |       _out_ from the singleton, shaving a layer
+         *     +--+--+  |       off the end of another rectangle. So if we
+         *     |  |  |  |       extended up, we'd make our singleton part
+         *     |  +--+--+       of a 1x3 and generate a 1x2 where the 2x2
+         *     |  |     |       used to be; or we could extend right into
+         *     +--+-----+       a 2x1, turning the 1x3 into a 1x2.
+         *
+         *     +-----+--+       Here, we can't even do _that_, since any
+         *     |     |  |       direction we choose to extend the singleton
+         *     +--+--+  |       will produce a new singleton as a result of
+         *     |  |  |  |       truncating one of the size-2 rectangles.
+         *     |  +--+--+       Fortunately, this case can _only_ occur when
+         *     |  |     |       a singleton is surrounded by four size-2s
+         *     +--+-----+       in this fashion; so instead we can simply
+         *                      replace the whole section with a single 3x3.
+         */
+        for (x = 0; x < params2->w; x++) {
+            for (y = 0; y < params2->h; y++) {
+                if (index(params2, grid, x, y) < 0) {
+                    int dirs[4], ndirs;
 
 #ifdef GENERATION_DIAGNOSTICS
-                display_grid(params2, grid, NULL, FALSE);
-                printf("singleton at %d,%d\n", x, y);
+                    display_grid(params2, grid, NULL, FALSE);
+                    printf("singleton at %d,%d\n", x, y);
 #endif
 
-                /*
-                 * Check in which directions we can feasibly extend
-                 * the singleton. We can extend in a particular
-                 * direction iff either:
-                 * 
-                 *  - the rectangle on that side of the singleton
-                 *    is not 2x1, and we are at one end of the edge
-                 *    of it we are touching
-                 * 
-                 *  - it is 2x1 but we are on its short side.
-                 * 
-                 * FIXME: we could plausibly choose between these
-                 * based on the sizes of the rectangles they would
-                 * create?
-                 */
-                ndirs = 0;
-                if (x < params2->w-1) {
-                    struct rect r = find_rect(params2, grid, x+1, y);
-                    if ((r.w * r.h > 2 && (r.y==y || r.y+r.h-1==y)) || r.h==1)
-                        dirs[ndirs++] = 1;   /* right */
-                }
-                if (y > 0) {
-                    struct rect r = find_rect(params2, grid, x, y-1);
-                    if ((r.w * r.h > 2 && (r.x==x || r.x+r.w-1==x)) || r.w==1)
-                        dirs[ndirs++] = 2;   /* up */
-                }
-                if (x > 0) {
-                    struct rect r = find_rect(params2, grid, x-1, y);
-                    if ((r.w * r.h > 2 && (r.y==y || r.y+r.h-1==y)) || r.h==1)
-                        dirs[ndirs++] = 4;   /* left */
-                }
-                if (y < params2->h-1) {
-                    struct rect r = find_rect(params2, grid, x, y+1);
-                    if ((r.w * r.h > 2 && (r.x==x || r.x+r.w-1==x)) || r.w==1)
-                        dirs[ndirs++] = 8;   /* down */
-                }
-
-                if (ndirs > 0) {
-                    int which, dir;
-                    struct rect r1, r2;
-
-                    which = random_upto(rs, ndirs);
-                    dir = dirs[which];
+                    /*
+                     * Check in which directions we can feasibly extend
+                     * the singleton. We can extend in a particular
+                     * direction iff either:
+                     *
+                     *  - the rectangle on that side of the singleton
+                     *    is not 2x1, and we are at one end of the edge
+                     *    of it we are touching
+                     *
+                     *  - it is 2x1 but we are on its short side.
+                     *
+                     * FIXME: we could plausibly choose between these
+                     * based on the sizes of the rectangles they would
+                     * create?
+                     */
+                    ndirs = 0;
+                    if (x < params2->w-1) {
+                        struct rect r = find_rect(params2, grid, x+1, y);
+                        if ((r.w * r.h > 2 && (r.y==y || r.y+r.h-1==y)) || r.h==1)
+                            dirs[ndirs++] = 1;   /* right */
+                    }
+                    if (y > 0) {
+                        struct rect r = find_rect(params2, grid, x, y-1);
+                        if ((r.w * r.h > 2 && (r.x==x || r.x+r.w-1==x)) || r.w==1)
+                            dirs[ndirs++] = 2;   /* up */
+                    }
+                    if (x > 0) {
+                        struct rect r = find_rect(params2, grid, x-1, y);
+                        if ((r.w * r.h > 2 && (r.y==y || r.y+r.h-1==y)) || r.h==1)
+                            dirs[ndirs++] = 4;   /* left */
+                    }
+                    if (y < params2->h-1) {
+                        struct rect r = find_rect(params2, grid, x, y+1);
+                        if ((r.w * r.h > 2 && (r.x==x || r.x+r.w-1==x)) || r.w==1)
+                            dirs[ndirs++] = 8;   /* down */
+                    }
 
-                    switch (dir) {
-                      case 1:          /* right */
-                        assert(x < params2->w+1);
+                    if (ndirs > 0) {
+                        int which, dir;
+                        struct rect r1, r2;
+                        memset(&r1, 0, sizeof(struct rect));
+                        memset(&r2, 0, sizeof(struct rect));
+                        which = random_upto(rs, ndirs);
+                        dir = dirs[which];
+
+                        switch (dir) {
+                          case 1:          /* right */
+                            assert(x < params2->w+1);
 #ifdef GENERATION_DIAGNOSTICS
-                        printf("extending right\n");
+                            printf("extending right\n");
 #endif
-                        r1 = find_rect(params2, grid, x+1, y);
-                        r2.x = x;
-                        r2.y = y;
-                        r2.w = 1 + r1.w;
-                        r2.h = 1;
-                        if (r1.y == y)
-                            r1.y++;
-                        r1.h--;
-                        break;
-                      case 2:          /* up */
-                        assert(y > 0);
+                            r1 = find_rect(params2, grid, x+1, y);
+                            r2.x = x;
+                            r2.y = y;
+                            r2.w = 1 + r1.w;
+                            r2.h = 1;
+                            if (r1.y == y)
+                                r1.y++;
+                            r1.h--;
+                            break;
+                          case 2:          /* up */
+                            assert(y > 0);
 #ifdef GENERATION_DIAGNOSTICS
-                        printf("extending up\n");
+                            printf("extending up\n");
 #endif
-                        r1 = find_rect(params2, grid, x, y-1);
-                        r2.x = x;
-                        r2.y = r1.y;
-                        r2.w = 1;
-                        r2.h = 1 + r1.h;
-                        if (r1.x == x)
-                            r1.x++;
-                        r1.w--;
-                        break;
-                      case 4:          /* left */
-                        assert(x > 0);
+                            r1 = find_rect(params2, grid, x, y-1);
+                            r2.x = x;
+                            r2.y = r1.y;
+                            r2.w = 1;
+                            r2.h = 1 + r1.h;
+                            if (r1.x == x)
+                                r1.x++;
+                            r1.w--;
+                            break;
+                          case 4:          /* left */
+                            assert(x > 0);
 #ifdef GENERATION_DIAGNOSTICS
-                        printf("extending left\n");
+                            printf("extending left\n");
 #endif
-                        r1 = find_rect(params2, grid, x-1, y);
-                        r2.x = r1.x;
-                        r2.y = y;
-                        r2.w = 1 + r1.w;
-                        r2.h = 1;
-                        if (r1.y == y)
-                            r1.y++;
-                        r1.h--;
-                        break;
-                      case 8:          /* down */
-                        assert(y < params2->h+1);
+                            r1 = find_rect(params2, grid, x-1, y);
+                            r2.x = r1.x;
+                            r2.y = y;
+                            r2.w = 1 + r1.w;
+                            r2.h = 1;
+                            if (r1.y == y)
+                                r1.y++;
+                            r1.h--;
+                            break;
+                          case 8:          /* down */
+                            assert(y < params2->h+1);
 #ifdef GENERATION_DIAGNOSTICS
-                        printf("extending down\n");
+                            printf("extending down\n");
 #endif
-                        r1 = find_rect(params2, grid, x, y+1);
-                        r2.x = x;
-                        r2.y = y;
-                        r2.w = 1;
-                        r2.h = 1 + r1.h;
-                        if (r1.x == x)
-                            r1.x++;
-                        r1.w--;
-                        break;
-                    }
-                    if (r1.h > 0 && r1.w > 0)
-                        place_rect(params2, grid, r1);
-                    place_rect(params2, grid, r2);
-                } else {
+                            r1 = find_rect(params2, grid, x, y+1);
+                            r2.x = x;
+                            r2.y = y;
+                            r2.w = 1;
+                            r2.h = 1 + r1.h;
+                            if (r1.x == x)
+                                r1.x++;
+                            r1.w--;
+                            break;
+                          default:     /* should never happen */
+                            assert(!"invalid direction");
+                        }
+                        if (r1.h > 0 && r1.w > 0)
+                            place_rect(params2, grid, r1);
+                        place_rect(params2, grid, r2);
+                    } else {
 #ifndef NDEBUG
-                    /*
-                     * Sanity-check that there really is a 3x3
-                     * rectangle surrounding this singleton and it
-                     * contains absolutely everything we could
-                     * possibly need.
-                     */
-                    {
-                        int xx, yy;
-                        assert(x > 0 && x < params2->w-1);
-                        assert(y > 0 && y < params2->h-1);
-
-                        for (xx = x-1; xx <= x+1; xx++)
-                            for (yy = y-1; yy <= y+1; yy++) {
-                                struct rect r = find_rect(params2,grid,xx,yy);
-                                assert(r.x >= x-1);
-                                assert(r.y >= y-1);
-                                assert(r.x+r.w-1 <= x+1);
-                                assert(r.y+r.h-1 <= y+1);
-                            }
-                    }
+                        /*
+                         * Sanity-check that there really is a 3x3
+                         * rectangle surrounding this singleton and it
+                         * contains absolutely everything we could
+                         * possibly need.
+                         */
+                        {
+                            int xx, yy;
+                            assert(x > 0 && x < params2->w-1);
+                            assert(y > 0 && y < params2->h-1);
+
+                            for (xx = x-1; xx <= x+1; xx++)
+                                for (yy = y-1; yy <= y+1; yy++) {
+                                    struct rect r = find_rect(params2,grid,xx,yy);
+                                    assert(r.x >= x-1);
+                                    assert(r.y >= y-1);
+                                    assert(r.x+r.w-1 <= x+1);
+                                    assert(r.y+r.h-1 <= y+1);
+                                }
+                        }
 #endif
-                    
+
 #ifdef GENERATION_DIAGNOSTICS
-                    printf("need the 3x3 trick\n");
+                        printf("need the 3x3 trick\n");
 #endif
 
-                    /*
-                     * FIXME: If the maximum rectangle area for
-                     * this grid is less than 9, we ought to
-                     * subdivide the 3x3 in some fashion. There are
-                     * five other possibilities:
-                     * 
-                     *  - a 6 and a 3
-                     *  - a 4, a 3 and a 2
-                     *  - three 3s
-                     *  - a 3 and three 2s (two different arrangements).
-                     */
-
-                    {
-                        struct rect r;
-                        r.x = x-1;
-                        r.y = y-1;
-                        r.w = r.h = 3;
-                        place_rect(params2, grid, r);
+                        /*
+                         * FIXME: If the maximum rectangle area for
+                         * this grid is less than 9, we ought to
+                         * subdivide the 3x3 in some fashion. There are
+                         * five other possibilities:
+                         *
+                         *  - a 6 and a 3
+                         *  - a 4, a 3 and a 2
+                         *  - three 3s
+                         *  - a 3 and three 2s (two different arrangements).
+                         */
+
+                        {
+                            struct rect r;
+                            r.x = x-1;
+                            r.y = y-1;
+                            r.w = r.h = 3;
+                            place_rect(params2, grid, r);
+                        }
                     }
                 }
             }
         }
-    }
 
-    /*
-     * We have now constructed a grid of the size specified in
-     * params2. Now we extend it into a grid of the size specified
-     * in params. We do this in two passes: we extend it vertically
-     * until it's the right height, then we transpose it, then
-     * extend it vertically again (getting it effectively the right
-     * width), then finally transpose again.
-     */
-    for (i = 0; i < 2; i++) {
-       int *grid2, *expand, *where;
-       game_params params3real, *params3 = &params3real;
+        /*
+         * We have now constructed a grid of the size specified in
+         * params2. Now we extend it into a grid of the size specified
+         * in params. We do this in two passes: we extend it vertically
+         * until it's the right height, then we transpose it, then
+         * extend it vertically again (getting it effectively the right
+         * width), then finally transpose again.
+         */
+        for (i = 0; i < 2; i++) {
+            int *grid2, *expand, *where;
+            game_params params3real, *params3 = &params3real;
 
 #ifdef GENERATION_DIAGNOSTICS
-       printf("before expansion:\n");
-       display_grid(params2, grid, NULL, TRUE);
+            printf("before expansion:\n");
+            display_grid(params2, grid, NULL, TRUE);
 #endif
 
-       /*
-        * Set up the new grid.
-        */
-       grid2 = snewn(params2->w * params->h, int);
-       expand = snewn(params2->h-1, int);
-       where = snewn(params2->w, int);
-       params3->w = params2->w;
-       params3->h = params->h;
-
-       /*
-        * Decide which horizontal edges are going to get expanded,
-        * and by how much.
-        */
-       for (y = 0; y < params2->h-1; y++)
-           expand[y] = 0;
-       for (y = params2->h; y < params->h; y++) {
-           x = random_upto(rs, params2->h-1);
-           expand[x]++;
-       }
+            /*
+             * Set up the new grid.
+             */
+            grid2 = snewn(params2->w * params->h, int);
+            expand = snewn(params2->h-1, int);
+            where = snewn(params2->w, int);
+            params3->w = params2->w;
+            params3->h = params->h;
+
+            /*
+             * Decide which horizontal edges are going to get expanded,
+             * and by how much.
+             */
+            for (y = 0; y < params2->h-1; y++)
+                expand[y] = 0;
+            for (y = params2->h; y < params->h; y++) {
+                x = random_upto(rs, params2->h-1);
+                expand[x]++;
+            }
 
 #ifdef GENERATION_DIAGNOSTICS
-       printf("expand[] = {");
-       for (y = 0; y < params2->h-1; y++)
-           printf(" %d", expand[y]);
-       printf(" }\n");
+            printf("expand[] = {");
+            for (y = 0; y < params2->h-1; y++)
+                printf(" %d", expand[y]);
+            printf(" }\n");
 #endif
 
-       /*
-        * Perform the expansion. The way this works is that we
-        * alternately:
-        * 
-        *  - copy a row from grid into grid2
-        * 
-        *  - invent some number of additional rows in grid2 where
-        *    there was previously only a horizontal line between
-        *    rows in grid, and make random decisions about where
-        *    among these to place each rectangle edge that ran
-        *    along this line.
-        */
-       for (y = y2 = y2last = 0; y < params2->h; y++) {
-           /*
-            * Copy a single line from row y of grid into row y2 of
-            * grid2.
-            */
-           for (x = 0; x < params2->w; x++) {
-               int val = index(params2, grid, x, y);
-               if (val / params2->w == y &&   /* rect starts on this line */
-                   (y2 == 0 ||        /* we're at the very top, or... */
-                    index(params3, grid2, x, y2-1) / params3->w < y2last
-                                      /* this rect isn't already started */))
-                   index(params3, grid2, x, y2) =
-                   INDEX(params3, val % params2->w, y2);
-               else
-                   index(params3, grid2, x, y2) =
-                   index(params3, grid2, x, y2-1);
-           }
+            /*
+             * Perform the expansion. The way this works is that we
+             * alternately:
+             *
+             *  - copy a row from grid into grid2
+             *
+             *  - invent some number of additional rows in grid2 where
+             *    there was previously only a horizontal line between
+             *    rows in grid, and make random decisions about where
+             *    among these to place each rectangle edge that ran
+             *    along this line.
+             */
+            for (y = y2 = y2last = 0; y < params2->h; y++) {
+                /*
+                 * Copy a single line from row y of grid into row y2 of
+                 * grid2.
+                 */
+                for (x = 0; x < params2->w; x++) {
+                    int val = index(params2, grid, x, y);
+                    if (val / params2->w == y &&   /* rect starts on this line */
+                        (y2 == 0 ||           /* we're at the very top, or... */
+                         index(params3, grid2, x, y2-1) / params3->w < y2last
+                         /* this rect isn't already started */))
+                        index(params3, grid2, x, y2) =
+                        INDEX(params3, val % params2->w, y2);
+                    else
+                        index(params3, grid2, x, y2) =
+                        index(params3, grid2, x, y2-1);
+                }
 
-           /*
-            * If that was the last line, terminate the loop early.
-            */
-           if (++y2 == params3->h)
-               break;
-
-           y2last = y2;
-
-           /*
-            * Invent some number of additional lines. First walk
-            * along this line working out where to put all the
-            * edges that coincide with it.
-            */
-           yx = -1;
-           for (x = 0; x < params2->w; x++) {
-               if (index(params2, grid, x, y) !=
-                   index(params2, grid, x, y+1)) {
-                   /*
-                    * This is a horizontal edge, so it needs
-                    * placing.
-                    */
-                   if (x == 0 ||
-                       (index(params2, grid, x-1, y) !=
-                        index(params2, grid, x, y) &&
-                        index(params2, grid, x-1, y+1) !=
-                        index(params2, grid, x, y+1))) {
-                       /*
-                        * Here we have the chance to make a new
-                        * decision.
-                        */
-                       yx = random_upto(rs, expand[y]+1);
-                   } else {
-                       /*
-                        * Here we just reuse the previous value of
-                        * yx.
-                        */
-                   }
-               } else
-                   yx = -1;
-               where[x] = yx;
-           }
+                /*
+                 * If that was the last line, terminate the loop early.
+                 */
+                if (++y2 == params3->h)
+                    break;
 
-           for (yx = 0; yx < expand[y]; yx++) {
-               /*
-                * Invent a single row. For each square in the row,
-                * we copy the grid entry from the square above it,
-                * unless we're starting the new rectangle here.
-                */
-               for (x = 0; x < params2->w; x++) {
-                   if (yx == where[x]) {
-                       int val = index(params2, grid, x, y+1);
-                       val %= params2->w;
-                       val = INDEX(params3, val, y2);
-                       index(params3, grid2, x, y2) = val;
-                   } else
-                       index(params3, grid2, x, y2) = 
-                       index(params3, grid2, x, y2-1);
-               }
+                y2last = y2;
 
-               y2++;
-           }
-       }
+                /*
+                 * Invent some number of additional lines. First walk
+                 * along this line working out where to put all the
+                 * edges that coincide with it.
+                 */
+                yx = -1;
+                for (x = 0; x < params2->w; x++) {
+                    if (index(params2, grid, x, y) !=
+                        index(params2, grid, x, y+1)) {
+                        /*
+                         * This is a horizontal edge, so it needs
+                         * placing.
+                         */
+                        if (x == 0 ||
+                            (index(params2, grid, x-1, y) !=
+                             index(params2, grid, x, y) &&
+                             index(params2, grid, x-1, y+1) !=
+                             index(params2, grid, x, y+1))) {
+                            /*
+                             * Here we have the chance to make a new
+                             * decision.
+                             */
+                            yx = random_upto(rs, expand[y]+1);
+                        } else {
+                            /*
+                             * Here we just reuse the previous value of
+                             * yx.
+                             */
+                        }
+                    } else
+                        yx = -1;
+                    where[x] = yx;
+                }
+
+                for (yx = 0; yx < expand[y]; yx++) {
+                    /*
+                     * Invent a single row. For each square in the row,
+                     * we copy the grid entry from the square above it,
+                     * unless we're starting the new rectangle here.
+                     */
+                    for (x = 0; x < params2->w; x++) {
+                        if (yx == where[x]) {
+                            int val = index(params2, grid, x, y+1);
+                            val %= params2->w;
+                            val = INDEX(params3, val, y2);
+                            index(params3, grid2, x, y2) = val;
+                        } else
+                            index(params3, grid2, x, y2) =
+                            index(params3, grid2, x, y2-1);
+                    }
+
+                    y2++;
+                }
+            }
 
-       sfree(expand);
-       sfree(where);
+            sfree(expand);
+            sfree(where);
 
 #ifdef GENERATION_DIAGNOSTICS
-       printf("after expansion:\n");
-       display_grid(params3, grid2, NULL, TRUE);
+            printf("after expansion:\n");
+            display_grid(params3, grid2, NULL, TRUE);
 #endif
-       /*
-        * Transpose.
-        */
-       params2->w = params3->h;
-       params2->h = params3->w;
-       sfree(grid);
-       grid = snewn(params2->w * params2->h, int);
-       for (x = 0; x < params2->w; x++)
-           for (y = 0; y < params2->h; y++) {
-               int idx1 = INDEX(params2, x, y);
-               int idx2 = INDEX(params3, y, x);
-               int tmp;
-
-               tmp = grid2[idx2];
-               tmp = (tmp % params3->w) * params2->w + (tmp / params3->w);
-               grid[idx1] = tmp;
-           }
+            /*
+             * Transpose.
+             */
+            params2->w = params3->h;
+            params2->h = params3->w;
+            sfree(grid);
+            grid = snewn(params2->w * params2->h, int);
+            for (x = 0; x < params2->w; x++)
+                for (y = 0; y < params2->h; y++) {
+                    int idx1 = INDEX(params2, x, y);
+                    int idx2 = INDEX(params3, y, x);
+                    int tmp;
+
+                    tmp = grid2[idx2];
+                    tmp = (tmp % params3->w) * params2->w + (tmp / params3->w);
+                    grid[idx1] = tmp;
+                }
 
-       sfree(grid2);
+            sfree(grid2);
 
-       {
-           int tmp;
-           tmp = params->w;
-           params->w = params->h;
-           params->h = tmp;
-       }
+            {
+                int tmp;
+                tmp = params->w;
+                params->w = params->h;
+                params->h = tmp;
+            }
 
 #ifdef GENERATION_DIAGNOSTICS
-       printf("after transposition:\n");
-       display_grid(params2, grid, NULL, TRUE);
+            printf("after transposition:\n");
+            display_grid(params2, grid, NULL, TRUE);
 #endif
-    }
+        }
 
-    /*
-     * Place numbers.
-     */
-    numbers = snewn(params->w * params->h, int);
+        /*
+         * Run the solver to narrow down the possible number
+         * placements.
+         */
+        {
+            struct numberdata *nd;
+            int nnumbers, i, ret;
+
+            /* Count the rectangles. */
+            nnumbers = 0;
+            for (y = 0; y < params->h; y++) {
+                for (x = 0; x < params->w; x++) {
+                    int idx = INDEX(params, x, y);
+                    if (index(params, grid, x, y) == idx)
+                        nnumbers++;
+                }
+            }
 
-    for (y = 0; y < params->h; y++)
-        for (x = 0; x < params->w; x++) {
-            index(params, numbers, x, y) = 0;
-        }
+            nd = snewn(nnumbers, struct numberdata);
+
+            /* Now set up each number's candidate position list. */
+            i = 0;
+            for (y = 0; y < params->h; y++) {
+                for (x = 0; x < params->w; x++) {
+                    int idx = INDEX(params, x, y);
+                    if (index(params, grid, x, y) == idx) {
+                        struct rect r = find_rect(params, grid, x, y);
+                        int j, k, m;
+
+                        nd[i].area = r.w * r.h;
+                        nd[i].npoints = nd[i].area;
+                        nd[i].points = snewn(nd[i].npoints, struct point);
+                        m = 0;
+                        for (j = 0; j < r.h; j++)
+                            for (k = 0; k < r.w; k++) {
+                                nd[i].points[m].x = k + r.x;
+                                nd[i].points[m].y = j + r.y;
+                                m++;
+                            }
+                        assert(m == nd[i].npoints);
 
-    for (x = 0; x < params->w; x++) {
-        for (y = 0; y < params->h; y++) {
-            int idx = INDEX(params, x, y);
-            if (index(params, grid, x, y) == idx) {
-                struct rect r = find_rect(params, grid, x, y);
-                int n, xx, yy;
+                        i++;
+                    }
+                }
+            }
 
+           if (params->unique)
+               ret = rect_solver(params->w, params->h, nnumbers, nd,
+                                 NULL, NULL, rs);
+           else
+               ret = 1;               /* allow any number placement at all */
+
+            if (ret == 1) {
                 /*
-                 * Decide where to put the number.
+                 * Now place the numbers according to the solver's
+                 * recommendations.
                  */
-                n = random_upto(rs, r.w*r.h);
-                yy = n / r.w;
-                xx = n % r.w;
-                index(params,numbers,x+xx,y+yy) = r.w*r.h;
+                numbers = snewn(params->w * params->h, int);
+
+                for (y = 0; y < params->h; y++)
+                    for (x = 0; x < params->w; x++) {
+                        index(params, numbers, x, y) = 0;
+                    }
+
+                for (i = 0; i < nnumbers; i++) {
+                    int idx = random_upto(rs, nd[i].npoints);
+                    int x = nd[i].points[idx].x;
+                    int y = nd[i].points[idx].y;
+                    index(params,numbers,x,y) = nd[i].area;
+                }
             }
+
+            /*
+             * Clean up.
+             */
+            for (i = 0; i < nnumbers; i++)
+                sfree(nd[i].points);
+            sfree(nd);
+
+            /*
+             * If we've succeeded, then terminate the loop.
+             */
+            if (ret == 1)
+                break;
         }
+
+        /*
+         * Give up and go round again.
+         */
+        sfree(grid);
+    }
+
+    /*
+     * Store the solution in aux.
+     */
+    {
+        char *ai;
+        int len;
+
+        len = 2 + (params->w-1)*params->h + (params->h-1)*params->w;
+        ai = snewn(len, char);
+
+        ai[0] = 'S';
+
+        p = ai+1;
+
+        for (y = 0; y < params->h; y++)
+            for (x = 1; x < params->w; x++)
+                *p++ = (index(params, grid, x, y) !=
+                        index(params, grid, x-1, y) ? '1' : '0');
+
+        for (y = 1; y < params->h; y++)
+            for (x = 0; x < params->w; x++)
+                *p++ = (index(params, grid, x, y) !=
+                        index(params, grid, x, y-1) ? '1' : '0');
+
+        assert(p - ai == len-1);
+        *p = '\0';
+
+        *aux = ai;
     }
 
 #ifdef GENERATION_DIAGNOSTICS
     display_grid(params, grid, numbers, FALSE);
 #endif
 
-    seed = snewn(11 * params->w * params->h, char);
-    p = seed;
+    desc = snewn(11 * params->w * params->h, char);
+    p = desc;
     run = 0;
     for (i = 0; i <= params->w * params->h; i++) {
         int n = (i < params->w * params->h ? numbers[i] : -1);
@@ -883,7 +1763,7 @@ static char *new_game_seed(game_params *params, random_state *rs,
                  * bottom right, there's no point putting an
                  * unnecessary _ before or after it.
                  */
-                if (p > seed && n > 0)
+                if (p > desc && n > 0)
                     *p++ = '_';
             }
             if (n > 0)
@@ -896,31 +1776,26 @@ static char *new_game_seed(game_params *params, random_state *rs,
     sfree(grid);
     sfree(numbers);
 
-    return seed;
-}
-
-void game_free_aux_info(game_aux_info *aux)
-{
-    assert(!"Shouldn't happen");
+    return desc;
 }
 
-static char *validate_seed(game_params *params, char *seed)
+static char *validate_desc(const game_params *params, const char *desc)
 {
     int area = params->w * params->h;
     int squares = 0;
 
-    while (*seed) {
-        int n = *seed++;
+    while (*desc) {
+        int n = *desc++;
         if (n >= 'a' && n <= 'z') {
             squares += n - 'a' + 1;
         } else if (n == '_') {
             /* do nothing */;
         } else if (n > '0' && n <= '9') {
             squares++;
-            while (*seed >= '0' && *seed <= '9')
-                seed++;
+            while (*desc >= '0' && *desc <= '9')
+                desc++;
         } else
-            return "Invalid character in game specification";
+            return "Invalid character in game description";
     }
 
     if (squares < area)
@@ -932,7 +1807,101 @@ static char *validate_seed(game_params *params, char *seed)
     return NULL;
 }
 
-static game_state *new_game(game_params *params, char *seed)
+static unsigned char *get_correct(game_state *state)
+{
+    unsigned char *ret;
+    int x, y;
+
+    ret = snewn(state->w * state->h, unsigned char);
+    memset(ret, 0xFF, state->w * state->h);
+
+    for (x = 0; x < state->w; x++)
+       for (y = 0; y < state->h; y++)
+           if (index(state,ret,x,y) == 0xFF) {
+               int rw, rh;
+               int xx, yy;
+               int num, area, valid;
+
+               /*
+                * Find a rectangle starting at this point.
+                */
+               rw = 1;
+               while (x+rw < state->w && !vedge(state,x+rw,y))
+                   rw++;
+               rh = 1;
+               while (y+rh < state->h && !hedge(state,x,y+rh))
+                   rh++;
+
+               /*
+                * We know what the dimensions of the rectangle
+                * should be if it's there at all. Find out if we
+                * really have a valid rectangle.
+                */
+               valid = TRUE;
+               /* Check the horizontal edges. */
+               for (xx = x; xx < x+rw; xx++) {
+                   for (yy = y; yy <= y+rh; yy++) {
+                       int e = !HRANGE(state,xx,yy) || hedge(state,xx,yy);
+                       int ec = (yy == y || yy == y+rh);
+                       if (e != ec)
+                           valid = FALSE;
+                   }
+               }
+               /* Check the vertical edges. */
+               for (yy = y; yy < y+rh; yy++) {
+                   for (xx = x; xx <= x+rw; xx++) {
+                       int e = !VRANGE(state,xx,yy) || vedge(state,xx,yy);
+                       int ec = (xx == x || xx == x+rw);
+                       if (e != ec)
+                           valid = FALSE;
+                   }
+               }
+
+               /*
+                * If this is not a valid rectangle with no other
+                * edges inside it, we just mark this square as not
+                * complete and proceed to the next square.
+                */
+               if (!valid) {
+                   index(state, ret, x, y) = 0;
+                   continue;
+               }
+
+               /*
+                * We have a rectangle. Now see what its area is,
+                * and how many numbers are in it.
+                */
+               num = 0;
+               area = 0;
+               for (xx = x; xx < x+rw; xx++) {
+                   for (yy = y; yy < y+rh; yy++) {
+                       area++;
+                       if (grid(state,xx,yy)) {
+                           if (num > 0)
+                               valid = FALSE;   /* two numbers */
+                           num = grid(state,xx,yy);
+                       }
+                   }
+               }
+               if (num != area)
+                   valid = FALSE;
+
+               /*
+                * Now fill in the whole rectangle based on the
+                * value of `valid'.
+                */
+               for (xx = x; xx < x+rw; xx++) {
+                   for (yy = y; yy < y+rh; yy++) {
+                       index(state, ret, xx, yy) = valid;
+                   }
+               }
+           }
+
+    return ret;
+}
+
+static game_state *new_game(midend *me, const game_params *params,
+                            const char *desc)
 {
     game_state *state = snew(game_state);
     int x, y, i, area;
@@ -945,11 +1914,11 @@ static game_state *new_game(game_params *params, char *seed)
     state->grid = snewn(area, int);
     state->vedge = snewn(area, unsigned char);
     state->hedge = snewn(area, unsigned char);
-    state->completed = FALSE;
+    state->completed = state->cheated = FALSE;
 
     i = 0;
-    while (*seed) {
-        int n = *seed++;
+    while (*desc) {
+        int n = *desc++;
         if (n >= 'a' && n <= 'z') {
             int run = n - 'a' + 1;
             assert(i + run <= area);
@@ -959,9 +1928,9 @@ static game_state *new_game(game_params *params, char *seed)
             /* do nothing */;
         } else if (n > '0' && n <= '9') {
             assert(i < area);
-            state->grid[i++] = atoi(seed-1);
-            while (*seed >= '0' && *seed <= '9')
-                seed++;
+            state->grid[i++] = atoi(desc-1);
+            while (*desc >= '0' && *desc <= '9')
+                desc++;
         } else {
             assert(!"We can't get here");
         }
@@ -972,38 +1941,119 @@ static game_state *new_game(game_params *params, char *seed)
        for (x = 0; x < state->w; x++)
            vedge(state,x,y) = hedge(state,x,y) = 0;
 
-    return state;
-}
+    state->correct = get_correct(state);
+
+    return state;
+}
+
+static game_state *dup_game(const game_state *state)
+{
+    game_state *ret = snew(game_state);
+
+    ret->w = state->w;
+    ret->h = state->h;
+
+    ret->vedge = snewn(state->w * state->h, unsigned char);
+    ret->hedge = snewn(state->w * state->h, unsigned char);
+    ret->grid = snewn(state->w * state->h, int);
+    ret->correct = snewn(ret->w * ret->h, unsigned char);
+
+    ret->completed = state->completed;
+    ret->cheated = state->cheated;
+
+    memcpy(ret->grid, state->grid, state->w * state->h * sizeof(int));
+    memcpy(ret->vedge, state->vedge, state->w*state->h*sizeof(unsigned char));
+    memcpy(ret->hedge, state->hedge, state->w*state->h*sizeof(unsigned char));
+
+    memcpy(ret->correct, state->correct, state->w*state->h*sizeof(unsigned char));
+
+    return ret;
+}
+
+static void free_game(game_state *state)
+{
+    sfree(state->grid);
+    sfree(state->vedge);
+    sfree(state->hedge);
+    sfree(state->correct);
+    sfree(state);
+}
+
+static char *solve_game(const game_state *state, const game_state *currstate,
+                        const char *ai, char **error)
+{
+    unsigned char *vedge, *hedge;
+    int x, y, len;
+    char *ret, *p;
+    int i, j, n;
+    struct numberdata *nd;
+
+    if (ai)
+        return dupstr(ai);
+
+    /*
+     * Attempt the in-built solver.
+     */
+
+    /* Set up each number's (very short) candidate position list. */
+    for (i = n = 0; i < state->h * state->w; i++)
+        if (state->grid[i])
+            n++;
+
+    nd = snewn(n, struct numberdata);
+
+    for (i = j = 0; i < state->h * state->w; i++)
+        if (state->grid[i]) {
+            nd[j].area = state->grid[i];
+            nd[j].npoints = 1;
+            nd[j].points = snewn(1, struct point);
+            nd[j].points[0].x = i % state->w;
+            nd[j].points[0].y = i / state->w;
+            j++;
+        }
+
+    assert(j == n);
 
-static game_state *dup_game(game_state *state)
-{
-    game_state *ret = snew(game_state);
+    vedge = snewn(state->w * state->h, unsigned char);
+    hedge = snewn(state->w * state->h, unsigned char);
+    memset(vedge, 0, state->w * state->h);
+    memset(hedge, 0, state->w * state->h);
 
-    ret->w = state->w;
-    ret->h = state->h;
+    rect_solver(state->w, state->h, n, nd, hedge, vedge, NULL);
 
-    ret->vedge = snewn(state->w * state->h, unsigned char);
-    ret->hedge = snewn(state->w * state->h, unsigned char);
-    ret->grid = snewn(state->w * state->h, int);
+    /*
+     * Clean up.
+     */
+    for (i = 0; i < n; i++)
+        sfree(nd[i].points);
+    sfree(nd);
 
-    ret->completed = state->completed;
+    len = 2 + (state->w-1)*state->h + (state->h-1)*state->w;
+    ret = snewn(len, char);
 
-    memcpy(ret->grid, state->grid, state->w * state->h * sizeof(int));
-    memcpy(ret->vedge, state->vedge, state->w*state->h*sizeof(unsigned char));
-    memcpy(ret->hedge, state->hedge, state->w*state->h*sizeof(unsigned char));
+    p = ret;
+    *p++ = 'S';
+    for (y = 0; y < state->h; y++)
+        for (x = 1; x < state->w; x++)
+            *p++ = vedge[y*state->w+x] ? '1' : '0';
+    for (y = 1; y < state->h; y++)
+       for (x = 0; x < state->w; x++)
+           *p++ = hedge[y*state->w+x] ? '1' : '0';
+    *p++ = '\0';
+    assert(p - ret == len);
+
+    sfree(vedge);
+    sfree(hedge);
 
     return ret;
 }
 
-static void free_game(game_state *state)
+static int game_can_format_as_text_now(const game_params *params)
 {
-    sfree(state->grid);
-    sfree(state->vedge);
-    sfree(state->hedge);
-    sfree(state);
+    return TRUE;
 }
 
-static char *game_text_format(game_state *state)
+static char *game_text_format(const game_state *state)
 {
     char *ret, *p, buf[80];
     int i, x, y, col, maxlen;
@@ -1026,7 +2076,7 @@ static char *game_text_format(game_state *state)
      */
     maxlen = (2*state->h+1) * (state->w * (col+1) + 2);
 
-    ret = snewn(maxlen, char);
+    ret = snewn(maxlen+1, char);
     p = ret;
 
     for (y = 0; y <= 2*state->h; y++) {
@@ -1096,99 +2146,6 @@ static char *game_text_format(game_state *state)
     return ret;
 }
 
-static unsigned char *get_correct(game_state *state)
-{
-    unsigned char *ret;
-    int x, y;
-
-    ret = snewn(state->w * state->h, unsigned char);
-    memset(ret, 0xFF, state->w * state->h);
-
-    for (x = 0; x < state->w; x++)
-       for (y = 0; y < state->h; y++)
-           if (index(state,ret,x,y) == 0xFF) {
-               int rw, rh;
-               int xx, yy;
-               int num, area, valid;
-
-               /*
-                * Find a rectangle starting at this point.
-                */
-               rw = 1;
-               while (x+rw < state->w && !vedge(state,x+rw,y))
-                   rw++;
-               rh = 1;
-               while (y+rh < state->h && !hedge(state,x,y+rh))
-                   rh++;
-
-               /*
-                * We know what the dimensions of the rectangle
-                * should be if it's there at all. Find out if we
-                * really have a valid rectangle.
-                */
-               valid = TRUE;
-               /* Check the horizontal edges. */
-               for (xx = x; xx < x+rw; xx++) {
-                   for (yy = y; yy <= y+rh; yy++) {
-                       int e = !HRANGE(state,xx,yy) || hedge(state,xx,yy);
-                       int ec = (yy == y || yy == y+rh);
-                       if (e != ec)
-                           valid = FALSE;
-                   }
-               }
-               /* Check the vertical edges. */
-               for (yy = y; yy < y+rh; yy++) {
-                   for (xx = x; xx <= x+rw; xx++) {
-                       int e = !VRANGE(state,xx,yy) || vedge(state,xx,yy);
-                       int ec = (xx == x || xx == x+rw);
-                       if (e != ec)
-                           valid = FALSE;
-                   }
-               }
-
-               /*
-                * If this is not a valid rectangle with no other
-                * edges inside it, we just mark this square as not
-                * complete and proceed to the next square.
-                */
-               if (!valid) {
-                   index(state, ret, x, y) = 0;
-                   continue;
-               }
-
-               /*
-                * We have a rectangle. Now see what its area is,
-                * and how many numbers are in it.
-                */
-               num = 0;
-               area = 0;
-               for (xx = x; xx < x+rw; xx++) {
-                   for (yy = y; yy < y+rh; yy++) {
-                       area++;
-                       if (grid(state,xx,yy)) {
-                           if (num > 0)
-                               valid = FALSE;   /* two numbers */
-                           num = grid(state,xx,yy);
-                       }
-                   }
-               }
-               if (num != area)
-                   valid = FALSE;
-
-               /*
-                * Now fill in the whole rectangle based on the
-                * value of `valid'.
-                */
-               for (xx = x; xx < x+rw; xx++) {
-                   for (yy = y; yy < y+rh; yy++) {
-                       index(state, ret, xx, yy) = valid;
-                   }
-               }
-           }
-
-    return ret;
-}
-
 struct game_ui {
     /*
      * These coordinates are 2 times the obvious grid coordinates.
@@ -1211,16 +2168,45 @@ struct game_ui {
      * treated as a small drag rather than a click.
      */
     int dragged;
+    /* This flag is set if we're doing an erase operation (i.e.
+     * removing edges in the centre of the rectangle without altering
+     * the outlines).
+     */
+    int erasing;
+    /*
+     * These are the co-ordinates of the top-left and bottom-right squares
+     * in the drag box, respectively, or -1 otherwise.
+     */
+    int x1;
+    int y1;
+    int x2;
+    int y2;
+    /*
+     * These are the coordinates of a cursor, whether it's visible, and
+     * whether it was used to start a drag.
+     */
+    int cur_x, cur_y, cur_visible, cur_dragging;
 };
 
-static game_ui *new_ui(game_state *state)
+static void reset_ui(game_ui *ui)
 {
-    game_ui *ui = snew(game_ui);
     ui->drag_start_x = -1;
     ui->drag_start_y = -1;
     ui->drag_end_x = -1;
     ui->drag_end_y = -1;
+    ui->x1 = -1;
+    ui->y1 = -1;
+    ui->x2 = -1;
+    ui->y2 = -1;
     ui->dragged = FALSE;
+}
+
+static game_ui *new_ui(const game_state *state)
+{
+    game_ui *ui = snew(game_ui);
+    reset_ui(ui);
+    ui->erasing = FALSE;
+    ui->cur_x = ui->cur_y = ui->cur_visible = ui->cur_dragging = 0;
     return ui;
 }
 
@@ -1229,6 +2215,15 @@ static void free_ui(game_ui *ui)
     sfree(ui);
 }
 
+static char *encode_ui(const game_ui *ui)
+{
+    return NULL;
+}
+
+static void decode_ui(game_ui *ui, const char *encoding)
+{
+}
+
 static void coord_round(float x, float y, int *xr, int *yr)
 {
     float xs, ys, xv, yv, dx, dy, dist;
@@ -1302,34 +2297,27 @@ static void coord_round(float x, float y, int *xr, int *yr)
                 /* Vertical edge: x-coord of corner,
                  * y-coord of square centre. */
                 *xr = 2 * (int)xv;
-                *yr = 1 + 2 * (int)ys;
+                *yr = 1 + 2 * (int)floor(ys);
             } else {
                 /* Horizontal edge: x-coord of square centre,
                  * y-coord of corner. */
-                *xr = 1 + 2 * (int)xs;
+                *xr = 1 + 2 * (int)floor(xs);
                 *yr = 2 * (int)yv;
             }
         }
     }
 }
 
-static void ui_draw_rect(game_state *state, game_ui *ui,
-                         unsigned char *hedge, unsigned char *vedge, int c)
+/*
+ * Returns TRUE if it has made any change to the grid.
+ */
+static int grid_draw_rect(const game_state *state,
+                         unsigned char *hedge, unsigned char *vedge,
+                         int c, int really, int outline,
+                         int x1, int y1, int x2, int y2)
 {
-    int x1, x2, y1, y2, x, y, t;
-
-    x1 = ui->drag_start_x;
-    x2 = ui->drag_end_x;
-    if (x2 < x1) { t = x1; x1 = x2; x2 = t; }
-
-    y1 = ui->drag_start_y;
-    y2 = ui->drag_end_y;
-    if (y2 < y1) { t = y1; y1 = y2; y2 = t; }
-
-    x1 = x1 / 2;               /* rounds down */
-    x2 = (x2+1) / 2;           /* rounds up */
-    y1 = y1 / 2;               /* rounds down */
-    y2 = (y2+1) / 2;           /* rounds up */
+    int x, y;
+    int changed = FALSE;
 
     /*
      * Draw horizontal edges of rectangles.
@@ -1338,11 +2326,14 @@ static void ui_draw_rect(game_state *state, game_ui *ui,
         for (y = y1; y <= y2; y++)
             if (HRANGE(state,x,y)) {
                 int val = index(state,hedge,x,y);
-                if (y == y1 || y == y2)
+                if (y == y1 || y == y2) {
+                    if (!outline) continue;
                     val = c;
-                else if (c == 1)
+                else if (c == 1)
                     val = 0;
-                index(state,hedge,x,y) = val;
+               changed = changed || (index(state,hedge,x,y) != val);
+               if (really)
+                   index(state,hedge,x,y) = val;
             }
 
     /*
@@ -1352,131 +2343,298 @@ static void ui_draw_rect(game_state *state, game_ui *ui,
         for (x = x1; x <= x2; x++)
             if (VRANGE(state,x,y)) {
                 int val = index(state,vedge,x,y);
-                if (x == x1 || x == x2)
+                if (x == x1 || x == x2) {
+                    if (!outline) continue;
                     val = c;
-                else if (c == 1)
+                else if (c == 1)
                     val = 0;
-                index(state,vedge,x,y) = val;
+               changed = changed || (index(state,vedge,x,y) != val);
+                if (really)
+                   index(state,vedge,x,y) = val;
             }
+
+    return changed;
+}
+
+static int ui_draw_rect(const game_state *state, const game_ui *ui,
+                       unsigned char *hedge, unsigned char *vedge, int c,
+                       int really, int outline)
+{
+    return grid_draw_rect(state, hedge, vedge, c, really, outline,
+                         ui->x1, ui->y1, ui->x2, ui->y2);
+}
+
+static void game_changed_state(game_ui *ui, const game_state *oldstate,
+                               const game_state *newstate)
+{
 }
 
-static game_state *make_move(game_state *from, game_ui *ui,
-                            int x, int y, int button)
+struct game_drawstate {
+    int started;
+    int w, h, tilesize;
+    unsigned long *visible;
+};
+
+static char *interpret_move(const game_state *from, game_ui *ui,
+                            const game_drawstate *ds,
+                            int x, int y, int button)
 {
     int xc, yc;
-    int startdrag = FALSE, enddrag = FALSE, active = FALSE;
-    game_state *ret;
+    int startdrag = FALSE, enddrag = FALSE, active = FALSE, erasing = FALSE;
+    char buf[80], *ret;
+
+    button &= ~MOD_MASK;
+
+    coord_round(FROMCOORD((float)x), FROMCOORD((float)y), &xc, &yc);
 
-    if (button == LEFT_BUTTON) {
+    if (button == LEFT_BUTTON || button == RIGHT_BUTTON) {
+        if (ui->drag_start_x >= 0 && ui->cur_dragging)
+            reset_ui(ui); /* cancel keyboard dragging */
         startdrag = TRUE;
-    } else if (button == LEFT_RELEASE) {
+        ui->cur_visible = ui->cur_dragging = FALSE;
+        active = TRUE;
+        erasing = (button == RIGHT_BUTTON);
+    } else if (button == LEFT_RELEASE || button == RIGHT_RELEASE) {
+        /* We assert we should have had a LEFT_BUTTON first. */
+        if (ui->cur_visible) {
+            ui->cur_visible = FALSE;
+            active = TRUE;
+        }
+        assert(!ui->cur_dragging);
         enddrag = TRUE;
-    } else if (button != LEFT_DRAG) {
+        erasing = (button == RIGHT_RELEASE);
+    } else if (IS_CURSOR_MOVE(button)) {
+        move_cursor(button, &ui->cur_x, &ui->cur_y, from->w, from->h, 0);
+        ui->cur_visible = TRUE;
+        active = TRUE;
+        if (!ui->cur_dragging) return "";
+        coord_round((float)ui->cur_x + 0.5F, (float)ui->cur_y + 0.5F, &xc, &yc);
+    } else if (IS_CURSOR_SELECT(button)) {
+        if (ui->drag_start_x >= 0 && !ui->cur_dragging) {
+            /*
+             * If a mouse drag is in progress, ignore attempts to
+             * start a keyboard one.
+             */
+            return NULL;
+        }
+        if (!ui->cur_visible) {
+            assert(!ui->cur_dragging);
+            ui->cur_visible = TRUE;
+            return "";
+        }
+        coord_round((float)ui->cur_x + 0.5F, (float)ui->cur_y + 0.5F, &xc, &yc);
+        erasing = (button == CURSOR_SELECT2);
+        if (ui->cur_dragging) {
+            ui->cur_dragging = FALSE;
+            enddrag = TRUE;
+            active = TRUE;
+        } else {
+            ui->cur_dragging = TRUE;
+            startdrag = TRUE;
+            active = TRUE;
+        }
+    } else if (button == '\b' || button == 27) {
+        if (!ui->cur_dragging) {
+            ui->cur_visible = FALSE;
+        } else {
+            assert(ui->cur_visible);
+            reset_ui(ui); /* cancel keyboard dragging */
+            ui->cur_dragging = FALSE;
+        }
+        return "";
+    } else if (button != LEFT_DRAG && button != RIGHT_DRAG) {
         return NULL;
     }
 
-    coord_round(FROMCOORD((float)x), FROMCOORD((float)y), &xc, &yc);
+    if (startdrag &&
+       xc >= 0 && xc <= 2*from->w &&
+       yc >= 0 && yc <= 2*from->h) {
 
-    if (startdrag) {
         ui->drag_start_x = xc;
         ui->drag_start_y = yc;
-        ui->drag_end_x = xc;
-        ui->drag_end_y = yc;
+        ui->drag_end_x = -1;
+        ui->drag_end_y = -1;
         ui->dragged = FALSE;
+        ui->erasing = erasing;
         active = TRUE;
     }
 
-    if (xc != ui->drag_end_x || yc != ui->drag_end_y) {
+    if (ui->drag_start_x >= 0 &&
+       (xc != ui->drag_end_x || yc != ui->drag_end_y)) {
+       int t;
+
+       if (ui->drag_end_x != -1 && ui->drag_end_y != -1)
+           ui->dragged = TRUE;
         ui->drag_end_x = xc;
         ui->drag_end_y = yc;
-        ui->dragged = TRUE;
         active = TRUE;
+
+       if (xc >= 0 && xc <= 2*from->w &&
+           yc >= 0 && yc <= 2*from->h) {
+            ui->x1 = ui->drag_start_x;
+            ui->x2 = ui->drag_end_x;
+            if (ui->x2 < ui->x1) { t = ui->x1; ui->x1 = ui->x2; ui->x2 = t; }
+
+            ui->y1 = ui->drag_start_y;
+            ui->y2 = ui->drag_end_y;
+            if (ui->y2 < ui->y1) { t = ui->y1; ui->y1 = ui->y2; ui->y2 = t; }
+
+            ui->x1 = ui->x1 / 2;               /* rounds down */
+            ui->x2 = (ui->x2+1) / 2;           /* rounds up */
+            ui->y1 = ui->y1 / 2;               /* rounds down */
+            ui->y2 = (ui->y2+1) / 2;           /* rounds up */
+        } else {
+            ui->x1 = -1;
+            ui->y1 = -1;
+            ui->x2 = -1;
+            ui->y2 = -1;
+        }
     }
 
     ret = NULL;
 
-    if (enddrag) {
+    if (enddrag && (ui->drag_start_x >= 0)) {
        if (xc >= 0 && xc <= 2*from->w &&
-           yc >= 0 && yc <= 2*from->h) {
-           ret = dup_game(from);
+           yc >= 0 && yc <= 2*from->h &&
+            erasing == ui->erasing) {
 
            if (ui->dragged) {
-               ui_draw_rect(ret, ui, ret->hedge, ret->vedge, 1);
+               if (ui_draw_rect(from, ui, from->hedge,
+                                from->vedge, 1, FALSE, !ui->erasing)) {
+                   sprintf(buf, "%c%d,%d,%d,%d",
+                           (int)(ui->erasing ? 'E' : 'R'),
+                           ui->x1, ui->y1, ui->x2 - ui->x1, ui->y2 - ui->y1);
+                   ret = dupstr(buf);
+               }
            } else {
                if ((xc & 1) && !(yc & 1) && HRANGE(from,xc/2,yc/2)) {
-                   hedge(ret,xc/2,yc/2) = !hedge(ret,xc/2,yc/2);
+                   sprintf(buf, "H%d,%d", xc/2, yc/2);
+                   ret = dupstr(buf);
                }
                if ((yc & 1) && !(xc & 1) && VRANGE(from,xc/2,yc/2)) {
-                   vedge(ret,xc/2,yc/2) = !vedge(ret,xc/2,yc/2);
+                   sprintf(buf, "V%d,%d", xc/2, yc/2);
+                   ret = dupstr(buf);
                }
            }
-
-           if (!memcmp(ret->hedge, from->hedge, from->w*from->h) &&
-               !memcmp(ret->vedge, from->vedge, from->w*from->h)) {
-               free_game(ret);
-               ret = NULL;
-           }
-
-            /*
-             * We've made a real change to the grid. Check to see
-             * if the game has been completed.
-             */
-            if (ret && !ret->completed) {
-                int x, y, ok;
-                unsigned char *correct = get_correct(ret);
-
-                ok = TRUE;
-                for (x = 0; x < ret->w; x++)
-                    for (y = 0; y < ret->h; y++)
-                        if (!index(ret, correct, x, y))
-                            ok = FALSE;
-
-                sfree(correct);
-
-                if (ok)
-                    ret->completed = TRUE;
-            }
        }
 
-       ui->drag_start_x = -1;
-       ui->drag_start_y = -1;
-       ui->drag_end_x = -1;
-       ui->drag_end_y = -1;
-       ui->dragged = FALSE;
+        reset_ui(ui);
        active = TRUE;
     }
 
     if (ret)
        return ret;                    /* a move has been made */
     else if (active)
-        return from;                   /* UI activity has occurred */
+        return "";                    /* UI activity has occurred */
     else
        return NULL;
 }
 
+static game_state *execute_move(const game_state *from, const char *move)
+{
+    game_state *ret;
+    int x1, y1, x2, y2, mode;
+
+    if (move[0] == 'S') {
+       const char *p = move+1;
+       int x, y;
+
+       ret = dup_game(from);
+       ret->cheated = TRUE;
+
+       for (y = 0; y < ret->h; y++)
+           for (x = 1; x < ret->w; x++) {
+               vedge(ret, x, y) = (*p == '1');
+               if (*p) p++;
+           }
+       for (y = 1; y < ret->h; y++)
+           for (x = 0; x < ret->w; x++) {
+               hedge(ret, x, y) = (*p == '1');
+               if (*p) p++;
+           }
+
+       sfree(ret->correct);
+       ret->correct = get_correct(ret);
+
+       return ret;
+
+    } else if ((move[0] == 'R' || move[0] == 'E') &&
+       sscanf(move+1, "%d,%d,%d,%d", &x1, &y1, &x2, &y2) == 4 &&
+       x1 >= 0 && x2 >= 0 && x1+x2 <= from->w &&
+       y1 >= 0 && y2 >= 0 && y1+y2 <= from->h) {
+       x2 += x1;
+       y2 += y1;
+       mode = move[0];
+    } else if ((move[0] == 'H' || move[0] == 'V') &&
+              sscanf(move+1, "%d,%d", &x1, &y1) == 2 &&
+              (move[0] == 'H' ? HRANGE(from, x1, y1) :
+               VRANGE(from, x1, y1))) {
+       mode = move[0];
+    } else
+       return NULL;                   /* can't parse move string */
+
+    ret = dup_game(from);
+
+    if (mode == 'R' || mode == 'E') {
+       grid_draw_rect(ret, ret->hedge, ret->vedge, 1, TRUE,
+                       mode == 'R', x1, y1, x2, y2);
+    } else if (mode == 'H') {
+       hedge(ret,x1,y1) = !hedge(ret,x1,y1);
+    } else if (mode == 'V') {
+       vedge(ret,x1,y1) = !vedge(ret,x1,y1);
+    }
+
+    sfree(ret->correct);
+    ret->correct = get_correct(ret);
+
+    /*
+     * We've made a real change to the grid. Check to see
+     * if the game has been completed.
+     */
+    if (!ret->completed) {
+       int x, y, ok;
+
+       ok = TRUE;
+       for (x = 0; x < ret->w; x++)
+           for (y = 0; y < ret->h; y++)
+               if (!index(ret, ret->correct, x, y))
+                   ok = FALSE;
+
+       if (ok)
+           ret->completed = TRUE;
+    }
+
+    return ret;
+}
+
 /* ----------------------------------------------------------------------
  * Drawing routines.
  */
 
-#define CORRECT 65536
-
-#define COLOUR(k) ( (k)==1 ? COL_LINE : COL_DRAG )
-#define MAX(x,y) ( (x)>(y) ? (x) : (y) )
-#define MAX4(x,y,z,w) ( MAX(MAX(x,y),MAX(z,w)) )
+#define CORRECT (1L<<16)
+#define CURSOR  (1L<<17)
 
-struct game_drawstate {
-    int started;
-    int w, h;
-    unsigned int *visible;
-};
+#define COLOUR(k) ( (k)==1 ? COL_LINE : (k)==2 ? COL_DRAG : COL_DRAGERASE )
+#define MAX4(x,y,z,w) ( max(max(x,y),max(z,w)) )
 
-static void game_size(game_params *params, int *x, int *y)
+static void game_compute_size(const game_params *params, int tilesize,
+                              int *x, int *y)
 {
+    /* Ick: fake up `ds->tilesize' for macro expansion purposes */
+    struct { int tilesize; } ads, *ds = &ads;
+    ads.tilesize = tilesize;
+
     *x = params->w * TILE_SIZE + 2*BORDER + 1;
     *y = params->h * TILE_SIZE + 2*BORDER + 1;
 }
 
-static float *game_colours(frontend *fe, game_state *state, int *ncolours)
+static void game_set_size(drawing *dr, game_drawstate *ds,
+                          const game_params *params, int tilesize)
+{
+    ds->tilesize = tilesize;
+}
+
+static float *game_colours(frontend *fe, int *ncolours)
 {
     float *ret = snewn(3 * NCOLOURS, float);
 
@@ -1490,6 +2648,10 @@ static float *game_colours(frontend *fe, game_state *state, int *ncolours)
     ret[COL_DRAG * 3 + 1] = 0.0F;
     ret[COL_DRAG * 3 + 2] = 0.0F;
 
+    ret[COL_DRAGERASE * 3 + 0] = 0.2F;
+    ret[COL_DRAGERASE * 3 + 1] = 0.2F;
+    ret[COL_DRAGERASE * 3 + 2] = 1.0F;
+
     ret[COL_CORRECT * 3 + 0] = 0.75F * ret[COL_BACKGROUND * 3 + 0];
     ret[COL_CORRECT * 3 + 1] = 0.75F * ret[COL_BACKGROUND * 3 + 1];
     ret[COL_CORRECT * 3 + 2] = 0.75F * ret[COL_BACKGROUND * 3 + 2];
@@ -1502,11 +2664,15 @@ static float *game_colours(frontend *fe, game_state *state, int *ncolours)
     ret[COL_TEXT * 3 + 1] = 0.0F;
     ret[COL_TEXT * 3 + 2] = 0.0F;
 
+    ret[COL_CURSOR * 3 + 0] = 1.0F;
+    ret[COL_CURSOR * 3 + 1] = 0.5F;
+    ret[COL_CURSOR * 3 + 2] = 0.5F;
+
     *ncolours = NCOLOURS;
     return ret;
 }
 
-static game_drawstate *game_new_drawstate(game_state *state)
+static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state)
 {
     struct game_drawstate *ds = snew(struct game_drawstate);
     int i;
@@ -1514,33 +2680,35 @@ static game_drawstate *game_new_drawstate(game_state *state)
     ds->started = FALSE;
     ds->w = state->w;
     ds->h = state->h;
-    ds->visible = snewn(ds->w * ds->h, unsigned int);
+    ds->visible = snewn(ds->w * ds->h, unsigned long);
+    ds->tilesize = 0;                  /* not decided yet */
     for (i = 0; i < ds->w * ds->h; i++)
         ds->visible[i] = 0xFFFF;
 
     return ds;
 }
 
-static void game_free_drawstate(game_drawstate *ds)
+static void game_free_drawstate(drawing *dr, game_drawstate *ds)
 {
     sfree(ds->visible);
     sfree(ds);
 }
 
-static void draw_tile(frontend *fe, game_state *state, int x, int y,
-               unsigned char *hedge, unsigned char *vedge,
-              unsigned char *corners, int correct)
+static void draw_tile(drawing *dr, game_drawstate *ds, const game_state *state,
+                      int x, int y, unsigned char *hedge, unsigned char *vedge,
+                      unsigned char *corners, unsigned long bgflags)
 {
     int cx = COORD(x), cy = COORD(y);
     char str[80];
 
-    draw_rect(fe, cx, cy, TILE_SIZE+1, TILE_SIZE+1, COL_GRID);
-    draw_rect(fe, cx+1, cy+1, TILE_SIZE-1, TILE_SIZE-1,
-             correct ? COL_CORRECT : COL_BACKGROUND);
+    draw_rect(dr, cx, cy, TILE_SIZE+1, TILE_SIZE+1, COL_GRID);
+    draw_rect(dr, cx+1, cy+1, TILE_SIZE-1, TILE_SIZE-1,
+             (bgflags & CURSOR) ? COL_CURSOR :
+              (bgflags & CORRECT) ? COL_CORRECT : COL_BACKGROUND);
 
     if (grid(state,x,y)) {
        sprintf(str, "%d", grid(state,x,y));
-       draw_text(fe, cx+TILE_SIZE/2, cy+TILE_SIZE/2, FONT_VARIABLE,
+       draw_text(dr, cx+TILE_SIZE/2, cy+TILE_SIZE/2, FONT_VARIABLE,
                  TILE_SIZE/2, ALIGN_HCENTRE | ALIGN_VCENTRE, COL_TEXT, str);
     }
 
@@ -1548,19 +2716,19 @@ static void draw_tile(frontend *fe, game_state *state, int x, int y,
      * Draw edges.
      */
     if (!HRANGE(state,x,y) || index(state,hedge,x,y))
-       draw_rect(fe, cx, cy, TILE_SIZE+1, 2,
+       draw_rect(dr, cx, cy, TILE_SIZE+1, 2,
                   HRANGE(state,x,y) ? COLOUR(index(state,hedge,x,y)) :
                   COL_LINE);
     if (!HRANGE(state,x,y+1) || index(state,hedge,x,y+1))
-       draw_rect(fe, cx, cy+TILE_SIZE-1, TILE_SIZE+1, 2,
+       draw_rect(dr, cx, cy+TILE_SIZE-1, TILE_SIZE+1, 2,
                   HRANGE(state,x,y+1) ? COLOUR(index(state,hedge,x,y+1)) :
                   COL_LINE);
     if (!VRANGE(state,x,y) || index(state,vedge,x,y))
-       draw_rect(fe, cx, cy, 2, TILE_SIZE+1,
+       draw_rect(dr, cx, cy, 2, TILE_SIZE+1,
                   VRANGE(state,x,y) ? COLOUR(index(state,vedge,x,y)) :
                   COL_LINE);
     if (!VRANGE(state,x+1,y) || index(state,vedge,x+1,y))
-       draw_rect(fe, cx+TILE_SIZE-1, cy, 2, TILE_SIZE+1,
+       draw_rect(dr, cx+TILE_SIZE-1, cy, 2, TILE_SIZE+1,
                   VRANGE(state,x+1,y) ? COLOUR(index(state,vedge,x+1,y)) :
                   COL_LINE);
 
@@ -1568,37 +2736,35 @@ static void draw_tile(frontend *fe, game_state *state, int x, int y,
      * Draw corners.
      */
     if (index(state,corners,x,y))
-       draw_rect(fe, cx, cy, 2, 2,
+       draw_rect(dr, cx, cy, 2, 2,
                   COLOUR(index(state,corners,x,y)));
     if (x+1 < state->w && index(state,corners,x+1,y))
-       draw_rect(fe, cx+TILE_SIZE-1, cy, 2, 2,
+       draw_rect(dr, cx+TILE_SIZE-1, cy, 2, 2,
                   COLOUR(index(state,corners,x+1,y)));
     if (y+1 < state->h && index(state,corners,x,y+1))
-       draw_rect(fe, cx, cy+TILE_SIZE-1, 2, 2,
+       draw_rect(dr, cx, cy+TILE_SIZE-1, 2, 2,
                   COLOUR(index(state,corners,x,y+1)));
     if (x+1 < state->w && y+1 < state->h && index(state,corners,x+1,y+1))
-       draw_rect(fe, cx+TILE_SIZE-1, cy+TILE_SIZE-1, 2, 2,
+       draw_rect(dr, cx+TILE_SIZE-1, cy+TILE_SIZE-1, 2, 2,
                   COLOUR(index(state,corners,x+1,y+1)));
 
-    draw_update(fe, cx, cy, TILE_SIZE+1, TILE_SIZE+1);
+    draw_update(dr, cx, cy, TILE_SIZE+1, TILE_SIZE+1);
 }
 
-static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
-                 game_state *state, int dir, game_ui *ui,
-                 float animtime, float flashtime)
+static void game_redraw(drawing *dr, game_drawstate *ds,
+                        const game_state *oldstate, const game_state *state,
+                        int dir, const game_ui *ui,
+                        float animtime, float flashtime)
 {
     int x, y;
-    unsigned char *correct;
     unsigned char *hedge, *vedge, *corners;
 
-    correct = get_correct(state);
-
     if (ui->dragged) {
         hedge = snewn(state->w*state->h, unsigned char);
         vedge = snewn(state->w*state->h, unsigned char);
         memcpy(hedge, state->hedge, state->w*state->h);
         memcpy(vedge, state->vedge, state->w*state->h);
-        ui_draw_rect(state, ui, hedge, vedge, 2);
+        ui_draw_rect(state, ui, hedge, vedge, ui->erasing ? 3 : 2, TRUE, TRUE);
     } else {
         hedge = state->hedge;
         vedge = state->vedge;
@@ -1627,20 +2793,20 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
        }
 
     if (!ds->started) {
-       draw_rect(fe, 0, 0,
+       draw_rect(dr, 0, 0,
                  state->w * TILE_SIZE + 2*BORDER + 1,
                  state->h * TILE_SIZE + 2*BORDER + 1, COL_BACKGROUND);
-       draw_rect(fe, COORD(0)-1, COORD(0)-1,
+       draw_rect(dr, COORD(0)-1, COORD(0)-1,
                  ds->w*TILE_SIZE+3, ds->h*TILE_SIZE+3, COL_LINE);
        ds->started = TRUE;
-       draw_update(fe, 0, 0,
+       draw_update(dr, 0, 0,
                    state->w * TILE_SIZE + 2*BORDER + 1,
                    state->h * TILE_SIZE + 2*BORDER + 1);
     }
 
     for (x = 0; x < state->w; x++)
        for (y = 0; y < state->h; y++) {
-           unsigned int c = 0;
+           unsigned long c = 0;
 
            if (HRANGE(state,x,y))
                 c |= index(state,hedge,x,y);
@@ -1656,42 +2822,137 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
            if (y+1 < state->h)
                c |= index(state,corners,x,y+1) << 12;
            if (x+1 < state->w && y+1 < state->h)
-               c |= index(state,corners,x+1,y+1) << 14;
-           if (index(state, correct, x, y) && !flashtime)
+               /* cast to prevent 2<<14 sign-extending on promotion to long */
+               c |= (unsigned long)index(state,corners,x+1,y+1) << 14;
+           if (index(state, state->correct, x, y) && !flashtime)
                c |= CORRECT;
+            if (ui->cur_visible && ui->cur_x == x && ui->cur_y == y)
+                c |= CURSOR;
 
            if (index(ds,ds->visible,x,y) != c) {
-               draw_tile(fe, state, x, y, hedge, vedge, corners, c & CORRECT);
+               draw_tile(dr, ds, state, x, y, hedge, vedge, corners,
+                          (c & (CORRECT|CURSOR)) );
                index(ds,ds->visible,x,y) = c;
            }
        }
 
+    {
+       char buf[256];
+
+       if (ui->dragged &&
+           ui->x1 >= 0 && ui->y1 >= 0 &&
+           ui->x2 >= 0 && ui->y2 >= 0) {
+           sprintf(buf, "%dx%d ",
+                   ui->x2-ui->x1,
+                   ui->y2-ui->y1);
+       } else {
+           buf[0] = '\0';
+       }
+
+        if (state->cheated)
+            strcat(buf, "Auto-solved.");
+        else if (state->completed)
+            strcat(buf, "COMPLETED!");
+
+        status_bar(dr, buf);
+    }
+
     if (hedge != state->hedge) {
         sfree(hedge);
         sfree(vedge);
-   }
+    }
 
     sfree(corners);
-    sfree(correct);
 }
 
-static float game_anim_length(game_state *oldstate,
-                             game_state *newstate, int dir)
+static float game_anim_length(const game_state *oldstate,
+                              const game_state *newstate, int dir, game_ui *ui)
 {
     return 0.0F;
 }
 
-static float game_flash_length(game_state *oldstate,
-                              game_state *newstate, int dir)
+static float game_flash_length(const game_state *oldstate,
+                               const game_state *newstate, int dir, game_ui *ui)
 {
-    if (!oldstate->completed && newstate->completed)
+    if (!oldstate->completed && newstate->completed &&
+       !oldstate->cheated && !newstate->cheated)
         return FLASH_TIME;
     return 0.0F;
 }
 
-static int game_wants_statusbar(void)
+static int game_status(const game_state *state)
+{
+    return state->completed ? +1 : 0;
+}
+
+static int game_timing_state(const game_state *state, game_ui *ui)
+{
+    return TRUE;
+}
+
+static void game_print_size(const game_params *params, float *x, float *y)
+{
+    int pw, ph;
+
+    /*
+     * I'll use 5mm squares by default.
+     */
+    game_compute_size(params, 500, &pw, &ph);
+    *x = pw / 100.0F;
+    *y = ph / 100.0F;
+}
+
+static void game_print(drawing *dr, const game_state *state, int tilesize)
 {
-    return FALSE;
+    int w = state->w, h = state->h;
+    int ink = print_mono_colour(dr, 0);
+    int x, y;
+
+    /* Ick: fake up `ds->tilesize' for macro expansion purposes */
+    game_drawstate ads, *ds = &ads;
+    game_set_size(dr, ds, NULL, tilesize);
+
+    /*
+     * Border.
+     */
+    print_line_width(dr, TILE_SIZE / 10);
+    draw_rect_outline(dr, COORD(0), COORD(0), w*TILE_SIZE, h*TILE_SIZE, ink);
+
+    /*
+     * Grid. We have to make the grid lines particularly thin,
+     * because users will be drawing lines _along_ them and we want
+     * those lines to be visible.
+     */
+    print_line_width(dr, TILE_SIZE / 256);
+    for (x = 1; x < w; x++)
+       draw_line(dr, COORD(x), COORD(0), COORD(x), COORD(h), ink);
+    for (y = 1; y < h; y++)
+       draw_line(dr, COORD(0), COORD(y), COORD(w), COORD(y), ink);
+
+    /*
+     * Solution.
+     */
+    print_line_width(dr, TILE_SIZE / 10);
+    for (y = 0; y <= h; y++)
+       for (x = 0; x <= w; x++) {
+           if (HRANGE(state,x,y) && hedge(state,x,y))
+               draw_line(dr, COORD(x), COORD(y), COORD(x+1), COORD(y), ink);
+           if (VRANGE(state,x,y) && vedge(state,x,y))
+               draw_line(dr, COORD(x), COORD(y), COORD(x), COORD(y+1), ink);
+       }
+
+    /*
+     * Clues.
+     */
+    for (y = 0; y < h; y++)
+       for (x = 0; x < w; x++)
+           if (grid(state,x,y)) {
+               char str[80];
+               sprintf(str, "%d", grid(state,x,y));
+               draw_text(dr, COORD(x)+TILE_SIZE/2, COORD(y)+TILE_SIZE/2,
+                         FONT_VARIABLE, TILE_SIZE/2,
+                         ALIGN_HCENTRE | ALIGN_VCENTRE, ink, str);
+           }
 }
 
 #ifdef COMBINED
@@ -1699,31 +2960,41 @@ static int game_wants_statusbar(void)
 #endif
 
 const struct game thegame = {
-    "Rectangles", "games.rectangles",
+    "Rectangles", "games.rectangles", "rect",
     default_params,
-    game_fetch_preset,
+    game_fetch_preset, NULL,
     decode_params,
     encode_params,
     free_params,
     dup_params,
     TRUE, game_configure, custom_params,
     validate_params,
-    new_game_seed,
-    game_free_aux_info,
-    validate_seed,
+    new_game_desc,
+    validate_desc,
     new_game,
     dup_game,
     free_game,
-    TRUE, game_text_format,
+    TRUE, solve_game,
+    TRUE, game_can_format_as_text_now, game_text_format,
     new_ui,
     free_ui,
-    make_move,
-    game_size,
+    encode_ui,
+    decode_ui,
+    game_changed_state,
+    interpret_move,
+    execute_move,
+    PREFERRED_TILE_SIZE, game_compute_size, game_set_size,
     game_colours,
     game_new_drawstate,
     game_free_drawstate,
     game_redraw,
     game_anim_length,
     game_flash_length,
-    game_wants_statusbar,
+    game_status,
+    TRUE, FALSE, game_print_size, game_print,
+    TRUE,                             /* wants_statusbar */
+    FALSE, game_timing_state,
+    0,                                /* flags */
 };
+
+/* vim: set shiftwidth=4 tabstop=8: */