From effb1145d42d0f2dc1e027d707799e7ca1a270ef Mon Sep 17 00:00:00 2001 From: mike Date: Tue, 6 Jan 2026 02:57:17 +0100 Subject: [PATCH] Gather data --- src/main/java/puzzle/ExportFormat.java | 2 +- src/main/java/puzzle/Main.java | 27 +++++---- src/main/java/puzzle/SwedishGenerator.java | 41 ++++++------- src/test/java/puzzle/MainTest.java | 70 ++++++++++++++++------ 4 files changed, 89 insertions(+), 51 deletions(-) diff --git a/src/main/java/puzzle/ExportFormat.java b/src/main/java/puzzle/ExportFormat.java index ae7be0b..4c5da3a 100644 --- a/src/main/java/puzzle/ExportFormat.java +++ b/src/main/java/puzzle/ExportFormat.java @@ -32,7 +32,7 @@ public final class ExportFormat { // 1) extract "placed" list from all clue digits in the filled grid var placed = new ArrayList(); - var allSlots = extractSlots(g); + var allSlots = puz.swe().extractSlots(g); var clueMap = puz.filled().clueMap(); for (var s : allSlots) { diff --git a/src/main/java/puzzle/Main.java b/src/main/java/puzzle/Main.java index 4fa9c0a..62a7eee 100644 --- a/src/main/java/puzzle/Main.java +++ b/src/main/java/puzzle/Main.java @@ -14,7 +14,6 @@ import java.util.concurrent.*; import java.util.stream.Collectors; import static puzzle.SwedishGenerator.*; -import static puzzle.SwedishGenerator.fillMask; import static puzzle.SwedishGenerator.loadWords; public class Main { @@ -41,6 +40,8 @@ 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) { @@ -73,13 +74,13 @@ public class Main { info(String.format(Locale.ROOT, "simplicity : %.2f", res.filled().simplicity())); section("Mask"); - System.out.print(indentLines(gridToString(res.mask()), " ")); + System.out.print(indentLines(res.swe().gridToString(res.mask()), " ")); section("Grid (raw)"); - System.out.print(indentLines(gridToString(res.filled().grid()), " ")); + System.out.print(indentLines(res.swe().gridToString(res.filled().grid()), " ")); section("Grid (human)"); - System.out.print(indentLines(renderHuman(res.filled().grid()), " ")); + System.out.print(indentLines(res.swe().renderHuman(res.filled().grid()), " ")); var exported = ExportFormat.exportFormatFromFilled(res, 1, new ExportFormat.Rewards(50, 2, 1)); @@ -167,7 +168,7 @@ public class Main { return s.substring(0, Math.max(0, max - 1)) + "…"; } - private static String indentLines(String s, String indent) { + static String indentLines(String s, String indent) { if (s == null || s.isEmpty()) return ""; var lines = s.split("\\R", -1); var sb = new StringBuilder(); @@ -245,8 +246,7 @@ public class Main { section("Search"); - var deadline = System.currentTimeMillis() + 40_000; - + var deadline = System.currentTimeMillis() + 40_000; if (opts.threads > 1) { info("mode : multi-threaded (" + opts.threads + ")"); @@ -258,7 +258,7 @@ public class Main { // Keep at least some tasks in flight for (int i = 0; i < opts.threads; i++) { final int attempt = ++submitted; - completionService.submit(() -> attempt(new Rng(opts.seed + attempt), dict, opts )); + completionService.submit(() -> attempt(new Rng(opts.seed + attempt), dict, opts)); } while (System.currentTimeMillis() < deadline) { @@ -274,7 +274,7 @@ public class Main { // Submit another task if we still have time if (System.currentTimeMillis() < deadline) { final int attempt = ++submitted; - completionService.submit(() -> attempt(new Rng(opts.seed + attempt), dict, opts )); + completionService.submit(() -> attempt(new Rng(opts.seed + attempt), dict, opts)); } } warn("status : UNSOLVED (timeout)"); @@ -310,12 +310,13 @@ public class Main { } } - static PuzzleResult attempt(Rng rng, Dict dict, Opts opts ) { - var mask = SwedishGenerator.generateMask(rng, dict.lenCounts(), opts.pop, opts.gens, opts.verbose); - var filled = fillMask(rng, mask, dict.index(), 200, opts.fillTimeout, opts.verbose); + static PuzzleResult attempt(Rng rng, Dict dict, Opts opts) { + var swe = new SwedishGenerator(opts.W, opts.H); + 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); if (filled.ok() && (opts.minSimplicity <= 0 || filled.simplicity() >= opts.minSimplicity)) { - return new PuzzleResult(dict, mask, filled); + return new PuzzleResult(swe, dict, mask, filled); } if (opts.verbose && filled.ok()) { diff --git a/src/main/java/puzzle/SwedishGenerator.java b/src/main/java/puzzle/SwedishGenerator.java index a55f876..70bbd82 100644 --- a/src/main/java/puzzle/SwedishGenerator.java +++ b/src/main/java/puzzle/SwedishGenerator.java @@ -15,13 +15,14 @@ import java.util.function.Predicate; * java SwedishGenerator [--seed N] [--pop N] [--gens N] [--tries N] [--words word-list.txt] */ @SuppressWarnings("ALL") -public class SwedishGenerator { +public record SwedishGenerator(int W, int H, int SIZE, int MAX_LEN) { - static final int W = 9, H = 8, - SIZE = W * H, - CLUE_SIZE = 4, + public SwedishGenerator(int W,int H) { this(W, H, W * H, Math.min(W, H)); } + public SwedishGenerator() { this(9, 8); } + + static final int CLUE_SIZE = 4, SIMPLICITY_DEFAULT_SCORE = 2; - static final int MIN_LEN = 2, MAX_LEN = 8; + static final int MIN_LEN = 2; // Directions for '1'..'6' static final int[][] OFFSETS = new int[7][2]; static final int[][] STEPS = new int[7][2]; @@ -81,19 +82,19 @@ public class SwedishGenerator { static int clamp(int x, int a, int b) { return Math.max(a, Math.min(b, x)); } // ---------------- Grid helpers ---------------- - static char[][] makeEmptyGrid() { + char[][] makeEmptyGrid() { var g = new char[H][W]; for (var r = 0; r < H; r++) Arrays.fill(g[r], '#'); return g; } - static char[][] deepCopyGrid(char[][] g) { + char[][] deepCopyGrid(char[][] g) { var out = new char[H][W]; for (var r = 0; r < H; r++) out[r] = Arrays.copyOf(g[r], W); return out; } - static String gridToString(char[][] g) { + String gridToString(char[][] g) { var sb = new StringBuilder(); for (var r = 0; r < H; r++) { if (r > 0) sb.append('\n'); @@ -102,7 +103,7 @@ public class SwedishGenerator { return sb.toString(); } - static String renderHuman(char[][] g) { + public String renderHuman(char[][] g) { var sb = new StringBuilder(); for (var r = 0; r < H; r++) { if (r > 0) sb.append('\n'); @@ -295,7 +296,7 @@ public class SwedishGenerator { public Slot(int clueR, int clueC, char dir, int[] rs, int[] cs) { this(clueR + "," + clueC + ":" + dir, clueR, clueC, dir, rs, cs, rs.length); } } - static ArrayList extractSlots(char[][] grid) { + ArrayList extractSlots(char[][] grid) { var slots = new ArrayList(); for (var r = 0; r < H; r++) { for (var c = 0; c < W; c++) { @@ -332,7 +333,7 @@ public class SwedishGenerator { } return slots; } - static boolean hasRoomForClue(char[][] grid, int r, int c, char d) { + boolean hasRoomForClue(char[][] grid, int r, int c, char d) { var di = d - '0'; int or = OFFSETS[di][0], oc = OFFSETS[di][1]; int dr = STEPS[di][0], dc = STEPS[di][1]; @@ -353,7 +354,7 @@ public class SwedishGenerator { { 1, -1 }, { 1, 0 }, { 1, 1 } }; static final int[][] nbrs4 = new int[][]{ { -1, 0 }, { 1, 0 }, { 0, -1 }, { 0, 1 } }; - static long maskFitness(char[][] grid, int[] lenCounts) { + long maskFitness(char[][] grid, int[] lenCounts) { long penalty = 0; var clueCount = 0; @@ -447,7 +448,7 @@ public class SwedishGenerator { // ---------------- Mask generation ---------------- - static char[][] randomMask(Rng rng) { + char[][] randomMask(Rng rng) { var g = makeEmptyGrid(); var targetClues = (int) Math.round(SIZE * 0.25); int placed = 0, guard = 0; @@ -468,7 +469,7 @@ public class SwedishGenerator { return g; } - static char[][] mutate(Rng rng, char[][] grid) { + char[][] mutate(Rng rng, char[][] grid) { var g = deepCopyGrid(grid); var cx = rng.randint(0, H - 1); var cy = rng.randint(0, W - 1); @@ -490,7 +491,7 @@ public class SwedishGenerator { return g; } - static char[][] crossover(Rng rng, char[][] a, char[][] b) { + char[][] crossover(Rng rng, char[][] a, char[][] b) { var out = makeEmptyGrid(); var cx = (H - 1) / 2.0; var cy = (W - 1) / 2.0; @@ -513,7 +514,7 @@ public class SwedishGenerator { return out; } - static char[][] hillclimb(Rng rng, char[][] start, int[] lenCounts, int limit) { + char[][] hillclimb(Rng rng, char[][] start, int[] lenCounts, int limit) { var best = deepCopyGrid(start); var bestF = maskFitness(best, lenCounts); var fails = 0; @@ -532,13 +533,13 @@ public class SwedishGenerator { return best; } - static double similarity(char[][] a, char[][] b) { + double similarity(char[][] a, char[][] b) { var same = 0; for (var r = 0; r < H; r++) for (var c = 0; c < W; c++) if (a[r][c] == b[r][c]) same++; return same / (double) (W * H); } - static char[][] generateMask(Rng rng, int[] lenCounts, int popSize, int gens, boolean verbose) { + public char[][] generateMask(Rng rng, int[] lenCounts, int popSize, int gens, boolean verbose) { if (verbose) System.out.println("generateMask init pop: " + popSize); var pop = new ArrayList(); @@ -660,7 +661,7 @@ public class SwedishGenerator { record Pick(Slot slot, CandidateInfo info, boolean done) { } - static FillResult fillMask(Rng rng, char[][] mask, DictEntry[] dictIndex, + public FillResult fillMask(Rng rng, char[][] mask, DictEntry[] dictIndex, int logEveryMs, int timeLimitMs, boolean verbose) { var grid = deepCopyGrid(mask); @@ -854,6 +855,6 @@ public class SwedishGenerator { } // ---------------- Top-level generatePuzzle ---------------- - public record PuzzleResult(Dict dict, char[][] mask, FillResult filled) { } + public record PuzzleResult(SwedishGenerator swe, Dict dict, char[][] mask, FillResult filled) { } } diff --git a/src/test/java/puzzle/MainTest.java b/src/test/java/puzzle/MainTest.java index b45c4d8..6d7a7a8 100644 --- a/src/test/java/puzzle/MainTest.java +++ b/src/test/java/puzzle/MainTest.java @@ -1,32 +1,68 @@ package puzzle; -//import org.junit.jupiter.api.Test; -//import static org.junit.jupiter.api.Assertions.*; +import puzzle.SwedishGenerator.Dict; +import puzzle.SwedishGenerator.Lemma; +import puzzle.SwedishGenerator.PuzzleResult; +import puzzle.SwedishGenerator.Rng; +import static puzzle.Main.indentLines; public class MainTest { - static void main() { - new MainTest().testGeneratePuzzle(); + public static void main(String[] args) { + new MainTest().testAttempt(); } - // @Test - public void testGeneratePuzzle() { + + public void testAttempt() { // Arrange var opts = new Main.Opts(); - opts.seed = 1234; - opts.pop = 18; - opts.gens = 300; - opts.wordsPath = "src/test/resources/puzzle/pool.txt"; + //seed=1811328180 + opts.seed = -1645461655;// (int) (System.nanoTime() ^ System.currentTimeMillis()); + opts.pop = 18; // Small for micro-scale + opts.gens = 200; opts.minSimplicity = 0; + opts.fillTimeout = 20_000; 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 + // But SwedishGenerator.loadWords(path) is what we have. + // Let's try to load a real one or a small subset if possible. + var dict = new Dict(new Lemma[]{ + new Lemma(0, "NU", 1, 100, "NU"), + new Lemma(1, "ET", 2, 100, "ET"), + new Lemma(2, "NUT", 3, 1001, "NUT"), + new Lemma(3, "ETE", 4, 100, "ETE"), + new Lemma(4, "IK", 5, 100, "IK"), + new Lemma(5, "IN", 6, 100, "IN"), + new Lemma(6, "AU", 7, 100, "AU"), + new Lemma(7, "JE", 8, 100, "JE"), + new Lemma(8, "AI", 9, 100, "AI"), + new Lemma(9, "NA", 10, 100, "NA"), + new Lemma(10, "AF", 11, 100, "AF"), + new Lemma(11, "AL", 14, 1001, "AL"), + new Lemma(12, "EA", 15, 100, "EA"), + new Lemma(13, "AV", 18, 100, "AV"), + new Lemma(14, "IL", 19, 100, "IL"), + new Lemma(15, "EN", 22, 100, "EN") + }); // Act - var result = new Main().generatePuzzle(opts); - - // Assert - /* assertNotNull(result); - assertNotNull(result.mask()); - assertNotNull(result.filled()); - assertTrue(result.filled().ok);*/ + for (int i = 0; i < 200; i++) { + int seed = opts.seed + i; + var rng = new Rng(seed); + PuzzleResult res = Main.attempt(rng, dict, opts); + // Assert + if (res != null && res.filled().ok()) { + System.out.println("Test Passed: Puzzle generated"); + System.out.println("Seed: " + seed); + System.out.print(indentLines(res.swe().renderHuman(res.filled().grid()), " ")); + return; + } + } + System.out.println("Test Note: Puzzle not generated in 1 attempt (this is possible depending on RNG)"); } }