Add Windows help topic
[sgt/puzzles] / solo.c
diff --git a/solo.c b/solo.c
index 20c288f..d564ad2 100644 (file)
--- a/solo.c
+++ b/solo.c
@@ -3,6 +3,30 @@
  *
  * TODO:
  *
+ *  - reports from users are that `Trivial'-mode puzzles are still
+ *    rather hard compared to newspapers' easy ones, so some better
+ *    low-end difficulty grading would be nice
+ *     + it's possible that really easy puzzles always have
+ *       _several_ things you can do, so don't make you hunt too
+ *       hard for the one deduction you can currently make
+ *     + it's also possible that easy puzzles require fewer
+ *       cross-eliminations: perhaps there's a higher incidence of
+ *       things you can deduce by looking only at (say) rows,
+ *       rather than things you have to check both rows and columns
+ *       for
+ *     + but really, what I need to do is find some really easy
+ *       puzzles and _play_ them, to see what's actually easy about
+ *       them
+ *     + while I'm revamping this area, filling in the _last_
+ *       number in a nearly-full row or column should certainly be
+ *       permitted even at the lowest difficulty level.
+ *     + also Owen noticed that `Basic' grids requiring numeric
+ *       elimination are actually very hard, so I wonder if a
+ *       difficulty gradation between that and positional-
+ *       elimination-only might be in order
+ *     + but it's not good to have _too_ many difficulty levels, or
+ *       it'll take too long to randomly generate a given level.
+ * 
  *  - it might still be nice to do some prioritisation on the
  *    removal of numbers from the grid
  *     + one possibility is to try to minimise the maximum number
  *      click, _or_ you highlight a square and then type. At most
  *      one thing is ever highlighted at a time, so there's no way
  *      to confuse the two.
