diff --git a/.env.test b/.env.test index 535fcd6..2ee4eda 100644 --- a/.env.test +++ b/.env.test @@ -8,7 +8,7 @@ OPENAI_API_KEY="local" # Constants for testing CLUE_SIZE=4 MIN_LEN=2 -MAX_TRIES_PER_SLOT=2000 +MAX_TRIES_PER_SLOT=1000 MAX_LEN=8 PUZZLE_ROWS=3 PUZZLE_COLS=3 diff --git a/src/main/java/puzzle/Main.java b/src/main/java/puzzle/Main.java index 0c01119..80f4cb0 100644 --- a/src/main/java/puzzle/Main.java +++ b/src/main/java/puzzle/Main.java @@ -44,12 +44,12 @@ public class Main { @NoArgsConstructor public static class Opts { - static int SSIZE = 20; + static int SSIZE = 25; public int seed = (int) (System.nanoTime() ^ System.currentTimeMillis()); public int clueSize = SSIZE; - public int pop = SSIZE * 2; - public int offspring = SSIZE * 3; - public int gens = 600; + public int pop = SSIZE*2; + public int offspring = SSIZE*3; + public int gens =1200; public String wordsPath = "nl_score_hints_v3.csv"; public double minSimplicity = 0; // 0 means no limit public int threads = Math.max(1, Runtime.getRuntime().availableProcessors()); @@ -368,11 +368,12 @@ public class Main { try { return _attempt(rng, dict, opts); } catch (Exception e) { + e.printStackTrace(); System.err.println("Failed to operate" + e.getMessage()); return null; } } - static Clues generateClues() { + static Clued generateClues() { String simple = "000 3000\n" + " 3 \n" + " 31 \n" + diff --git a/src/main/java/puzzle/Masker.java b/src/main/java/puzzle/Masker.java index fd467df..8d419de 100644 --- a/src/main/java/puzzle/Masker.java +++ b/src/main/java/puzzle/Masker.java @@ -2,10 +2,13 @@ package puzzle; import lombok.AllArgsConstructor; import lombok.val; +import puzzle.Export.Clued; import puzzle.Export.Gridded; +import java.sql.Array; import java.util.ArrayList; import java.util.Comparator; +import java.util.Objects; import java.util.stream.IntStream; import static java.lang.Long.*; @@ -48,8 +51,8 @@ public record Masker(Rng rng, int[] stack, Clues cache) { long stop = 1L << msb; rayLo &= -(stop << 1); } - - visitor.visit(key, rayLo, rayHi); + // if (Long.bitCount(rayLo) + Long.bitCount(rayHi) > 1) + visitor.visit(key, rayLo, rayHi); } private static void processSlot(Clues c, SlotVisitor visitor, int key) { long rayLo = PATH_LO[key]; @@ -66,14 +69,13 @@ public record Masker(Rng rng, int[] stack, Clues cache) { // keep all lo (lo indices are < any hi index), but cut hi below stop rayHi &= (stop - 1); } - - visitor.visit(key, rayLo, rayHi); + // if (Long.bitCount(rayLo) + Long.bitCount(rayHi) > 1) + visitor.visit(key, rayLo, rayHi); } public static Slot[] extractSlots(Clues grid, DictEntry[] index) { - var slots = new Slot[grid.clueCount()]; - int[] N = new int[]{ 0 }; - grid.forEachSlot((key, lo, hi) -> slots[N[0]++] = Slot.from(key, lo, hi, index[Slot.length(lo, hi)])); - return slots; + var slots = new ArrayList(grid.clueCount()); + grid.forEachSlot((key, lo, hi) -> slots.add(Slot.from(key, lo, hi, Objects.requireNonNull(index[Slot.length(lo, hi)])))); + return slots.toArray(Slot[]::new); } public static Slotinfo[] slots(Clues mask, DictEntry[] index) { var slots = Masker.extractSlots(mask, index); @@ -102,6 +104,7 @@ public record Masker(Rng rng, int[] stack, Clues cache) { public long maskFitness(final Clues grid, int clueSize) { long cHLo = 0L, cHHi = 0L, cVLo = 0L, cVHi = 0L; + long cHLo2 = 0L, cHHi2 = 0L, cVLo2 = 0L, cVHi2 = 0L; long lo_cl = grid.lo, hi_cl = grid.hi; long penalty = (((long) Math.abs(grid.clueCount() - clueSize)) * 16000L); boolean hasSlots = false; @@ -130,9 +133,11 @@ public record Masker(Rng rng, int[] stack, Clues cache) { if ((rLo | rHi) != X) { hasSlots = true; if (Slot.horiz(key)) { + cHLo2 |= (cHLo & rLo); cHHi2 |= (cHHi & rHi); cHLo |= rLo; cHHi |= rHi; } else { + cVLo2 |= (cVLo & rLo); cVHi2 |= (cVHi & rHi); cVLo |= rLo; cVHi |= rHi; } @@ -163,9 +168,11 @@ public record Masker(Rng rng, int[] stack, Clues cache) { if ((rLo | rHi) != X) { hasSlots = true; if (Slot.horiz(key)) { + cHLo2 |= (cHLo & rLo); cHHi2 |= (cHHi & rHi); cHLo |= rLo; cHHi |= rHi; } else { + cVLo2 |= (cVLo & rLo); cVHi2 |= (cVHi & rHi); cVLo |= rLo; cVHi |= rHi; } @@ -241,8 +248,9 @@ public record Masker(Rng rng, int[] stack, Clues cache) { boolean h = (cHLo & (1L << clueIdx)) != X; boolean v = (cVLo & (1L << clueIdx)) != X; if (!h && !v) penalty += 1500; - else if (h && v) { /* ok */ } else if (h | v) penalty += 200; - else penalty += 600; + else if (h && v) { /* ok */ } + else if (((h ? cHLo2 : cVLo2) & (1L << clueIdx)) != X) penalty += 600; + else penalty += 200; } for (long bits = ~hi_cl & MASK_HI; bits != X; bits &= bits - 1) { int clueIdx = numberOfTrailingZeros(bits); @@ -251,8 +259,9 @@ public record Masker(Rng rng, int[] stack, Clues cache) { boolean h = (cHHi & (1L << clueIdx)) != X; boolean v = (cVHi & (1L << clueIdx)) != X; if (!h && !v) penalty += 1500; - else if (h && v) { /* ok */ } else if (h | v) penalty += 200; - else penalty += 600; + else if (h && v) { /* ok */ } + else if (((h ? cHHi2 : cVHi2) & (1L << clueIdx)) != X) penalty += 600; + else penalty += 200; } return penalty; @@ -329,7 +338,7 @@ public record Masker(Rng rng, int[] stack, Clues cache) { for (var h = c.hi & ~c.rhi & ~c.vhi; h != X; h &= h - 1) clearCluesHi(c, numberOfTrailingZeros(h), 0); for (var h = c.hi & ~c.rhi & c.vhi; h != X; h &= h - 1) clearCluesHi(c, numberOfTrailingZeros(h), 1); for (var h = c.hi & c.rhi & ~c.vhi; h != X; h &= h - 1) clearCluesHi(c, (numberOfTrailingZeros(h)), 2); - for (var h = c.hi & c.rhi & ~c.vhi; h != X; h &= h - 1) clearCluesHi(c, (numberOfTrailingZeros(h)), 3); + for (var h = c.hi & c.rhi & c.vhi; h != X; h &= h - 1) clearCluesHi(c, (numberOfTrailingZeros(h)), 3); return c; } @@ -421,7 +430,7 @@ public record Masker(Rng rng, int[] stack, Clues cache) { long lo, hi, vlo, vhi, rlo, rhi; public static Clues createEmpty() { return new Clues(0, 0, 0, 0, 0, 0); } - public static Clues parse(String s) { + public static Clued parse(String s) { var c = createEmpty(); var lines = s.split("\n"); for (int r = 0; r < Math.min(lines.length, R); r++) { @@ -436,7 +445,7 @@ public record Masker(Rng rng, int[] stack, Clues cache) { } } } - return c; + return new Clued(c); } public boolean cluelessLo(int idx) { if (!isClueLo(idx)) return false; diff --git a/src/main/java/puzzle/Meta.java b/src/main/java/puzzle/Meta.java index 1323990..8ca7157 100644 --- a/src/main/java/puzzle/Meta.java +++ b/src/main/java/puzzle/Meta.java @@ -45,7 +45,6 @@ public class Meta { val parts = string.split("\t", 3); return new ShardLem(Lemma.from(parts[0]), Integer.parseInt(parts[1]), GSON.fromJson(parts[2], String[].class)); } catch (Exception e) { - e.printStackTrace(); return new ShardLem(Lemma.from("XXX"), -1, new String[0]); } } diff --git a/src/main/java/puzzle/SwedishGenerator.java b/src/main/java/puzzle/SwedishGenerator.java index a25f55a..374d8ac 100644 --- a/src/main/java/puzzle/SwedishGenerator.java +++ b/src/main/java/puzzle/SwedishGenerator.java @@ -48,8 +48,8 @@ public record SwedishGenerator() { public static final long MASK_HI = (SIZE <= 64) ? 0L : (SIZE >= 128 ? -1L : (1L << (SIZE - 64)) - 1); public static final int MAX_WORD_LENGTH = C <= R ? C : R; public static final int MAX_WORD_LENGTH_PLUS_ONE = MAX_WORD_LENGTH + 1; - public static final int MIN_LEN = Config.MIN_LEN; - public static final int MAX_TRIES_PER_SLOT = Config.MAX_TRIES_PER_SLOT; + public static final int MIN_LEN = 3;//Config.MIN_LEN; + public static final int MAX_TRIES_PER_SLOT = 700;//Config.MAX_TRIES_PER_SLOT; public static final int STACK_SIZE = 64; public static final char C_DASH = '\0'; public static final byte DASH = (byte) C_DASH; @@ -194,6 +194,14 @@ public record SwedishGenerator() { return k; } public static boolean increasing(int dir) { return (dir & 2) == 0; } + public static Grid grid(Slotinfo[] slots) { + long lo = X, hi = X; + for (var slot : slots) { + lo |= slot.lo; + hi |= slot.hi; + } + return new Grid(new byte[SIZE], ~lo, ~hi /*& 0xFF*/); + } } public static boolean isLo(int n) { return (n & 64) == 0; } @@ -318,8 +326,7 @@ public record SwedishGenerator() { int idx; for (long b = lo & glo; b != X; b &= b - 1) if (g[idx = numberOfTrailingZeros(b)] != Lemma.byteAt(w, bitCount(lo & ((1L << idx) - 1)))) return false; int bcLo = bitCount(lo); - for (long b = hi & ghi; b != X; b &= b - 1) - if (g[64 | (idx = numberOfTrailingZeros(b))] != Lemma.byteAt(w, bcLo + bitCount(hi & ((1L << idx) - 1)))) return false; + for (long b = hi & ghi; b != X; b &= b - 1) if (g[64 | (idx = numberOfTrailingZeros(b))] != Lemma.byteAt(w, bcLo + bitCount(hi & ((1L << idx) - 1)))) return false; long maskLo = lo & ~glo, maskHi = hi & ~ghi; if ((maskLo | maskHi) != X) { @@ -393,9 +400,9 @@ public record SwedishGenerator() { for (var t = 0; t < tries; t++) { //var r = rng.nextFloat(); //var idxInArray = (int) (r * r * r * (L - 1)); - int idxInArray = rng.biasedIndexPow3(L - 1); - var w = entry.words[idxs[idxInArray/*(int) (r * r * r * (L - 1))*/]]; - var lemIdx = Lemma.unpackIndex(w); + int idxInArray = rng.biasedIndexPow3(L - 1); + var w = entry.words[idxs[idxInArray/*(int) (r * r * r * (L - 1))*/]]; + var lemIdx = Lemma.unpackIndex(w); if (Bit1029.get(used, lemIdx)) continue; low = glo; top = ghi; diff --git a/src/test/java/puzzle/MainTest.java b/src/test/java/puzzle/MainTest.java index a4f055c..5804ad7 100644 --- a/src/test/java/puzzle/MainTest.java +++ b/src/test/java/puzzle/MainTest.java @@ -5,7 +5,6 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import puzzle.Export.ClueAt; import puzzle.Export.Clued; -import puzzle.Export.Dicts; import puzzle.Export.Gridded; import puzzle.Export.LetterVisit.LetterAt; import puzzle.Export.PuzzleResult; @@ -13,26 +12,39 @@ import puzzle.Export.Rewards; import puzzle.Main.Opts; import puzzle.Masker.Clues; import puzzle.SwedishGenerator.Rng; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; 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.ExportFormatTest.Clue.*; -import static puzzle.SwedishGenerator.*; -import static puzzle.SwedishGeneratorTest.*; +import static puzzle.ExportFormatTest.Clue.DOWN; +import static puzzle.ExportFormatTest.Clue.LEFT; +import static puzzle.ExportFormatTest.Clue.RIGHT; +import static puzzle.ExportFormatTest.Clue.UP; +import static puzzle.SwedishGenerator.Dict; +import static puzzle.SwedishGenerator.Lemma; +import static puzzle.SwedishGenerator.STACK_SIZE; +import static puzzle.SwedishGenerator.Slotinfo; +import static puzzle.SwedishGenerator.fillMask; +import static puzzle.SwedishGeneratorTest.AB; +import static puzzle.SwedishGeneratorTest.AZ; +import static puzzle.SwedishGeneratorTest.CLUE_LEFT; +import static puzzle.SwedishGeneratorTest.CLUE_RIGHT; +import static puzzle.SwedishGeneratorTest.CLUE_UP; import static puzzle.SwedishGeneratorTest.Idx.IDX_0_0; import static puzzle.SwedishGeneratorTest.Idx.IDX_0_1; import static puzzle.SwedishGeneratorTest.Idx.IDX_1_0; import static puzzle.SwedishGeneratorTest.Idx.IDX_1_1; import static puzzle.SwedishGeneratorTest.Idx.IDX_2_1; +import static puzzle.SwedishGeneratorTest.LETTER_A; +import static puzzle.SwedishGeneratorTest.LETTER_Z; import static puzzle.SwedishGeneratorTest.OFF_0_0; import static puzzle.SwedishGeneratorTest.OFF_0_1; import static puzzle.SwedishGeneratorTest.OFF_0_2; +import static puzzle.SwedishGeneratorTest.OFF_1_1; +import static puzzle.SwedishGeneratorTest.OFF_1_2; +import static puzzle.SwedishGeneratorTest.OFF_2_1; +import static puzzle.SwedishGeneratorTest.OFF_2_3; public class MainTest { @@ -47,7 +59,7 @@ public class MainTest { this.tries = 1; this.verbose = false; }}; - static final Dict dict = DictData.DICT;//loadDict(opts.wordsPath); + final Dict dict = DictData.DICT;//loadDict(opts.wordsPath); @Test void testExtractSlots() { @@ -162,18 +174,12 @@ public class MainTest { var masker = new Masker(new Rng(12348), new int[STACK_SIZE], Masker.Clues.createEmpty()); var mask = masker.generateMask(opts.clueSize, opts.pop, opts.gens, opts.offspring); val clued = new Clued(mask); - val test = clued.gridToString(); - val RESULT = "1 \n" + - " \n" + - " 3\n" + - " \n" + - " \n" + - "1 \n" + - " \n" + - " 3"; - - Assertions.assertEquals(4, clued.clueCount(), "Found seed changed"); - Assertions.assertEquals(RESULT, test, "Found seed changed"); + val map = clued.stream().collect(Collectors.toMap(ClueAt::index, ClueAt::clue)); + Assertions.assertEquals(4, map.size()); + Assertions.assertEquals(LEFT.dir, map.get(64)); + Assertions.assertEquals(RIGHT.dir, map.get(2)); + Assertions.assertEquals(LEFT.dir, map.get(67)); + Assertions.assertEquals(RIGHT.dir, map.get(7)); } @Test void testFiller2() { @@ -188,10 +194,12 @@ public class MainTest { "3 \n" + "222 3"); Assertions.assertEquals(20, mask.clueCount()); - var slots = Masker.extractSlots(mask, dict.index()); + val map = mask.stream().collect(Collectors.toMap(ClueAt::index, ClueAt::clue)); + Assertions.assertEquals(20, map.size()); + var slots = Masker.extractSlots(mask.c(), dict.index()); val slotInfo = Masker.scoreSlots(slots); var grid = mask.toGrid(); - // var filled = fillMask(rng, slotInfo, grid, false); + // var filled = fillMask(rng, slotInfo, grid, false); // val res = new PuzzleResult(new Clued(mask), new Gridded(grid), slotInfo, filled).exportFormatFromFilled(0, new Rewards(0, 0, 0)); } @Test @@ -206,7 +214,7 @@ public class MainTest { " 1 \n" + " 1 2\n" + "21 22 3"); - var slots = Masker.extractSlots(mask, dict.index()); + var slots = Masker.extractSlots(mask.c(), dict.index()); val slotInfo = Masker.scoreSlots(slots); var grid = mask.toGrid(); var filled = fillMask(rng, slotInfo, grid, false); @@ -216,8 +224,8 @@ public class MainTest { Assertions.assertEquals(-1L, grid.lo); Assertions.assertEquals(255L, grid.hi); var g = new Gridded(grid); - g.gridToString(mask); - var aa = new PuzzleResult(new Clued(mask), g, slotInfo, filled).exportFormatFromFilled(1, new Rewards(1, 1, 1)); + g.gridToString(mask.c()); + var aa = new PuzzleResult(mask, g, slotInfo, filled).exportFormatFromFilled(1, new Rewards(1, 1, 1)); System.out.println(String.join("\n", aa.grid())); } diff --git a/src/test/java/puzzle/PerformanceTest.java b/src/test/java/puzzle/PerformanceTest.java index 765be6c..d5d526d 100644 --- a/src/test/java/puzzle/PerformanceTest.java +++ b/src/test/java/puzzle/PerformanceTest.java @@ -3,64 +3,197 @@ package puzzle; import lombok.val; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; +import puzzle.SwedishGenerator.DictEntry; import puzzle.SwedishGenerator.Rng; import puzzle.SwedishGenerator.Slotinfo; +import puzzle.SwedishGenerator.Grid; +import java.util.Arrays; +import java.util.Comparator; import java.util.Locale; +import java.util.stream.IntStream; import static org.junit.jupiter.api.Assertions.assertTrue; import static puzzle.SwedishGenerator.fillMask; public class PerformanceTest { - - @Test - @Disabled - void testPerformance() { - val rng = new Rng(42); - val dict = DictData.DICT; - - // 1. Stress test Clue Generation (Mask Generation) - System.out.println("[DEBUG_LOG] --- Mask Generation Performance ---"); - int[] clueSizes = {20, 25, 30}; - for (int size : clueSizes) { - long t0 = System.currentTimeMillis(); - val masker = new Masker(rng, new int[SwedishGenerator.STACK_SIZE], Masker.Clues.createEmpty()); - // Increased population and generations for stress - val mask = masker.generateMask(size, 200, 100, 50); - long t1 = System.currentTimeMillis(); - double duration = (t1 - t0) / 1000.0; - System.out.printf(Locale.ROOT, "[DEBUG_LOG] Size %d (pop=200, gen=100): %.3fs%n", size, duration); - // Basic sanity check: should not take forever - assertTrue(duration < 10.0, "Mask generation took too long for size " + size); - } - - // 2. Stress test Word Filler - System.out.println("[DEBUG_LOG] \n--- Word Filler Performance ---"); - for (int size : clueSizes) { - val masker = new Masker(rng, new int[SwedishGenerator.STACK_SIZE], Masker.Clues.createEmpty()); - val mask = masker.generateMask(size, 100, 50, 20); - - val slots = Masker.extractSlots(mask, dict.index()); - val slotInfo = Masker.scoreSlots(slots); - val grid = mask.toGrid(); - - long t0 = System.currentTimeMillis(); - // Try to fill multiple times to get a better average - int iterations = 3; - long totalNodes = 0; - long totalBacktracks = 0; - int successCount = 0; - for (int i = 0; i < iterations; i++) { - val result = fillMask(rng, slotInfo, grid.copy(), false); - if (result.ok()) successCount++; - totalNodes += result.nodes(); - totalBacktracks += result.backtracks(); + + final DictEntry[] EN = DictData.DICT.index(); + @Test + void testPerformance() { + val rng = new Rng(42); + + // 1. Stress test Clue Generation (Mask Generation) + System.out.println("[DEBUG_LOG] --- Mask Generation Performance ---"); + int[] clueSizes = { 20, 25, 30 }; + for (int size : clueSizes) { + long t0 = System.currentTimeMillis(); + val masker = new Masker(rng, new int[SwedishGenerator.STACK_SIZE], Masker.Clues.createEmpty()); + // Increased population and generations for stress + val mask = masker.generateMask(size, 200, 100, 50); + long t1 = System.currentTimeMillis(); + double duration = (t1 - t0) / 1000.0; + System.out.printf(Locale.ROOT, "[DEBUG_LOG] Size %d (pop=200, gen=100): %.3fs%n", size, duration); + // Basic sanity check: should not take forever + assertTrue(duration < 10.0, "Mask generation took too long for size " + size); + } + + // 2. Stress test Word Filler + System.out.println("[DEBUG_LOG] \n--- Word Filler Performance ---"); + for (int size : clueSizes) { + val masker = new Masker(rng, new int[SwedishGenerator.STACK_SIZE], Masker.Clues.createEmpty()); + val mask = masker.generateMask(size, 100, 50, 20); + + val slotInfo = Masker.slots(mask, EN); + val grid = mask.toGrid(); + + long t0 = System.currentTimeMillis(); + // Try to fill multiple times to get a better average + int iterations = 1; + long totalNodes = 0; + long totalBacktracks = 0; + int successCount = 0; + for (int i = 0; i < iterations; i++) { + val result = fillMask(rng, slotInfo, grid.copy(), false); + if (result.ok()) successCount++; + totalNodes += result.nodes(); + totalBacktracks += result.backtracks(); + } + long t1 = System.currentTimeMillis(); + double totalDuration = (t1 - t0) / 1000.0; + + System.out.printf(Locale.ROOT, "[DEBUG_LOG] Size %d: %d/%d SUCCESS | avg nodes=%d | avg backtracks=%d | total time=%.3fs%n", + size, successCount, iterations, totalNodes / iterations, totalBacktracks / iterations, totalDuration); + } + } + void main() { + testIncrementalComplexity(); + } + @Test + void testIncrementalComplexity() { + + // Use the complex mask from Main.java + String maskStr = "1 0000\n" + + "1 \n" + + "00 01 \n" + + " 1 \n" + + " 1 \n" + + " 2 1 \n" + + " 1 \n" + + "221 22\n"; + val mask = Masker.Clues.parse(maskStr); + val allSlots = Masker.slots(mask.c(), EN); +//mask.toGrid() + System.out.println("[DEBUG_LOG] \n--- Incremental Complexity Test ---"); + System.out.println("[DEBUG_LOG] Full Slot Layout:"); + visualizeSlots(allSlots); + + for (int i = 10; i <= allSlots.length; i++) { + val subset = Arrays.copyOf(allSlots, i); + // Arrays.sort(subset, Comparator.comparingInt(Slotinfo::score)); + System.out.printf("[DEBUG_LOG] Testing with first %d slots%n of %s", i, allSlots.length); + var grid = Slotinfo.grid(subset); + visualizeSlots(subset); + measureFill(new Rng(123 + i), subset, grid, "Subset size " + i); + } + } + + @Test + void testSingleSlotResolution() { + val rng = new Rng(42); + + // A single horizontal slot at (0,0) + val mask = Masker.Clues.parse("1 \n"); + val slots = Masker.slots(mask.c(), EN); + + System.out.println("[DEBUG_LOG] \n--- Single Slot Resolution ---"); + if (slots.length > 0) { + measureFill(rng, slots, mask.toGrid(), "Single Slot"); + } else { + System.out.println("[DEBUG_LOG] Error: No slots found in mask."); + } + } + + private void measureFill(Rng rng, Slotinfo[] slots, Grid grid, String label) { + long t0 = System.currentTimeMillis(); + int iterations = 1; + long totalNodes = 0; + long totalBacktracks = 0; + int successCount = 0; + + for (int i = 0; i < iterations; i++) { + // Reset assignments for each iteration + for (Slotinfo s : slots) s.assign().w = 0; + + val result = fillMask(rng, slots, grid.copy(), false); + if (result.ok()) successCount++; + totalNodes += result.nodes(); + totalBacktracks += result.backtracks(); + } + long t1 = System.currentTimeMillis(); + double totalDuration = (t1 - t0) / 1000.0; + + System.out.printf(Locale.ROOT, "[DEBUG_LOG] %s: %d/%d SUCCESS | avg nodes=%d | avg backtracks=%d | total time=%.3fs%n", + label, successCount, iterations, totalNodes / iterations, totalBacktracks / iterations, totalDuration); + } + + private void visualizeSlots(Slotinfo[] slots) { + int R = SwedishGenerator.R; + int C = SwedishGenerator.C; + char[][] display = new char[R][C]; + for (int r = 0; r < R; r++) Arrays.fill(display[r], ' '); + + for (Slotinfo slot : slots) { + int key = slot.key(); + int dir = Masker.Slot.dir(key); + int clueIdx = Masker.Slot.clueIndex(key); + + int cr = SwedishGenerator.IT[clueIdx].r(); + int cc = SwedishGenerator.IT[clueIdx].c(); + + // User requested: aAAAA for a four letter to RIGHT clue slot. + // SwedishGenerator: 1=RIGHT, 0=DOWN, 2=UP, 3=LEFT + char clueChar; + char slotChar; + switch (dir) { + case 1: + clueChar = 'a'; + slotChar = 'A'; + break; // RIGHT + case 0: + clueChar = 'b'; + slotChar = 'B'; + break; // DOWN + case 2: + clueChar = 'c'; + slotChar = 'C'; + break; // UP + case 3: + clueChar = 'd'; + slotChar = 'D'; + break; // LEFT + default: + clueChar = '?'; + slotChar = '?'; + } + + display[cr][cc] = clueChar; + + Masker.Slot.from(slot.key(), slot.lo(), slot.hi(), null).walk().forEach(idx -> { + int r = SwedishGenerator.IT[idx].r(); + int c = SwedishGenerator.IT[idx].c(); + if (display[r][c] == ' ' || (display[r][c] >= 'A' && display[r][c] <= 'D')) { + if (display[r][c] != ' ' && display[r][c] != slotChar) { + display[r][c] = '+'; // Intersection + } else { + display[r][c] = slotChar; + } } - long t1 = System.currentTimeMillis(); - double totalDuration = (t1 - t0) / 1000.0; - - System.out.printf(Locale.ROOT, "[DEBUG_LOG] Size %d: %d/%d SUCCESS | avg nodes=%d | avg backtracks=%d | total time=%.3fs%n", - size, successCount, iterations, totalNodes / iterations, totalBacktracks / iterations, totalDuration); - } - } + }); + } + + for (int r = 0; r < R; r++) { + System.out.println("[DEBUG_LOG] " + new String(display[r])); + } + } }