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 { 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; } } }); } for (int r = 0; r < R; r++) { System.out.println("[DEBUG_LOG] " + new String(display[r])); } } }