Tweaks and more complete documentation for Same Game.
[sgt/puzzles] / solo.c
diff --git a/solo.c b/solo.c
index c8fac6e..d564ad2 100644 (file)
--- a/solo.c
+++ b/solo.c
@@ -107,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
 
@@ -123,6 +124,7 @@ enum {
     COL_CLUE,
     COL_USER,
     COL_HIGHLIGHT,
+    COL_ERROR,
     COL_PENCIL,
     NCOLOURS
 };
@@ -1852,6 +1854,33 @@ static void free_ui(game_ui *ui)
     sfree(ui);
 }
 
+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)
 {
@@ -1955,23 +1984,23 @@ static game_state *make_move(game_state *from, game_ui *ui, game_drawstate *ds,
  * Drawing routines.
  */
 
-struct game_drawstate {
-    int started;
-    int c, r, cr;
-    digit *grid;
-    unsigned char *pencil;
-    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)
@@ -1996,6 +2025,10 @@ 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];
@@ -2019,7 +2052,8 @@ static game_drawstate *game_new_drawstate(game_state *state)
     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;
 }
 
@@ -2028,6 +2062,7 @@ static void game_free_drawstate(game_drawstate *ds)
     sfree(ds->hl);
     sfree(ds->pencil);
     sfree(ds->grid);
+    sfree(ds->entered_items);
     sfree(ds);
 }
 
@@ -2064,10 +2099,10 @@ static void draw_number(frontend *fe, game_drawstate *ds, game_state *state,
     clip(fe, cx, cy, cw, ch);
 
     /* background needs erasing */
-    draw_rect(fe, cx, cy, cw, ch, hl == 1 ? COL_HIGHLIGHT : COL_BACKGROUND);
+    draw_rect(fe, cx, cy, cw, ch, (hl & 15) == 1 ? COL_HIGHLIGHT : COL_BACKGROUND);
 
     /* pencil-mode highlight */
-    if (hl == 2) {
+    if ((hl & 15) == 2) {
         int coords[6];
         coords[0] = cx;
         coords[1] = cy;
@@ -2086,21 +2121,42 @@ 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 {
-        /* pencil marks required? */
-        int i, j;
+        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 % r, dy = j / r, crm = max(c, r);
+                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*r+2),
-                          ty + (4*dy+3) * TILE_SIZE / (4*c+2),
-                          FONT_VARIABLE, TILE_SIZE/(crm*5/4),
+                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++;
             }
@@ -2129,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.
@@ -2147,17 +2203,46 @@ 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++) {
             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);
        }
     }
@@ -2166,7 +2251,7 @@ 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;
     }
 }
@@ -2220,6 +2305,7 @@ const struct game thegame = {
     TRUE, game_text_format,
     new_ui,
     free_ui,
+    game_changed_state,
     make_move,
     game_size,
     game_colours,