shithub: puzzles

Download patch

ref: 2d333750272c3967cfd5cd3677572cddeaad5932
parent: e3821d1f68bf531fa485fd5b4c2882b838824347
author: Simon Tatham <anakin@pobox.com>
date: Mon Apr 24 14:32:57 EDT 2017

Loopy: optional 'autofollow' UI feature.

This is mostly intended to make play more convenient for grid types
like the new Great-Great-Dodecagonal, and other grids with very
high-degree faces, in which it's annoying to have to spend half a
dozen mouse clicks on filling in a path of edges round the outside of
one of those faces which clearly have to all be set (or clear) if any
one of them is.

For now, the new feature is enabled by yet another of my hacky
environment variables, called LOOPY_AUTOFOLLOW. You can set it to
"off", "fixed" or "adaptive", where "off" is currently the default
(hence, no user-visible change in the default behaviour from this
change). If set to 'fixed', then toggling the state of any edge will
automatically toggle any further edges which are in the same state and
share a degree-2 vertex of the underlying grid design with the
original one. In 'adaptive' mode, the game goes even further, and will
consider outgoing edges in LINE_NO state not to count for purposes of
deciding if a vertex has degree 2.

--- a/loopy.c
+++ b/loopy.c
@@ -2947,7 +2947,8 @@
     grid *g = state->game_grid;
     grid_edge *e;
     int i;
-    char *ret, buf[80];
+    char *movebuf;
+    int movelen, movesize;
     char button_char = ' ';
     enum line_state old_state;
 
@@ -3009,11 +3010,64 @@
 	return NULL;
     }
 
+    movelen = 0;
+    movesize = 80;
+    movebuf = snewn(movesize, char);
+    movelen = sprintf(movebuf, "%d%c", i, (int)button_char);
+    {
+        static enum { OFF, FIXED, ADAPTIVE, DUNNO } autofollow = DUNNO;
+        if (autofollow == DUNNO) {
+            const char *env = getenv("LOOPY_AUTOFOLLOW");
+            if (env && !strcmp(env, "off"))
+                autofollow = OFF;
+            else if (env && !strcmp(env, "fixed"))
+                autofollow = FIXED;
+            else if (env && !strcmp(env, "adaptive"))
+                autofollow = ADAPTIVE;
+            else
+                autofollow = OFF;
+        }
 
-    sprintf(buf, "%d%c", i, (int)button_char);
-    ret = dupstr(buf);
+        if (autofollow != OFF) {
+            int dotid;
+            for (dotid = 0; dotid < 2; dotid++) {
+                grid_dot *dot = (dotid == 0 ? e->dot1 : e->dot2);
+                grid_edge *e_this = e;
 
-    return ret;
+                while (1) {
+                    int j, n_found;
+                    grid_edge *e_next = NULL;
+
+                    for (j = n_found = 0; j < dot->order; j++) {
+                        grid_edge *e_candidate = dot->edges[j];
+                        int i_candidate = e_candidate - g->edges;
+                        if (e_candidate != e_this &&
+                            (autofollow == FIXED ||
+                             state->lines[i] == LINE_NO ||
+                             state->lines[i_candidate] != LINE_NO)) {
+                            e_next = e_candidate;
+                            n_found++;
+                        }
+                    }
+
+                    if (n_found != 1 ||
+                        state->lines[e_next - g->edges] != state->lines[i])
+                        break;
+
+                    dot = (e_next->dot1 != dot ? e_next->dot1 : e_next->dot2);
+                    if (movelen > movesize - 40) {
+                        movesize = movesize * 5 / 4 + 128;
+                        movebuf = sresize(movebuf, movesize, char);
+                    }
+                    e_this = e_next;
+                    movelen += sprintf(movebuf+movelen, "%d%c",
+                                       (int)(e_this - g->edges), button_char);
+                }
+            }
+        }
+    }
+
+    return sresize(movebuf, movelen+1, char);
 }
 
 static game_state *execute_move(const game_state *state, const char *move)