check_valid() wasn't checking that Killer cages contain at most one
[sgt/puzzles] / solo.c
diff --git a/solo.c b/solo.c
index b36eb66..a298d15 100644 (file)
--- a/solo.c
+++ b/solo.c
@@ -146,19 +146,19 @@ enum {
 #define MAX_2SUMS 5
 #define MAX_3SUMS 8
 #define MAX_4SUMS 12
-unsigned int sum_bits2[18][MAX_2SUMS];
-unsigned int sum_bits3[25][MAX_3SUMS];
-unsigned int sum_bits4[31][MAX_4SUMS];
+unsigned long sum_bits2[18][MAX_2SUMS];
+unsigned long sum_bits3[25][MAX_3SUMS];
+unsigned long sum_bits4[31][MAX_4SUMS];
 
-static int find_sum_bits(unsigned int *array, int idx, int value_left,
+static int find_sum_bits(unsigned long *array, int idx, int value_left,
                         int addends_left, int min_addend,
-                        unsigned int bitmask_so_far)
+                        unsigned long bitmask_so_far)
 {
     int i;
     assert(addends_left >= 2);
 
     for (i = min_addend; i < value_left; i++) {
-       unsigned int new_bitmask = bitmask_so_far | (1 << i);
+       unsigned long new_bitmask = bitmask_so_far | (1L << i);
        assert(bitmask_so_far != new_bitmask);
 
        if (addends_left == 2) {
@@ -167,7 +167,7 @@ static int find_sum_bits(unsigned int *array, int idx, int value_left,
                break;
            if (j > 9)
                continue;
-           array[idx++] = new_bitmask | (1 << j);
+           array[idx++] = new_bitmask | (1L << j);
        } else
            idx = find_sum_bits(array, idx, value_left - i,
                                addends_left - 1, i + 1,
@@ -1448,7 +1448,7 @@ static int solver_killer_sums(struct solver_usage *usage, int b,
     int cr = usage->cr;
     int i, ret, max_sums;
     int nsquares = cages->nr_squares[b];
-    unsigned int *sumbits, possible_addends;
+    unsigned long *sumbits, possible_addends;
 
     if (clue == 0) {
        assert(nsquares == 0);
@@ -1514,18 +1514,18 @@ static int solver_killer_sums(struct solver_usage *usage, int b,
     possible_addends = 0;
     for (i = 0; i < max_sums; i++) {
        int j;
-       unsigned int bits = sumbits[i];
+       unsigned long bits = sumbits[i];
 
        if (bits == 0)
            break;
 
        for (j = 0; j < nsquares; j++) {
            int n;
-           unsigned int square_bits = bits;
+           unsigned long square_bits = bits;
            int x = cages->blocks[b][j];
            for (n = 1; n <= cr; n++)
                if (!cube2(x, n))
-                   square_bits &= ~(1 << n);
+                   square_bits &= ~(1L << n);
            if (square_bits == 0) {
                break;
            }
@@ -2933,8 +2933,8 @@ static int gridgen(int cr, struct block_structure *blocks,
 /*
  * Check whether a grid contains a valid complete puzzle.
  */
-static int check_valid(int cr, struct block_structure *blocks, int xtype,
-                      digit *grid)
+static int check_valid(int cr, struct block_structure *blocks,
+                      struct block_structure *kblocks, int xtype, digit *grid)
 {
     unsigned char *used;
     int x, y, i, j, n;
@@ -2988,6 +2988,25 @@ static int check_valid(int cr, struct block_structure *blocks, int xtype,
     }
 
     /*
+     * Check that each Killer cage, if any, contains at most one of
+     * everything.
+     */
+    if (kblocks) {
+       for (i = 0; i < kblocks->nr_blocks; i++) {
+           memset(used, FALSE, cr);
+           for (j = 0; j < kblocks->nr_squares[i]; j++)
+               if (grid[kblocks->blocks[i][j]] > 0 &&
+                   grid[kblocks->blocks[i][j]] <= cr) {
+                   if (used[grid[kblocks->blocks[i][j]]-1]) {
+                       sfree(used);
+                       return FALSE;
+                   }
+                   used[grid[kblocks->blocks[i][j]]-1] = TRUE;
+               }
+       }
+    }
+
+    /*
      * Check that each diagonal contains precisely one of everything.
      */
     if (xtype) {
@@ -3528,7 +3547,7 @@ static char *new_game_desc(game_params *params, random_state *rs,
 
         if (!gridgen(cr, blocks, kblocks, params->xtype, grid, rs, area*area))
            continue;
-        assert(check_valid(cr, blocks, params->xtype, grid));
+        assert(check_valid(cr, blocks, kblocks, params->xtype, grid));
 
        /*
         * Save the solved grid in aux.
@@ -4409,7 +4428,7 @@ struct game_drawstate {
     unsigned char *pencil;
     unsigned char *hl;
     /* This is scratch space used within a single call to game_redraw. */
-    int *entered_items;
+    int nregions, *entered_items;
 };
 
 static char *interpret_move(game_state *state, game_ui *ui, game_drawstate *ds,
@@ -4553,8 +4572,8 @@ static game_state *execute_move(game_state *from, char *move)
              * We've made a real change to the grid. Check to see
              * if the game has been completed.
              */
-            if (!ret->completed && check_valid(cr, ret->blocks, ret->xtype,
-                                              ret->grid)) {
+            if (!ret->completed && check_valid(cr, ret->blocks, ret->kblocks,
+                                              ret->xtype, ret->grid)) {
                 ret->completed = TRUE;
             }
         }
@@ -4643,7 +4662,15 @@ static game_drawstate *game_new_drawstate(drawing *dr, 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->entered_items needs one row of cr entries per entity in
+     * which digits may not be duplicated. That's one for each row,
+     * each column, each block, each diagonal, and each Killer cage.
+     */
+    ds->nregions = cr*3 + 2;
+    if (state->kblocks)
+       ds->nregions += state->kblocks->nr_blocks;
+    ds->entered_items = snewn(cr * ds->nregions, int);
     ds->tilesize = 0;                  /* not decided yet */
     return ds;
 }
@@ -4723,10 +4750,37 @@ static void draw_number(drawing *dr, game_drawstate *ds, game_state *state,
 
     if (state->kblocks) {
        int t = GRIDEXTRA * 3;
-       int kl = cx - 1, kt = cy - 1, kr = cx + cw, kb = cy + ch;
+       int kcx, kcy, kcw, kch;
+       int kl, kt, kr, kb;
        int has_left = 0, has_right = 0, has_top = 0, has_bottom = 0;
 
        /*
+        * In non-jigsaw mode, the Killer cages are placed at a
+        * fixed offset from the outer edge of the cell dividing
+        * lines, so that they look right whether those lines are
+        * thick or thin. In jigsaw mode, however, doing this will
+        * sometimes cause the cage outlines in adjacent squares to
+        * fail to match up with each other, so we must offset a
+        * fixed amount from the _centre_ of the cell dividing
+        * lines.
+        */
+       if (state->blocks->r == 1) {
+           kcx = tx;
+           kcy = ty;
+           kcw = tw;
+           kch = th;
+       } else {
+           kcx = cx;
+           kcy = cy;
+           kcw = cw;
+           kch = ch;
+       }
+       kl = kcx - 1;
+       kt = kcy - 1;
+       kr = kcx + kcw;
+       kb = kcy + kch;
+
+       /*
         * First, draw the lines dividing this area from neighbouring
         * different areas.
         */
@@ -4759,20 +4813,20 @@ static void draw_number(drawing *dr, game_drawstate *ds, game_state *state,
        if (x+1 < cr && y > 0 && !has_right && !has_top
            && state->kblocks->whichblock[y*cr+x] != state->kblocks->whichblock[(y-1)*cr+x+1])
        {
-           draw_line(dr, cx + cw - t, kt + t, cx + cw, kt + t, col_killer);
-           draw_line(dr, cx + cw - t, kt, cx + cw - t, kt + t, col_killer);
+           draw_line(dr, kcx + kcw - t, kt + t, kcx + kcw, kt + t, col_killer);
+           draw_line(dr, kcx + kcw - t, kt, kcx + kcw - t, kt + t, col_killer);
        }
        if (x > 0 && y+1 < cr && !has_left && !has_bottom
            && state->kblocks->whichblock[y*cr+x] != state->kblocks->whichblock[(y+1)*cr+x-1])
        {
-           draw_line(dr, kl, cy + ch - t, kl + t, cy + ch - t, col_killer);
-           draw_line(dr, kl + t, cy + ch - t, kl + t, cy + ch, col_killer);
+           draw_line(dr, kl, kcy + kch - t, kl + t, kcy + kch - t, col_killer);
+           draw_line(dr, kl + t, kcy + kch - t, kl + t, kcy + kch, col_killer);
        }
        if (x+1 < cr && y+1 < cr && !has_right && !has_bottom
            && state->kblocks->whichblock[y*cr+x] != state->kblocks->whichblock[(y+1)*cr+x+1])
        {
-           draw_line(dr, cx + cw - t, cy + ch - t, cx + cw - t, cy + ch, col_killer);
-           draw_line(dr, cx + cw - t, cy + ch - t, cx + cw, cy + ch - t, col_killer);
+           draw_line(dr, kcx + kcw - t, kcy + kch - t, kcx + kcw - t, kcy + kch, col_killer);
+           draw_line(dr, kcx + kcw - t, kcy + kch - t, kcx + kcw, kcy + kch - t, col_killer);
        }
 
     }
@@ -4943,21 +4997,36 @@ static void game_redraw(drawing *dr, 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++)
+    for (x = 0; x < cr * ds->nregions; 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 = state->blocks->whichblock[y*cr+x];
-               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;
+               int box, kbox;
+
+               /* Rows */
+               ds->entered_items[x*cr+d-1]++;
+
+               /* Columns */
+               ds->entered_items[(y+cr)*cr+d-1]++;
+
+               /* Blocks */
+               box = state->blocks->whichblock[y*cr+x];
+               ds->entered_items[(box+2*cr)*cr+d-1]++;
+
+               /* Diagonals */
                if (ds->xtype) {
                    if (ondiag0(y*cr+x))
-                       ds->entered_items[d-1] |= ((ds->entered_items[d-1] & 64) << 1) | 64;
+                       ds->entered_items[(3*cr)*cr+d-1]++;
                    if (ondiag1(y*cr+x))
-                       ds->entered_items[cr+d-1] |= ((ds->entered_items[cr+d-1] & 64) << 1) | 64;
+                       ds->entered_items[(3*cr+1)*cr+d-1]++;
+               }
+
+               /* Killer cages */
+               if (state->kblocks) {
+                   kbox = state->kblocks->whichblock[y*cr+x];
+                   ds->entered_items[(kbox+3*cr+2)*cr+d-1]++;
                }
            }
        }
@@ -4981,11 +5050,17 @@ static void game_redraw(drawing *dr, game_drawstate *ds, game_state *oldstate,
 
            /* 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[state->blocks->whichblock[y*cr+x]*cr+d-1] & 32) ||
-                     (ds->xtype && ((ondiag0(y*cr+x) && (ds->entered_items[d-1] & 128)) ||
-                                    (ondiag1(y*cr+x) && (ds->entered_items[cr+d-1] & 128))))))
+           if (d && (ds->entered_items[x*cr+d-1] > 1 ||
+                     ds->entered_items[(y+cr)*cr+d-1] > 1 ||
+                     ds->entered_items[(state->blocks->whichblock[y*cr+x]
+                                        +2*cr)*cr+d-1] > 1 ||
+                     (ds->xtype && ((ondiag0(y*cr+x) &&
+                                     ds->entered_items[(3*cr)*cr+d-1] > 1) ||
+                                    (ondiag1(y*cr+x) &&
+                                     ds->entered_items[(3*cr+1)*cr+d-1]>1)))||
+                     (state->kblocks &&
+                      ds->entered_items[(state->kblocks->whichblock[y*cr+x]
+                                         +3*cr+2)*cr+d-1] > 1)))
                highlight |= 16;
 
            if (d && state->kblocks) {