diff --git a/pom.xml b/pom.xml index 37afb23..4061d85 100644 --- a/pom.xml +++ b/pom.xml @@ -69,6 +69,12 @@ 1.7-SNAPSHOT provided + @@ -94,6 +100,13 @@ maven-compiler-plugin 3.13.0 + @@ -111,6 +124,7 @@ 25 25 + 25 diff --git a/src/main/java/puzzle/Main.java b/src/main/java/puzzle/Main.java index d530aea..a9a0e6f 100644 --- a/src/main/java/puzzle/Main.java +++ b/src/main/java/puzzle/Main.java @@ -386,6 +386,10 @@ public class Main { var grid = Masker_Neighbors9x8.grid(slotInfo);// mask.toGrid(); var filled = fillMask(rng, slotInfo, grid.lo, grid.hi, grid.g); + if (Thread.currentThread().isInterrupted() && !filled.ok()) { + return null; + } + if (!multiThreaded) { System.out.print("\r" + " ".repeat(120 - "".length()) + "\r"); System.out.flush(); diff --git a/src/main/java/puzzle/Person.java b/src/main/java/puzzle/Person.java deleted file mode 100644 index ff93d03..0000000 --- a/src/main/java/puzzle/Person.java +++ /dev/null @@ -1,18 +0,0 @@ -package puzzle; - -import anno.GenAccessors; - -@GenAccessors -public class Person { - @GenAccessors.Getter final String name; - @GenAccessors.Getter final int age; - - public Person(String name, int age) { this.name = name; this.age = age; } - - public static void main(String[] args) { - var p = new Person("Mike", 42); - - System.out.println(Person__Accessors.getName(p)); - System.out.println(Person__Accessors.getAge(p)); - } -} \ No newline at end of file diff --git a/src/test/java/puzzle/MarkerTest.java b/src/test/java/puzzle/MarkerTest.java index 18fc180..0d46e4e 100644 --- a/src/test/java/puzzle/MarkerTest.java +++ b/src/test/java/puzzle/MarkerTest.java @@ -159,9 +159,9 @@ public class MarkerTest { // Intersection is exactly 1 cell (0,2). Valid. assertTrue(masker.isValid(Riddle.Signa.of(r0c0d1, r2c2d2).c())); - // Clue 3: (1,1) Right. Slot cells: (1,2), (1,3), ... + // Clue 3: (1,3) Right. Slot cells: (1,4), (1,5), ... // No intersection with Clue 1 or 2. Valid. - assertTrue(masker.isValid(Riddle.Signa.of(r0c0d1, r2c2d2, r1c1d1).c())); + assertTrue(masker.isValid(Riddle.Signa.of(r0c0d1, r2c2d2, precomp.Const9x8.Cell.r1c3d1).c())); // Now create a violation: two slots sharing 2 cells. // We can do this with Corner Down and another clue. @@ -205,26 +205,76 @@ public class MarkerTest { } @Test - void testPhysicalAdjacency() { - var rng = new Rng(42); - var masker = new Masker(rng, new int[STACK_SIZE], Clues.createEmpty()); + void testDeadCellPenalty() { + var masker = emptyMasker(); - // Twee clues naast elkaar, maar slots kruisen niet. - // Clue 1: (1,1) Right. Slot (1,2), (1,3), (1,4) - // Clue 2: (2,1) Right. Slot (2,2), (2,3), (2,4) - var clues = Clues.createEmpty().setClue(r1c1d1).setClue(r2c1d1); + // A cell surrounded by 4 clues is a "dead cell" + // Let's take cell (4,4) + // Clues at (4,3), (4,5), (3,4), (5,4) + var deadClues = Clues.createEmpty(); + deadClues.setClue(precomp.Const9x8.CELLS[precomp.Const9x8.OFF_4_3 * 33 + 1]); // (4,3) Down + deadClues.setClue(precomp.Const9x8.CELLS[precomp.Const9x8.OFF_4_5 * 33 + 1]); // (4,5) Down + deadClues.setClue(precomp.Const9x8.CELLS[precomp.Const9x8.OFF_3_4 * 33]); // (3,4) Right + deadClues.setClue(precomp.Const9x8.CELLS[precomp.Const9x8.OFF_5_4 * 33]); // (5,4) Right - var fitness = masker.maskFitness(clues, 2); - // Als 8-naburigheid NIET MEER meetelt, moet de penalty hoog zijn. - System.out.println("[DEBUG_LOG] Fitness physically adjacent: " + fitness); - assertTrue(fitness > 20000, "Should have island penalty even if physically adjacent"); + var fitness = masker.maskFitness(deadClues, 4); + System.out.println("[DEBUG_LOG] Fitness dead cell: " + fitness); - // Twee clues naast elkaar, maar slots kruisen niet. - var clues2 = Clues.createEmpty().setClue(r1c1d1).setClue(r3c1d1); - var fitness2 = new Masker(rng, new int[STACK_SIZE], Clues.createEmpty()).maskFitness(clues2, 2); - // Als 8-naburigheid NIET MEER meetelt, moet de penalty hoog zijn. - System.out.println("[DEBUG_LOG] Fitness physically adjacent: " + fitness2); - assertTrue(fitness2 > 20000, "Should have island penalty even if physically adjacent"); + // The cell (4,4) should have 4 clue neighbors -> penalty 2000. + // It is also not covered by any word -> penalty 4000. + // Total penalty should be high. + assertTrue(fitness > 5000, "Dead cell should have high penalty"); + } + + @Test + void testDeadCellIsValid() { + var masker = emptyMasker(); + var deadClues = Clues.createEmpty(); + deadClues.setClue(precomp.Const9x8.Cell.r0c1d1); + deadClues.setClue(precomp.Const9x8.Cell.r2c1d1); + deadClues.setClue(precomp.Const9x8.Cell.r1c0d0); + deadClues.setClue(precomp.Const9x8.Cell.r1c2d0); + + int offending = masker.findOffendingClue(deadClues); + if (offending != -1) { + System.out.println("[DEBUG_LOG] Offending clue index: " + offending); + } + + assertFalse(masker.isValid(deadClues), "Mask with dead cell should be invalid"); + } + + @Test + void testCleanupDeadCell() { + var masker = emptyMasker(); + var deadClues = Clues.createEmpty(); + deadClues.setClue(precomp.Const9x8.Cell.r0c1d1); + deadClues.setClue(precomp.Const9x8.Cell.r2c1d1); + deadClues.setClue(precomp.Const9x8.Cell.r1c0d0); + deadClues.setClue(precomp.Const9x8.Cell.r1c2d0); + + assertFalse(masker.isValid(deadClues)); + + masker.cleanup(deadClues); + + assertTrue(masker.isValid(deadClues), "After cleanup, mask should be valid"); + assertTrue(deadClues.clueCount() < 4, "Cleanup should have removed at least one clue"); + } + + @Test + void testOnlyOneWordCoveragePenalty() { + var masker = emptyMasker(); + + // A white cell covered by only one word. + // Clue (4,0) Right. Cell (4,1) is covered only by this horizontal word. + var oneWord = Clues.createEmpty().setClue(precomp.Const9x8.CELLS[precomp.Const9x8.OFF_4_0 * 33]); + + var fitness = masker.maskFitness(oneWord, 1); + System.out.println("[DEBUG_LOG] Fitness only one word: " + fitness); + + // Each white cell of (4,0) Right (length 8) will have 1 word coverage. + // Penalty per cell is 1500. 8 cells * 1500 = 12000. + // Plus some 1-clue-per-row/col penalties etc. + assertTrue(fitness > 11000, "Should have penalty for single word coverage"); } @Test diff --git a/src/test/java/puzzle/PerformanceTest.java b/src/test/java/puzzle/PerformanceTest.java index abc1c7c..54e258d 100644 --- a/src/test/java/puzzle/PerformanceTest.java +++ b/src/test/java/puzzle/PerformanceTest.java @@ -57,7 +57,7 @@ import static puzzle.dict900.DictData900.DICT900; public class PerformanceTest { void main() { - testIncrementalComplexity(); + testPerformance(); } @Test void testPerformance() { @@ -65,14 +65,15 @@ public class PerformanceTest { // 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 clueSizes = new int[]{ 20,22,24, 25, 30 }; + var arr = new Clues[clueSizes.length]; var c = 0; + val masker = new Masker(rng, new int[Masker.STACK_SIZE], Clues.createEmpty()); for (var size : clueSizes) { - var t0 = System.currentTimeMillis(); - val masker = new Masker(rng, new int[Masker.STACK_SIZE], Clues.createEmpty()); + var t0 = System.currentTimeMillis(); + // Increased population and generations for stress - arr[c++] = masker.generateMask(size, 200, 100, 50); + arr[c++] = masker.generateMask(size, 200, 700, 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);