From fa806a10786a04ef7125916c23c80f5366a4e6cd Mon Sep 17 00:00:00 2001 From: mike Date: Fri, 9 Jan 2026 08:18:55 +0100 Subject: [PATCH] Gather data --- src/main/java/puzzle/SwedishGenerator.java | 194 ++++++++++++------ src/test/java/puzzle/ExportFormatTest.java | 4 +- src/test/java/puzzle/MainTest.java | 8 +- .../java/puzzle/SwedishGeneratorTest.java | 4 +- 4 files changed, 134 insertions(+), 76 deletions(-) diff --git a/src/main/java/puzzle/SwedishGenerator.java b/src/main/java/puzzle/SwedishGenerator.java index 0519b75..a153321 100644 --- a/src/main/java/puzzle/SwedishGenerator.java +++ b/src/main/java/puzzle/SwedishGenerator.java @@ -56,6 +56,7 @@ public record SwedishGenerator(int[] buff) { static final int R = Config.PUZZLE_ROWS; static final double CROSS_X = (R - 1) / 2.0; static final int SIZE = C * R;// ~18 + static final double SIZED = (double) SIZE;// ~18 static final int TARGET_CLUES = SIZE >> 2; static final int MAX_WORD_LENGTH = Math.min(C, R); static final int MIN_LEN = Config.MIN_LEN; @@ -144,19 +145,34 @@ public record SwedishGenerator(int[] buff) { static final byte _48 = 48; - record Grid(byte[] g) { + record Grid(byte[] g, long[] bo) { - int digitAt(int r, int c) { return g[offset(r, c)] - 48; } - int digitAt(int index) { return g[index] - 48; } - public static int r(int offset) { return offset & 7; } - public static int c(int offset) { return offset >>> 3; } - static int offset(int r, int c) { return r | (c << 3); } - Grid deepCopyGrid() { return new Grid(g.clone()); } - public byte byteAt(int r, int c) { return g[offset(r, c)]; } - public byte byteAt(int pos) { return g[pos]; } - void setCharAt(int r, int c, char ch) { g[offset(r, c)] = (byte) ch; } - void setByteAt(int idx, byte ch) { g[idx] = ch; } - void clear(int idx) { g[idx] = DASH; } + public Grid(byte[] g) { this(g, new long[2]); } + int digitAt(int r, int c) { return g[offset(r, c)] - 48; } + int digitAt(int index) { return g[index] - 48; } + public static int r(int offset) { return offset & 7; } + public static int c(int offset) { return offset >>> 3; } + static int offset(int r, int c) { return r | (c << 3); } + Grid deepCopyGrid() { return new Grid(g.clone(), bo.clone()); } + public byte byteAt(int r, int c) { return g[offset(r, c)]; } + public byte byteAt(int pos) { return g[pos]; } + void setCharAt(int r, int c, char ch) { g[offset(r, c)] = (byte) ch; } + 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); + else bo[1] |= (1L << (idx & 63)); + } + void clear(int idx) { g[idx] = DASH; } + void clearClue(int idx) { + g[idx] = DASH; + if (idx < 64) bo[0] &= ~(1L << idx); + else bo[1] &= ~(1L << (idx & 63)); + } public boolean isLetterAt(int r, int c) { return ((g[offset(r, c)] & 64) != 0); } boolean isDigitAt(int r, int c) { return (g[offset(r, c)] & 48) == 48; } boolean isDigitAt(int index) { return (g[index] & 48) == 48; } @@ -166,10 +182,30 @@ public record SwedishGenerator(int[] buff) { public double similarity(Grid b) { var same = 0; for (int i = 0; i < SIZE; i++) if (g[i] == b.g[i]) same++; - return same / (double) (SIZE); + return same / SIZED; + } + int clueCount() { + return Long.bitCount(bo[0]) + Long.bitCount(bo[1]); + } + void forEachSetBit71(java.util.function.IntConsumer consumer) { + long lo = bo[0], hi = bo[1]; + // low 64 bits + while (lo != 0L) { + int bit = Long.numberOfTrailingZeros(lo); + consumer.accept(bit); // 0..63 + lo &= (lo - 1); // clear lowest set bit + } + + // high 7 bits (positions 64..70) + // hi &= 0x7FL; + while (hi != 0L) { + int bit = Long.numberOfTrailingZeros(hi); + consumer.accept(64 + bit); // 64..70 + hi &= (hi - 1L); + } } } - static Grid makeEmptyGrid() { return new Grid(new byte[SIZE]); } + static Grid makeEmptyGrid() { return new Grid(new byte[SIZE], new long[2]); } static final class IntList { @@ -365,7 +401,25 @@ public record SwedishGenerator(int[] buff) { } void forEachSlot(Grid grid, SlotVisitor visitor) { - for (var rci : IT) { + grid.forEachSetBit71(idx -> { + var d = grid.digitAt(idx); + var nbrs16 = OFFSETS[d]; + int r = Grid.r(idx), c = Grid.c(idx), rr = r + nbrs16.r, cc = c + nbrs16.c; + if (rr < 0 || rr >= R || cc < 0 || cc >= C || grid.isDigitAt(rr, cc)) return; + long packedPos = 0; + var n = 0; + + while (rr >= 0 && rr < R && cc >= 0 && cc < C && grid.isLettercell(rr, cc) && n < MAX_WORD_LENGTH) { + packedPos |= (long) Grid.offset(rr, cc) << (n * 7); + n++; + rr += nbrs16.dr; + cc += nbrs16.dc; + } + if (n > 0) { + visitor.visit((r << 8) | (c << 4) | d, packedPos, n); + } + }); + /* for (var rci : IT) { if (!grid.isDigitAt(rci.i)) continue; var d = grid.digitAt(rci.i); var nbrs16 = OFFSETS[d]; @@ -384,7 +438,7 @@ public record SwedishGenerator(int[] buff) { if (n > 0) { visitor.visit((rci.r << 8) | (rci.c << 4) | d, packedPos, n); } - } + }*/ } ArrayList extractSlots(Grid grid) { @@ -405,12 +459,10 @@ public record SwedishGenerator(int[] buff) { } long maskFitness(Grid grid, int[] lenCounts) { - long penalty = 0; + final long[] penalty = { 0 }; - var clueCount = 0; - for (var v : grid.g) if (Grid.isDigit(v)) clueCount++; - - penalty += 8L * Math.abs(clueCount - TARGET_CLUES); + var clueCount = grid.clueCount(); + penalty[0] += 8L * Math.abs(clueCount - TARGET_CLUES); var ctx = CTX.get(); var covH = ctx.covH; @@ -418,13 +470,12 @@ public record SwedishGenerator(int[] buff) { Arrays.fill(covH, 0, SIZE, 0); Arrays.fill(covV, 0, SIZE, 0); - boolean hasSlots = false; - for (var rci : IT) { - if (!grid.isDigitAt(rci.i)) continue; - var d = grid.digitAt(rci.i); + boolean[] hasSlots = {false}; + grid.forEachSetBit71(clueIdx -> { + var d = grid.digitAt(clueIdx); var nbrs16 = OFFSETS[d]; - int rr = rci.r + nbrs16.r, cc = rci.c + nbrs16.c; - if (rr < 0 || rr >= R || cc < 0 || cc >= C || grid.isDigitAt(rr, cc)) continue; + int rr = Grid.r(clueIdx) + nbrs16.r, cc = Grid.c(clueIdx) + nbrs16.c; + if (rr < 0 || rr >= R || cc < 0 || cc >= C || grid.isDigitAt(rr, cc)) return; long packedPos = 0; var n = 0; @@ -436,43 +487,49 @@ public record SwedishGenerator(int[] buff) { rr += nbrs16.dr; cc += nbrs16.dc; } - if (n == 0) continue; - hasSlots = true; + if (n == 0) return; + hasSlots[0] = true; if (n < MIN_LEN) { - penalty += 8000; + penalty[0] += 8000; } else { - if (lenCounts[n] <= 0) penalty += 12000; + if (lenCounts[n] <= 0) penalty[0] += 12000; } var horiz = Slot.horiz(d) ? covH : covV; for (var i = 0; i < n; i++) horiz[Slot.offset(packedPos, i)] += 1; - } + }); - if (!hasSlots) return 1_000_000_000L; + if (!hasSlots[0]) return 1_000_000_000L; + + /* grid.forEachSetBit71(clueIdx -> { + var h = covH[clueIdx]; + var v = covV[clueIdx]; + 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; + });*/ int idx, h, v; for (idx = 0; idx < SIZE; idx++) { if (grid.isDigitAt(idx)) continue; h = covH[idx]; v = covV[idx]; - if (h == 0 && v == 0) penalty += 1500; - else if (h > 0 && v > 0) { /* ok */ } else if (h + v == 1) penalty += 200; - else penalty += 600; + 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; } // clue clustering (8-connected) var seen = ctx.seen; seen.clear(); - var stack = ctx.stack; - int sp, nr, nc; - long size; - for (var rci : IT) { - idx = rci.i; - if (seen.get(idx) || grid.isLetterAt(idx)) continue; - sp = 0; - stack[sp++] = idx; - seen.set(idx); - size = 0; + 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]; @@ -480,19 +537,21 @@ public record SwedishGenerator(int[] buff) { size++; for (var d : nbrs8) { - nr = rr + d.r; - nc = cc + d.c; + var nr = rr + d.r; + var nc = cc + d.c; if (nr < 0 || nr >= R || nc < 0 || nc >= C) continue; - idx = Grid.offset(nr, nc); - if (seen.get(idx) || grid.isLetterAt(idx)) continue; - seen.set(idx); - stack[sp++] = idx; + var nidx = Grid.offset(nr, nc); + if (seen.get(nidx) || grid.isLetterAt(nidx)) continue; + seen.set(nidx); + stack[sp++] = nidx; } } - if (size >= 2) penalty += (size - 1L) * 120L; - } - int walls, wr, wc; + if (size >= 2) penalty[0] += (size - 1L) * 120L; + }); + int sp, nr, nc; + long size; + int walls, wr, wc; // dead-end-ish letter cell (3+ walls) for (var rci : IT) { if (grid.isDigitAt(rci.i)) continue; @@ -502,10 +561,10 @@ public record SwedishGenerator(int[] buff) { wc = rci.c + d.c; if (wr < 0 || wr >= R || wc < 0 || wc >= C || grid.isDigitAt(wr, wc)) walls++; } - if (walls >= 3) penalty += 400; + if (walls >= 3) penalty[0] += 400; } - return penalty; + return penalty[0]; } Grid randomMask(Rng rng) { @@ -518,12 +577,10 @@ public record SwedishGenerator(int[] buff) { if (g.isDigitAt(idx)) continue; var d = OFFSETS[rng.randbyte(1, 4)]; - g.setByteAt(idx, d.dbyte); - if (!hasRoomForClue(g, idx, d)) { - g.clear(idx); - continue; + if (hasRoomForClue(g, idx, d)) { + g.setClue(idx, d.dbyte); + placed++; } - placed++; } return g; } @@ -539,11 +596,10 @@ public record SwedishGenerator(int[] buff) { clamp(cy + (rng.randint(-2, 2) + rng.randint(-2, 2)), 0, C - 1)); if (g.isDigitAt(ri)) { - g.clear(ri); + g.clearClue(ri); } else { var d = OFFSETS[rng.randint(1, 4)]; - g.setByteAt(ri, d.dbyte); - if (!hasRoomForClue(g, ri, d)) g.clear(ri); + if (hasRoomForClue(g, ri, d)) g.setClue(ri, d.dbyte); } } return g; @@ -554,13 +610,15 @@ public record SwedishGenerator(int[] buff) { var nx = Math.cos(theta); var ny = Math.sin(theta); - for (var rci : IT) out.setByteAt(rci.i, ((rci.r - CROSS_X) * nx + (rci.c - CROSS_Y) * ny >= 0) ? a.byteAt(rci.i) : b.byteAt(rci.i)); - for (var rci : IT) if (out.isDigitAt(rci.i) && !hasRoomForClue(out, rci.i, OFFSETS[out.digitAt(rci.i)])) out.clear(rci.i); + for (var rci : IT) { + out.setAt(rci.i, ((rci.r - CROSS_X) * nx + (rci.c - CROSS_Y) * ny >= 0) ? a.byteAt(rci.i) : b.byteAt(rci.i)); + } + for (var rci : IT) if (out.isDigitAt(rci.i) && !hasRoomForClue(out, rci.i, OFFSETS[out.digitAt(rci.i)])) out.clearClue(rci.i); return out; } Grid hillclimb(Rng rng, Grid start, int[] lenCounts, int limit) { - var best = start;//.deepCopyGrid(); + var best = start;//.deepCopyGrid(); var bestF = maskFitness(best, lenCounts); var fails = 0; diff --git a/src/test/java/puzzle/ExportFormatTest.java b/src/test/java/puzzle/ExportFormatTest.java index 85c56cb..d9c09a9 100644 --- a/src/test/java/puzzle/ExportFormatTest.java +++ b/src/test/java/puzzle/ExportFormatTest.java @@ -21,7 +21,7 @@ public class ExportFormatTest { var grid = SwedishGenerator.makeEmptyGrid(); // Place a '2' (right) at (0,0) - grid.setByteAt(0, (byte) '2'); + grid.setClue(0, (byte) '2'); // This creates a slot starting at (0,1) var clueMap = new HashMap(); @@ -36,7 +36,7 @@ public class ExportFormatTest { grid.setByteAt(Grid.offset(0, 3), (byte) 'S'); grid.setByteAt(Grid.offset(0, 4), (byte) 'T'); // Terminate thGrid.offset(e slot at) (0,5) with another digit to avoid it extending to MAX_WORD_LENGTH - grid.setByteAt(Grid.offset(0, 5), (byte) '1'); + grid.setClue(Grid.offset(0, 5), (byte) '1'); var fillResult = new FillResult(true, new Gridded(grid), clueMap, null); var puzzleResult = new PuzzleResult(swe, null, null, fillResult); diff --git a/src/test/java/puzzle/MainTest.java b/src/test/java/puzzle/MainTest.java index 78880c2..175dbc0 100644 --- a/src/test/java/puzzle/MainTest.java +++ b/src/test/java/puzzle/MainTest.java @@ -28,7 +28,7 @@ public class MainTest { // Set up digits on the grid to create slots. // '2' (right) at (0,0) -> slot at (0,1), (0,2) - grid.setCharAt(0, 0, '2'); + grid.setClue(0, (byte) '2'); grid.setCharAt(0, 1, 'A'); grid.setCharAt(0, 2, 'B'); @@ -61,7 +61,7 @@ public class MainTest { void testForEachSlot() { var generator = new SwedishGenerator(); var grid = makeEmptyGrid(); - grid.setCharAt(0, 0, '2'); // right + grid.setClue(0, (byte) '2'); // right var count = new AtomicInteger(0); generator.forEachSlot(grid, (key, packedPos, len) -> { @@ -175,8 +175,8 @@ public class MainTest { // Regression baseline for seed search starting at 12347, pop 4, gens 20 Assertions.assertEquals(12347, foundSeed, "Found seed changed"); Assertions.assertEquals(20, res.filled().clueMap().size(), "Number of assigned words changed"); - Assertions.assertEquals(763.8, res.filled().simplicity(), 1e-9, "Simplicity value changed"); - Assertions.assertArrayEquals(new byte[]{ 'M', 'A', 'N', 'T', 'A' }, res.filled().clueMap().get(1377).word()); + Assertions.assertEquals(775.45, res.filled().simplicity(), 1e-9, "Simplicity value changed"); + Assertions.assertArrayEquals(new byte[]{ 'I', 'N', 'E', 'R', 'T' }, res.filled().clueMap().get(1377).word()); } @Test public void testIsLetterA() { diff --git a/src/test/java/puzzle/SwedishGeneratorTest.java b/src/test/java/puzzle/SwedishGeneratorTest.java index 4fd3d70..85d187a 100644 --- a/src/test/java/puzzle/SwedishGeneratorTest.java +++ b/src/test/java/puzzle/SwedishGeneratorTest.java @@ -191,7 +191,7 @@ public class SwedishGeneratorTest { var grid = SwedishGenerator.makeEmptyGrid(); // 3x3 grid (Config.PUZZLE_ROWS/COLS are 3 in test env) // Set '2' (right) at 0,0 - grid.setCharAt(0, 0, '2'); + grid.setClue(0, (byte) '2'); // This should detect a slot starting at 0,1 with length 2 (0,1 and 0,2) var slots = gen.extractSlots(grid); @@ -226,7 +226,7 @@ public class SwedishGeneratorTest { assertTrue(f1 >= 1_000_000_000L); // Add a slot - grid.setCharAt(0, 0, '2'); + grid.setClue(0, SwedishGenerator.OFFSETS[2].dbyte()); var f2 = gen.maskFitness(grid, lenCounts); assertTrue(f2 < f1); }