diff --git a/src/main/java/puzzle/Export.java b/src/main/java/puzzle/Export.java index d01bb0b..fa0e89a 100644 --- a/src/main/java/puzzle/Export.java +++ b/src/main/java/puzzle/Export.java @@ -215,7 +215,8 @@ public record Export() { public record WordOut(String word, int[] cell, int startRow, int startCol, char direction, int arrowRow, int arrowCol, boolean isReversed, int complex, String[] clue) { public WordOut(long l, int startRow, int startCol, char d, int arrowRow, int arrowCol, boolean isReversed) { - this(Lemma.asWord(l), new int[]{ arrowRow, arrowCol, startRow, startCol }, startRow, startCol, d, arrowRow, arrowCol, isReversed, Lemma.simpel(l), Lemma.clue(l)); + this(Lemma.asWord(l), new int[]{ arrowRow, arrowCol, startRow, startCol }, startRow, startCol, d, arrowRow, arrowCol, isReversed, + CsvIndexService.simpel(Lemma.unpackIndex(l)), CsvIndexService.clues(Lemma.unpackIndex(l))); } } @@ -223,6 +224,18 @@ public record Export() { public record PuzzleResult(Clued clues, Gridded grid, Slotinfo[] slots, FillResult filled) { + static public long calcSimpel(Slotinfo[] slots) { + int k = 0; + long simpel = 0L; + for (var n = 1; n < slots.length; n++) { + if (slots[n].assign().w != X) { + k++; + simpel += CsvIndexService.simpel(Lemma.unpackIndex(slots[n].assign().w)); + } + } + simpel = k == 0 ? 0 : simpel / k; + return simpel; + } public ExportedPuzzle exportFormatFromFilled(int difficulty, Rewards rewards) { var placed = new ArrayList(); for (var slot : slots) { diff --git a/src/main/java/puzzle/Main.java b/src/main/java/puzzle/Main.java index b3e768a..010e149 100644 --- a/src/main/java/puzzle/Main.java +++ b/src/main/java/puzzle/Main.java @@ -58,7 +58,7 @@ public class Main { } - public void main(String[] args) { + void main(String[] args) { var csv = Paths.get("nl_score_hints_v3.csv"); var idx = Paths.get("nl_score_hints_v3.idx"); try { @@ -97,7 +97,7 @@ public class Main { } section("Result"); - res.filled().stats().simplicity = FillResult.calcSimpel(res.slots()); + res.filled().stats().simplicity = PuzzleResult.calcSimpel(res.slots()); info(String.format(Locale.ROOT, "simplicity : %.2f", res.filled().stats().simplicity)); section("Mask"); @@ -206,19 +206,19 @@ public class Main { } static void usage() { - System.out.println(""" - Usage: - java puzzle.Main [--seed N] [--clues N] [--pop N] [--offspring N] [--gens N] [--tries N] [--words FILE] [--min-simplicity N.N] [--threads N] [--reindex] - - Defaults: - --clues 18 - --pop 40 - --offspring 60 - --gens 500 - --words nl_score_hints.csv - --min-simplicity 0 (no limit) - --threads %d - """.formatted(Math.max(1, Runtime.getRuntime().availableProcessors()))); + System.out.printf(""" + Usage: + java puzzle.Main [--seed N] [--clues N] [--pop N] [--offspring N] [--gens N] [--tries N] [--words FILE] [--min-simplicity N.N] [--threads N] [--reindex] + + Defaults: + --clues 18 + --pop 40 + --offspring 60 + --gens 500 + --words nl_score_hints.csv + --min-simplicity 0 (no limit) + --threads %d + %n""", Math.max(1, Runtime.getRuntime().availableProcessors())); } static Opts parseArgs(String[] argv) { @@ -392,7 +392,7 @@ public class Main { val slotInfo = Masker.scoreSlots(new int[slots.length], slots); var grid = mask.toGrid(); var filled = fillMask(rng, slotInfo, grid, multiThreaded); - + if (!multiThreaded) { System.out.print("\r" + Strings.padRight("", 120) + "\r"); System.out.flush(); @@ -401,22 +401,22 @@ public class Main { if (Main.VERBOSE && !multiThreaded) { System.out.printf(Locale.ROOT, "[######################] %d/%d slots | nodes=%d | backtracks=%d | mrv=%d | %.1fs%n", - Slotinfo.wordCount(0, slotInfo), slotInfo.length, filled.nodes(), filled.backtracks(), filled.stats().lastMRV(), filled.stats().seconds() - ); + Slotinfo.wordCount(0, slotInfo), slotInfo.length, filled.nodes(), filled.backtracks(), filled.lastMRV(), filled.elapsed() * 0.001 + ); } TOTAL_NODES.addAndGet(filled.nodes()); TOTAL_BACKTRACKS.addAndGet(filled.backtracks()); if (filled.ok()) { - val simpel = FillResult.calcSimpel(slotInfo); + val simpel = PuzzleResult.calcSimpel(slotInfo); TOTAL_SUCCESS.incrementAndGet(); - TOTAL_SIMPLICITY.addAndGet((long) (simpel * 100)); + TOTAL_SIMPLICITY.addAndGet(simpel * 100); } var name = Thread.currentThread().getName(); var status = filled.ok() ? "SUCCESS" : "FAILED"; var simplicity = String.format(Locale.ROOT, "%.2f", filled.stats().simplicity); - var nps = (int) (filled.nodes() / Math.max(0.001, filled.stats().seconds)); + var nps = (int) (filled.nodes() / Math.max(0.001, filled.elapsed() * 0.001)); var totalTime = (System.currentTimeMillis() - t0) / 1000.0; System.out.printf(Locale.ROOT, diff --git a/src/main/java/puzzle/SwedishGenerator.java b/src/main/java/puzzle/SwedishGenerator.java index 9933c06..a1c4572 100644 --- a/src/main/java/puzzle/SwedishGenerator.java +++ b/src/main/java/puzzle/SwedishGenerator.java @@ -91,25 +91,11 @@ public class SwedishGenerator { @Accessors(fluent = true) public static final class FillStats { - final public double seconds; - final public int lastMRV; - public double simplicity; + public double simplicity; } - public static record FillResult(boolean ok, long nodes, long backtracks, @Delegate FillStats stats) { + public static record FillResult(boolean ok, long nodes, long backtracks, int lastMRV, long elapsed, @Delegate FillStats stats) { - static public long calcSimpel(Slotinfo[] slots) { - int k = 0; - long simpel = 0L; - for (var n = 1; n < slots.length; n++) { - if (slots[n].assign.w != X) { - k++; - simpel += Lemma.simpel(slots[n].assign.w); - } - } - simpel = k == 0 ? 0 : simpel / k; - return simpel; - } } public static final class Rng { @@ -161,8 +147,6 @@ public class SwedishGenerator { } static public long from(int index, String word) { return pack(index, word.getBytes(US_ASCII)); } static byte byteAt(long word, int idx) { return (byte) ((word >>> (idx * 5)) & 0b11111); } - static String[] clue(long w) { return CsvIndexService.clues(unpackIndex(w)); } - static int simpel(long w) { return CsvIndexService.simpel(unpackIndex(w)); } static int length(long word) { return ((63 - numberOfLeadingZeros(word & LETTER_MASK)) / 5) + 1; } static ThreadLocal BYTES = ThreadLocal.withInitial(() -> new byte[MAX_WORD_LENGTH]); public static String asWord(long word) { @@ -225,59 +209,6 @@ public class SwedishGenerator { } return p; } - public static boolean placeWord(final Grid grid, final byte[] g, final int key, final long lo, final long hi, final long w) { - final long glo = grid.lo, ghi = grid.hi; - if (Slotinfo.increasing(key)) { - for (long b = lo & glo; b != X; b &= b - 1) { - int idx = numberOfTrailingZeros(b); - if (g[idx] != Lemma.byteAt(w, bitCount(lo & ((1L << idx) - 1)))) return false; - } - int bcLo = bitCount(lo); - for (long b = hi & ghi; b != X; b &= b - 1) { - int idx = numberOfTrailingZeros(b); - if (g[64 | idx] != Lemma.byteAt(w, bcLo + bitCount(hi & ((1L << idx) - 1)))) return false; - } - - long maskLo = lo & ~glo, maskHi = hi & ~ghi; - if ((maskLo | maskHi) != X) { - for (long b = maskLo; b != X; b &= b - 1) { - int idx = numberOfTrailingZeros(b); - g[idx] = Lemma.byteAt(w, bitCount(lo & ((1L << idx) - 1))); - } - for (long b = maskHi; b != X; b &= b - 1) { - int idx = numberOfTrailingZeros(b); - g[64 | idx] = Lemma.byteAt(w, bcLo + bitCount(hi & ((1L << idx) - 1))); - } - grid.lo |= maskLo; - grid.hi |= maskHi; - } - } else { - int bcHi = bitCount(hi); - for (long b = hi & ghi; b != X; b &= b - 1) { - int idx = numberOfTrailingZeros(b); - if (g[64 | idx] != Lemma.byteAt(w, bitCount(hi & ~((1L << idx) | ((1L << idx) - 1))))) return false; - } - for (long b = lo & glo; b != X; b &= b - 1) { - int idx = numberOfTrailingZeros(b); - if (g[idx] != Lemma.byteAt(w, bcHi + bitCount(lo & ~((1L << idx) | ((1L << idx) - 1))))) return false; - } - - long maskLo = lo & ~glo, maskHi = hi & ~ghi; - if ((maskLo | maskHi) != X) { - for (long b = maskHi; b != X; b &= b - 1) { - int idx = numberOfTrailingZeros(b); - g[64 | idx] = Lemma.byteAt(w, bitCount(hi & ~((1L << idx) | ((1L << idx) - 1)))); - } - for (long b = maskLo; b != X; b &= b - 1) { - int idx = numberOfTrailingZeros(b); - g[idx] = Lemma.byteAt(w, bcHi + bitCount(lo & ~((1L << idx) | ((1L << idx) - 1)))); - } - grid.lo |= maskLo; - grid.hi |= maskHi; - } - } - return true; - } /// pattern cannot be X public static int[] candidateInfoForPattern(long[] res, long pattern, long[][] posBitsets, int numLongs) { @@ -503,6 +434,6 @@ public class SwedishGenerator { grid.lo = solver.glo; grid.hi = solver.ghi; - return new FillResult(ok, solver.nodes, solver.backtracks, new FillStats((System.currentTimeMillis() - t0) / 1000.0, solver.lastMRV)); + return new FillResult(ok, solver.nodes, solver.backtracks, solver.lastMRV, System.currentTimeMillis() - t0, new FillStats()); } } diff --git a/src/test/java/puzzle/ExportFormatTest.java b/src/test/java/puzzle/ExportFormatTest.java index 1254c06..fe2b4dc 100644 --- a/src/test/java/puzzle/ExportFormatTest.java +++ b/src/test/java/puzzle/ExportFormatTest.java @@ -23,7 +23,7 @@ import static puzzle.Masker.Clues; import static puzzle.SwedishGenerator.FillStats; import static puzzle.SwedishGenerator.R; import static puzzle.Masker.Slot; -import static puzzle.SwedishGenerator.placeWord; +import static puzzle.GridBuilder.placeWord; import static puzzle.SwedishGeneratorTest.OFF_0_1; import static puzzle.SwedishGeneratorTest.OFF_0_2; import static puzzle.SwedishGeneratorTest.OFF_0_3; @@ -67,7 +67,7 @@ public class ExportFormatTest { assertTrue(placeWord(grid.grid(), grid.grid().g, key, lo, 0L, TEST)); - var fillResult = new FillResult(true, 0, 0, new FillStats(0, 0)); + var fillResult = new FillResult(true, 0, 0, 0, 0, new FillStats()); var puzzleResult = new PuzzleResult(new Clued(clues), grid, new Slotinfo[]{ new Slotinfo(key, lo, 0L, 0, new Assign(TEST), null) }, fillResult); @@ -109,7 +109,7 @@ public class ExportFormatTest { void testExportFormatEmpty() { var grid = SwedishGeneratorTest.createEmpty(); val clues = Clues.createEmpty(); - var fillResult = new FillResult(true, 0, 0, new FillStats(0, 0)); + var fillResult = new FillResult(true, 0, 0, 0, 0, new FillStats()); var puzzleResult = new PuzzleResult(new Clued(clues), new Gridded(grid), new Slotinfo[0], fillResult); var exported = puzzleResult.exportFormatFromFilled(1, new Rewards(0, 0, 0)); diff --git a/src/test/java/puzzle/GridBuilder.java b/src/test/java/puzzle/GridBuilder.java new file mode 100644 index 0000000..49d1810 --- /dev/null +++ b/src/test/java/puzzle/GridBuilder.java @@ -0,0 +1,63 @@ +package puzzle; + +import puzzle.SwedishGenerator.Grid; +import puzzle.SwedishGenerator.Lemma; +import puzzle.SwedishGenerator.Slotinfo; +import static java.lang.Long.bitCount; +import static java.lang.Long.numberOfTrailingZeros; +public class GridBuilder { + + public static boolean placeWord(final Grid grid, final byte[] g, final int key, final long lo, final long hi, final long w) { + final long glo = grid.lo, ghi = grid.hi; + if (Slotinfo.increasing(key)) { + for (long b = lo & glo; b != SwedishGenerator.X; b &= b - 1) { + int idx = numberOfTrailingZeros(b); + if (g[idx] != Lemma.byteAt(w, bitCount(lo & ((1L << idx) - 1)))) return false; + } + int bcLo = bitCount(lo); + for (long b = hi & ghi; b != SwedishGenerator.X; b &= b - 1) { + int idx = numberOfTrailingZeros(b); + if (g[64 | idx] != Lemma.byteAt(w, bcLo + bitCount(hi & ((1L << idx) - 1)))) return false; + } + + long maskLo = lo & ~glo, maskHi = hi & ~ghi; + if ((maskLo | maskHi) != SwedishGenerator.X) { + for (long b = maskLo; b != SwedishGenerator.X; b &= b - 1) { + int idx = numberOfTrailingZeros(b); + g[idx] = Lemma.byteAt(w, bitCount(lo & ((1L << idx) - 1))); + } + for (long b = maskHi; b != SwedishGenerator.X; b &= b - 1) { + int idx = numberOfTrailingZeros(b); + g[64 | idx] = Lemma.byteAt(w, bcLo + bitCount(hi & ((1L << idx) - 1))); + } + grid.lo |= maskLo; + grid.hi |= maskHi; + } + } else { + int bcHi = bitCount(hi); + for (long b = hi & ghi; b != SwedishGenerator.X; b &= b - 1) { + int idx = numberOfTrailingZeros(b); + if (g[64 | idx] != Lemma.byteAt(w, bitCount(hi & ~((1L << idx) | ((1L << idx) - 1))))) return false; + } + for (long b = lo & glo; b != SwedishGenerator.X; b &= b - 1) { + int idx = numberOfTrailingZeros(b); + if (g[idx] != Lemma.byteAt(w, bcHi + bitCount(lo & ~((1L << idx) | ((1L << idx) - 1))))) return false; + } + + long maskLo = lo & ~glo, maskHi = hi & ~ghi; + if ((maskLo | maskHi) != SwedishGenerator.X) { + for (long b = maskHi; b != SwedishGenerator.X; b &= b - 1) { + int idx = numberOfTrailingZeros(b); + g[64 | idx] = Lemma.byteAt(w, bitCount(hi & ~((1L << idx) | ((1L << idx) - 1)))); + } + for (long b = maskLo; b != SwedishGenerator.X; b &= b - 1) { + int idx = numberOfTrailingZeros(b); + g[idx] = Lemma.byteAt(w, bcHi + bitCount(lo & ~((1L << idx) | ((1L << idx) - 1)))); + } + grid.lo |= maskLo; + grid.hi |= maskHi; + } + } + return true; + } +} diff --git a/src/test/java/puzzle/MainTest.java b/src/test/java/puzzle/MainTest.java index 749dc25..0d7a06d 100644 --- a/src/test/java/puzzle/MainTest.java +++ b/src/test/java/puzzle/MainTest.java @@ -52,7 +52,7 @@ public class MainTest { clues.setClueLo(IDX_0_0.lo, CLUE_RIGHT); var grid = new Gridded(clues.toGrid()); val g = grid.grid().g; - placeWord(grid.grid(), g, key, (1L << OFF_0_1) | (1L << OFF_0_2), 0, AB); + GridBuilder.placeWord(grid.grid(), g, key, (1L << OFF_0_1) | (1L << OFF_0_2), 0, AB); var slots = Masker.extractSlots(clues, dict.index()); assertEquals(1, slots.length); @@ -102,7 +102,7 @@ public class MainTest { var grid = new Gridded(clues.toGrid()); // Test set/get - placeWord(grid.grid(), grid.grid().g, key, (1L << OFF_1_1) | (1L << OFF_0_1), 0, AZ); + GridBuilder.placeWord(grid.grid(), grid.grid().g, key, (1L << OFF_1_1) | (1L << OFF_0_1), 0, AZ); val map = grid.stream(clues.c()).collect(Collectors.toMap(LetterAt::index, LetterAt::letter)); Assertions.assertEquals(LETTER_A, map.get(OFF_1_1)); Assertions.assertEquals(LETTER_Z, map.get(OFF_0_1)); @@ -172,6 +172,17 @@ public class MainTest { Assertions.assertEquals(RESULT, test, "Found seed changed"); } @Test + void testFiller2() { + val mask = "1 000000\n" + + "1 \n" + + "1 \n" + + "3 3 \n" + + "3 0 3 \n" + + "3 \n" + + "3 \n" + + "222 3"; + } + @Test void testFiller() { val rng = new Rng(-343913721); val mask = new Clues( diff --git a/src/test/java/puzzle/SwedishGeneratorTest.java b/src/test/java/puzzle/SwedishGeneratorTest.java index 8bb66d9..33b224d 100644 --- a/src/test/java/puzzle/SwedishGeneratorTest.java +++ b/src/test/java/puzzle/SwedishGeneratorTest.java @@ -142,7 +142,7 @@ public class SwedishGeneratorTest { var key = Masker.Slot.packSlotKey(OFF_0_0, CLUE_RIGHT); val clues = Masker.Clues.createEmpty(); clues.setClueLo(IDX_0_0.lo, CLUE_RIGHT); - placeWord(grid.grid(), grid.grid().g, key, (1L << OFF_0_1) | (1L << OFF_0_2) | (1L << OFF_0_3), 0L, ABC); + GridBuilder.placeWord(grid.grid(), grid.grid().g, key, (1L << OFF_0_1) | (1L << OFF_0_2) | (1L << OFF_0_3), 0L, ABC); val map = grid.stream(clues).collect(Collectors.toMap(LetterAt::index, LetterAt::letter)); assertEquals(LETTER_A, map.get(OFF_0_1)); assertEquals(LETTER_B, map.get(OFF_0_2)); @@ -152,8 +152,8 @@ public class SwedishGeneratorTest { @Test void testPatternForSlotMixed() { var grid = createEmpty(); - placeWord(grid, grid.g, Masker.Slot.packSlotKey(0, CLUE_RIGHT), 1L << OFF_0_0, 0, Lemma.from(0, "A")); - placeWord(grid, grid.g, Masker.Slot.packSlotKey(0, CLUE_RIGHT), 1L << OFF_2_0, 0, Lemma.from(0, "C")); + GridBuilder.placeWord(grid, grid.g, Masker.Slot.packSlotKey(0, CLUE_RIGHT), 1L << OFF_0_0, 0, Lemma.from(0, "A")); + GridBuilder.placeWord(grid, grid.g, Masker.Slot.packSlotKey(0, CLUE_RIGHT), 1L << OFF_2_0, 0, Lemma.from(0, "C")); var key = Masker.Slot.packSlotKey(OFF_1_0, CLUE_RIGHT); var pattern = patternForSlot(grid.lo, grid.hi, grid.g, key, 7L, 0L); assertEquals(14081L, pattern); @@ -170,7 +170,7 @@ public class SwedishGeneratorTest { @Test void testPatternForSlotSingleLetter() { var grid = createEmpty(); - placeWord(grid, grid.g, Masker.Slot.packSlotKey(0, CLUE_RIGHT), 1L << OFF_0_0, 0, Lemma.from(0, "A")); + GridBuilder.placeWord(grid, grid.g, Masker.Slot.packSlotKey(0, CLUE_RIGHT), 1L << OFF_0_0, 0, Lemma.from(0, "A")); var key = Masker.Slot.packSlotKey(1, CLUE_RIGHT); var pattern = patternForSlot(grid.lo, grid.hi, grid.g, key, 7L, 0L); assertEquals(1L, pattern); @@ -196,7 +196,7 @@ public class SwedishGeneratorTest { @Test void testGrid() { var grid = new Gridded(createEmpty()); - placeWord(grid.grid(), grid.grid().g, Masker.Slot.packSlotKey(0, CLUE_RIGHT), 1L << OFF_0_0, 0, Lemma.from(0, "A")); + GridBuilder.placeWord(grid.grid(), grid.grid().g, Masker.Slot.packSlotKey(0, CLUE_RIGHT), 1L << OFF_0_0, 0, Lemma.from(0, "A")); val arr = grid.stream(Masker.Clues.createEmpty()).collect(Collectors.toMap(LetterAt::index, LetterAt::letter)); assertEquals(1, arr.size()); assertEquals(LETTER_A, arr.get(OFF_0_0)); @@ -342,7 +342,7 @@ public class SwedishGeneratorTest { var w1 = ABC; // 1. Successful placement in empty grid - assertTrue(placeWord(grid.grid(), grid.grid().g, key, lo, hi, w1)); + assertTrue(GridBuilder.placeWord(grid.grid(), grid.grid().g, key, lo, hi, w1)); var map = grid.stream(Masker.Clues.createEmpty()).collect(Collectors.toMap(LetterAt::index, LetterAt::letter)); assertEquals(3, map.size()); assertEquals(LETTER_A, map.get(OFF_0_0)); @@ -350,9 +350,9 @@ public class SwedishGeneratorTest { assertEquals(LETTER_C, map.get(OFF_0_2)); // 2. Successful placement with partial overlap (same characters) - assertTrue(placeWord(grid.grid(), grid.grid().g, key, lo, hi, w1)); + assertTrue(GridBuilder.placeWord(grid.grid(), grid.grid().g, key, lo, hi, w1)); // 3. Conflict: place "ABD" where "ABC" is - assertFalse(placeWord(grid.grid(), grid.grid().g, key, lo, hi, ABD)); + assertFalse(GridBuilder.placeWord(grid.grid(), grid.grid().g, key, lo, hi, ABD)); // Verify grid is unchanged (still "ABC") map = grid.stream(Masker.Clues.createEmpty()).collect(Collectors.toMap(LetterAt::index, LetterAt::letter)); assertEquals(3, map.size()); @@ -362,8 +362,8 @@ public class SwedishGeneratorTest { // 4. Partial placement then conflict (rollback) grid = new Gridded(createEmpty()); - placeWord(grid.grid(), grid.grid().g, Masker.Slot.packSlotKey(0, CLUE_RIGHT), 1L << OFF_0_2, 0, Lemma.from(0, "X")); // Conflict at the end - assertFalse(placeWord(grid.grid(), grid.grid().g, key, lo, hi, w1)); + GridBuilder.placeWord(grid.grid(), grid.grid().g, Masker.Slot.packSlotKey(0, CLUE_RIGHT), 1L << OFF_0_2, 0, Lemma.from(0, "X")); // Conflict at the end + assertFalse(GridBuilder.placeWord(grid.grid(), grid.grid().g, key, lo, hi, w1)); map = grid.stream(Masker.Clues.createEmpty()).collect(Collectors.toMap(LetterAt::index, LetterAt::letter)); assertEquals(1, map.size()); assertEquals(LETTER_X, map.get(OFF_0_2)); @@ -378,7 +378,7 @@ public class SwedishGeneratorTest { var w = AZ; val low = grid.grid().lo; val top = grid.grid().hi; - var placed = placeWord(grid.grid(), grid.grid().g, key, lo, 0L, w); + var placed = GridBuilder.placeWord(grid.grid(), grid.grid().g, key, lo, 0L, w); assertTrue(placed); var map = grid.stream(Masker.Clues.createEmpty()).collect(Collectors.toMap(LetterAt::index, LetterAt::letter)); assertEquals(2, map.size());