chiark / gitweb /
Tents: mark squares as non-tents with {Shift,Control}-cursor keys.
[sgt-puzzles.git] / singles.c
index ef7730dbe539f712d337c7ca942310d89f5dc3b6..a9b1d9d3b3005de5c5d487a04d980a8f3ec3817b 100644 (file)
--- a/singles.c
+++ b/singles.c
@@ -170,7 +170,7 @@ 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 */
@@ -200,7 +200,7 @@ static void decode_params(game_params *ret, char const *string)
     }
 }
 
-static char *encode_params(game_params *params, int full)
+static char *encode_params(const game_params *params, int full)
 {
     char data[256];
 
@@ -212,7 +212,7 @@ static char *encode_params(game_params *params, int full)
     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];
@@ -244,7 +244,7 @@ static config_item *game_configure(game_params *params)
     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);
 
@@ -255,7 +255,7 @@ static game_params *custom_params(config_item *cfg)
     return ret;
 }
 
-static char *validate_params(game_params *params, int full)
+static char *validate_params(const game_params *params, int full)
 {
     if (params->w < 2 || params->h < 2)
        return "Width and neight must be at least two";
@@ -292,7 +292,7 @@ static game_state *blank_game(int w, int h)
     return state;
 }
 
-static game_state *dup_game(game_state *state)
+static game_state *dup_game(const game_state *state)
 {
     game_state *ret = blank_game(state->w, state->h);
 
@@ -324,7 +324,7 @@ static char n2c(int num) {
 }
 
 static int c2n(char c) {
-    if (isdigit(c))
+    if (isdigit((unsigned char)c))
         return (int)(c - '0');
     else if (c >= 'a' && c <= 'z')
         return (int)(c - 'a' + 10);
@@ -333,7 +333,7 @@ static int c2n(char c) {
     return -1;
 }
 
-static void unpick_desc(game_params *params, char *desc,
+static void unpick_desc(const game_params *params, const char *desc,
                         game_state **sout, char **mout)
 {
     game_state *state = blank_game(params->w, params->h);
@@ -378,12 +378,12 @@ static char *generate_desc(game_state *state, int issolve)
 
 /* --- Useful game functions (completion, etc.) --- */
 
-static int game_can_format_as_text_now(game_params *params)
+static int game_can_format_as_text_now(const game_params *params)
 {
     return TRUE;
 }
 
-static char *game_text_format(game_state *state)
+static char *game_text_format(const game_state *state)
 {
     int len, x, y, i;
     char *ret, *p;
@@ -450,7 +450,10 @@ static void connect_dsf(game_state *state, int *dsf)
     }
 }
 
-static int check_rowcol(game_state *state, int starti, int di, int sz, int mark_errors)
+#define CC_MARK_ERRORS  1
+#define CC_MUST_FILL    2
+
+static int check_rowcol(game_state *state, int starti, int di, int sz, unsigned flags)
 {
     int nerr = 0, n, m, i, j;
 
@@ -466,7 +469,7 @@ static int check_rowcol(game_state *state, int starti, int di, int sz, int mark_
             if (state->nums[i] != state->nums[j]) continue;
 
             nerr++; /* ok, we have two numbers the same in a row. */
-            if (!mark_errors) continue;
+            if (!(flags & CC_MARK_ERRORS)) continue;
 
             /* If we have two circles in the same row around
              * two identical numbers, they are _both_ wrong. */
@@ -491,17 +494,27 @@ static int check_rowcol(game_state *state, int starti, int di, int sz, int mark_
     return nerr;
 }
 
-static int check_complete(game_state *state, int mark_errors)
+static int check_complete(game_state *state, unsigned flags)
 {
     int *dsf = snewn(state->n, int);
     int x, y, i, error = 0, nwhite, w = state->w, h = state->h;
 
-    if (mark_errors) {
+    if (flags & CC_MARK_ERRORS) {
         for (i = 0; i < state->n; i++)
             state->flags[i] &= ~F_ERROR;
     }
     connect_dsf(state, dsf);
 
+    /* If we're the solver we need the grid all to be definitively
+     * black or definitively white (i.e. circled) otherwise the solver
+     * has found an ambiguous grid. */
+    if (flags & CC_MUST_FILL) {
+        for (i = 0; i < state->n; i++) {
+            if (!(state->flags[i] & F_BLACK) && !(state->flags[i] & F_CIRCLE))
+                error += 1;
+        }
+    }
+
     /* Mark any black squares in groups of >1 as errors.
      * Count number of white squares. */
     nwhite = 0;
@@ -509,7 +522,7 @@ static int check_complete(game_state *state, int mark_errors)
         if (state->flags[i] & F_BLACK) {
             if (dsf_size(dsf, i) > 1) {
                 error += 1;
-                if (mark_errors)
+                if (flags & CC_MARK_ERRORS)
                     state->flags[i] |= F_ERROR;
             }
         } else
@@ -518,20 +531,32 @@ static int check_complete(game_state *state, int mark_errors)
 
     /* Check attributes of white squares, row- and column-wise. */
     for (x = 0; x < w; x++) /* check cols from (x,0) */
-        error += check_rowcol(state, x,   w, h, mark_errors);
+        error += check_rowcol(state, x,   w, h, flags);
     for (y = 0; y < h; y++) /* check rows from (0,y) */
-        error += check_rowcol(state, y*w, 1, w, mark_errors);
+        error += check_rowcol(state, y*w, 1, w, flags);
 
-    /* mark (all) white regions as an error if there is more than one.
-     * may want to make this less in-your-face (by only marking
-     * the smallest region as an error, for example -- but what if we
-     * have two regions of identical size?) */
-    for (i = 0; i < state->n; i++) {
-        if (!(state->flags[i] & F_BLACK) &&
-            dsf_size(dsf, i) < nwhite) {
-            error += 1;
-            if (mark_errors)
-                state->flags[i] |= F_ERROR;
+    /* If there's more than one white region, pick the largest one to
+     * be the canonical one (arbitrarily tie-breaking towards lower
+     * array indices), and mark all the others as erroneous. */
+    {
+        int largest = 0, canonical = -1;
+        for (i = 0; i < state->n; i++)
+            if (!(state->flags[i] & F_BLACK)) {
+                int size = dsf_size(dsf, i);
+                if (largest < size) {
+                    largest = size;
+                    canonical = i;
+                }
+            }
+
+        if (largest < nwhite) {
+            for (i = 0; i < state->n; i++)
+                if (!(state->flags[i] & F_BLACK) &&
+                    dsf_canonify(dsf, i) != canonical) {
+                    error += 1;
+                    if (flags & CC_MARK_ERRORS)
+                        state->flags[i] |= F_ERROR;
+                }
         }
     }
 
@@ -539,7 +564,8 @@ static int check_complete(game_state *state, int mark_errors)
     return (error > 0) ? 0 : 1;
 }
 
-static char *game_state_diff(game_state *src, game_state *dst, int issolve)
+static char *game_state_diff(const game_state *src, const game_state *dst,
+                             int issolve)
 {
     char *ret = NULL, buf[80], c;
     int retlen = 0, x, y, i, k;
@@ -617,7 +643,7 @@ static void solver_op_add(struct solver_state *ss, int x, int y, int op, const c
     }
     sop = &(ss->ops[ss->n_ops++]);
     sop->x = x; sop->y = y; sop->op = op; sop->desc = desc;
-    debug(("added solver op %s ('%s') at (%d,%d)",
+    debug(("added solver op %s ('%s') at (%d,%d)\n",
            op == BLACK ? "BLACK" : "CIRCLE", desc, x, y));
 }
 
@@ -628,7 +654,7 @@ static void solver_op_circle(game_state *state, struct solver_state *ss,
 
     if (!INGRID(state, x, y)) return;
     if (state->flags[i] & F_BLACK) {
-        debug(("... solver wants to add auto-circle on black (%d,%d)", x, y));
+        debug(("... solver wants to add auto-circle on black (%d,%d)\n", x, y));
         state->impossible = 1;
         return;
     }
@@ -646,7 +672,7 @@ static void solver_op_blacken(game_state *state, struct solver_state *ss,
     if (!INGRID(state, x, y)) return;
     if (state->nums[i] != num) return;
     if (state->flags[i] & F_CIRCLE) {
-        debug(("... solver wants to add auto-black on circled(%d,%d)", x, y));
+        debug(("... solver wants to add auto-black on circled(%d,%d)\n", x, y));
         state->impossible = 1;
         return;
     }
@@ -670,12 +696,12 @@ static int solver_ops_do(game_state *state, struct solver_state *ss)
 
         if (op.op == BLACK) {
             if (state->flags[i] & F_CIRCLE) {
-                debug(("Solver wants to blacken circled square (%d,%d)!", op.x, op.y));
+                debug(("Solver wants to blacken circled square (%d,%d)!\n", op.x, op.y));
                 state->impossible = 1;
                 return n_ops;
             }
             if (!(state->flags[i] & F_BLACK)) {
-                debug(("... solver adding black at (%d,%d): %s", op.x, op.y, op.desc));
+                debug(("... solver adding black at (%d,%d): %s\n", op.x, op.y, op.desc));
 #ifdef STANDALONE_SOLVER
                 if (verbose)
                     printf("Adding black at (%d,%d): %s\n", op.x, op.y, op.desc);
@@ -690,12 +716,12 @@ static int solver_ops_do(game_state *state, struct solver_state *ss)
                 }
         } else {
             if (state->flags[i] & F_BLACK) {
-                debug(("Solver wants to circle blackened square (%d,%d)!", op.x, op.y));
+                debug(("Solver wants to circle blackened square (%d,%d)!\n", op.x, op.y));
                 state->impossible = 1;
                 return n_ops;
             }
             if (!(state->flags[i] & F_CIRCLE)) {
-                debug(("... solver adding circle at (%d,%d): %s", op.x, op.y, op.desc));
+                debug(("... solver adding circle at (%d,%d): %s\n", op.x, op.y, op.desc));
 #ifdef STANDALONE_SOLVER
                 if (verbose)
                     printf("Adding circle at (%d,%d): %s\n", op.x, op.y, op.desc);
@@ -822,7 +848,7 @@ static int solve_allblackbutone(game_state *state, struct solver_state *ss)
                 solver_op_add(ss, ifree%state->w, ifree/state->w, CIRCLE,
                               "CC/CE/QM: white cell with single non-black around it");
             else {
-                debug(("White cell with no escape at (%d,%d)", x, y));
+                debug(("White cell with no escape at (%d,%d)\n", x, y));
                 state->impossible = 1;
                 return 0;
             }
@@ -928,9 +954,9 @@ static void solve_offsetpair_pair(game_state *state, struct solver_state *ss,
             if (an == dn) {
                 /* We have a match; so (WLOG) the 'A' marked above are at
                  * (x1,y1) and (x2,y2), and the 'B' are at (ax,ay) and (dx,dy). */
-                debug(("Found offset-pair: %d at (%d,%d) and (%d,%d)",
+                debug(("Found offset-pair: %d at (%d,%d) and (%d,%d)\n",
                        state->nums[y1*w + x1], x1, y1, x2, y2));
-                debug(("              and: %d at (%d,%d) and (%d,%d)",
+                debug(("              and: %d at (%d,%d) and (%d,%d)\n",
                        an, ax, ay, dx[d], dy[d]));
 
                 xd = dx[d] - x2; yd = dy[d] - y2;
@@ -984,7 +1010,7 @@ static int solve_hassinglewhiteregion(game_state *state, struct solver_state *ss
         state->flags[i] &= ~F_SCRATCH;
     }
     if (lwhite == -1) {
-        debug(("solve_hassinglewhite: no white squares found!"));
+        debug(("solve_hassinglewhite: no white squares found!\n"));
         state->impossible = 1;
         return 0;
     }
@@ -1043,7 +1069,7 @@ static int solve_removesplits(game_state *state, struct solver_state *ss)
     int i, x, y, n_ops = ss->n_ops;
 
     if (!solve_hassinglewhiteregion(state, ss)) {
-        debug(("solve_removesplits: white region is not contiguous at start!"));
+        debug(("solve_removesplits: white region is not contiguous at start!\n"));
         state->impossible = 1;
         return 0;
     }
@@ -1155,11 +1181,11 @@ static int solve_specific(game_state *state, int diff, int sneaky)
     }
 
     solver_state_free(ss);
-    return state->impossible ? -1 : check_complete(state, 0);
+    return state->impossible ? -1 : check_complete(state, CC_MUST_FILL);
 }
 
-static char *solve_game(game_state *state, game_state *currstate,
-                       char *aux, char **error)
+static char *solve_game(const game_state *state, const game_state *currstate,
+                        const char *aux, char **error)
 {
     game_state *solved = dup_game(currstate);
     char *move = NULL;
@@ -1194,7 +1220,7 @@ solved:
       the solver gets a headstart working out where they are.
  */
 
-static int new_game_is_good(game_params *params,
+static int new_game_is_good(const game_params *params,
                             game_state *state, game_state *tosolve)
 {
     int sret, sret_easy = 0;
@@ -1229,7 +1255,7 @@ static int new_game_is_good(game_params *params,
     }
 
     if (sret <= 0 || sret_easy > 0) {
-        debug(("Generated puzzle %s at chosen difficulty %s",
+        debug(("Generated puzzle %s at chosen difficulty %s\n",
                sret <= 0 ? "insoluble" : "too easy",
                singles_diffnames[params->diff]));
         return 0;
@@ -1276,7 +1302,7 @@ found:
     return j;
 }
 
-static char *new_game_desc(game_params *params, random_state *rs,
+static char *new_game_desc(const game_params *params, random_state *rs,
                           char **aux, int interactive)
 {
     game_state *state = blank_game(params->w, params->h);
@@ -1293,7 +1319,7 @@ static char *new_game_desc(game_params *params, random_state *rs,
 
 generate:
     ss->n_ops = 0;
-    debug(("Starting game generation, size %dx%d", w, h));
+    debug(("Starting game generation, size %dx%d\n", w, h));
 
     memset(state->flags, 0, state->n*sizeof(unsigned int));
 
@@ -1313,7 +1339,7 @@ generate:
     for (j = 0; j < state->n; j++) {
         i = scratch[j];
         if ((state->flags[i] & F_CIRCLE) || (state->flags[i] & F_BLACK)) {
-            debug(("generator skipping (%d,%d): %s", i%w, i/w,
+            debug(("generator skipping (%d,%d): %s\n", i%w, i/w,
                    (state->flags[i] & F_CIRCLE) ? "CIRCLE" : "BLACK"));
             continue; /* solver knows this must be one or the other already. */
         }
@@ -1330,7 +1356,7 @@ generate:
         solver_ops_do(state, ss);
 
         if (state->impossible) {
-            debug(("generator made impossible, restarting..."));
+            debug(("generator made impossible, restarting...\n"));
             goto generate;
         }
     }
@@ -1369,10 +1395,10 @@ randomise:
         !new_game_is_good(params, state, tosolve)) {
         ntries++;
         if (ntries > MAXTRIES) {
-            debug(("Ran out of randomisation attempts, re-generating."));
+            debug(("Ran out of randomisation attempts, re-generating.\n"));
             goto generate;
         }
-        debug(("Re-randomising numbers under black squares."));
+        debug(("Re-randomising numbers under black squares.\n"));
         goto randomise;
     }
 
@@ -1388,7 +1414,7 @@ randomise:
     return ret;
 }
 
-static char *validate_desc(game_params *params, char *desc)
+static char *validate_desc(const game_params *params, const char *desc)
 {
     char *ret = NULL;
 
@@ -1396,7 +1422,8 @@ static char *validate_desc(game_params *params, char *desc)
     return ret;
 }
 
-static game_state *new_game(midend *me, game_params *params, char *desc)
+static game_state *new_game(midend *me, const game_params *params,
+                            const char *desc)
 {
     game_state *state = NULL;
 
@@ -1412,7 +1439,7 @@ struct game_ui {
     int show_black_nums;
 };
 
-static game_ui *new_ui(game_state *state)
+static game_ui *new_ui(const game_state *state)
 {
     game_ui *ui = snew(game_ui);
 
@@ -1427,17 +1454,17 @@ static void free_ui(game_ui *ui)
     sfree(ui);
 }
 
-static char *encode_ui(game_ui *ui)
+static char *encode_ui(const game_ui *ui)
 {
     return NULL;
 }
 
-static void decode_ui(game_ui *ui, char *encoding)
+static void decode_ui(game_ui *ui, const char *encoding)
 {
 }
 
-static void game_changed_state(game_ui *ui, game_state *oldstate,
-                               game_state *newstate)
+static void game_changed_state(game_ui *ui, const game_state *oldstate,
+                               const game_state *newstate)
 {
     if (!oldstate->completed && newstate->completed)
         ui->cshow = 0;
@@ -1458,8 +1485,9 @@ struct game_drawstate {
     unsigned int *flags;
 };
 
-static char *interpret_move(game_state *state, game_ui *ui, game_drawstate *ds,
-                           int mx, int my, int button)
+static char *interpret_move(const game_state *state, game_ui *ui,
+                            const game_drawstate *ds,
+                            int mx, int my, int button)
 {
     char buf[80], c;
     int i, x = FROMCOORD(mx), y = FROMCOORD(my);
@@ -1509,12 +1537,12 @@ static char *interpret_move(game_state *state, game_ui *ui, game_drawstate *ds,
     return NULL;
 }
 
-static game_state *execute_move(game_state *state, char *move)
+static game_state *execute_move(const game_state *state, const char *move)
 {
     game_state *ret = dup_game(state);
     int x, y, i, n;
 
-    debug(("move: %s", move));
+    debug(("move: %s\n", move));
 
     while (*move) {
         char c = *move;
@@ -1542,7 +1570,7 @@ static game_state *execute_move(game_state *state, char *move)
         else if (*move)
             goto badmove;
     }
-    if (check_complete(ret, 1)) ret->completed = 1;
+    if (check_complete(ret, CC_MARK_ERRORS)) ret->completed = 1;
     return ret;
 
 badmove:
@@ -1554,8 +1582,8 @@ badmove:
  * Drawing routines.
  */
 
-static void game_compute_size(game_params *params, int tilesize,
-                             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;
@@ -1566,7 +1594,7 @@ static void game_compute_size(game_params *params, int tilesize,
 }
 
 static void game_set_size(drawing *dr, game_drawstate *ds,
-                         game_params *params, int tilesize)
+                          const game_params *params, int tilesize)
 {
     ds->tilesize = tilesize;
 }
@@ -1595,7 +1623,7 @@ static float *game_colours(frontend *fe, int *ncolours)
     return ret;
 }
 
-static game_drawstate *game_new_drawstate(drawing *dr, game_state *state)
+static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state)
 {
     struct game_drawstate *ds = snew(struct game_drawstate);
 
@@ -1660,9 +1688,10 @@ static void tile_redraw(drawing *dr, game_drawstate *ds, int x, int y,
     draw_update(dr, x, y, TILE_SIZE, TILE_SIZE);
 }
 
-static void game_redraw(drawing *dr, 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, i, flash;
     unsigned int f;
@@ -1707,14 +1736,14 @@ static void game_redraw(drawing *dr, game_drawstate *ds, game_state *oldstate,
     ds->started = 1;
 }
 
-static float game_anim_length(game_state *oldstate, game_state *newstate,
-                             int dir, game_ui *ui)
+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, game_ui *ui)
+static float game_flash_length(const game_state *oldstate,
+                               const game_state *newstate, int dir, game_ui *ui)
 {
     if (!oldstate->completed &&
         newstate->completed && !newstate->used_solve)
@@ -1722,12 +1751,17 @@ static float game_flash_length(game_state *oldstate, game_state *newstate,
     return 0.0F;
 }
 
-static int game_timing_state(game_state *state, game_ui *ui)
+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(game_params *params, float *x, float *y)
+static void game_print_size(const game_params *params, float *x, float *y)
 {
     int pw, ph;
 
@@ -1737,7 +1771,7 @@ static void game_print_size(game_params *params, float *x, float *y)
     *y = ph / 100.0F;
 }
 
-static void game_print(drawing *dr, game_state *state, int tilesize)
+static void game_print(drawing *dr, const game_state *state, int tilesize)
 {
     int ink = print_mono_colour(dr, 0);
     int paper = print_mono_colour(dr, 1);
@@ -1808,6 +1842,7 @@ const struct game thegame = {
     game_redraw,
     game_anim_length,
     game_flash_length,
+    game_status,
     TRUE, FALSE, game_print_size, game_print,
     FALSE,                            /* wants_statusbar */
     FALSE, game_timing_state,
@@ -1941,13 +1976,13 @@ int main(int argc, char **argv)
 
         if (verbose) {
             tgame = game_text_format(s);
-            printf(tgame);
+            fputs(tgame, stdout);
             sfree(tgame);
         }
 
         soln = solve_specific(s, DIFF_ANY, 0);
         tgame = game_text_format(s);
-        printf(tgame);
+        fputs(tgame, stdout);
         sfree(tgame);
         printf("Game was %s.\n\n",
                soln < 0 ? "impossible" : soln > 0 ? "solved" : "not solved");