- *     + `pencil marks' might be useful for more subtle forms of
- *       deduction, now we can create puzzles that require them.
+ *     + then again, I don't actually like sudoku.com's interface;
+ *       it's too much like a paint package whereas I prefer to
+ *       think of Solo as a text editor.
+ *     + another PDA-friendly possibility is a drag interface:
+ *       _drag_ numbers from the palette into the grid squares.
+ *       Thought experiments suggest I'd prefer that to the
+ *       sudoku.com approach, but I haven't actually tried it.
  */
 
 /*
@@ -67,8 +96,6 @@ int solver_show_working;
 
 #include "puzzles.h"
 
-#define max(x,y) ((x)>(y)?(x):(y))
-
 /*
  * To save space, I store digits internally as unsigned char. This
  * imposes a hard limit of 255 on the order of the puzzle. Since
@@ -80,8 +107,9 @@ int solver_show_working;
 typedef unsigned char digit;
 #define ORDER_MAX 255
 
-#define TILE_SIZE 32
-#define BORDER 18
+#define PREFERRED_TILE_SIZE 32
+#define TILE_SIZE (ds->tilesize)
+#define BORDER (TILE_SIZE / 2)
 
 #define FLASH_TIME 0.4F
 
@@ -96,6 +124,8 @@ enum {
     COL_CLUE,
     COL_USER,
     COL_HIGHLIGHT,
+    COL_ERROR,
+    COL_PENCIL,
     NCOLOURS
 };
 
@@ -106,6 +136,7 @@ struct game_params {
 struct game_state {
     int c, r;
     digit *grid;
+    unsigned char *pencil;             /* c*r*c*r elements */
     unsigned char *immutable;         /* marks which digits are clues */
     int completed, cheated;
 };
@@ -146,8 +177,10 @@ static int game_fetch_preset(int i, char **name, game_params **params)
         { "3x3 Intermediate", { 3, 3, SYMM_ROT2, DIFF_INTERSECT } },
         { "3x3 Advanced", { 3, 3, SYMM_ROT2, DIFF_SET } },
         { "3x3 Unreasonable", { 3, 3, SYMM_ROT2, DIFF_RECURSIVE } },
+#ifndef SLOW_SYSTEM
         { "3x4 Basic", { 3, 4, SYMM_ROT2, DIFF_SIMPLE } },
         { "4x4 Basic", { 4, 4, SYMM_ROT2, DIFF_SIMPLE } },
+#endif
     };
 
     if (i < 0 || i >= lenof(presets))
@@ -813,7 +846,12 @@ static int nsolve_intersect(struct nsolve_usage *usage,
     return ret;
 }
 
+struct nsolve_scratch {
+    unsigned char *grid, *rowidx, *colidx, *set;
+};
+
 static int nsolve_set(struct nsolve_usage *usage,
+                      struct nsolve_scratch *scratch,
                       int start, int step1, int step2
 #ifdef STANDALONE_SOLVER
                       , char *fmt, ...
@@ -822,10 +860,10 @@ static int nsolve_set(struct nsolve_usage *usage,
 {
     int c = usage->c, r = usage->r, cr = c*r;
     int i, j, n, count;
-    unsigned char *grid = snewn(cr*cr, unsigned char);
-    unsigned char *rowidx = snewn(cr, unsigned char);
-    unsigned char *colidx = snewn(cr, unsigned char);
-    unsigned char *set = snewn(cr, unsigned char);
+    unsigned char *grid = scratch->grid;
+    unsigned char *rowidx = scratch->rowidx;
+    unsigned char *colidx = scratch->colidx;
+    unsigned char *set = scratch->set;
 
     /*
      * We are passed a cr-by-cr matrix of booleans. Our first job
@@ -968,10 +1006,6 @@ static int nsolve_set(struct nsolve_usage *usage,
                 }
 
                 if (progress) {
-                    sfree(set);
-                    sfree(colidx);
-                    sfree(rowidx);
-                    sfree(grid);
                     return TRUE;
                 }
             }
@@ -990,17 +1024,33 @@ static int nsolve_set(struct nsolve_usage *usage,
             break;                     /* done */
     }
 
-    sfree(set);
-    sfree(colidx);
-    sfree(rowidx);
-    sfree(grid);
-
     return FALSE;
 }
 
+static struct nsolve_scratch *nsolve_new_scratch(struct nsolve_usage *usage)
+{
+    struct nsolve_scratch *scratch = snew(struct nsolve_scratch);
+    int cr = usage->cr;
+    scratch->grid = snewn(cr*cr, unsigned char);
+    scratch->rowidx = snewn(cr, unsigned char);
+    scratch->colidx = snewn(cr, unsigned char);
+    scratch->set = snewn(cr, unsigned char);
+    return scratch;
+}
+
+static void nsolve_free_scratch(struct nsolve_scratch *scratch)
+{
+    sfree(scratch->set);
+    sfree(scratch->colidx);
+    sfree(scratch->rowidx);
+    sfree(scratch->grid);
+    sfree(scratch);
+}
+
 static int nsolve(int c, int r, digit *grid)
 {
     struct nsolve_usage *usage;
+    struct nsolve_scratch *scratch;
     int cr = c*r;
     int x, y, n;
     int diff = DIFF_BLOCK;
@@ -1024,6 +1074,8 @@ static int nsolve(int c, int r, digit *grid)
     memset(usage->col, FALSE, cr * cr);
     memset(usage->blk, FALSE, cr * cr);
 
+    scratch = nsolve_new_scratch(usage);
+
     /*
      * Place all the clue numbers we are given.
      */
@@ -1173,7 +1225,7 @@ static int nsolve(int c, int r, digit *grid)
         */
        for (x = 0; x < cr; x += r)
            for (y = 0; y < r; y++)
-                if (nsolve_set(usage, cubepos(x,y,1), r*cr, 1
+                if (nsolve_set(usage, scratch, cubepos(x,y,1), r*cr, 1
 #ifdef STANDALONE_SOLVER
                                , "set elimination, block (%d,%d)", 1+x/r, 1+y
 #endif
@@ -1186,7 +1238,7 @@ static int nsolve(int c, int r, digit *grid)
         * Row-wise set elimination.
         */
        for (y = 0; y < cr; y++)
-            if (nsolve_set(usage, cubepos(0,y,1), cr*cr, 1
+            if (nsolve_set(usage, scratch, cubepos(0,y,1), cr*cr, 1
 #ifdef STANDALONE_SOLVER
                            , "set elimination, row %d", 1+YUNTRANS(y)
 #endif
@@ -1199,7 +1251,7 @@ static int nsolve(int c, int r, digit *grid)
         * Column-wise set elimination.
         */
        for (x = 0; x < cr; x++)
-            if (nsolve_set(usage, cubepos(x,0,1), cr, 1
+            if (nsolve_set(usage, scratch, cubepos(x,0,1), cr, 1
 #ifdef STANDALONE_SOLVER
                            , "set elimination, column %d", 1+x
 #endif
@@ -1215,6 +1267,8 @@ static int nsolve(int c, int r, digit *grid)
        break;
     }
 
+    nsolve_free_scratch(scratch);
+
     sfree(usage->cube);
     sfree(usage->row);
     sfree(usage->col);
@@ -1366,7 +1420,7 @@ struct game_aux_info {
 };
 
 static char *new_game_desc(game_params *params, random_state *rs,
-                          game_aux_info **aux)
+                          game_aux_info **aux, int interactive)
 {
     int c = params->c, r = params->r, cr = c*r;
     int area = cr*cr;
@@ -1417,6 +1471,16 @@ static char *new_game_desc(game_params *params, random_state *rs,
            ai->r = r;
            ai->grid = snewn(cr * cr, digit);
            memcpy(ai->grid, grid, cr * cr * sizeof(digit));
+           /*
+            * We might already have written *aux the last time we
+            * went round this loop, in which case we should free
+            * the old aux_info before overwriting it with the new
+            * one.
+            */
+            if (*aux) {
+               sfree((*aux)->grid);
+               sfree(*aux);
+            }
            *aux = ai;
        }
 
@@ -1589,7 +1653,7 @@ static char *validate_desc(game_params *params, char *desc)
     return NULL;
 }
 
-static game_state *new_game(game_params *params, char *desc)
+static game_state *new_game(midend_data *me, game_params *params, char *desc)
 {
     game_state *state = snew(game_state);
     int c = params->c, r = params->r, cr = c*r, area = cr * cr;
@@ -1599,6 +1663,8 @@ static game_state *new_game(game_params *params, char *desc)
     state->r = params->r;
 
     state->grid = snewn(area, digit);
+    state->pencil = snewn(area * cr, unsigned char);
+    memset(state->pencil, 0, area * cr);
     state->immutable = snewn(area, unsigned char);
     memset(state->immutable, FALSE, area);
 
@@ -1640,6 +1706,9 @@ static game_state *dup_game(game_state *state)
     ret->grid = snewn(area, digit);
     memcpy(ret->grid, state->grid, area);
 
+    ret->pencil = snewn(area * cr, unsigned char);
+    memcpy(ret->pencil, state->pencil, area * cr);
+
     ret->immutable = snewn(area, unsigned char);
     memcpy(ret->immutable, state->immutable, area);
 
@@ -1652,6 +1721,7 @@ static game_state *dup_game(game_state *state)
 static void free_game(game_state *state)
 {
     sfree(state->immutable);
+    sfree(state->pencil);
     sfree(state->grid);
     sfree(state);
 }
@@ -1762,6 +1832,11 @@ struct game_ui {
      * enter that number or letter in the grid.
      */
     int hx, hy;
+    /*
+     * This indicates whether the current highlight is a
+     * pencil-mark one or a real one.
+     */
+    int hpencil;
 };
 
 static game_ui *new_ui(game_state *state)
@@ -1769,6 +1844,7 @@ static game_ui *new_ui(game_state *state)
     game_ui *ui = snew(game_ui);
 
     ui->hx = ui->hy = -1;
+    ui->hpencil = 0;
 
     return ui;
 }
@@ -1778,26 +1854,75 @@ static void free_ui(game_ui *ui)
     sfree(ui);
 }
 
-static game_state *make_move(game_state *from, game_ui *ui, int x, int y,
-                            int button)
+static void game_changed_state(game_ui *ui, game_state *oldstate,
+                               game_state *newstate)
+{
+    int c = newstate->c, r = newstate->r, cr = c*r;
+    /*
+     * We prevent pencil-mode highlighting of a filled square. So
+     * if the user has just filled in a square which we had a
+     * pencil-mode highlight in (by Undo, or by Redo, or by Solve),
+     * then we cancel the highlight.
+     */
+    if (ui->hx >= 0 && ui->hy >= 0 && ui->hpencil &&
+        newstate->grid[ui->hy * cr + ui->hx] != 0) {
+        ui->hx = ui->hy = -1;
+    }
+}
+
+struct game_drawstate {
+    int started;
+    int c, r, cr;
+    int tilesize;
+    digit *grid;
+    unsigned char *pencil;
+    unsigned char *hl;
+    /* This is scratch space used within a single call to game_redraw. */
+    int *entered_items;
+};
+
+static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds,
+                             int x, int y, int button)
 {
     int c = from->c, r = from->r, cr = c*r;
     int tx, ty;
     game_state *ret;
 
-    button &= ~MOD_NUM_KEYPAD;        /* we treat this the same as normal */
+    button &= ~MOD_MASK;
 
     tx = (x + TILE_SIZE - BORDER) / TILE_SIZE - 1;
     ty = (y + TILE_SIZE - BORDER) / TILE_SIZE - 1;
 
-    if (tx >= 0 && tx < cr && ty >= 0 && ty < cr && button == LEFT_BUTTON) {
-       if (tx == ui->hx && ty == ui->hy) {
-           ui->hx = ui->hy = -1;
-       } else {
-           ui->hx = tx;
-           ui->hy = ty;
-       }
-       return from;                   /* UI activity occurred */
+    if (tx >= 0 && tx < cr && ty >= 0 && ty < cr) {
+        if (button == LEFT_BUTTON) {
+            if (from->immutable[ty*cr+tx]) {
+                ui->hx = ui->hy = -1;
+            } else if (tx == ui->hx && ty == ui->hy && ui->hpencil == 0) {
+                ui->hx = ui->hy = -1;
+            } else {
+                ui->hx = tx;
+                ui->hy = ty;
+                ui->hpencil = 0;
+            }
+            return from;                      /* UI activity occurred */
+        }
+        if (button == RIGHT_BUTTON) {
+            /*
+             * Pencil-mode highlighting for non filled squares.
+             */
+            if (from->grid[ty*cr+tx] == 0) {
+                if (tx == ui->hx && ty == ui->hy && ui->hpencil) {
+                    ui->hx = ui->hy = -1;
+                } else {
+                    ui->hpencil = 1;
+                    ui->hx = tx;
+                    ui->hy = ty;
+                }
+            } else {
+                ui->hx = ui->hy = -1;
+            }
+            return from;                      /* UI activity occurred */
+        }
     }
 
     if (ui->hx != -1 && ui->hy != -1 &&
@@ -1813,20 +1938,41 @@ static game_state *make_move(game_state *from, game_ui *ui, int x, int y,
        if (button == ' ')
            n = 0;
 
+        /*
+         * Can't overwrite this square. In principle this shouldn't
+         * happen anyway because we should never have even been
+         * able to highlight the square, but it never hurts to be
+         * careful.
+         */
        if (from->immutable[ui->hy*cr+ui->hx])
-           return NULL;               /* can't overwrite this square */
+           return NULL;
+
+        /*
+         * Can't make pencil marks in a filled square. In principle
+         * this shouldn't happen anyway because we should never
+         * have even been able to pencil-highlight the square, but
+         * it never hurts to be careful.
+         */
+        if (ui->hpencil && from->grid[ui->hy*cr+ui->hx])
+            return NULL;
 
        ret = dup_game(from);
-       ret->grid[ui->hy*cr+ui->hx] = n;
-       ui->hx = ui->hy = -1;
+        if (ui->hpencil && n > 0) {
+            int index = (ui->hy*cr+ui->hx) * cr + (n-1);
+            ret->pencil[index] = !ret->pencil[index];
+        } else {
+            ret->grid[ui->hy*cr+ui->hx] = n;
+            memset(ret->pencil + (ui->hy*cr+ui->hx)*cr, 0, cr);
 
-       /*
-        * We've made a real change to the grid. Check to see
-        * if the game has been completed.
-        */
-       if (!ret->completed && check_valid(c, r, ret->grid)) {
-           ret->completed = TRUE;
-       }
+            /*
+             * We've made a real change to the grid. Check to see
+             * if the game has been completed.
+             */
+            if (!ret->completed && check_valid(c, r, ret->grid)) {
+                ret->completed = TRUE;
+            }
+        }
+       ui->hx = ui->hy = -1;
 
        return ret;                    /* made a valid move */
     }
@@ -1838,22 +1984,23 @@ static game_state *make_move(game_state *from, game_ui *ui, int x, int y,
  * Drawing routines.
  */
 
-struct game_drawstate {
-    int started;
-    int c, r, cr;
-    digit *grid;
-    unsigned char *hl;
-};
+#define SIZE(cr) ((cr) * TILE_SIZE + 2*BORDER + 1)
+#define GETTILESIZE(cr, w) ( (w-1) / (cr+1) )
 
-#define XSIZE(cr) ((cr) * TILE_SIZE + 2*BORDER + 1)
-#define YSIZE(cr) ((cr) * TILE_SIZE + 2*BORDER + 1)
-
-static void game_size(game_params *params, int *x, int *y)
+static void game_size(game_params *params, game_drawstate *ds,
+                      int *x, int *y, int expand)
 {
     int c = params->c, r = params->r, cr = c*r;
+    int ts;
+
+    ts = min(GETTILESIZE(cr, *x), GETTILESIZE(cr, *y));
+    if (expand)
+        ds->tilesize = ts;
+    else
+        ds->tilesize = min(ts, PREFERRED_TILE_SIZE);
 
-    *x = XSIZE(cr);
-    *y = YSIZE(cr);
+    *x = SIZE(cr);
+    *y = SIZE(cr);
 }
 
 static float *game_colours(frontend *fe, game_state *state, int *ncolours)
@@ -1878,6 +2025,14 @@ static float *game_colours(frontend *fe, game_state *state, int *ncolours)
     ret[COL_HIGHLIGHT * 3 + 1] = 0.85F * ret[COL_BACKGROUND * 3 + 1];
     ret[COL_HIGHLIGHT * 3 + 2] = 0.85F * ret[COL_BACKGROUND * 3 + 2];
 
+    ret[COL_ERROR * 3 + 0] = 1.0F;
+    ret[COL_ERROR * 3 + 1] = 0.0F;
+    ret[COL_ERROR * 3 + 2] = 0.0F;
+
+    ret[COL_PENCIL * 3 + 0] = 0.5F * ret[COL_BACKGROUND * 3 + 0];
+    ret[COL_PENCIL * 3 + 1] = 0.5F * ret[COL_BACKGROUND * 3 + 1];
+    ret[COL_PENCIL * 3 + 2] = ret[COL_BACKGROUND * 3 + 2];
+
     *ncolours = NCOLOURS;
     return ret;
 }
@@ -1893,16 +2048,21 @@ static game_drawstate *game_new_drawstate(game_state *state)
     ds->cr = cr;
     ds->grid = snewn(cr*cr, digit);
     memset(ds->grid, 0, cr*cr);
+    ds->pencil = snewn(cr*cr*cr, digit);
+    memset(ds->pencil, 0, cr*cr*cr);
     ds->hl = snewn(cr*cr, unsigned char);
     memset(ds->hl, 0, cr*cr);
-
+    ds->entered_items = snewn(cr*cr, int);
+    ds->tilesize = 0;                  /* not decided yet */
     return ds;
 }
 
 static void game_free_drawstate(game_drawstate *ds)
 {
     sfree(ds->hl);
+    sfree(ds->pencil);
     sfree(ds->grid);
+    sfree(ds->entered_items);
     sfree(ds);
 }
 
@@ -1914,7 +2074,9 @@ static void draw_number(frontend *fe, game_drawstate *ds, game_state *state,
     int cx, cy, cw, ch;
     char str[2];
 
-    if (ds->grid[y*cr+x] == state->grid[y*cr+x] && ds->hl[y*cr+x] == hl)
+    if (ds->grid[y*cr+x] == state->grid[y*cr+x] &&
+        ds->hl[y*cr+x] == hl &&
+        !memcmp(ds->pencil+(y*cr+x)*cr, state->pencil+(y*cr+x)*cr, cr))
        return;                        /* no change required */
 
     tx = BORDER + x * TILE_SIZE + 2;
@@ -1936,9 +2098,20 @@ static void draw_number(frontend *fe, game_drawstate *ds, game_state *state,
 
     clip(fe, cx, cy, cw, ch);
 
-    /* background needs erasing? */
-    if (ds->grid[y*cr+x] || ds->hl[y*cr+x] != hl)
-       draw_rect(fe, cx, cy, cw, ch, hl ? COL_HIGHLIGHT : COL_BACKGROUND);
+    /* background needs erasing */
+    draw_rect(fe, cx, cy, cw, ch, (hl & 15) == 1 ? COL_HIGHLIGHT : COL_BACKGROUND);
+
+    /* pencil-mode highlight */
+    if ((hl & 15) == 2) {
+        int coords[6];
+        coords[0] = cx;
+        coords[1] = cy;
+        coords[2] = cx+cw/2;
+        coords[3] = cy;
+        coords[4] = cx;
+        coords[5] = cy+ch/2;
+        draw_polygon(fe, coords, 3, TRUE, COL_HIGHLIGHT);
+    }
 
     /* new number needs drawing? */
     if (state->grid[y*cr+x]) {
@@ -1948,7 +2121,45 @@ static void draw_number(frontend *fe, game_drawstate *ds, game_state *state,
            str[0] += 'a' - ('9'+1);
        draw_text(fe, tx + TILE_SIZE/2, ty + TILE_SIZE/2,
                  FONT_VARIABLE, TILE_SIZE/2, ALIGN_VCENTRE | ALIGN_HCENTRE,
-                 state->immutable[y*cr+x] ? COL_CLUE : COL_USER, str);
+                 state->immutable[y*cr+x] ? COL_CLUE : (hl & 16) ? COL_ERROR : COL_USER, str);
+    } else {
+        int i, j, npencil;
+       int pw, ph, pmax, fontsize;
+
+        /* count the pencil marks required */
+        for (i = npencil = 0; i < cr; i++)
+            if (state->pencil[(y*cr+x)*cr+i])
+               npencil++;
+
+       /*
+        * It's not sensible to arrange pencil marks in the same
+        * layout as the squares within a block, because this leads
+        * to the font being too small. Instead, we arrange pencil
+        * marks in the nearest thing we can to a square layout,
+        * and we adjust the square layout depending on the number
+        * of pencil marks in the square.
+        */
+       for (pw = 1; pw * pw < npencil; pw++);
+       if (pw < 3) pw = 3;            /* otherwise it just looks _silly_ */
+       ph = (npencil + pw - 1) / pw;
+       if (ph < 2) ph = 2;            /* likewise */
+       pmax = max(pw, ph);
+       fontsize = TILE_SIZE/(pmax*(11-pmax)/8);
+
+        for (i = j = 0; i < cr; i++)
+            if (state->pencil[(y*cr+x)*cr+i]) {
+                int dx = j % pw, dy = j / pw;
+
+                str[1] = '\0';
+                str[0] = i + '1';
+                if (str[0] > '9')
+                    str[0] += 'a' - ('9'+1);
+                draw_text(fe, tx + (4*dx+3) * TILE_SIZE / (4*pw+2),
+                          ty + (4*dy+3) * TILE_SIZE / (4*ph+2),
+                          FONT_VARIABLE, fontsize,
+                          ALIGN_VCENTRE | ALIGN_HCENTRE, COL_PENCIL, str);
+                j++;
+            }
     }
 
     unclip(fe);
@@ -1956,6 +2167,7 @@ static void draw_number(frontend *fe, game_drawstate *ds, game_state *state,
     draw_update(fe, cx, cy, cw, ch);
 
     ds->grid[y*cr+x] = state->grid[y*cr+x];
+    memcpy(ds->pencil+(y*cr+x)*cr, state->pencil+(y*cr+x)*cr, cr);
     ds->hl[y*cr+x] = hl;
 }
 
@@ -1973,7 +2185,7 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
         * all games should start by drawing a big
         * background-colour rectangle covering the whole window.
         */
-       draw_rect(fe, 0, 0, XSIZE(cr), YSIZE(cr), COL_BACKGROUND);
+       draw_rect(fe, 0, 0, SIZE(cr), SIZE(cr), COL_BACKGROUND);
 
        /*
         * Draw the grid.
@@ -1991,15 +2203,47 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
     }
 
     /*
+     * This array is used to keep track of rows, columns and boxes
+     * which contain a number more than once.
+     */
+    for (x = 0; x < cr * cr; x++)
+       ds->entered_items[x] = 0;
+    for (x = 0; x < cr; x++)
+       for (y = 0; y < cr; y++) {
+           digit d = state->grid[y*cr+x];
+           if (d) {
+               int box = (x/r)+(y/c)*c;
+               ds->entered_items[x*cr+d-1] |= ((ds->entered_items[x*cr+d-1] & 1) << 1) | 1;
+               ds->entered_items[y*cr+d-1] |= ((ds->entered_items[y*cr+d-1] & 4) << 1) | 4;
+               ds->entered_items[box*cr+d-1] |= ((ds->entered_items[box*cr+d-1] & 16) << 1) | 16;
+           }
+       }
+
+    /*
      * Draw any numbers which need redrawing.
      */
     for (x = 0; x < cr; x++) {
        for (y = 0; y < cr; y++) {
-           draw_number(fe, ds, state, x, y,
-                       (x == ui->hx && y == ui->hy) ||
-                       (flashtime > 0 &&
-                        (flashtime <= FLASH_TIME/3 ||
-                         flashtime >= FLASH_TIME*2/3)));
+            int highlight = 0;
+            digit d = state->grid[y*cr+x];
+
+            if (flashtime > 0 &&
+                (flashtime <= FLASH_TIME/3 ||
+                 flashtime >= FLASH_TIME*2/3))
+                highlight = 1;
+
+            /* Highlight active input areas. */
+            if (x == ui->hx && y == ui->hy)
+                highlight = ui->hpencil ? 2 : 1;
+
+           /* Mark obvious errors (ie, numbers which occur more than once
+            * in a single row, column, or box). */
+           if (d && ((ds->entered_items[x*cr+d-1] & 2) ||
+                     (ds->entered_items[y*cr+d-1] & 8) ||
+                     (ds->entered_items[((x/r)+(y/c)*c)*cr+d-1] & 32)))
+               highlight |= 16;
+
+           draw_number(fe, ds, state, x, y, highlight);
        }
     }
 
@@ -2007,19 +2251,19 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
      * Update the _entire_ grid if necessary.
      */
     if (!ds->started) {
-       draw_update(fe, 0, 0, XSIZE(cr), YSIZE(cr));
+       draw_update(fe, 0, 0, SIZE(cr), SIZE(cr));
        ds->started = TRUE;
     }
 }
 
 static float game_anim_length(game_state *oldstate, game_state *newstate,
-                             int dir)
+                             int dir, game_ui *ui)
 {
     return 0.0F;
 }
 
 static float game_flash_length(game_state *oldstate, game_state *newstate,
-                              int dir)
+                              int dir, game_ui *ui)
 {
     if (!oldstate->completed && newstate->completed &&
        !oldstate->cheated && !newstate->cheated)
@@ -2032,6 +2276,11 @@ static int game_wants_statusbar(void)
     return FALSE;
 }
 
+static int game_timing_state(game_state *state)
+{
+    return TRUE;
+}
+
 #ifdef COMBINED
 #define thegame solo
 #endif
@@ -2056,6 +2305,7 @@ const struct game thegame = {
     TRUE, game_text_format,
     new_ui,
     free_ui,
+    game_changed_state,
     make_move,
     game_size,
     game_colours,
@@ -2065,6 +2315,8 @@ const struct game thegame = {
     game_anim_length,
     game_flash_length,
     game_wants_statusbar,
+    FALSE, game_timing_state,
+    0,                                /* mouse_priorities */
 };
 
 #ifdef STANDALONE_SOLVER
@@ -2145,13 +2397,14 @@ int main(int argc, char **argv)
     }
     *desc++ = '\0';
 
-    p = decode_params(id);
+    p = default_params();
+    decode_params(p, id);
     err = validate_desc(p, desc);
     if (err) {
         fprintf(stderr, "%s: %s\n", argv[0], err);
         return 1;
     }
-    s = new_game(p, desc);
+    s = new_game(NULL, p, desc);
 
     if (recurse) {
         int ret = rsolve(p->c, p->r, s->grid, NULL, 2);