package puzzle; import module java.base; import anno.DictGen; import anno.Dictionaries; import lombok.val; import org.junit.jupiter.api.Test; import puzzle.Export.Clue; import puzzle.Export.Signa; import puzzle.Export.Puzzle; import puzzle.SwedishGenerator.Rng; import puzzle.SwedishGenerator.Slotinfo; import static org.junit.jupiter.api.Assertions.assertTrue; import static precomp.Const9x8.Cell.*; import static puzzle.SwedishGenerator.fillMask; import static puzzle.dict800.DictData800.DICT800; import static puzzle.dict900.DictData900.DICT900; @Dictionaries({ @DictGen( packageName = "puzzle.dict900", className = "DictData900", scv = "/home/mike/dev/puzzle-generator/nl_score_hints_v4.csv", simpleMax = 900, minLen = 2, maxLen = 8 ), @DictGen( packageName = "puzzle.dict800", className = "DictData800", scv = "/home/mike/dev/puzzle-generator/nl_score_hints_v4.csv", simpleMax = 800, minLen = 2, maxLen = 8 ) }) public class PerformanceTest { void main() { testIncrementalComplexity(); } @Test void testPerformance() { val rng = new Rng(42); // 1. Stress test Clue Generation (Mask Generation) System.out.println("[DEBUG_LOG] --- Mask Generation Performance ---"); var clueSizes = new int[]{ 20, 25, 30 }; var arr = new Clues[3]; var c = 0; for (var size : clueSizes) { var t0 = System.currentTimeMillis(); val masker = new Masker(rng, new int[Masker.STACK_SIZE], Clues.createEmpty()); // Increased population and generations for stress arr[c++] = masker.generateMask(size, 200, 100, 50); var t1 = System.currentTimeMillis(); var 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 ---"); c = 0; for (var size : clueSizes) { var t0 = System.currentTimeMillis(); // Try to fill multiple times to get a better average var iterations = 10; long totalNodes = 0; long totalBacktracks = 0; var successCount = 0; for (var i = 0; i < iterations; i++) { val slotInfo = Masker.slots(arr[c], DICT800); var grid = Slotinfo.grid(slotInfo); val result = fillMask(rng, slotInfo,grid.lo,grid.hi, grid.g); if (result.ok()) successCount++; totalNodes += result.nodes(); totalBacktracks += result.backtracks(); } c++; var t1 = System.currentTimeMillis(); var 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); } } @Test void testIncrementalComplexity() { // Use the complex mask from Main.java var mask = Signa.of( r0c0d1, r0c5d0, r0c6d0, r0c7d0, r0c8d0, r1c0d1, r2c0d0, r2c1d0, r2c3d0, r2c4d1, r3c4d1, r4c4d1, r5c2d2, r5c4d1, r6c2d1, r7c0d2, r7c1d2, r7c2d1, r7c7d2, r7c8d2 ); val allSlots = Masker.slots(mask.c(), DICT900); //mask.toGrid() System.out.println("[DEBUG_LOG] \n--- Incremental Complexity Test ---"); System.out.println("[DEBUG_LOG] Full Slot Layout:"); visualizeSlots(allSlots); for (var 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); visualizeSlots(subset); measureFill(new Rng(123 + i), subset, "Subset size " + i); } } @Test void testSingleSlotResolution() { val rng = new Rng(42); // A single horizontal slot at (0,0) val mask = Signa.of(r0c0d1); val slots = Masker.slots(mask.c(), DICT800); System.out.println("[DEBUG_LOG] \n--- Single Slot Resolution ---"); if (slots.length > 0) { measureFill(rng, slots, "Single Slot"); } else { System.out.println("[DEBUG_LOG] Error: No slots found in mask."); } } private void measureFill(Rng rng, Slotinfo[] slots, String label) { var t0 = System.currentTimeMillis(); var iterations = 1; long totalNodes = 0; long totalBacktracks = 0; var successCount = 0; for (var i = 0; i < iterations; i++) { // Reset assignments for each iteration for (var s : slots) s.assign().w = 0; var grid = Slotinfo.grid(slots); val result = fillMask(rng, slots, grid.lo,grid.hi,grid.g); if (result.ok()) { successCount++; } totalNodes += result.nodes(); totalBacktracks += result.backtracks(); } var t1 = System.currentTimeMillis(); var 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); visualizeSlots(slots); } private void visualizeSlots(Slotinfo[] slots) { var R = Masker.R; var C = Masker.C; var display = new char[R][C]; for (var r = 0; r < R; r++) Arrays.fill(display[r], ' '); for (var slot : slots) { var key = slot.key(); var dir = Clue.from(Masker.Slot.dir(key)); var clueIdx = Masker.Slot.clueIndex(key); var cr = Masker.IT[clueIdx].r(); var cc = Masker.IT[clueIdx].c(); // User requested: aAAAA for a four letter to RIGHT clue slot. // SwedishGenerator: 1=RIGHT, 0=DOWN, 2=UP, 3=LEFT var clueChar = dir.clueChar; var slotChar = dir.slotChar; display[cr][cc] = clueChar; Puzzle.cellWalk((byte) slot.key(), slot.lo(), slot.hi()).forEach(idx -> { var r = Masker.IT[idx].r(); var c = Masker.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; } } }); } for (var r = 0; r < R; r++) { System.out.println("[DEBUG_LOG] " + new String(display[r])); } } }