shithub: puzzles

Download patch

ref: 2d91261eb3a37b919f48450888f8f5bfd226c053
parent: c0bd524848f98e5c4a495c4bc31dd55087a28aaa
author: Simon Tatham <anakin@pobox.com>
date: Sun Apr 23 10:02:39 EDT 2023

Net: preference for how loop highlighting interacts with locking.

Net's loop highlighting detects any loop in the current state of the
grid. I've occasionally found that to be a bit of a spoiler, since
sometimes it can point out a deduction I should make before I've
figured it out for myself - e.g. when I've just locked all but two of
the squares involved in the loop, and the last two _just happen_ to be
oriented so as to complete the loop. In that situation I'd prefer if
the loop _didn't_ immediately light up and point out to me that I need
to arrange that those squares aren't connected to each other.

The simple answer is to only count edges connecting two _locked_
squares, for the purposes of loop detection. But this is obviously
unacceptable to some players - in particular, those who play without
the locking feature at all. So it should be a user preference.

--- a/net.c
+++ b/net.c
@@ -1135,7 +1135,8 @@
 
 static int *compute_loops_inner(int w, int h, bool wrapping,
                                 const unsigned char *tiles,
-                                const unsigned char *barriers);
+                                const unsigned char *barriers,
+                                bool include_unlocked_squares);
 
 static char *new_game_desc(const game_params *params, random_state *rs,
 			   char **aux, bool interactive)
