diff --git a/src/main/java/puzzle/Main.java b/src/main/java/puzzle/Main.java index 28198fb..5794184 100644 --- a/src/main/java/puzzle/Main.java +++ b/src/main/java/puzzle/Main.java @@ -248,7 +248,7 @@ public class Main { var tLoad1 = System.nanoTime(); section("Load"); - info(String.format(Locale.ROOT, "words : %,d", dict.length() )); + info(String.format(Locale.ROOT, "words : %,d", dict.length())); info(String.format(Locale.ROOT, "loadTime : %.3f s", (tLoad1 - tLoad0) / 1e9)); section("Search"); @@ -339,8 +339,8 @@ public class Main { static PuzzleResult attempt(Rng rng, Dict dict, Opts opts) { TOTAL_ATTEMPTS.incrementAndGet(); - var swe = new SwedishGenerator(); - var mask = swe.generateMask(rng, opts.pop, opts.gens); + var swe = new SwedishGenerator(rng); + var mask = swe.generateMask(opts.pop, opts.gens); var filled = new CSP(rng).fillMask(mask, dict.index(), 200, opts.fillTimeout); TOTAL_NODES.addAndGet(filled.stats().nodes); diff --git a/src/main/java/puzzle/SwedishGenerator.java b/src/main/java/puzzle/SwedishGenerator.java index 70b3ace..3023917 100644 --- a/src/main/java/puzzle/SwedishGenerator.java +++ b/src/main/java/puzzle/SwedishGenerator.java @@ -28,7 +28,7 @@ import java.util.function.Supplier; * java SwedishGenerator [--seed N] [--pop N] [--gens N] [--tries N] [--words word-list.txt] */ @SuppressWarnings("ALL") -public record SwedishGenerator() { +public record SwedishGenerator(Rng rng) { record CandidateInfo(int[] indices, int count) { } @@ -36,11 +36,11 @@ public record SwedishGenerator() { @FunctionalInterface interface SlotVisitor { void visit(int key, long packedPos, int len); } //@formatter:on static final int BAR_LEN = 22; - static final int C = Config.PUZZLE_COLS; - static final double CROSS_Y = (C - 1) / 2.0; - 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 int C = Config.PUZZLE_COLS; + static final double CROSS_R = (C - 1) / 2.0; + static final int R = Config.PUZZLE_ROWS; + static final double CROSS_C = (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 = C <= R ? C : R; @@ -237,7 +237,7 @@ public record SwedishGenerator() { return same / SIZED; } int clueCount() { return Long.bitCount(bo[0]) + Long.bitCount(bo[1]); } - void forEachSetBit71() { forEachSetBit71(null); } + void forEachSetBit71(IntConsumer consumer) { for (var lo = bo[0]; lo != 0; lo &= lo - 1) consumer.accept(Long.numberOfTrailingZeros(lo)); for (var hi = bo[1]; hi != 0; hi &= hi - 1) consumer.accept(64 + Long.numberOfTrailingZeros(hi)); @@ -285,7 +285,7 @@ public record SwedishGenerator() { static final Gson GSON = new Gson(); public Dict(Lemma[] wordz) { - var index = new DictEntry[MAX_WORD_LENGTH_PLUS_ONE]; + var index = new DictEntry[MAX_WORD_LENGTH_PLUS_ONE]; Arrays.setAll(index, i -> new DictEntry(i)); for (var lemma : wordz) { var L = lemma.word.length; @@ -385,7 +385,7 @@ public record SwedishGenerator() { public static int offset(long packedPos, int i) { return (int) ((packedPos >> (i * 7)) & 127); } } - private static void processSlot(Grid grid, SwedishGenerator.SlotVisitor visitor, int idx) { + private static void processSlot(Grid grid, SlotVisitor visitor, int 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; @@ -544,7 +544,7 @@ public record SwedishGenerator() { return penalty[0]; } - Grid randomMask(Rng rng) { + Grid randomMask() { var g = Grid.createEmpty(); int placed = 0, guard = 0; @@ -561,7 +561,7 @@ public record SwedishGenerator() { } return g; } - Grid mutate(Rng rng, Grid grid) { + Grid mutate(Grid grid) { var g = grid.deepCopyGrid(); var cx = rng.randint(0, R - 1); var cy = rng.randint(0, C - 1); @@ -581,24 +581,24 @@ public record SwedishGenerator() { } return g; } - Grid crossover(Rng rng, Grid a, Grid b) { + Grid crossover(Grid a, Grid b) { var out = Grid.createEmpty(); var theta = rng.nextFloat() * Math.PI; - var nx = Math.cos(theta); - var ny = Math.sin(theta); + var nc = Math.cos(theta); + var nr = Math.sin(theta); - 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) 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.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 limit) { + Grid hillclimb(Grid start, int limit) { var best = start; var bestF = maskFitness(best); var fails = 0; while (fails < limit) { - var cand = mutate(rng, best); + var cand = mutate(best); var f = maskFitness(cand); if (f < bestF) { best = cand; @@ -611,7 +611,7 @@ public record SwedishGenerator() { return best; } - public Grid generateMask(Rng rng, int popSize, int gens) { + public Grid generateMask(int popSize, int gens) { class GridAndFit { Grid grid; @@ -625,7 +625,7 @@ public record SwedishGenerator() { if (Main.VERBOSE) System.out.println("generateMask init pop: " + popSize); var pop = new ArrayList(); for (var i = 0; i < popSize; i++) { - pop.add(new GridAndFit(hillclimb(rng, randomMask(rng), 180))); + pop.add(new GridAndFit(hillclimb(randomMask(), 180))); } for (var gen = 0; gen < gens; gen++) { @@ -636,8 +636,8 @@ public record SwedishGenerator() { for (var k = 0; k < pairs; k++) { var p1 = pop.get(rng.randint(0, pop.size() - 1)); var p2 = pop.get(rng.randint(0, pop.size() - 1)); - var child = crossover(rng, p1.grid, p2.grid); - children.add(new GridAndFit(hillclimb(rng, child, 70))); + var child = crossover(p1.grid, p2.grid); + children.add(new GridAndFit(hillclimb(child, 70))); } pop.addAll(children); @@ -797,7 +797,7 @@ public record SwedishGenerator() { var entry = dictIndex[s.len()]; if (entry == null) return PICK_NOT_DONE; patternForSlot(grid, s, ctx.pattern); - var info = SwedishGenerator.candidateInfoForPattern(ctx, entry, s.len()); + var info = candidateInfoForPattern(ctx, entry, s.len()); if (info.count == 0) return PICK_NOT_DONE; var slotScore = -1; diff --git a/src/test/java/puzzle/ExportFormatTest.java b/src/test/java/puzzle/ExportFormatTest.java index f3086ca..291bc24 100644 --- a/src/test/java/puzzle/ExportFormatTest.java +++ b/src/test/java/puzzle/ExportFormatTest.java @@ -7,16 +7,18 @@ import puzzle.Export.PuzzleResult; import puzzle.SwedishGenerator.FillResult; import puzzle.SwedishGenerator.Grid; import puzzle.SwedishGenerator.Lemma; +import puzzle.SwedishGenerator.Rng; import java.util.HashMap; import static org.junit.jupiter.api.Assertions.*; +import static puzzle.SwedishGenerator.*; public class ExportFormatTest { @Test void testExportFormatFromFilled() { - var swe = new SwedishGenerator(); + var swe = new SwedishGenerator(new Rng(0)); var grid = Grid.createEmpty(); // Place a '2' (right) at (0,0) @@ -75,7 +77,7 @@ public class ExportFormatTest { @Test void testExportFormatEmpty() { - var swe = new SwedishGenerator(); + var swe = new SwedishGenerator(new Rng(0)); var grid = Grid.createEmpty(); var fillResult = new FillResult(true, new Gridded(grid), new HashMap<>(), null); var puzzleResult = new PuzzleResult(swe, null, null, fillResult); @@ -85,9 +87,9 @@ public class ExportFormatTest { assertNotNull(exported); assertEquals(0, exported.words().length); // Should return full grid with '#' - assertEquals(SwedishGenerator.R, exported.gridv2().size()); + assertEquals(R, exported.gridv2().size()); for (var row : exported.gridv2()) { - assertEquals(SwedishGenerator.C, row.length()); + assertEquals(C, row.length()); assertTrue(row.matches("#+")); } } diff --git a/src/test/java/puzzle/MainTest.java b/src/test/java/puzzle/MainTest.java index 5fc21cb..4a96208 100644 --- a/src/test/java/puzzle/MainTest.java +++ b/src/test/java/puzzle/MainTest.java @@ -14,16 +14,8 @@ import static puzzle.SwedishGenerator.DASH; public class MainTest { - public static void main(String[] args) { - MainTest t = new MainTest(); - t.testGridBasics(); - t.testGridDeepCopy(); - t.testMini(); - t.testAttempt(); - } @Test void testExtractSlots() { - var generator = new SwedishGenerator(); var grid = Grid.createEmpty(); // Set up digits on the grid to create slots. @@ -32,9 +24,9 @@ public class MainTest { grid.setCharAt(0, 1, 'A'); grid.setCharAt(0, 2, 'B'); - var slots = generator.extractSlots(grid); + var slots = extractSlots(grid); assertEquals(1, slots.size()); - var s = slots.get(0); + var s = slots.getFirst(); assertEquals(8, s.len()); assertEquals(0, Grid.r(s.pos(0))); assertEquals(1, Grid.c(s.pos(0))); @@ -59,7 +51,6 @@ public class MainTest { @Test void testForEachSlot() { - var generator = new SwedishGenerator(); var grid = Grid.createEmpty(); grid.setClue(0, (byte) '2'); // right diff --git a/src/test/java/puzzle/SwedishGeneratorTest.java b/src/test/java/puzzle/SwedishGeneratorTest.java index be457b6..ebebbc4 100644 --- a/src/test/java/puzzle/SwedishGeneratorTest.java +++ b/src/test/java/puzzle/SwedishGeneratorTest.java @@ -2,11 +2,11 @@ package puzzle; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import puzzle.SwedishGenerator.*; import java.nio.charset.StandardCharsets; import java.util.Arrays; import static org.junit.jupiter.api.Assertions.*; +import static puzzle.SwedishGenerator.*; public class SwedishGeneratorTest { @@ -15,39 +15,39 @@ public class SwedishGeneratorTest { var grid = new Grid(new byte[]{ 65, 66, 67 }); // A B C var slot = Slot.from(18, ((long) 0) | ((long) 1 << 7) | ((long) 2 << 14), 3); var pattern = new byte[3]; - SwedishGenerator.patternForSlot(grid, slot, pattern); + patternForSlot(grid, slot, pattern); assertArrayEquals(new byte[]{ 'A', 'B', 'C' }, pattern); } @Test void testPatternForSlotMixed() { - var grid = new Grid(new byte[]{ 65, SwedishGenerator.DASH, 67 }); // A - C + var grid = new Grid(new byte[]{ 65, DASH, 67 }); // A - C var slot = Slot.from(1 << 4 | 2, ((long) 0) | ((long) 1 << 7) | ((long) 2 << 14), 3); var pattern = new byte[3]; - SwedishGenerator.patternForSlot(grid, slot, pattern); + patternForSlot(grid, slot, pattern); - assertArrayEquals(new byte[]{ 'A', SwedishGenerator.DASH, 'C' }, pattern); + assertArrayEquals(new byte[]{ 'A', DASH, 'C' }, pattern); } @Test void testPatternForSlotAllDashes() { - var grid = new Grid(new byte[]{ SwedishGenerator.DASH, SwedishGenerator.DASH, SwedishGenerator.DASH }); // - - - + var grid = new Grid(new byte[]{ DASH, DASH, DASH }); // - - - var slot = Slot.from(1 << 4 | 2, ((long) 0) | ((long) 1 << 7) | ((long) 2 << 14), 3); var pattern = new byte[3]; - SwedishGenerator.patternForSlot(grid, slot, pattern); + patternForSlot(grid, slot, pattern); - assertArrayEquals(new byte[]{ SwedishGenerator.DASH, SwedishGenerator.DASH, SwedishGenerator.DASH }, pattern); + assertArrayEquals(new byte[]{ DASH, DASH, DASH }, pattern); } @Test void testPatternForSlotSingleLetter() { - var grid = new Grid(new byte[]{ 65, SwedishGenerator.DASH, SwedishGenerator.DASH }); // A - - + var grid = new Grid(new byte[]{ 65, DASH, DASH }); // A - - var slot = Slot.from(1 << 4 | 2, ((long) 0) | ((long) 1 << 7) | ((long) 2 << 14), 3); var pattern = new byte[3]; - SwedishGenerator.patternForSlot(grid, slot, pattern); + patternForSlot(grid, slot, pattern); - assertArrayEquals(new byte[]{ 'A', SwedishGenerator.DASH, SwedishGenerator.DASH }, pattern); + assertArrayEquals(new byte[]{ 'A', DASH, DASH }, pattern); } @Test void testRng() { @@ -166,14 +166,14 @@ public class SwedishGeneratorTest { var a = new int[]{ 1, 3, 5, 7, 9 }; var b = new int[]{ 2, 3, 6, 7, 10 }; - var count = SwedishGenerator.intersectSorted(a, a.length, b, b.length, buff); + var count = intersectSorted(a, a.length, b, b.length, buff); assertEquals(2, count); assertEquals(3, buff[0]); assertEquals(7, buff[1]); var c = new int[]{ 1, 2, 3 }; var d = new int[]{ 4, 5, 6 }; - count = SwedishGenerator.intersectSorted(c, c.length, d, d.length, buff); + count = intersectSorted(c, c.length, d, d.length, buff); assertEquals(0, count); } @@ -189,12 +189,11 @@ public class SwedishGeneratorTest { var l2 = new Lemma("APPLY", 1, "verb"); var l3 = new Lemma("BANAN", 1, "fruit"); var dict = new Dict(new Lemma[]{ l0, l1, l2, l3, l3a, l4a, l6a, l7a, l8a }); - var gen = new SwedishGenerator(); // Pattern "APP--" for length 5 var context = new Context(); - context.setPatter(new byte[]{ 'A', 'P', 'P', SwedishGenerator.DASH, SwedishGenerator.DASH }); - var info = SwedishGenerator.candidateInfoForPattern(context, dict.index()[5], 5); + context.setPatter(new byte[]{ 'A', 'P', 'P', DASH, DASH }); + var info = candidateInfoForPattern(context, dict.index()[5], 5); assertEquals(2, info.count()); assertNotNull(info.indices()); @@ -202,7 +201,7 @@ public class SwedishGeneratorTest { @Test void testForEachSlotAndExtractSlots() { - var gen = new SwedishGenerator(); + var gen = new SwedishGenerator(new Rng(0)); var grid = Grid.createEmpty(); // 3x3 grid (Config.PUZZLE_ROWS/COLS are 3 in test env) // Set '2' (right) at 0,0 @@ -230,7 +229,7 @@ public class SwedishGeneratorTest { @Test void testMaskFitnessBasic() { - var gen = new SwedishGenerator(); + var gen = new SwedishGenerator(new Rng(0)); var grid = Grid.createEmpty(); var lenCounts = new int[12]; lenCounts[2] = 10; @@ -241,29 +240,29 @@ public class SwedishGeneratorTest { assertTrue(f1 >= 1_000_000_000L); // Add a slot - grid.setClue(0, SwedishGenerator.OFFSETS[2].dbyte()); + grid.setClue(0, OFFSETS[2].dbyte()); var f2 = gen.maskFitness(grid); assertTrue(f2 < f1); } @Test void testGeneticAlgorithmComponents() { - var gen = new SwedishGenerator(); var rng = new Rng(42); + var gen = new SwedishGenerator(rng); - var g1 = gen.randomMask(rng); + var g1 = gen.randomMask(); assertNotNull(g1); - var g2 = gen.mutate(rng, g1); + var g2 = gen.mutate(g1); assertNotNull(g2); assertNotSame(g1, g2); - var g3 = gen.crossover(rng, g1, g2); + var g3 = gen.crossover(g1, g2); assertNotNull(g3); var lenCounts = new int[12]; Arrays.fill(lenCounts, 10); - var g4 = gen.hillclimb(rng, g1, 10); + var g4 = gen.hillclimb(g1, 10); assertNotNull(g4); } @@ -279,19 +278,19 @@ public class SwedishGeneratorTest { var undoBuffer = new int[10]; // 1. Successful placement in empty grid - assertTrue(SwedishGenerator.placeWord(grid, s, w1, undoBuffer, 0)); + assertTrue(placeWord(grid, s, w1, undoBuffer, 0)); assertEquals('A', grid.byteAt(0, 0)); assertEquals('B', grid.byteAt(0, 1)); assertEquals('C', grid.byteAt(0, 2)); assertEquals(0b111L, undoBuffer[0]); // 2. Successful placement with partial overlap (same characters) - assertTrue(SwedishGenerator.placeWord(grid, s, w1, undoBuffer, 1)); + assertTrue(placeWord(grid, s, w1, undoBuffer, 1)); assertEquals(0L, undoBuffer[1]); // 0 new characters placed // 3. Conflict: place "ABD" where "ABC" is var w2 = new Lemma("ABD", 1, "conflict"); - assertFalse(SwedishGenerator.placeWord(grid, s, w2, undoBuffer, 2)); + assertFalse(placeWord(grid, s, w2, undoBuffer, 2)); // Verify grid is unchanged (still "ABC") assertEquals('A', grid.byteAt(0, 0)); assertEquals('B', grid.byteAt(0, 1)); @@ -300,10 +299,10 @@ public class SwedishGeneratorTest { // 4. Partial placement then conflict (rollback) grid = Grid.createEmpty(); grid.setCharAt(0, 2, 'X'); // Conflict at the end - assertFalse(SwedishGenerator.placeWord(grid, s, w1, undoBuffer, 3)); + assertFalse(placeWord(grid, s, w1, undoBuffer, 3)); // Verify grid is still empty (except for 'X') - assertEquals(SwedishGenerator.DASH, grid.byteAt(0, 0)); - assertEquals(SwedishGenerator.DASH, grid.byteAt(0, 1)); + assertEquals(DASH, grid.byteAt(0, 0)); + assertEquals(DASH, grid.byteAt(0, 1)); assertEquals('X', grid.byteAt(0, 2)); } @@ -316,14 +315,14 @@ public class SwedishGeneratorTest { var w = new Lemma("AZ", 1, "A to Z"); var undoBuffer = new int[10]; - var placed = SwedishGenerator.placeWord(grid, s, w, undoBuffer, 0); + var placed = placeWord(grid, s, w, undoBuffer, 0); assertTrue(placed); assertEquals('A', grid.byteAt(0, 1)); assertEquals('Z', grid.byteAt(0, 2)); assertEquals(0b11L, undoBuffer[0]); s.undoPlace(grid, undoBuffer[0]); - assertEquals(SwedishGenerator.DASH, grid.byteAt(0, 1)); - assertEquals(SwedishGenerator.DASH, grid.byteAt(0, 2)); + assertEquals(DASH, grid.byteAt(0, 1)); + assertEquals(DASH, grid.byteAt(0, 2)); } } \ No newline at end of file