From a3df24e90f7530ed17e234fe11040622457c8f85 Mon Sep 17 00:00:00 2001 From: mike Date: Sat, 10 Jan 2026 08:32:27 +0100 Subject: [PATCH] introduce bitloops --- .run/Main.run.xml | 15 +++ src/main/java/puzzle/SwedishGenerator.java | 126 ++++++++++++--------- src/test/java/puzzle/MainTest.java | 4 +- 3 files changed, 92 insertions(+), 53 deletions(-) create mode 100644 .run/Main.run.xml diff --git a/.run/Main.run.xml b/.run/Main.run.xml new file mode 100644 index 0000000..686f4cc --- /dev/null +++ b/.run/Main.run.xml @@ -0,0 +1,15 @@ + + + + \ No newline at end of file diff --git a/src/main/java/puzzle/SwedishGenerator.java b/src/main/java/puzzle/SwedishGenerator.java index 5295936..97faf96 100644 --- a/src/main/java/puzzle/SwedishGenerator.java +++ b/src/main/java/puzzle/SwedishGenerator.java @@ -20,9 +20,7 @@ import java.util.Arrays; import java.util.Comparator; import java.util.HashMap; import java.util.Locale; -import java.util.concurrent.atomic.AtomicLong; import java.util.function.IntConsumer; -import java.util.function.Supplier; /** * SwedishGenerator.java @@ -67,6 +65,19 @@ public record SwedishGenerator(Rng rng) { static final nbrs_8[] nbrs4 = Neighbors9x8.nbrs4; static final rci[] IT = Neighbors9x8.IT; static final long[] INBR8_PACKEDT = Neighbors9x8.NBR8_PACKED; + static final int[][] MUTATE_RI = new int[SIZE][625]; + + static { + for (int i = 0; i < SIZE; i++) { + int k = 0; + for (int dr1 = -2; dr1 <= 2; dr1++) + for (int dr2 = -2; dr2 <= 2; dr2++) + for (int dc1 = -2; dc1 <= 2; dc1++) + for (int dc2 = -2; dc2 <= 2; dc2++) + MUTATE_RI[i][k++] = Grid.offset(clamp(Grid.r(i) + dr1 + dr2, 0, R - 1), + clamp(Grid.c(i) + dc1 + dc2, 0, C - 1)); + } + } static final Pick PICK_DONE = new Pick(null, null, true); static final Pick PICK_NOT_DONE = new Pick(null, null, false); @@ -151,10 +162,6 @@ public record SwedishGenerator(Rng rng) { Grid deepCopyGrid() { return new Grid(g.clone(), bo.clone()); } public byte byteAt(int pos) { return g[pos]; } void setByteAt(int idx, byte ch) { g[idx] = ch; } - void setAt(int idx, byte ch) { - if (isDigit(ch)) setClue(idx, ch); - else g[idx] = ch; - } void setClue(int idx, byte ch) { g[idx] = ch; if (idx < 64) bo[0] |= (1L << idx); @@ -378,19 +385,18 @@ public record SwedishGenerator(Rng rng) { grid.forEachSlot((key, packedPos, len) -> slots.add(Slot.from(key, packedPos, len))); return slots; } - boolean hasRoomForClue(Grid grid, long packed) { + static boolean hasRoomForClue(Grid grid, long packed) { for (int n = (int) (packed >>> 56), k = 0; k < n && k < MAX_WORD_LENGTH; ) { - if (!grid.isLetterAt((int) ((packed >>> (k * 7)) & 0x7F))) break; + if (grid.isClue((int) ((packed >>> (k * 7)) & 0x7F))) break; if (++k >= MIN_LEN) return true; } return false; } long maskFitness(Grid grid) { - final long[] penalty = { 0 }; - var clueCount = grid.clueCount(); - penalty[0] += ((long) Math.abs(clueCount - TARGET_CLUES)) << 3; + var clueCount = grid.clueCount(); + long penalty = ((long) Math.abs(clueCount - TARGET_CLUES)) << 3; var ctx = CTX.get(); var covH = ctx.covH; @@ -415,7 +421,7 @@ public record SwedishGenerator(Rng rng) { } if (k == 0) continue; hasSlots = true; - if (k < MIN_LEN) penalty[0] += 8000; + if (k < MIN_LEN) penalty += 8000; } while (hi_cl != 0L) { int clueIdx = 64 + Long.numberOfTrailingZeros(hi_cl); @@ -432,7 +438,7 @@ public record SwedishGenerator(Rng rng) { } if (k == 0) continue; hasSlots = true; - if (k < MIN_LEN) penalty[0] += 8000; + if (k < MIN_LEN) penalty += 8000; } if (!hasSlots) return 1_000_000_000L; @@ -442,27 +448,9 @@ public record SwedishGenerator(Rng rng) { seen.clear(); var stack = ctx.stack; - grid.forEachSetBit71(clueIdx -> { - if (seen.get(clueIdx)) return; - var sp = 0; - stack[sp++] = clueIdx; - seen.set(clueIdx); - var size = 0; - - while (sp > 0) { - var p = stack[--sp]; - size++; - long packed = Neighbors9x8.NBR8_PACKED[p]; - int n = (int) (packed >>> 56); - for (int k = 0; k < n; k++) { - int nidx = (int) ((packed >>> (k * 7)) & 0x7F); - if (seen.get(nidx) || grid.notClue(nidx)) continue; - seen.set(nidx); - stack[sp++] = nidx; - } - } - if (size >= 2) penalty[0] += (size - 1L) * 120L; - }); + for (var lo = grid.bo[0]; lo != 0; lo &= lo - 1) penalty += clueStackPenalty(seen, stack, grid, Long.numberOfTrailingZeros(lo)); + for (var hi = grid.bo[1]; hi != 0; hi &= hi - 1) penalty += clueStackPenalty(seen, stack, grid, 64 + Long.numberOfTrailingZeros(hi)); + // dead-end-ish letter cell (3+ walls) int walls, wc, wr; /* for (var rci : IT) { @@ -478,21 +466,40 @@ public record SwedishGenerator(Rng rng) { int h, v; for (var rci : IT) { if (grid.isClue(rci.i())) continue; - if ((4 - rci.nbrCount()) + Long.bitCount(rci.n1() & grid.bo[0]) + Long.bitCount(rci.n2() & grid.bo[1]) >= 3) penalty[0] += 400; + if ((4 - rci.nbrCount()) + Long.bitCount(rci.n1() & grid.bo[0]) + Long.bitCount(rci.n2() & grid.bo[1]) >= 3) penalty += 400; h = covH[rci.i()]; v = covV[rci.i()]; - if (h == 0 && v == 0) penalty[0] += 1500; - else if (h > 0 && v > 0) { /* ok */ } else if (h + v == 1) penalty[0] += 200; - else penalty[0] += 600; + if (h == 0 && v == 0) penalty += 1500; + else if (h > 0 && v > 0) { /* ok */ } else if (h + v == 1) penalty += 200; + else penalty += 600; } - return penalty[0]; + return penalty; + } + static long clueStackPenalty(Bit seen, int[] stack, Grid grid, int clueIdx) { + if (seen.get(clueIdx)) return 0; + var sp = 0; + stack[sp++] = clueIdx; + seen.set(clueIdx); + var size = 0; + + while (sp > 0) { + var p = stack[--sp]; + size++; + long packed = Neighbors9x8.NBR8_PACKED[p]; + int n = (int) (packed >>> 56); + for (int k = 0; k < n; k++) { + int nidx = (int) ((packed >>> (k * 7)) & 0x7F); + if (seen.get(nidx) || grid.notClue(nidx)) continue; + seen.set(nidx); + stack[sp++] = nidx; + } + } + return (size >= 2) ? (size - 1L) * 120L : 0; } - Grid randomMask() { var g = Grid.createEmpty(); for (int placed = 0, guard = 0, idx; placed < TARGET_CLUES && guard < 4000; guard++) { - idx = Grid.offset(rng.randint(0, R - 1), - rng.randint(0, C - 1)); + idx = rng.randint(0, SIZE - 1); if (g.isClue(idx)) continue; var d = OFFSETS[rng.randbyte(1, 4)]; @@ -504,15 +511,12 @@ public record SwedishGenerator(Rng rng) { return g; } Grid mutate(Grid grid) { - var g = grid.deepCopyGrid(); - var cx = rng.randint(0, R - 1); - var cy = rng.randint(0, C - 1); + var g = grid.deepCopyGrid(); + var centerIdx = rng.randint(0, SIZE - 1); var steps = 4; for (var k = 0; k < steps; k++) { - var ri = Grid.offset( - clamp(cx + (rng.randint(-2, 2) + rng.randint(-2, 2)), 0, R - 1), - clamp(cy + (rng.randint(-2, 2) + rng.randint(-2, 2)), 0, C - 1)); + var ri = MUTATE_RI[centerIdx][rng.randint(0, 624)]; if (!g.clueless(ri)) { var d = OFFSETS[rng.randint(1, 4)]; @@ -522,15 +526,35 @@ public record SwedishGenerator(Rng rng) { return g; } Grid crossover(Grid a, Grid b) { - var out = Grid.createEmpty(); + var out = a.deepCopyGrid(); var theta = rng.nextFloat() * Math.PI; var nc = Math.cos(theta); var nr = Math.sin(theta); - for (var rci : IT) out.setAt(rci.i(), ((rci.r() - CROSS_C) * nc + (rci.c() - CROSS_R) * nr >= 0) ? a.byteAt(rci.i()) : b.byteAt(rci.i())); - for (var rci : IT) if (out.isClue(rci.i()) && !hasRoomForClue(out, OFFSETS[out.digitAt(rci.i())].path()[rci.i()])) out.clearClue(rci.i()); + long bo0 = out.bo[0], bo1 = out.bo[1]; + for (var rci : IT) { + int i = rci.i(); + if ((rci.r() - CROSS_C) * nc + (rci.c() - CROSS_R) * nr < 0) { + byte ch = b.g[i]; + if (out.g[i] != ch) { + out.g[i] = ch; + if (Grid.isDigit(ch)) { + if (i < 64) bo0 |= (1L << i); + else bo1 |= (1L << (i & 63)); + } else { + if (i < 64) bo0 &= ~(1L << i); + else bo1 &= ~(1L << (i & 63)); + } + } + } + } + out.bo[0] = bo0; + out.bo[1] = bo1; + for (var lo = out.bo[0]; lo != 0L; lo &= lo - 1L) clearClues(out, Long.numberOfTrailingZeros(lo)); + for (var hi = out.bo[1]; hi != 0L; hi &= hi - 1L) clearClues(out, 64 + Long.numberOfTrailingZeros(hi)); return out; } + public static void clearClues(Grid out, int idx) { if (!hasRoomForClue(out, OFFSETS[out.digitAt(idx)].path()[idx])) out.clearClue(idx); } Grid hillclimb(Grid start, int limit) { var best = start; diff --git a/src/test/java/puzzle/MainTest.java b/src/test/java/puzzle/MainTest.java index b1ec0af..39c45a1 100644 --- a/src/test/java/puzzle/MainTest.java +++ b/src/test/java/puzzle/MainTest.java @@ -167,8 +167,8 @@ public class MainTest { // Regression baseline for seed search starting at 12347, pop 4, gens 20 Assertions.assertEquals(12348, foundSeed, "Found seed changed"); Assertions.assertEquals(22, res.filled().clueMap().size(), "Number of assigned words changed"); - Assertions.assertEquals(767.8636363636364, res.filled().stats().simplicity, 1e-9, "Simplicity value changed"); - Assertions.assertArrayEquals(new byte[]{ 'T', 'R', 'U', 'I' }, res.filled().clueMap().get(515).word()); + Assertions.assertEquals(747.5454545454545, res.filled().stats().simplicity, 1e-9, "Simplicity value changed"); + Assertions.assertArrayEquals(new byte[]{ 'I', 'E', 'M', 'A', 'N', 'D', 'S' }, res.filled().clueMap().get(515).word()); } @Test public void testIsLetterA() {