shithub: puzzles

Download patch

ref: adf2a098298f1aa73aca2c816174d5e63ff45a32
parent: 51818780f835b459b9671df38eefef06fca3fd89
author: Simon Tatham <anakin@pobox.com>
date: Sun Mar 12 11:18:33 EDT 2023

Galaxies: skew grid generation in favour of wiggliness.

Ben complained yesterday that Galaxies had a nasty habit of generating
games whose solution was a boring set of rectangles. Now that even at
Normal mode the solver is better at coping with wiggly tentacled
regions, it seems like a good moment to fix that.

This change arranges that when we initially generate a filled grid, we
try ten times, and pick the wiggliest of the grids we found. This
doesn't make any boring rectangle-filled grid _impossible_ - players
will still have to stay on their toes, and can't rely 100% on at least
a certain number of wiggles existing - but it makes the interesting
grids a lot more likely to come up.

This skew happens before checking solubility. So it doesn't increase
grid generation time by a factor of ten (as it would if we generated
ten _soluble_ grids and picked the wiggliest). It's still a speed
drop, of course, but a more modest one than that.

--- a/galaxies.c
+++ b/galaxies.c
@@ -1330,6 +1330,8 @@
 {
     int sz = state->sx*state->sy, nspc, i, ret;
 
+    /* Random list of squares to try and process, one-by-one. */
+    for (i = 0; i < sz; i++) scratch[i] = i;
     shuffle(scratch, sz, sizeof(int), rs);
 
     /* This bug took me a, er, little while to track down. On PalmOS,
@@ -1385,6 +1387,57 @@
     dbg_state(state);
 }
 
+/*
+ * We try several times to generate a grid at all, before even feeding
+ * it to the solver. Then we pick whichever of the resulting grids was
+ * the most 'wiggly', as measured by the number of inward corners in
+ * the shape of any region.
+ *
+ * Rationale: wiggly shapes are what make this puzzle fun, and it's
+ * disappointing to be served a game whose entire solution is a
+ * collection of rectangles. But we also don't want to introduce a
+ * _hard requirement_ of wiggliness, because a player who knew that
+ * was there would be able to use it as an extra clue. This way, we
+ * just probabilistically skew in favour of wiggliness.
+ */
+#define GENERATE_TRIES 10
+
+static bool is_wiggle(const game_state *state, int x, int y, int dx, int dy)
+{
+    int x1 = x+2*dx, y1 = y+2*dy;
+    int x2 = x-2*dy, y2 = y+2*dx;
+    space *t, *t1, *t2;
+
+    if (!INGRID(state, x1, y1) || !INGRID(state, x2, y2))
+        return false;
+
+    t = &SPACE(state, x, y);
+    t1 = &SPACE(state, x1, y1);
+    t2 = &SPACE(state, x2, y2);
+    return ((t1->dotx == t2->dotx && t1->doty == t2->doty) &&
+            !(t1->dotx == t->dotx && t1->doty == t->doty));
+}
+
+static int measure_wiggliness(const game_state *state, int *scratch)
+{
+    int sz = state->sx*state->sy;
+    int x, y, nwiggles = 0;
+    memset(scratch, 0, sz);
+
+    for (y = 1; y < state->sy; y += 2) {
+        for (x = 1; x < state->sx; x += 2) {
+            if (y+2 < state->sy) {
+                nwiggles += is_wiggle(state, x, y, 0, +1);
+                nwiggles += is_wiggle(state, x, y, 0, -1);
+                nwiggles += is_wiggle(state, x, y, +1, 0);
+                nwiggles += is_wiggle(state, x, y, -1, 0);
+            }
+        }
+    }
+
+    return nwiggles;
+}
+
 static char *new_game_desc(const game_params *params, random_state *rs,
 			   char **aux, bool interactive)
 {
@@ -1391,23 +1444,37 @@
     game_state *state = blank_game(params->w, params->h), *copy;
     char *desc;
     int *scratch, sz = state->sx*state->sy, i;
-    int diff;
+    int diff, best_wiggliness;
     bool cc;
 
-    /* Random list of squares to try and process, one-by-one. */
     scratch = snewn(sz, int);
-    for (i = 0; i < sz; i++) scratch[i] = i;
 
 generate:
-    clear_game(state, true);
+    best_wiggliness = -1;
+    copy = NULL;
+    for (i = 0; i < GENERATE_TRIES; i++) {
+        int this_wiggliness;
 
-    /* generate_pass(state, rs, scratch, 10, GP_DOTS); */
-    /* generate_pass(state, rs, scratch, 100, 0); */
-    generate_pass(state, rs, scratch, 100, GP_DOTS);
+        do {
+            clear_game(state, true);
+            generate_pass(state, rs, scratch, 100, GP_DOTS);
+            game_update_dots(state);
+        } while (state->ndots == 1);
 
-    game_update_dots(state);
-
-    if (state->ndots == 1) goto generate;
+        this_wiggliness = measure_wiggliness(state, scratch);
+        debug(("Grid gen #%d: wiggliness=%d", i, this_wiggliness));
+        if (this_wiggliness > best_wiggliness) {
+            best_wiggliness = this_wiggliness;
+            if (copy)
+                free_game(copy);
+            copy = dup_game(state);
+            debug((" new best"));
+        }
+        debug(("\n"));
+    }
+    assert(copy);
+    free_game(state);
+    state = copy;
 
 #ifdef DEBUGGING
     {