From 78f72a024e0ae659c062d65015effde77a94d565 Mon Sep 17 00:00:00 2001 From: mike Date: Wed, 21 Jan 2026 00:09:37 +0100 Subject: [PATCH] introduce bitloops --- src/main/java/puzzle/DictMarker.java | 17 + src/main/java/puzzle/Export.java | 5 + src/main/java/puzzle/Masker.java | 6 - src/test/java/puzzle/ConnectivityTest.java | 79 ---- src/test/java/puzzle/CornerClueTest.java | 76 ---- src/test/java/puzzle/ExportFormatTest.java | 146 ------- src/test/java/puzzle/MainTest.java | 18 +- src/test/java/puzzle/MarkerTest.java | 408 ++++++++++++++++++ src/test/java/puzzle/MaskerCluesTest.java | 154 ------- src/test/java/puzzle/PerformanceTest.java | 92 ++-- .../java/puzzle/SwedishGeneratorTest.java | 61 +-- 11 files changed, 516 insertions(+), 546 deletions(-) create mode 100644 src/main/java/puzzle/DictMarker.java delete mode 100644 src/test/java/puzzle/ConnectivityTest.java delete mode 100644 src/test/java/puzzle/CornerClueTest.java delete mode 100644 src/test/java/puzzle/ExportFormatTest.java create mode 100644 src/test/java/puzzle/MarkerTest.java delete mode 100644 src/test/java/puzzle/MaskerCluesTest.java diff --git a/src/main/java/puzzle/DictMarker.java b/src/main/java/puzzle/DictMarker.java new file mode 100644 index 0000000..88cc15e --- /dev/null +++ b/src/main/java/puzzle/DictMarker.java @@ -0,0 +1,17 @@ +package puzzle; + +import gen.GenerateDict; + +@GenerateDict( + packageName = "puzzle", + className = "LemmaData", + scv = "nl_score_hints_v4.csv", + words = { + "EEN", "NAAR", "IEDEREEN", "A", "C", "X", "TEST", "IN", "INE", "INER", "INEREN", "INERENA", "INERENAE", + "APPLE", "AXE", "ABC", "ABD", "AZ", "AB", + "AT", "CAT", "DOGS", "APPLY", "BANAN", "BANANA", "BANANAS", "BANANASS" + }, + minLen = 2, + maxLen = 8 +) +public class DictMarker { } \ No newline at end of file diff --git a/src/main/java/puzzle/Export.java b/src/main/java/puzzle/Export.java index 5f16cf6..5d6d971 100644 --- a/src/main/java/puzzle/Export.java +++ b/src/main/java/puzzle/Export.java @@ -75,6 +75,11 @@ public record Export() { for (var cell1 : cell) empty.setClue(cell1); return empty; } + public static Clues of(Cell... cells) { + var c = createEmpty(); + for (var cell : cells) c.setClue(cell); + return c; + } public Clued deepCopyGrid() { return new Clued(new Clues(c.lo, c.hi, c.vlo, c.vhi, c.rlo, c.rhi, c.xlo, c.xhi)); } public static Clued parse(String s) { var c = createEmpty(); diff --git a/src/main/java/puzzle/Masker.java b/src/main/java/puzzle/Masker.java index ecf5362..326cc7b 100644 --- a/src/main/java/puzzle/Masker.java +++ b/src/main/java/puzzle/Masker.java @@ -648,12 +648,6 @@ public final class Masker { long lo, hi, vlo, vhi, rlo, rhi, xlo, xhi; public static Clues createEmpty() { return new Clues(0, 0, 0, 0, 0, 0, 0, 0); } - public static Clues of(precomp.Const9x8.Cell... cells) { - var c = createEmpty(); - for (var cell : cells) c.setClue(cell); - return c; - } - public boolean hasRoomForClue(int key) { if (Slotinfo.increasing(key)) if (!validSlot(lo, hi, key)) return false; return validSlotRev(lo, hi, key); diff --git a/src/test/java/puzzle/ConnectivityTest.java b/src/test/java/puzzle/ConnectivityTest.java deleted file mode 100644 index 556bb5f..0000000 --- a/src/test/java/puzzle/ConnectivityTest.java +++ /dev/null @@ -1,79 +0,0 @@ -package puzzle; - -import org.junit.jupiter.api.Test; -import puzzle.Masker.Clues; -import puzzle.SwedishGenerator.Rng; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static precomp.Const9x8.*; -import static precomp.Const9x8.Cell.*; -import static puzzle.SwedishGenerator.STACK_SIZE; - -public class ConnectivityTest { - - @Test - void testConnectivityPenalty() { - Rng rng = new Rng(42); - Masker masker = new Masker(rng, new int[STACK_SIZE], Clues.createEmpty()); - - // 1. Maak een masker met één component van clues (bijv. 2 clues die elkaar kruisen) - Clues singleComp = Clues.createEmpty().setClue(r0c0d1).setClue(r2c2d2); - - long fitnessSingle = masker.maskFitness(singleComp, 2); - - // 2. Maak een masker met twee eilandjes van clues - Clues twoIslands = Clues.createEmpty().setClue(r0c0d1).setClue(r7c7d1); - - long fitnessIslands = masker.maskFitness(twoIslands, 2); - - System.out.println("[DEBUG_LOG] Fitness single component: " + fitnessSingle); - System.out.println("[DEBUG_LOG] Fitness two islands: " + fitnessIslands); - - assertTrue(fitnessIslands > fitnessSingle + 10000, "Islands should have much higher penalty"); - } - - @Test - void testPhysicalAdjacency() { - Rng rng = new Rng(42); - Masker masker = new Masker(rng, new int[STACK_SIZE], Clues.createEmpty()); - - // 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) - Clues clues = Clues.createEmpty().setClue(r1c1d1).setClue(r2c1d1); - - long 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"); - - // Twee clues naast elkaar, maar slots kruisen niet. - Clues clues2 = Clues.createEmpty().setClue(r1c1d1).setClue(r3c1d1); - long 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"); - } - - @Test - void testCornerClueConnectivity() { - Rng rng = new Rng(42); - Masker masker = new Masker(rng, new int[STACK_SIZE], Clues.createEmpty()); - - // Clue A: (2,0) Right. Slot: (2,1), (2,2), (2,3), ... - // Clue B: (1,2) Corner Down. Word starts at (1,3) en gaat omlaag: (1,3), (2,3), (3,3)... - // Ze kruisen op (2,3). - Clues clues = Clues.createEmpty().setClue(r2c0d1).setClue(r1c2d4); - - long fitness = masker.maskFitness(clues, 2); - System.out.println("[DEBUG_LOG] Fitness corner clue connected: " + fitness); - - // Als ze verbonden zijn, is de penalty voor eilandjes 0. - // We vergelijken met een island scenario (2 clues die elkaar NIET raken) - Clues island = Clues.createEmpty().setClue(r2c0d1).setClue(r7c7d1); - - long fitnessIsland = masker.maskFitness(island, 2); - System.out.println("[DEBUG_LOG] Fitness island: " + fitnessIsland); - - assertTrue(fitnessIsland > fitness + 20000, "Island should add significant penalty compared to connected corner clue"); - } -} diff --git a/src/test/java/puzzle/CornerClueTest.java b/src/test/java/puzzle/CornerClueTest.java deleted file mode 100644 index 3edce87..0000000 --- a/src/test/java/puzzle/CornerClueTest.java +++ /dev/null @@ -1,76 +0,0 @@ -package puzzle; - -import lombok.AccessLevel; -import lombok.experimental.FieldDefaults; -import org.junit.jupiter.api.Test; -import puzzle.Export.Clue; -import puzzle.Masker.Clues; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static precomp.Const9x8.*; -import static precomp.Const9x8.Cell.*; - -public class CornerClueTest { - - @Test - void testCornerDownSlot() { - var clues = Clues.of(r0c0d4); - // Clue op (0,0), type 4 (Corner Down) - - assertEquals(r0c0d4.d, clues.getDir(r0c0d4.index)); - - // Controleer of forEachSlot het slot vindt - final var found = new boolean[]{ false }; - clues.forEachSlot((key, lo, hi) -> { - if (key == r0c0d4.slotKey) { - found[0] = true; - // Woord zou moeten starten op (0,1) - assertTrue((lo & (1L << OFF_0_1)) != 0, "Slot should start at (0,1)"); - // En omlaag gaan - assertTrue((lo & (1L << OFF_1_1)) != 0, "Slot should continue to (1,1)"); - - // Lengte van het slot zou 8 moeten zijn (van rij 0 t/m 7 in kolom 1) - assertEquals(8, Masker.Slot.length(lo, hi)); - } - }); - assertTrue(found[0], "Corner Down slot should be found"); - } - - @Test - void testCornerDownExtraction() { - var slots = Masker.slots(Clues.of(r0c0d4), DictData.DICT.index()); - assertEquals(1, slots.length); - assertEquals(r0c0d4.d, Masker.Slot.dir(slots[0].key())); - } - - @Test - void testCornerDownLeftSlot() { - var clues = Clues.of(r0c1d5); - - assertEquals(r0c1d5.d, clues.getDir(r0c1d5.index)); - - // Controleer of forEachSlot het slot vindt - final var found = new boolean[]{ false }; - clues.forEachSlot((key, lo, hi) -> { - if (key == r0c1d5.slotKey) { - found[0] = true; - // Woord zou moeten starten op (0,0) - assertTrue((lo & (1L << OFF_0_0)) != 0, "Slot should start at (0,0)"); - // En omlaag gaan - assertTrue((lo & (1L << OFF_1_0)) != 0, "Slot should continue to (1,0)"); - - // Lengte van het slot zou 8 moeten zijn (van rij 0 t/m 7 in kolom 0) - assertEquals(8, Masker.Slot.length(lo, hi)); - } - }); - assertTrue(found[0], "Corner Down Left slot should be found"); - } - - @Test - void testCornerDownLeftExtraction() { - var slots = Masker.slots(Clues.of(r0c1d5), DictData.DICT.index()); - - assertEquals(1, slots.length); - assertEquals(r0c1d5.d, Masker.Slot.dir(slots[0].key())); - } -} diff --git a/src/test/java/puzzle/ExportFormatTest.java b/src/test/java/puzzle/ExportFormatTest.java deleted file mode 100644 index 53584fe..0000000 --- a/src/test/java/puzzle/ExportFormatTest.java +++ /dev/null @@ -1,146 +0,0 @@ -package puzzle; - -import lombok.val; -import org.junit.jupiter.api.Test; -import puzzle.Export.Clued; -import puzzle.Export.Gridded; -import puzzle.Export.Placed; -import puzzle.Export.PuzzleResult; -import puzzle.Export.Rewards; -import puzzle.SwedishGenerator.Assign; -import puzzle.SwedishGenerator.FillResult; -import puzzle.SwedishGenerator.Lemma; -import puzzle.SwedishGenerator.Slotinfo; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static precomp.Const9x8.Cell.r0c0d1; -import static precomp.Const9x8.Cell.r0c5d3; -import static precomp.Const9x8.OFF_0_1; -import static precomp.Const9x8.OFF_0_2; -import static precomp.Const9x8.OFF_0_3; -import static precomp.Const9x8.OFF_0_4; -import static puzzle.GridBuilder.placeWord; -import static puzzle.Masker.Clues; -import static puzzle.SwedishGenerator.C; -import static puzzle.SwedishGenerator.FillStats; -import static puzzle.SwedishGenerator.R; -import static puzzle.SwedishGeneratorTest.TEST; - -public class ExportFormatTest { - - @Test - void testExportFormatFromFilled() { - val clues = Clues.of(r0c0d1, r0c5d3); - var grid = new Gridded(clues); - - // key = (cellIndex << 2) | (direction) - var key = r0c0d1.slotKey; - var lo = (1L << OFF_0_1) | (1L << OFF_0_2) | (1L << OFF_0_3) | (1L << OFF_0_4); - - assertTrue(placeWord(grid.grid(), grid.grid().g, key, lo, 0L, TEST)); - - 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, 0) - }, fillResult); - - var rewards = new Rewards(10, 5, 1); - var exported = puzzleResult.exportFormatFromFilled(rewards); - - assertNotNull(exported); - assertEquals(709, exported.difficulty()); - assertEquals(rewards, exported.rewards()); - - // Check words - assertEquals(1, exported.words().length); - var w = exported.words()[0]; - assertEquals("TEST", w.word()); - assertEquals(Placed.HORIZONTAL, w.direction()); - - // The bounding box should include (0,0) for the arrow and (0,1)-(0,4) for the word. - // minR=0, maxR=0, minC=0, maxC=4 - // startRow = 0 - minR = 0 - // startCol = 1 - minC = 1 - assertEquals(0, w.startRow()); - assertEquals(1, w.startCol()); - assertEquals(0, w.arrowRow()); - assertEquals(0, w.arrowCol()); - - // Check gridv2 - // It should be 1 row, containing "2TEST" -> but letters are mapped, digits are not explicitly in letterAt. - // Wait, look at exportFormatFromFilled logic: - // row.append(letterAt.getOrDefault(pack(r, c), '#')); - // letterAt only contains letters from placed words. - // arrow cells are NOT in letterAt unless they are also part of a word (unlikely). - // So (0,0) should be '#' - assertEquals(1, exported.grid().length); - assertEquals("#TEST", exported.grid()[0]); - } - - @Test - void testExportFormatEmpty() { - var grid = SwedishGeneratorTest.createEmpty(); - val clues = Clues.createEmpty(); - var fillResult = new FillResult(true, 0, 0, 0, 0, new FillStats()); - var puzzleResult = new PuzzleResult(new Clued(clues), new Gridded(grid, clues), new Slotinfo[0], fillResult); - - var exported = puzzleResult.exportFormatFromFilled(new Rewards(0, 0, 0)); - - assertNotNull(exported); - assertEquals(0, exported.words().length); - // Should return full grid with '#' - assertEquals(R, exported.grid().length); - for (var row : exported.grid()) { - assertEquals(C, row.length()); - assertTrue(row.matches("#+")); - } - } - - @Test - void testShardToClue() { - for (var length = 2; length <= 8; length++) { - val entry = DictData.DICT.index()[length]; - if (entry == null) continue; - val words = entry.words(); - for (var i = 0; i < Math.min(words.length, 5); i++) { - val wordVal = words[i]; - val word = Lemma.asWord(wordVal, Export.BYTES.get()); - val clueRec = Meta.lookup(wordVal); - - assertNotNull(clueRec); - assertEquals(word, Lemma.asWord(clueRec.w(), Export.BYTES.get())); - assertTrue(clueRec.simpel() >= 0); - assertTrue(clueRec.clues().length > 0); - } - } - } - - @Test - void testSpecificWords() { - // These words are known to be in the CSV and likely in the dictionary - var testWords = new String[]{ "EEN", "NAAR", "IEDEREEN" }; - for (var wStr : testWords) { - var w = Lemma.from(wStr); - - val clueRec = Meta.lookup(w); - assertNotNull(clueRec); - assertEquals(wStr, Lemma.asWord(clueRec.w(), Export.BYTES.get())); - // Check some expected complexity values (from CSV head output, column 3) - if (wStr.equals("EEN")) { - assertEquals(451, clueRec.simpel()); - assertEquals("een geheel vormend", clueRec.clues()[0]); - } - if (wStr.equals("NAAR")) { - assertEquals(497, clueRec.simpel()); - assertEquals("onaangenaam, vervelend, rot, niet leuk", clueRec.clues()[0]); - } - if (wStr.equals("IEDEREEN")) { - assertEquals(501, clueRec.simpel()); - assertEquals("elke persoon", clueRec.clues()[0]); - } - - assertTrue(clueRec.clues().length > 0); - } - } -} diff --git a/src/test/java/puzzle/MainTest.java b/src/test/java/puzzle/MainTest.java index 2811075..3e8a761 100644 --- a/src/test/java/puzzle/MainTest.java +++ b/src/test/java/puzzle/MainTest.java @@ -20,15 +20,11 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static precomp.Const9x8.*; import static precomp.Const9x8.Cell.*; import static puzzle.Export.Clue.*; -import static puzzle.Masker.Clues; +import static puzzle.LemmaData.AB; +import static puzzle.LemmaData.AZ; import static puzzle.SwedishGenerator.Lemma; 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.LETTER_A; import static puzzle.SwedishGeneratorTest.LETTER_Z; @@ -47,7 +43,7 @@ public class MainTest { }}; @Test void testExtractSlots() { - var clues = Clues.of(r0c0d1); + var clues = Clued.of(r0c0d1); val key = r0c0d1.slotKey; var grid = new Gridded(clues); val g = grid.grid().g; @@ -75,7 +71,7 @@ public class MainTest { @Test void testForEachSlot() { - var clues = Clues.of(r0c0d1); + var clues = Clued.of(r0c0d1); var count = new AtomicInteger(0); clues.forEachSlot((key, lo, hi) -> { count.incrementAndGet(); @@ -96,7 +92,7 @@ public class MainTest { } @Test public void testGridBasics() { - var clues = new Clued(Clues.of(r2c1d2)); + var clues = new Clued(Clued.of(r2c1d2)); val key = r2c1d2.slotKey; var grid = new Gridded(clues.c()); @@ -130,7 +126,7 @@ public class MainTest { } @Test public void testCluesDeepCopy() { - var clues = new Clued(Clues.of(r0c0d1, r0c1d2, r1c0d3, r1c1d0)); + var clues = new Clued(Clued.of(r0c0d1, r0c1d2, r1c0d3, r1c1d0)); var copy = clues.deepCopyGrid(); var clueMap = clues.stream().collect(Collectors.toMap(ClueAt::index, ClueAt::clue)); @@ -143,7 +139,7 @@ public class MainTest { } @Test public void testMini() { - var clues = Clues.of(r1c1d3); + var clues = Clued.of(r1c1d3); Assertions.assertTrue(clues.isClueLo(OFF_1_1)); } @Test diff --git a/src/test/java/puzzle/MarkerTest.java b/src/test/java/puzzle/MarkerTest.java new file mode 100644 index 0000000..3f116ed --- /dev/null +++ b/src/test/java/puzzle/MarkerTest.java @@ -0,0 +1,408 @@ +package puzzle; + +import lombok.val; +import org.junit.jupiter.api.Test; +import puzzle.Export.Clued; +import puzzle.Export.Gridded; +import puzzle.Export.Placed; +import puzzle.Export.PuzzleResult; +import puzzle.Export.Rewards; +import puzzle.Masker.Clues; +import puzzle.SwedishGenerator.Assign; +import puzzle.SwedishGenerator.FillResult; +import puzzle.SwedishGenerator.FillStats; +import puzzle.SwedishGenerator.Lemma; +import puzzle.SwedishGenerator.Rng; +import puzzle.SwedishGenerator.Slotinfo; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static precomp.Const9x8.*; +import static precomp.Const9x8.Cell.*; +import static puzzle.GridBuilder.placeWord; +import static puzzle.LemmaData.TEST; +import static puzzle.SwedishGenerator.C; +import static puzzle.SwedishGenerator.R; +import static puzzle.SwedishGenerator.STACK_SIZE; + +public class MarkerTest { + + @Test + void testValidRandomMask() { + var rng = new Rng(42); + var masker = new Masker(rng, new int[STACK_SIZE], Clues.createEmpty()); + for (var i = 0; i < 200; i++) { + for (var j = 19; j < 24; j++) { + var clues = masker.randomMask(j); + assertTrue(masker.isValid(clues), "Mask should be valid for length \n" + new Clued(clues).gridToString()); + } + } + } + @Test + void testValidMutate() { + var rng = new Rng(42); + var cache = Clues.createEmpty(); + var masker = new Masker(rng, new int[STACK_SIZE], cache); + var sim = 0.0; + var simCount = 0.0; + for (var i = 0; i < 200; i++) { + for (var j = 19; j < 24; j++) { + var clues = masker.randomMask(j); + val orig = cache.from(clues); + simCount++; + masker.mutate(clues); + sim += orig.similarity(clues); + assertTrue(masker.isValid(clues), "Mask should be valid for length \n" + new Clued(clues).gridToString()); + } + } + System.out.println("Average similarity: " + sim / simCount); + } + @Test + void testCross() { + var rng = new Rng(42); + var cache = Clues.createEmpty(); + var masker = new Masker(rng, new int[STACK_SIZE], cache); + var sim = 0.0; + var simCount = 0.0; + for (var i = 0; i < 200; i++) { + for (var j = 19; j < 24; j++) { + var clues = masker.randomMask(j); + var clues2 = masker.randomMask(j); + simCount++; + var cross = masker.crossover(clues, clues2); + sim += Math.max(cross.similarity(clues), cross.similarity(clues2)); + assertTrue(masker.isValid(cross), "Mask should be valid for length \n" + new Clued(cross).gridToString()); + } + } + System.out.println("Average similarity: " + sim / simCount); + } + @Test + void testSimilarity() { + var a = Clued.of(r0c0d1, r2c1d0); + var b = Clued.of(r0c0d1, r2c1d0); + + // Identity + assertEquals(1.0, a.similarity(b), 0.001); + + // Different direction + var c = Clued.of(r0c0d0, r2c1d0); + assertTrue(a.similarity(c) < 1.0); + + // Completely different + var d = Clues.createEmpty(); + // Matching empty cells count towards similarity. + // a has 2 clues, d has 0. They match on 70 empty cells. + assertEquals(70.0 / 72.0, a.similarity(d), 0.001); + } + + @Test + void testIsValid() { + var masker = new Masker(new Rng(42), new int[STACK_SIZE], Clues.createEmpty()); + assertTrue(masker.isValid(Clues.createEmpty())); + + // Valid clue: Right from (0,0) in 9x8 grid. Length is 8. + assertTrue(masker.isValid(Clued.of(r0c0d1))); + + // Invalid clue: Right from (0,7) in 9x8 grid. Length is 1 (too short if MIN_LEN >= 2). + assertFalse(masker.isValid(Clued.of(r0c7d1))); + } + + @Test + void testHasRoomForClue() { + var g = Clues.createEmpty(); + + // Room for Right clue at (0,0) (length 8) + assertTrue(g.hasRoomForClue(r0c0d1.slotKey)); + + // No room for Right clue at (0,8) (length 0 < MIN_LEN) + assertFalse(g.hasRoomForClue(r0c8d1.slotKey)); + + // Blocked room + // Let's place a clue that leaves only 1 cell for another clue. + g.setClue(r0c2d1); + // Now Right at (0,0) only has (0,1) available -> length 1 < MIN_LEN (which is 2) + assertFalse(g.hasRoomForClue(r0c0d1.slotKey)); + + // But enough room + g.clearClueLo(0L); + g.setClue(r0c3d1); + // Now Right at (0,0) has (0,1), (0,2) -> length 2 == MIN_LEN + assertTrue(g.hasRoomForClue(r0c0d1.slotKey)); + } + + @Test + void testIntersectionConstraint() { + var masker = new Masker(new Rng(42), new int[STACK_SIZE], Clues.createEmpty()); + // Clue 1: (0,0) Right. Slot cells: (0,1), (0,2), (0,3), (0,4), (0,5), (0,6), (0,7), (0,8) + // Clue 2: (1,2) Up. Slot cells: (0,2) + // Intersection is exactly 1 cell (0,2). Valid. + assertTrue(masker.isValid(Clued.of(r0c0d1, r2c2d2))); + + // Clue 3: (1,1) Right. Slot cells: (1,2), (1,3), ... + // No intersection with Clue 1 or 2. Valid. + assertTrue(masker.isValid(Clued.of(r0c0d1, r2c2d2, r1c1d1))); + + // Now create a violation: two slots sharing 2 cells. + // We can do this with Corner Down and another clue. + // Clue A: (0,0) Corner Down. Starts at (0,1) goes down: (0,1), (1,1), (2,1), (3,1), ... + // Clue B: (0,2) Corner Down Left. Starts at (0,1) goes down: (0,1), (1,1), (2,1), ... + // They share MANY cells starting from (0,1). + assertFalse(masker.isValid(Clued.of(r0c0d4, r0c2d5))); + } + + @Test + void testInvalidDirectionBits() { + var masker = new Masker(new Rng(42), new int[STACK_SIZE], Clues.createEmpty()); + var g = Clues.createEmpty(); + // Dir 6 (x=1, r=1, v=0) is invalid + g.setClueLo(1L << 0, (byte) 6); + assertFalse(masker.isValid(g)); + + // Dir 7 (x=1, r=1, v=1) is invalid + var g2 = Clues.createEmpty(); + g2.setClueLo(1L << 0, (byte) 7); + assertFalse(masker.isValid(g2)); + } + @Test + void testConnectivityPenalty() { + var rng = new Rng(42); + var masker = new Masker(rng, new int[STACK_SIZE], Clues.createEmpty()); + + // 1. Maak een masker met één component van clues (bijv. 2 clues die elkaar kruisen) + var singleComp = Clues.createEmpty().setClue(r0c0d1).setClue(r2c2d2); + + var fitnessSingle = masker.maskFitness(singleComp, 2); + + // 2. Maak een masker met twee eilandjes van clues + var twoIslands = Clues.createEmpty().setClue(r0c0d1).setClue(r7c7d1); + + var fitnessIslands = masker.maskFitness(twoIslands, 2); + + System.out.println("[DEBUG_LOG] Fitness single component: " + fitnessSingle); + System.out.println("[DEBUG_LOG] Fitness two islands: " + fitnessIslands); + + assertTrue(fitnessIslands > fitnessSingle + 10000, "Islands should have much higher penalty"); + } + + @Test + void testPhysicalAdjacency() { + var rng = new Rng(42); + var masker = new Masker(rng, new int[STACK_SIZE], Clues.createEmpty()); + + // 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); + + 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"); + + // 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"); + } + + @Test + void testCornerClueConnectivity() { + var rng = new Rng(42); + var masker = new Masker(rng, new int[STACK_SIZE], Clues.createEmpty()); + + // Clue A: (2,0) Right. Slot: (2,1), (2,2), (2,3), ... + // Clue B: (1,2) Corner Down. Word starts at (1,3) en gaat omlaag: (1,3), (2,3), (3,3)... + // Ze kruisen op (2,3). + var clues = Clues.createEmpty().setClue(r2c0d1).setClue(r1c2d4); + + var fitness = masker.maskFitness(clues, 2); + System.out.println("[DEBUG_LOG] Fitness corner clue connected: " + fitness); + + // Als ze verbonden zijn, is de penalty voor eilandjes 0. + // We vergelijken met een island scenario (2 clues die elkaar NIET raken) + var island = Clues.createEmpty().setClue(r2c0d1).setClue(r7c7d1); + + var fitnessIsland = masker.maskFitness(island, 2); + System.out.println("[DEBUG_LOG] Fitness island: " + fitnessIsland); + + assertTrue(fitnessIsland > fitness + 20000, "Island should add significant penalty compared to connected corner clue"); + } + @Test + void testCornerDownSlot() { + var clues = Clued.of(r0c0d4); + // Clue op (0,0), type 4 (Corner Down) + + assertEquals(r0c0d4.d, clues.getDir(r0c0d4.index)); + + // Controleer of forEachSlot het slot vindt + final var found = new boolean[]{ false }; + clues.forEachSlot((key, lo, hi) -> { + if (key == r0c0d4.slotKey) { + found[0] = true; + // Woord zou moeten starten op (0,1) + assertTrue((lo & (1L << OFF_0_1)) != 0, "Slot should start at (0,1)"); + // En omlaag gaan + assertTrue((lo & (1L << OFF_1_1)) != 0, "Slot should continue to (1,1)"); + + // Lengte van het slot zou 8 moeten zijn (van rij 0 t/m 7 in kolom 1) + assertEquals(8, Masker.Slot.length(lo, hi)); + } + }); + assertTrue(found[0], "Corner Down slot should be found"); + } + + @Test + void testCornerDownExtraction() { + var slots = Masker.slots(Clued.of(r0c0d4), DictData.DICT.index()); + assertEquals(1, slots.length); + assertEquals(r0c0d4.d, Masker.Slot.dir(slots[0].key())); + } + + @Test + void testCornerDownLeftSlot() { + var clues = Clued.of(r0c1d5); + + assertEquals(r0c1d5.d, clues.getDir(r0c1d5.index)); + + // Controleer of forEachSlot het slot vindt + final var found = new boolean[]{ false }; + clues.forEachSlot((key, lo, hi) -> { + if (key == r0c1d5.slotKey) { + found[0] = true; + // Woord zou moeten starten op (0,0) + assertTrue((lo & (1L << OFF_0_0)) != 0, "Slot should start at (0,0)"); + // En omlaag gaan + assertTrue((lo & (1L << OFF_1_0)) != 0, "Slot should continue to (1,0)"); + + // Lengte van het slot zou 8 moeten zijn (van rij 0 t/m 7 in kolom 0) + assertEquals(8, Masker.Slot.length(lo, hi)); + } + }); + assertTrue(found[0], "Corner Down Left slot should be found"); + } + + @Test + void testCornerDownLeftExtraction() { + var slots = Masker.slots(Clued.of(r0c1d5), DictData.DICT.index()); + + assertEquals(1, slots.length); + assertEquals(r0c1d5.d, Masker.Slot.dir(slots[0].key())); + } + @Test + void testExportFormatFromFilled() { + val clues = Clued.of(r0c0d1, r0c5d3); + var grid = new Gridded(clues); + + // key = (cellIndex << 2) | (direction) + var key = r0c0d1.slotKey; + var lo = (1L << OFF_0_1) | (1L << OFF_0_2) | (1L << OFF_0_3) | (1L << OFF_0_4); + + assertTrue(placeWord(grid.grid(), grid.grid().g, key, lo, 0L, TEST)); + + 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, 0) + }, fillResult); + + var rewards = new Rewards(10, 5, 1); + var exported = puzzleResult.exportFormatFromFilled(rewards); + + assertNotNull(exported); + assertEquals(709, exported.difficulty()); + assertEquals(rewards, exported.rewards()); + + // Check words + assertEquals(1, exported.words().length); + var w = exported.words()[0]; + assertEquals("TEST", w.word()); + assertEquals(Placed.HORIZONTAL, w.direction()); + + // The bounding box should include (0,0) for the arrow and (0,1)-(0,4) for the word. + // minR=0, maxR=0, minC=0, maxC=4 + // startRow = 0 - minR = 0 + // startCol = 1 - minC = 1 + assertEquals(0, w.startRow()); + assertEquals(1, w.startCol()); + assertEquals(0, w.arrowRow()); + assertEquals(0, w.arrowCol()); + + // Check gridv2 + // It should be 1 row, containing "2TEST" -> but letters are mapped, digits are not explicitly in letterAt. + // Wait, look at exportFormatFromFilled logic: + // row.append(letterAt.getOrDefault(pack(r, c), '#')); + // letterAt only contains letters from placed words. + // arrow cells are NOT in letterAt unless they are also part of a word (unlikely). + // So (0,0) should be '#' + assertEquals(1, exported.grid().length); + assertEquals("#TEST", exported.grid()[0]); + } + + @Test + void testExportFormatEmpty() { + var grid = SwedishGeneratorTest.createEmpty(); + val clues = Clues.createEmpty(); + var fillResult = new FillResult(true, 0, 0, 0, 0, new FillStats()); + var puzzleResult = new PuzzleResult(new Clued(clues), new Gridded(grid, clues), new Slotinfo[0], fillResult); + + var exported = puzzleResult.exportFormatFromFilled(new Rewards(0, 0, 0)); + + assertNotNull(exported); + assertEquals(0, exported.words().length); + // Should return full grid with '#' + assertEquals(R, exported.grid().length); + for (var row : exported.grid()) { + assertEquals(C, row.length()); + assertTrue(row.matches("#+")); + } + } + + @Test + void testShardToClue() { + for (var length = 2; length <= 8; length++) { + val entry = DictData.DICT.index()[length]; + if (entry == null) continue; + val words = entry.words(); + for (var i = 0; i < Math.min(words.length, 5); i++) { + val wordVal = words[i]; + val word = Lemma.asWord(wordVal, Export.BYTES.get()); + val clueRec = Meta.lookup(wordVal); + + assertNotNull(clueRec); + assertEquals(word, Lemma.asWord(clueRec.w(), Export.BYTES.get())); + assertTrue(clueRec.simpel() >= 0); + assertTrue(clueRec.clues().length > 0); + } + } + } + + @Test + void testSpecificWords() { + // These words are known to be in the CSV and likely in the dictionary + var testWords = new String[]{ "EEN", "NAAR", "IEDEREEN" }; + for (var wStr : testWords) { + var w = Lemma.from(wStr); + + val clueRec = Meta.lookup(w); + assertNotNull(clueRec); + assertEquals(wStr, Lemma.asWord(clueRec.w(), Export.BYTES.get())); + // Check some expected complexity values (from CSV head output, column 3) + if (wStr.equals("EEN")) { + assertEquals(451, clueRec.simpel()); + assertEquals("een geheel vormend", clueRec.clues()[0]); + } + if (wStr.equals("NAAR")) { + assertEquals(497, clueRec.simpel()); + assertEquals("onaangenaam, vervelend, rot, niet leuk", clueRec.clues()[0]); + } + if (wStr.equals("IEDEREEN")) { + assertEquals(501, clueRec.simpel()); + assertEquals("elke persoon", clueRec.clues()[0]); + } + + assertTrue(clueRec.clues().length > 0); + } + } +} diff --git a/src/test/java/puzzle/MaskerCluesTest.java b/src/test/java/puzzle/MaskerCluesTest.java deleted file mode 100644 index 7a58498..0000000 --- a/src/test/java/puzzle/MaskerCluesTest.java +++ /dev/null @@ -1,154 +0,0 @@ -package puzzle; - -import lombok.val; -import org.junit.jupiter.api.Test; -import puzzle.Export.Clued; -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 precomp.Const9x8.*; -import static precomp.Const9x8.Cell.*; -import static puzzle.Masker.Clues; -import static puzzle.Masker.Slot; -import static puzzle.SwedishGenerator.Rng; -import static puzzle.SwedishGenerator.STACK_SIZE; - -public class MaskerCluesTest { - - @Test - void testValidRandomMask() { - Rng rng = new Rng(42); - Masker masker = new Masker(rng, new int[STACK_SIZE], Clues.createEmpty()); - for (int i = 0; i < 200; i++) { - for (int j = 19; j < 24; j++) { - var clues = masker.randomMask(j); - assertTrue(masker.isValid(clues), "Mask should be valid for length \n" + new Clued(clues).gridToString()); - } - } - } - @Test - void testValidMutate() { - Rng rng = new Rng(42); - var cache = Clues.createEmpty(); - Masker masker = new Masker(rng, new int[STACK_SIZE], cache); - double sim = 0.0; - double simCount = 0.0; - for (int i = 0; i < 200; i++) { - for (int j = 19; j < 24; j++) { - var clues = masker.randomMask(j); - val orig = cache.from(clues); - simCount++; - masker.mutate(clues); - sim += orig.similarity(clues); - assertTrue(masker.isValid(clues), "Mask should be valid for length \n" + new Clued(clues).gridToString()); - } - } - System.out.println("Average similarity: " + sim / simCount); - } - @Test - void testCross() { - Rng rng = new Rng(42); - var cache = Clues.createEmpty(); - Masker masker = new Masker(rng, new int[STACK_SIZE], cache); - double sim = 0.0; - double simCount = 0.0; - for (int i = 0; i < 200; i++) { - for (int j = 19; j < 24; j++) { - var clues = masker.randomMask(j); - var clues2 = masker.randomMask(j); - simCount++; - var cross = masker.crossover(clues, clues2); - sim += Math.max(cross.similarity(clues), cross.similarity(clues2)); - assertTrue(masker.isValid(cross), "Mask should be valid for length \n" + new Clued(cross).gridToString()); - } - } - System.out.println("Average similarity: " + sim / simCount); - } - @Test - void testSimilarity() { - Clues a = Clues.of(r0c0d1, r2c1d0); - Clues b = Clues.of(r0c0d1, r2c1d0); - - // Identity - assertEquals(1.0, a.similarity(b), 0.001); - - // Different direction - Clues c = Clues.of(r0c0d0, r2c1d0); - assertTrue(a.similarity(c) < 1.0); - - // Completely different - Clues d = Clues.createEmpty(); - // Matching empty cells count towards similarity. - // a has 2 clues, d has 0. They match on 70 empty cells. - assertEquals(70.0 / 72.0, a.similarity(d), 0.001); - } - - @Test - void testIsValid() { - Masker masker = new Masker(new Rng(42), new int[STACK_SIZE], Clues.createEmpty()); - assertTrue(masker.isValid(Clues.createEmpty())); - - // Valid clue: Right from (0,0) in 9x8 grid. Length is 8. - assertTrue(masker.isValid(Clues.of(r0c0d1))); - - // Invalid clue: Right from (0,7) in 9x8 grid. Length is 1 (too short if MIN_LEN >= 2). - assertFalse(masker.isValid(Clues.of(r0c7d1))); - } - - @Test - void testHasRoomForClue() { - Clues g = Clues.createEmpty(); - - // Room for Right clue at (0,0) (length 8) - assertTrue(g.hasRoomForClue(r0c0d1.slotKey)); - - // No room for Right clue at (0,8) (length 0 < MIN_LEN) - assertFalse(g.hasRoomForClue(r0c8d1.slotKey)); - - // Blocked room - // Let's place a clue that leaves only 1 cell for another clue. - g.setClue(r0c2d1); - // Now Right at (0,0) only has (0,1) available -> length 1 < MIN_LEN (which is 2) - assertFalse(g.hasRoomForClue(r0c0d1.slotKey)); - - // But enough room - g.clearClueLo(0L); - g.setClue(r0c3d1); - // Now Right at (0,0) has (0,1), (0,2) -> length 2 == MIN_LEN - assertTrue(g.hasRoomForClue(r0c0d1.slotKey)); - } - - @Test - void testIntersectionConstraint() { - Masker masker = new Masker(new Rng(42), new int[STACK_SIZE], Clues.createEmpty()); - // Clue 1: (0,0) Right. Slot cells: (0,1), (0,2), (0,3), (0,4), (0,5), (0,6), (0,7), (0,8) - // Clue 2: (1,2) Up. Slot cells: (0,2) - // Intersection is exactly 1 cell (0,2). Valid. - assertTrue(masker.isValid(Clues.of(r0c0d1, r2c2d2))); - - // Clue 3: (1,1) Right. Slot cells: (1,2), (1,3), ... - // No intersection with Clue 1 or 2. Valid. - assertTrue(masker.isValid(Clues.of(r0c0d1, r2c2d2, r1c1d1))); - - // Now create a violation: two slots sharing 2 cells. - // We can do this with Corner Down and another clue. - // Clue A: (0,0) Corner Down. Starts at (0,1) goes down: (0,1), (1,1), (2,1), (3,1), ... - // Clue B: (0,2) Corner Down Left. Starts at (0,1) goes down: (0,1), (1,1), (2,1), ... - // They share MANY cells starting from (0,1). - assertFalse(masker.isValid(Clues.of(r0c0d4, r0c2d5))); - } - - @Test - void testInvalidDirectionBits() { - Masker masker = new Masker(new Rng(42), new int[STACK_SIZE], Clues.createEmpty()); - Clues g = Clues.createEmpty(); - // Dir 6 (x=1, r=1, v=0) is invalid - g.setClueLo(1L << 0, (byte) 6); - assertFalse(masker.isValid(g)); - - // Dir 7 (x=1, r=1, v=1) is invalid - Clues g2 = Clues.createEmpty(); - g2.setClueLo(1L << 0, (byte) 7); - assertFalse(masker.isValid(g2)); - } -} diff --git a/src/test/java/puzzle/PerformanceTest.java b/src/test/java/puzzle/PerformanceTest.java index e52e70f..01aba0a 100644 --- a/src/test/java/puzzle/PerformanceTest.java +++ b/src/test/java/puzzle/PerformanceTest.java @@ -28,16 +28,16 @@ public class PerformanceTest { // 1. Stress test Clue Generation (Mask Generation) System.out.println("[DEBUG_LOG] --- Mask Generation Performance ---"); - int[] clueSizes = { 20, 25, 30 }; - var arr = new Clues[3]; - int c = 0; - for (int size : clueSizes) { - long t0 = System.currentTimeMillis(); - val masker = new Masker(rng, new int[SwedishGenerator.STACK_SIZE], Masker.Clues.createEmpty()); + 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[SwedishGenerator.STACK_SIZE], Masker.Clues.createEmpty()); // Increased population and generations for stress arr[c++] = masker.generateMask(size, 200, 100, 50); - long t1 = System.currentTimeMillis(); - double duration = (t1 - t0) / 1000.0; + 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); @@ -46,16 +46,16 @@ public class PerformanceTest { // 2. Stress test Word Filler System.out.println("[DEBUG_LOG] \n--- Word Filler Performance ---"); c = 0; - for (int size : clueSizes) { + for (var size : clueSizes) { - long t0 = System.currentTimeMillis(); + var t0 = System.currentTimeMillis(); // Try to fill multiple times to get a better average - int iterations = 10; + var iterations = 10; long totalNodes = 0; long totalBacktracks = 0; - int successCount = 0; + var successCount = 0; - for (int i = 0; i < iterations; i++) { + for (var i = 0; i < iterations; i++) { val slotInfo = Masker.slots(arr[c], EN); val result = fillMask(rng, slotInfo, Slotinfo.grid(slotInfo)); if (result.ok()) successCount++; @@ -63,8 +63,8 @@ public class PerformanceTest { totalBacktracks += result.backtracks(); } c++; - long t1 = System.currentTimeMillis(); - double totalDuration = (t1 - t0) / 1000.0; + 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); @@ -74,14 +74,14 @@ public class PerformanceTest { 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"; + var maskStr = "1 0000\n" + + "1 \n" + + "00 01 \n" + + " 1 \n" + + " 1 \n" + + " 2 1 \n" + + " 1 \n" + + "221 22\n"; val mask = Clued.parse(maskStr); val allSlots = Masker.slots(mask.c(), DICT900.index()); //mask.toGrid() @@ -89,7 +89,7 @@ public class PerformanceTest { System.out.println("[DEBUG_LOG] Full Slot Layout:"); visualizeSlots(allSlots); - for (int i = 10; i <= allSlots.length; i++) { + 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); @@ -115,15 +115,15 @@ public class PerformanceTest { } private void measureFill(Rng rng, Slotinfo[] slots, String label) { - long t0 = System.currentTimeMillis(); - int iterations = 1; + var t0 = System.currentTimeMillis(); + var iterations = 1; long totalNodes = 0; long totalBacktracks = 0; - int successCount = 0; + var successCount = 0; - for (int i = 0; i < iterations; i++) { + for (var i = 0; i < iterations; i++) { // Reset assignments for each iteration - for (Slotinfo s : slots) s.assign().w = 0; + for (var s : slots) s.assign().w = 0; val result = fillMask(rng, slots, Slotinfo.grid(slots)); if (result.ok()) { @@ -132,8 +132,8 @@ public class PerformanceTest { totalNodes += result.nodes(); totalBacktracks += result.backtracks(); } - long t1 = System.currentTimeMillis(); - double totalDuration = (t1 - t0) / 1000.0; + 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); @@ -141,28 +141,28 @@ public class PerformanceTest { } 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], ' '); + var R = SwedishGenerator.R; + var C = SwedishGenerator.C; + var display = new char[R][C]; + for (var r = 0; r < R; r++) Arrays.fill(display[r], ' '); - for (Slotinfo slot : slots) { - int key = slot.key(); - Clue dir = Clue.from(Masker.Slot.dir(key)); - int clueIdx = Masker.Slot.clueIndex(key); + for (var slot : slots) { + var key = slot.key(); + var dir = Clue.from(Masker.Slot.dir(key)); + var clueIdx = Masker.Slot.clueIndex(key); - int cr = Masker.IT[clueIdx].r(); - int cc = Masker.IT[clueIdx].c(); + 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 - char clueChar = dir.clueChar; - char slotChar = dir.slotChar; + var clueChar = dir.clueChar; + var slotChar = dir.slotChar; display[cr][cc] = clueChar; Gridded.cellWalk((byte) slot.key(), slot.lo(), slot.hi()).forEach(idx -> { - int r = Masker.IT[idx].r(); - int c = Masker.IT[idx].c(); + 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 @@ -173,7 +173,7 @@ public class PerformanceTest { }); } - for (int r = 0; r < R; r++) { + for (var r = 0; r < R; r++) { System.out.println("[DEBUG_LOG] " + new String(display[r])); } } diff --git a/src/test/java/puzzle/SwedishGeneratorTest.java b/src/test/java/puzzle/SwedishGeneratorTest.java index 222a0dd..ad50341 100644 --- a/src/test/java/puzzle/SwedishGeneratorTest.java +++ b/src/test/java/puzzle/SwedishGeneratorTest.java @@ -14,6 +14,25 @@ import puzzle.Masker.Slot; import static org.junit.jupiter.api.Assertions.*; import static precomp.Const9x8.*; import static precomp.Const9x8.Cell.*; +import static puzzle.LemmaData.ABC; +import static puzzle.LemmaData.ABD; +import static puzzle.LemmaData.APPLE; +import static puzzle.LemmaData.APPLY; +import static puzzle.LemmaData.AT; +import static puzzle.LemmaData.AZ; +import static puzzle.LemmaData.BANAN; +import static puzzle.LemmaData.BANANA; +import static puzzle.LemmaData.BANANAS; +import static puzzle.LemmaData.BANANASS; +import static puzzle.LemmaData.CAT; +import static puzzle.LemmaData.DOGS; +import static puzzle.LemmaData.EXE; +import static puzzle.LemmaData.IN; +import static puzzle.LemmaData.INE; +import static puzzle.LemmaData.INER; +import static puzzle.LemmaData.INEREN; +import static puzzle.LemmaData.INERENA; +import static puzzle.LemmaData.INERENAE; import static puzzle.SwedishGenerator.*; public class SwedishGeneratorTest { @@ -27,35 +46,23 @@ public class SwedishGeneratorTest { public static Context get() { return CTX.get(); } } - static final long TEST = Lemma.from("TEST"); - static final long IN = Lemma.from("IN"); - static final long INER = Lemma.from("INER"); - static final long INEREN = Lemma.from("INEREN"); - static final long INERENA = Lemma.from("INERENA"); - static final long INERENAE = Lemma.from("INERENAE"); - static final long APPLE = Lemma.from("APPLE"); - static final long EXE = Lemma.from("AXE"); - static final long ABC = Lemma.from("ABC"); - static final long ABD = Lemma.from("ABD"); - static final long AZ = Lemma.from("AZ"); - static final long AB = Lemma.from("AB"); - static final long[] WORDS = new long[]{ - Lemma.from("AT"), - Lemma.from("CAT"), - Lemma.from("DOGS"), + static final long[] WORDS = new long[]{ + AT, + CAT, + DOGS, APPLE, - Lemma.from("APPLY"), - Lemma.from("BANAN"), - Lemma.from("BANANA"), - Lemma.from("BANANAS"), - Lemma.from("BANANASS") // length 8 + APPLY, + BANAN, + BANANA, + BANANAS, + BANANASS }; static final long[] WORDS2 = new long[]{ IN, APPLE, - Lemma.from("APPLY"), - Lemma.from("BANAN"), - Lemma.from("INE"), + APPLY, + BANAN, + INE, INER, INEREN, INERENA, @@ -77,12 +84,10 @@ public class SwedishGeneratorTest { static final byte CLUE_UP = 2; static final byte CLUE_LEFT = 3; - static final byte D_BYTE_2 = CLUE_RIGHT; - @Test void testPatternForSlotAllLetters() { var key = r0c0d1.slotKey; - val clues = Clues.of(r0c0d1); + val clues = Clued.of(r0c0d1); var grid = new Gridded(clues); 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)); @@ -246,7 +251,7 @@ public class SwedishGeneratorTest { @Test void testForEachSlotAndExtractSlots() { // This should detect a slot starting at 0,1 with length 2 (0,1 and 0,2) - var clues = Clues.of(r0c0d1); + var clues = Clued.of(r0c0d1); var dict = DictJavaGeneratorMulti.Dicts.makeDict(WORDS2); var slots = Masker.extractSlots(clues, dict.index()); assertEquals(1, slots.length);