@@ -1464,7 +1465,8 @@
          */
         prev_loopsquares = w*h+1;
         while (1) {
-            loops = compute_loops_inner(w, h, params->wrapping, tiles, NULL);
+            loops = compute_loops_inner(w, h, params->wrapping, tiles, NULL,
+                                        true);
             this_loopsquares = 0;
             for (i = 0; i < w*h; i++) {
                 if (loops[i]) {
@@ -1919,6 +1921,7 @@
     int w, h;
     const unsigned char *tiles, *barriers;
     int i, n, neighbours[4];
+    bool include_unlocked_squares;
 };
 static int net_neighbour(int vertex, void *vctx)
 {
@@ -1939,6 +1942,9 @@
                 continue;
             OFFSETWH(x1, y1, x, y, dir, ctx->w, ctx->h);
             v1 = y1 * ctx->w + x1;
+            if (!ctx->include_unlocked_squares &&
+                !(tile & ctx->tiles[v1] & LOCKED))
+                continue;
             if (ctx->tiles[v1] & F(dir))
                 ctx->neighbours[ctx->n++] = v1;
         }
@@ -1952,12 +1958,13 @@
 
 static int *compute_loops_inner(int w, int h, bool wrapping,
                                 const unsigned char *tiles,
-                                const unsigned char *barriers)
+                                const unsigned char *barriers,
+                                bool include_unlocked_squares)
 {
     struct net_neighbour_ctx ctx;
     struct findloopstate *fls;
     int *loops;
-    int x, y;
+    int x, y, v;
 
     fls = findloop_new_state(w*h);
     ctx.w = w;
@@ -1964,6 +1971,7 @@
     ctx.h = h;
     ctx.tiles = tiles;
     ctx.barriers = barriers;
+    ctx.include_unlocked_squares = include_unlocked_squares;
     findloop_run(fls, w*h, net_neighbour, &ctx);
 
     loops = snewn(w*h, int);
@@ -1970,14 +1978,19 @@
 
     for (y = 0; y < h; y++) {
         for (x = 0; x < w; x++) {
-            int x1, y1, dir;
+            int x1, y1, v1, dir;
             int flags = 0;
 
+            v = y * w + x;
             for (dir = 1; dir < 0x10; dir <<= 1) {
-                if ((tiles[y*w+x] & dir) &&
+                if ((tiles[v] & dir) &&
                     !(barriers && (barriers[y*w+x] & dir))) {
                     OFFSETWH(x1, y1, x, y, dir, w, h);
-                    if ((tiles[y1*w+x1] & F(dir)) &&
+                    v1 = y1 * w + x1;
+                    if (!include_unlocked_squares &&
+                        !(tiles[v] & tiles[v1] & LOCKED))
+                        continue;
+                    if ((tiles[v1] & F(dir)) &&
                         findloop_is_loop_edge(fls, y*w+x, y1*w+x1))
                         flags |= ERR(dir);
                 }
@@ -1990,10 +2003,12 @@
     return loops;
 }
 
-static int *compute_loops(const game_state *state)
+static int *compute_loops(const game_state *state,
+                          bool include_unlocked_squares)
 {
     return compute_loops_inner(state->width, state->height, state->wrapping,
-                               state->tiles, state->imm->barriers);
+                               state->tiles, state->imm->barriers,
+                               include_unlocked_squares);
 }
 
 struct game_ui {
@@ -2006,6 +2021,8 @@
     int dragtilex, dragtiley, dragstartx, dragstarty;
     bool dragged;
 #endif
+
+    bool unlocked_loops;
 };
 
 static game_ui *new_ui(const game_state *state)
@@ -2013,20 +2030,28 @@
     void *seed;
     int seedsize;
     game_ui *ui = snew(game_ui);
-    ui->org_x = ui->org_y = 0;
-    ui->cur_x = ui->cx = state->width / 2;
-    ui->cur_y = ui->cy = state->height / 2;
-    ui->cur_visible = getenv_bool("PUZZLES_SHOW_CURSOR", false);
-    get_random_seed(&seed, &seedsize);
-    ui->rs = random_new(seed, seedsize);
-    sfree(seed);
 
+    ui->unlocked_loops = true;
+
+    if (state) {
+        ui->org_x = ui->org_y = 0;
+        ui->cur_x = ui->cx = state->width / 2;
+        ui->cur_y = ui->cy = state->height / 2;
+        ui->cur_visible = getenv_bool("PUZZLES_SHOW_CURSOR", false);
+        get_random_seed(&seed, &seedsize);
+        ui->rs = random_new(seed, seedsize);
+        sfree(seed);
+    } else {
+        ui->rs = NULL;
+    }
+
     return ui;
 }
 
 static void free_ui(game_ui *ui)
 {
-    random_free(ui->rs);
+    if (ui->rs)
+        random_free(ui->rs);
     sfree(ui);
 }
 
@@ -2060,6 +2085,28 @@
     }
 }
 
+static config_item *get_prefs(game_ui *ui)
+{
+    config_item *ret;
+
+    ret = snewn(2, config_item);
+
+    ret[0].name = "Highlight loops involving unlocked squares";
+    ret[0].kw = "unlocked-loops";
+    ret[0].type = C_BOOLEAN;
+    ret[0].u.boolean.bval = ui->unlocked_loops;
+
+    ret[1].name = NULL;
+    ret[1].type = C_END;
+
+    return ret;
+}
+
+static void set_prefs(game_ui *ui, const config_item *cfg)
+{
+    ui->unlocked_loops = cfg[0].u.boolean.bval;
+}
+
 static void game_changed_state(game_ui *ui, const game_state *oldstate,
                                const game_state *newstate)
 {
@@ -2891,7 +2938,7 @@
      * of barriers.
      */
     active = compute_active(state, ui->cx, ui->cy);
-    loops = compute_loops(state);
+    loops = compute_loops(state, ui->unlocked_loops);
 
     for (dy = -1; dy < ds->height+1; dy++) {
         for (dx = -1; dx < ds->width+1; dx++) {
@@ -3270,7 +3317,7 @@
     free_game,
     true, solve_game,
     false, NULL, NULL, /* can_format_as_text_now, text_format */
-    NULL, NULL, /* get_prefs, set_prefs */
+    get_prefs, set_prefs,
     new_ui,
     free_ui,
     encode_ui,