From 2680e70418f2c830d89b4c68b5cc03c361d3c271 Mon Sep 17 00:00:00 2001 From: mike Date: Thu, 8 Jan 2026 00:37:54 +0100 Subject: [PATCH] Gather data --- src/main/java/puzzle/ExportFormat.java | 2 - src/main/java/puzzle/Main.java | 9 +-- src/main/java/puzzle/SwedishGenerator.java | 73 +++++++++---------- src/test/java/puzzle/MainTest.java | 73 +++++++++++++++++-- src/test/java/puzzle/SlotTest.java | 18 ----- .../java/puzzle/SwedishGeneratorTest.java | 63 ---------------- 6 files changed, 105 insertions(+), 133 deletions(-) delete mode 100644 src/test/java/puzzle/SlotTest.java delete mode 100644 src/test/java/puzzle/SwedishGeneratorTest.java diff --git a/src/main/java/puzzle/ExportFormat.java b/src/main/java/puzzle/ExportFormat.java index 0443366..f575608 100644 --- a/src/main/java/puzzle/ExportFormat.java +++ b/src/main/java/puzzle/ExportFormat.java @@ -26,8 +26,6 @@ public final class ExportFormat { public static ExportedPuzzle exportFormatFromFilled(PuzzleResult puz, int difficulty, Rewards rewards) { Objects.requireNonNull(puz, "puz"); var g = puz.filled().grid(); - var H = g.H(); - var W = g.W(); // 1) extract "placed" list from all clue digits in the filled grid var placed = new ArrayList(); diff --git a/src/main/java/puzzle/Main.java b/src/main/java/puzzle/Main.java index 7313afb..cecbd39 100644 --- a/src/main/java/puzzle/Main.java +++ b/src/main/java/puzzle/Main.java @@ -32,6 +32,7 @@ public class Main { @Data public static class Opts { + public int seed = (int) (System.nanoTime() ^ System.currentTimeMillis()); public int pop = 18; public int gens = 500; @@ -42,8 +43,6 @@ public class Main { public boolean reindex = false; public int fillTimeout = 20_000; public boolean verbose = false; - public int W = 9; - public int H = 8; } public void main(String[] args) { @@ -159,7 +158,7 @@ public class Main { safe(w.direction(), 3), fmtPoint(w.startRow(), w.startCol()), fmtPoint(w.arrowRow(), w.arrowCol()), - Arrays.toString(w.lemma().clue().toArray(String[]::new))); + Arrays.toString(w.lemma().clue().toArray(String[]::new))); } } @@ -312,7 +311,7 @@ public class Main { } static PuzzleResult attempt(Rng rng, Dict dict, Opts opts) { - var swe = new SwedishGenerator(opts.W, opts.H); + var swe = new SwedishGenerator(); var mask = swe.generateMask(rng, dict.lenCounts(), opts.pop, opts.gens, opts.verbose); var filled = swe.fillMask(rng, mask, dict.index(), 200, opts.fillTimeout, opts.verbose); @@ -352,7 +351,7 @@ public class Main { sb.append(" \"words\": [\n"); for (var i = 0; i < puzzle.words().length; i++) { var w = puzzle.words()[i]; - var clues = w. clue().toArray(String[]::new); + var clues = w.clue().toArray(String[]::new); Arrays.sort(clues, Comparator.comparingInt(String::length)); sb.append(" {\n"); sb.append(" \"word\": \"").append(escapeJson(w.word())).append("\",\n"); diff --git a/src/main/java/puzzle/SwedishGenerator.java b/src/main/java/puzzle/SwedishGenerator.java index 8bd0b54..755c89d 100644 --- a/src/main/java/puzzle/SwedishGenerator.java +++ b/src/main/java/puzzle/SwedishGenerator.java @@ -1,17 +1,13 @@ package puzzle; -import lombok.experimental.Accessors; import lombok.*; -import javax.naming.Context; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.util.*; import java.util.concurrent.atomic.AtomicLong; -import java.util.function.Predicate; import java.util.function.Supplier; -import java.util.stream.Stream; /** * SwedishGenerator.java @@ -21,23 +17,29 @@ import java.util.stream.Stream; * java SwedishGenerator [--seed N] [--pop N] [--gens N] [--tries N] [--words word-list.txt] */ @SuppressWarnings("ALL") -public record SwedishGenerator(int W, int H, int SIZE, int MAX_LEN, int[] buff) { +public record SwedishGenerator(int[] buff) { - public static final char C_DASH = '\0'; - static final byte _1 = 49, _9 = 57, A = 65, Z = 90, DASH = (byte) C_DASH; + static final int W = Config.PUZZLE_COLS; + static final int H = Config.PUZZLE_ROWS; + static final int SIZE = W * H; + static final int MAX_WORD_LENGTH = Math.min(W, H); + static final int MIN_LEN = Config.MIN_LEN; + static final int CLUE_SIZE = Config.CLUE_SIZE; + static final int SIMPLICITY_DEFAULT_SCORE = 2; + static final int MAX_TRIES_PER_SLOT = Config.MAX_TRIES_PER_SLOT; + static final char C_DASH = '\0'; + static final byte _1 = 49, _9 = 57, A = 65, Z = 90, DASH = (byte) C_DASH; + static final ThreadLocal CTX = ThreadLocal.withInitial(Context::new); + static boolean isLetter(char ch) { return ch >= 'A' && ch <= 'Z'; } + static int clamp(int x, int a, int b) { return Math.max(a, Math.min(b, x)); } + static record CandidateInfo(int[] indices, int count) { } static record nbrs_8(int x, int y) { } + public SwedishGenerator() { this(new int[8124]); } - public SwedishGenerator(int W, int H) { this(W, H, W * H, Math.min(W, H), new int[10000]); } - public SwedishGenerator() { this(9, 8); } - - static final int CLUE_SIZE = 4, - SIMPLICITY_DEFAULT_SCORE = 2; - static final int MIN_LEN = 2; - static final int MAX_TRIES_PER_SLOT = 2000; // Directions for '1'..'6' - static final nbrs_8[] OFFSETS = new nbrs_8[7]; - static final nbrs_8[] STEPS = new nbrs_8[7]; + static final nbrs_8[] OFFSETS = new nbrs_8[7]; + static final nbrs_8[] STEPS = new nbrs_8[7]; static { // 1: up OFFSETS[1] = new nbrs_8(-1, 0); @@ -58,7 +60,7 @@ public record SwedishGenerator(int W, int H, int SIZE, int MAX_LEN, int[] buff) OFFSETS[6] = new nbrs_8(0, 1); STEPS[6] = new nbrs_8(1, 0); } - final static nbrs_8[] nbrs8 = new nbrs_8[]{ + final static nbrs_8[] nbrs8 = new nbrs_8[]{ new nbrs_8(-1, -1), new nbrs_8(-1, 0), new nbrs_8(-1, 1), @@ -68,14 +70,12 @@ public record SwedishGenerator(int W, int H, int SIZE, int MAX_LEN, int[] buff) new nbrs_8(1, 0), new nbrs_8(1, 1) }; - static final nbrs_8[] nbrs4 = new nbrs_8[]{ + static final nbrs_8[] nbrs4 = new nbrs_8[]{ new nbrs_8(-1, 0), new nbrs_8(1, 0), new nbrs_8(0, -1), new nbrs_8(0, 1) }; - static final char FIRST_ABC = 'A'; - static final char LAST_ABC = 'Z'; static record Context(int[] covH, int[] covV, @@ -89,10 +89,6 @@ public record SwedishGenerator(int W, int H, int SIZE, int MAX_LEN, int[] buff) public Context() { this(new int[256], new int[256], new int[256], new int[256], new BitSet(256), new char[32], new IntList[32], new long[2048]); } } - static final ThreadLocal CTX = ThreadLocal.withInitial(Context::new); - - static boolean isLetter(char ch) { return ch >= FIRST_ABC && ch <= LAST_ABC; } - static final class Rng { @Getter private int x; @@ -117,12 +113,9 @@ public record SwedishGenerator(int W, int H, int SIZE, int MAX_LEN, int[] buff) double nextFloat() { return (nextU32() & 0xFFFFFFFFL) / 4294967295.0; } } - static int clamp(int x, int a, int b) { return Math.max(a, Math.min(b, x)); } - static record CandidateInfo(int[] indices, int count) { } - - record Grid(byte[] g, int W) { + record Grid(byte[] g) { - Grid deepCopyGrid() { return new Grid(g.clone(), W); } + Grid deepCopyGrid() { return new Grid(g.clone()); } private int offset(int r, int c) { return r * W + c; } boolean isLettercell(int r, int c) { return !isDigitAt(r, c); } char getCharAt(int r, int c) { return (char) (g[offset(r, c)]); } @@ -135,7 +128,7 @@ public record SwedishGenerator(int W, int H, int SIZE, int MAX_LEN, int[] buff) return g.length / W; } } - Grid makeEmptyGrid() { return new Grid(new byte[SIZE], W); } + static Grid makeEmptyGrid() { return new Grid(new byte[SIZE]); } String gridToString(Grid g) { var sb = new StringBuilder(); @@ -364,14 +357,13 @@ public record SwedishGenerator(int W, int H, int SIZE, int MAX_LEN, int[] buff) long packedCs = 0; var n = 0; - while (rr >= 0 && rr < H && cc >= 0 && cc < W) { + while (rr >= 0 && rr < H && cc >= 0 && cc < W && n < MAX_WORD_LENGTH) { if (grid.isDigitAt(rr, cc)) break; packedRs |= (long) rr << (n << 2); packedCs |= (long) cc << (n << 2); n++; rr += dr; cc += dc; - if (n >= MAX_LEN) break; } if (n > 0) { visitor.visit((r << 8) | (c << 4) | d, packedRs, packedCs, n); @@ -392,12 +384,13 @@ public record SwedishGenerator(int W, int H, int SIZE, int MAX_LEN, int[] buff) int dr = STEPS[di].x, dc = STEPS[di].y; int rr = r + or, cc = c + oc; var run = 0; - while (rr >= 0 && rr < H && cc >= 0 && cc < W && (grid.isLettercell(rr, cc)) && run < MAX_LEN) { + while (rr >= 0 && rr < H && cc >= 0 && cc < W && (grid.isLettercell(rr, cc)) && run < MAX_WORD_LENGTH) { run++; rr += dr; cc += dc; + if (run >= MIN_LEN) return true; } - return run >= MIN_LEN; + return false; } long maskFitness(Grid grid, int[] lenCounts) { @@ -431,14 +424,13 @@ public record SwedishGenerator(int W, int H, int SIZE, int MAX_LEN, int[] buff) long packedCs = 0; var n = 0; - while (rr >= 0 && rr < H && cc >= 0 && cc < W) { + while (rr >= 0 && rr < H && cc >= 0 && cc < W && n < MAX_WORD_LENGTH) { if (grid.isDigitAt(rr, cc)) break; packedRs |= (long) rr << (n << 2); packedCs |= (long) cc << (n << 2); n++; rr += dr; cc += dc; - if (n >= MAX_LEN) break; } if (n == 0) continue; hasSlots = true; @@ -667,6 +659,7 @@ public record SwedishGenerator(int W, int H, int SIZE, int MAX_LEN, int[] buff) @Data public static final class FillStats { + public long nodes; public long backtracks; public double seconds; @@ -678,10 +671,10 @@ public record SwedishGenerator(int W, int H, int SIZE, int MAX_LEN, int[] buff) boolean done) { } public static record FillResult(boolean ok, - Grid grid, - HashMap clueMap, - FillStats stats, - double simplicity) { + Grid grid, + HashMap clueMap, + FillStats stats, + double simplicity) { public FillResult(boolean ok, Grid grid, HashMap assigned, FillStats stats) { double totalSimplicity = 0; diff --git a/src/test/java/puzzle/MainTest.java b/src/test/java/puzzle/MainTest.java index fce71da..c371ed9 100644 --- a/src/test/java/puzzle/MainTest.java +++ b/src/test/java/puzzle/MainTest.java @@ -1,12 +1,18 @@ package puzzle; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import puzzle.SwedishGenerator.Dict; import puzzle.SwedishGenerator.Grid; import puzzle.SwedishGenerator.Lemma; import puzzle.SwedishGenerator.PuzzleResult; import puzzle.SwedishGenerator.Rng; +import puzzle.SwedishGenerator.Slot; +import java.util.concurrent.atomic.AtomicInteger; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; import static puzzle.Main.indentLines; import static puzzle.SwedishGenerator.C_DASH; @@ -19,10 +25,68 @@ public class MainTest { t.testMini(); t.testAttempt(); } + @Test + void testExtractSlots() { + var generator = new SwedishGenerator(); + var grid = SwedishGenerator.makeEmptyGrid(); + + // 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.setCharAt(0, 1, 'A'); + grid.setCharAt(0, 2, 'B'); + + var slots = generator.extractSlots(grid); + assertEquals(1, slots.size()); + var s = slots.get(0); + assertEquals(8, s.len()); + assertEquals(0, s.r(0)); + assertEquals(1, s.c(0)); + assertEquals(0, s.r(1)); + assertEquals(2, s.c(1)); + } + @Test + void testStaticSlotMethods() { + // Test static r and c extraction + // packedRs: 1 at index 0, 2 at index 1 + var packedRs = (1L << 0) | (2L << 4); + assertEquals(1, Slot.r(packedRs, 0)); + assertEquals(2, Slot.r(packedRs, 1)); + + // Test static horiz + // dir 2 (right) is horizontal + assertTrue(Slot.horiz(2)); + // dir 3 (down) is vertical + assertFalse(Slot.horiz(3)); + } + + @Test + void testForEachSlot() { + var generator = new SwedishGenerator(); + var grid = SwedishGenerator.makeEmptyGrid(); + grid.setCharAt(0, 0, '2'); // right + + var count = new AtomicInteger(0); + generator.forEachSlot(grid, (key, rs, cs, len) -> { + count.incrementAndGet(); + assertEquals(8, len); + assertEquals(0, Slot.r(rs, 0)); + assertEquals(1, Slot.c(cs, 0)); + }); + assertEquals(1, count.get()); + } + @Test + public void testHoriz() { + assertTrue(new Slot(2, 0L, 0L, 1).horiz()); + assertTrue(new Slot(4, 0L, 0L, 1).horiz()); + assertFalse(new Slot(1, 0L, 3L, 1).horiz()); + assertFalse(new Slot(3, 0L, 3L, 1).horiz()); + assertFalse(new Slot(5, 0L, 3L, 1).horiz()); + } @Test public void testGridBasics() { - var grid = new Grid(new byte[3 * 4], 4); + var grid = SwedishGenerator.makeEmptyGrid(); // Initialize with # for (int r = 0; r < 3; r++) { for (int c = 0; c < 4; c++) { @@ -61,7 +125,7 @@ public class MainTest { @Test public void testGridDeepCopy() { - var grid = new Grid(new byte[2 * 2], 2); + var grid = SwedishGenerator.makeEmptyGrid(); grid.setCharAt(0, 0, 'A'); grid.setCharAt(0, 1, 'B'); grid.setCharAt(1, 0, 'C'); @@ -77,11 +141,12 @@ public class MainTest { @Test public void testMini() { - var grid = new Grid(new byte[3 * 3], 3); + var grid = SwedishGenerator.makeEmptyGrid(); grid.setCharAt(1, 1, '1'); Assertions.assertTrue(grid.isDigitAt(1, 1)); } @Test + @Disabled public void testAttempt() { // Arrange var opts = new Main.Opts(); @@ -94,8 +159,6 @@ public class MainTest { opts.threads = 1; opts.tries = 1; opts.verbose = true; - opts.W = 3; - opts.H = 3; // We need a small dictionary for testing // Instead of loading from file, we might want a way to create a mock Dict diff --git a/src/test/java/puzzle/SlotTest.java b/src/test/java/puzzle/SlotTest.java deleted file mode 100644 index 5f9c133..0000000 --- a/src/test/java/puzzle/SlotTest.java +++ /dev/null @@ -1,18 +0,0 @@ -package puzzle; - -import org.junit.jupiter.api.Test; -import puzzle.SwedishGenerator.Slot; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -class SlotTest { - - @Test - public void testHoriz() { - assertTrue(new Slot(2, 0L, 0L ,1).horiz()); - assertTrue(new Slot(4, 0L, 0L ,1).horiz()); - assertFalse(new Slot(1, 0L, 3L ,1).horiz()); - assertFalse(new Slot(3, 0L, 3L ,1).horiz()); - assertFalse(new Slot(5, 0L, 3L ,1).horiz()); - } -} \ No newline at end of file diff --git a/src/test/java/puzzle/SwedishGeneratorTest.java b/src/test/java/puzzle/SwedishGeneratorTest.java deleted file mode 100644 index dc4c3c4..0000000 --- a/src/test/java/puzzle/SwedishGeneratorTest.java +++ /dev/null @@ -1,63 +0,0 @@ -package puzzle; - -import org.junit.jupiter.api.Test; -import puzzle.SwedishGenerator.Grid; -import puzzle.SwedishGenerator.Slot; -import java.util.ArrayList; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -class SwedishGeneratorTest { - - @Test - void testExtractSlots() { - SwedishGenerator generator = new SwedishGenerator(3, 3); - Grid grid = generator.makeEmptyGrid(); - - // 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.setCharAt(0, 1, 'A'); - grid.setCharAt(0, 2, 'B'); - - ArrayList slots = generator.extractSlots(grid); - assertEquals(1, slots.size()); - Slot s = slots.get(0); - assertEquals(2, s.len()); - assertEquals(0, s.r(0)); - assertEquals(1, s.c(0)); - assertEquals(0, s.r(1)); - assertEquals(2, s.c(1)); - } - - @Test - void testStaticSlotMethods() { - // Test static r and c extraction - // packedRs: 1 at index 0, 2 at index 1 - long packedRs = (1L << 0) | (2L << 4); - assertEquals(1, Slot.r(packedRs, 0)); - assertEquals(2, Slot.r(packedRs, 1)); - - // Test static horiz - // dir 2 (right) is horizontal - assertTrue(Slot.horiz(2)); - // dir 3 (down) is vertical - assertTrue(!Slot.horiz(3)); - } - - @Test - void testForEachSlot() { - SwedishGenerator generator = new SwedishGenerator(3, 3); - Grid grid = generator.makeEmptyGrid(); - grid.setCharAt(0, 0, '2'); // right - - java.util.concurrent.atomic.AtomicInteger count = new java.util.concurrent.atomic.AtomicInteger(0); - generator.forEachSlot(grid, (key, rs, cs, len) -> { - count.incrementAndGet(); - assertEquals(2, len); - assertEquals(0, Slot.r(rs, 0)); - assertEquals(1, Slot.c(cs, 0)); - }); - assertEquals(1, count.get()); - } -} \ No newline at end of file