From 46b2bb04dc4c94f97e033ec9995b5de312130915 Mon Sep 17 00:00:00 2001 From: mike Date: Tue, 20 Jan 2026 23:24:24 +0100 Subject: [PATCH] introduce bitloops --- src/main/java/puzzle/Masker.java | 6 ++ src/test/java/puzzle/CornerClueTest.java | 9 ++- src/test/java/puzzle/ExportFormatTest.java | 20 ++----- src/test/java/puzzle/MainTest.java | 60 ++++++------------- src/test/java/puzzle/MaskerCluesTest.java | 27 +++------ .../java/puzzle/SwedishGeneratorTest.java | 55 ++--------------- 6 files changed, 50 insertions(+), 127 deletions(-) diff --git a/src/main/java/puzzle/Masker.java b/src/main/java/puzzle/Masker.java index 326cc7b..ecf5362 100644 --- a/src/main/java/puzzle/Masker.java +++ b/src/main/java/puzzle/Masker.java @@ -648,6 +648,12 @@ 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/CornerClueTest.java b/src/test/java/puzzle/CornerClueTest.java index 906b7d6..3edce87 100644 --- a/src/test/java/puzzle/CornerClueTest.java +++ b/src/test/java/puzzle/CornerClueTest.java @@ -14,8 +14,7 @@ public class CornerClueTest { @Test void testCornerDownSlot() { - - var clues = Clues.createEmpty().setClue(r0c0d4); + var clues = Clues.of(r0c0d4); // Clue op (0,0), type 4 (Corner Down) assertEquals(r0c0d4.d, clues.getDir(r0c0d4.index)); @@ -39,14 +38,14 @@ public class CornerClueTest { @Test void testCornerDownExtraction() { - var slots = Masker.slots(Clues.createEmpty().setClue(r0c0d4), DictData.DICT.index()); + 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.createEmpty().setClue(r0c1d5); + var clues = Clues.of(r0c1d5); assertEquals(r0c1d5.d, clues.getDir(r0c1d5.index)); @@ -69,7 +68,7 @@ public class CornerClueTest { @Test void testCornerDownLeftExtraction() { - var slots = Masker.slots(Clues.createEmpty().setClue(r0c1d5), DictData.DICT.index()); + 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 index 31ee1e5..53584fe 100644 --- a/src/test/java/puzzle/ExportFormatTest.java +++ b/src/test/java/puzzle/ExportFormatTest.java @@ -1,6 +1,5 @@ package puzzle; -import module java.base; import lombok.val; import org.junit.jupiter.api.Test; import puzzle.Export.Clued; @@ -12,38 +11,31 @@ import puzzle.SwedishGenerator.Assign; import puzzle.SwedishGenerator.FillResult; import puzzle.SwedishGenerator.Lemma; import puzzle.SwedishGenerator.Slotinfo; -import puzzle.SwedishGeneratorTest.Idx; 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.Export.Clue.LEFT3; -import static puzzle.Export.Clue.RIGHT1; -import static puzzle.SwedishGenerator.C; +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.Masker.Slot; -import static puzzle.GridBuilder.placeWord; import static puzzle.SwedishGeneratorTest.TEST; public class ExportFormatTest { @Test void testExportFormatFromFilled() { - val clues = Clues.createEmpty(); - // Place a RIGHT clue at (0,0) - clues.setClueLo(Idx.IDX_0_0.lo, RIGHT1.dir); - // This creates a slot starting at (0,1) - // Terminate the slot at (0,5) with another digit to avoid it extending to MAX_WORD_LENGTH - clues.setClueLo(Idx.IDX_0_5.lo, LEFT3.dir); + val clues = Clues.of(r0c0d1, r0c5d3); var grid = new Gridded(clues); // key = (cellIndex << 2) | (direction) - var key = Slot.packSlotKey(0, RIGHT1.dir); + 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)); diff --git a/src/test/java/puzzle/MainTest.java b/src/test/java/puzzle/MainTest.java index fe915be..2811075 100644 --- a/src/test/java/puzzle/MainTest.java +++ b/src/test/java/puzzle/MainTest.java @@ -17,17 +17,10 @@ import puzzle.SwedishGenerator.Rng; 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.OFF_0_0; -import static precomp.Const9x8.OFF_0_1; -import static precomp.Const9x8.OFF_0_2; -import static precomp.Const9x8.OFF_1_1; -import static precomp.Const9x8.OFF_1_2; -import static precomp.Const9x8.OFF_2_1; -import static precomp.Const9x8.OFF_2_3; -import static puzzle.Export.Clue.DOWN0; -import static puzzle.Export.Clue.LEFT3; -import static puzzle.Export.Clue.RIGHT1; -import static puzzle.Export.Clue.UP2; +import static precomp.Const9x8.*; +import static precomp.Const9x8.Cell.*; +import static puzzle.Export.Clue.*; +import static puzzle.Masker.Clues; import static puzzle.SwedishGenerator.Lemma; import static puzzle.SwedishGenerator.Slotinfo; import static puzzle.SwedishGenerator.fillMask; @@ -36,11 +29,6 @@ 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; @@ -59,12 +47,10 @@ public class MainTest { }}; @Test void testExtractSlots() { - - var clues = Masker.Clues.createEmpty(); - val key = Masker.Slot.packSlotKey(OFF_0_0, CLUE_RIGHT); - clues.setClueLo(IDX_0_0.lo, CLUE_RIGHT); - var grid = new Gridded(clues); - val g = grid.grid().g; + var clues = Clues.of(r0c0d1); + val key = r0c0d1.slotKey; + var grid = new Gridded(clues); + val g = grid.grid().g; GridBuilder.placeWord(grid.grid(), g, key, (1L << OFF_0_1) | (1L << OFF_0_2), 0, AB); var slots = Masker.extractSlots(clues, DictData.DICT.index()); @@ -89,8 +75,7 @@ public class MainTest { @Test void testForEachSlot() { - var clues = Masker.Clues.createEmpty(); - clues.setClueLo(IDX_0_0.lo, CLUE_RIGHT); + var clues = Clues.of(r0c0d1); var count = new AtomicInteger(0); clues.forEachSlot((key, lo, hi) -> { count.incrementAndGet(); @@ -111,10 +96,9 @@ public class MainTest { } @Test public void testGridBasics() { - var clues = new Clued(Masker.Clues.createEmpty()); - val key = Masker.Slot.packSlotKey(OFF_2_1, CLUE_UP); - clues.setClueLo(IDX_2_1.lo, CLUE_UP); - var grid = new Gridded(clues.c()); + var clues = new Clued(Clues.of(r2c1d2)); + val key = r2c1d2.slotKey; + var grid = new Gridded(clues.c()); // Test set/get GridBuilder.placeWord(grid.grid(), grid.grid().g, key, (1L << OFF_1_1) | (1L << OFF_0_1), 0, AZ); @@ -123,7 +107,7 @@ public class MainTest { Assertions.assertEquals(LETTER_Z, map.get(OFF_0_1)); var clueMap = clues.stream().collect(Collectors.toMap(ClueAt::index, ClueAt::clue)); Assertions.assertEquals(1, clueMap.size()); - Assertions.assertEquals(CLUE_UP, clueMap.get(OFF_2_1)); + Assertions.assertEquals(UP2.dir, clueMap.get(OFF_2_1)); // Test isLetterAt Assertions.assertFalse(clueMap.containsKey(OFF_0_0)); @@ -135,7 +119,7 @@ public class MainTest { Assertions.assertFalse(clues.isClueLo(OFF_0_0)); Assertions.assertTrue(clues.isClueLo(OFF_2_1)); clueMap = clues.stream().collect(Collectors.toMap(ClueAt::index, ClueAt::clue)); - Assertions.assertEquals(CLUE_UP, clueMap.get(OFF_2_1)); + Assertions.assertEquals(UP2.dir, clueMap.get(OFF_2_1)); Assertions.assertFalse(clues.isClueLo(OFF_2_3)); Assertions.assertFalse(clues.isClueLo(OFF_1_1)); @@ -146,27 +130,21 @@ public class MainTest { } @Test public void testCluesDeepCopy() { - var clues = new Clued(Masker.Clues.createEmpty()); - clues.setClueLo(IDX_0_0.lo, RIGHT1.dir); - clues.setClueLo(IDX_0_1.lo, UP2.dir); - clues.setClueLo(IDX_1_0.lo, LEFT3.dir); - clues.setClueLo(IDX_1_1.lo, DOWN0.dir); + var clues = new Clued(Clues.of(r0c0d1, r0c1d2, r1c0d3, r1c1d0)); var copy = clues.deepCopyGrid(); var clueMap = clues.stream().collect(Collectors.toMap(ClueAt::index, ClueAt::clue)); - Assertions.assertEquals(CLUE_RIGHT, clueMap.get(OFF_0_0)); + Assertions.assertEquals(RIGHT1.dir, clueMap.get(OFF_0_0)); - copy.setClueLo(IDX_0_0.lo, DOWN0.dir); + copy.setClue(r0c0d0); var copied = copy.stream().collect(Collectors.toMap(ClueAt::index, ClueAt::clue)); Assertions.assertEquals(DOWN0.dir, copied.get(OFF_0_0)); Assertions.assertEquals(RIGHT1.dir, clueMap.get(OFF_0_0)); } @Test public void testMini() { - val idx = IDX_1_1; - var clues = Masker.Clues.createEmpty(); - clues.setClueLo(idx.lo, CLUE_LEFT); - Assertions.assertTrue(clues.isClueLo(idx.index)); + var clues = Clues.of(r1c1d3); + Assertions.assertTrue(clues.isClueLo(OFF_1_1)); } @Test void testFiller2() { diff --git a/src/test/java/puzzle/MaskerCluesTest.java b/src/test/java/puzzle/MaskerCluesTest.java index 41d7ad0..7a58498 100644 --- a/src/test/java/puzzle/MaskerCluesTest.java +++ b/src/test/java/puzzle/MaskerCluesTest.java @@ -66,14 +66,14 @@ public class MaskerCluesTest { } @Test void testSimilarity() { - Clues a = Clued.create(r0c0d1, r2c1d0); - Clues b = Clues.createEmpty().setClue(r0c0d1).setClue(r2c1d0); + 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.createEmpty().setClue(r0c0d0).setClue(r2c1d0); + Clues c = Clues.of(r0c0d0, r2c1d0); assertTrue(a.similarity(c) < 1.0); // Completely different @@ -86,16 +86,13 @@ public class MaskerCluesTest { @Test void testIsValid() { Masker masker = new Masker(new Rng(42), new int[STACK_SIZE], Clues.createEmpty()); - Clues g = Clues.createEmpty(); - assertTrue(masker.isValid(g)); + assertTrue(masker.isValid(Clues.createEmpty())); // Valid clue: Right from (0,0) in 9x8 grid. Length is 8. - g.setClue(r0c0d1); - assertTrue(masker.isValid(g)); + assertTrue(masker.isValid(Clues.of(r0c0d1))); // Invalid clue: Right from (0,7) in 9x8 grid. Length is 1 (too short if MIN_LEN >= 2). - Clues g2 = Clues.createEmpty().setClue(r0c7d1); - assertFalse(masker.isValid(g2)); + assertFalse(masker.isValid(Clues.of(r0c7d1))); } @Test @@ -125,26 +122,20 @@ public class MaskerCluesTest { 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) - Clues g = Clues.createEmpty().setClue(r0c0d1); - // Clue 2: (1,2) Up. Slot cells: (0,2) // Intersection is exactly 1 cell (0,2). Valid. - g.setClue(r2c2d2); - assertTrue(masker.isValid(g)); + 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. - g.setClue(r1c1d1); - assertTrue(masker.isValid(g)); + 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). - - Clues g3 = Clues.createEmpty().setClue(r0c0d4).setClue(r0c2d5); - assertFalse(masker.isValid(g3)); + assertFalse(masker.isValid(Clues.of(r0c0d4, r0c2d5))); } @Test diff --git a/src/test/java/puzzle/SwedishGeneratorTest.java b/src/test/java/puzzle/SwedishGeneratorTest.java index f9971f6..222a0dd 100644 --- a/src/test/java/puzzle/SwedishGeneratorTest.java +++ b/src/test/java/puzzle/SwedishGeneratorTest.java @@ -13,9 +13,8 @@ import puzzle.Masker.Slot; import static org.junit.jupiter.api.Assertions.*; import static precomp.Const9x8.*; -import static precomp.Const9x8.Cell.r0c0d1; +import static precomp.Const9x8.Cell.*; import static puzzle.SwedishGenerator.*; -import static puzzle.SwedishGeneratorTest.Idx.IDX_0_0; public class SwedishGeneratorTest { @@ -80,52 +79,11 @@ public class SwedishGeneratorTest { static final byte D_BYTE_2 = CLUE_RIGHT; - enum Idx { - IDX_0_0(OFF_0_0, 0, 0), - IDX_0_1(OFF_0_1, 0, 1), - IDX_0_2(OFF_0_2, 0, 2), - IDX_0_3(OFF_0_3, 0, 3), - IDX_0_4(OFF_0_4, 0, 4), - IDX_0_5(OFF_0_5, 0, 5), - IDX_1_0(OFF_1_0, 1, 0), - IDX_1_1(OFF_1_1, 1, 1), - IDX_1_2(OFF_1_2, 1, 2), - IDX_1_3(OFF_1_3, 1, 3), - IDX_1_4(OFF_1_4, 1, 4), - IDX_1_5(OFF_1_5, 1, 5), - IDX_2_0(OFF_2_0, 2, 0), - IDX_2_1(OFF_2_1, 2, 1), - IDX_2_2(OFF_2_2, 2, 2), - IDX_2_3(OFF_2_3, 2, 3), - IDX_2_4(OFF_2_4, 2, 4), - IDX_2_5(OFF_2_5, 2, 5), - IDX_3_0(OFF_3_0, 3, 0), - IDX_3_1(OFF_3_1, 3, 1), - IDX_3_2(OFF_3_2, 3, 2), - IDX_3_3(OFF_3_3, 3, 3), - IDX_3_4(OFF_3_4, 3, 4), - IDX_3_5(OFF_3_5, 3, 5); - Idx(int idx, int r, int c) { - this.index = idx; - this.r = r; - this.c = c; - if (isLo(idx)) { - this.lo = 1L << idx; - this.hi = 0L; - } else { - this.lo = 0L; - this.hi = 1L << (idx & 63); - } - } - final int index, r, c; - final long lo, hi; - } @Test void testPatternForSlotAllLetters() { - var key = Slot.packSlotKey(OFF_0_0, CLUE_RIGHT); - val clues = Masker.Clues.createEmpty(); + var key = r0c0d1.slotKey; + val clues = Clues.of(r0c0d1); var grid = new Gridded(clues); - clues.setClueLo(IDX_0_0.lo, CLUE_RIGHT); 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)); assertEquals(LETTER_A, map.get(OFF_0_1)); @@ -288,8 +246,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 = Masker.Clues.createEmpty(); - clues.setClueLo(IDX_0_0.lo, CLUE_RIGHT); + var clues = Clues.of(r0c0d1); var dict = DictJavaGeneratorMulti.Dicts.makeDict(WORDS2); var slots = Masker.extractSlots(clues, dict.index()); assertEquals(1, slots.length); @@ -309,7 +266,7 @@ public class SwedishGeneratorTest { assertTrue(f1 >= 1_000_000_000L); // Add a slot - grid.setClueLo(IDX_0_0.lo, D_BYTE_2); + grid.setClue(r0c0d1); var f2 = gen.maskFitness(grid, 18); assertTrue(f2 < f1); } @@ -437,7 +394,7 @@ public class SwedishGeneratorTest { // Empty grid: huge penalty var fitEmpty = gen.maskFitness(grid, 18); assertTrue(fitEmpty >= 1_000_000_000L); - grid.setClueLo(IDX_0_0.lo, D_BYTE_2); // Right from 0,0. Len 2 if 3x3. + grid.setClue(r0c0d1); // Right from 0,0. Len 2 if 3x3. var fitOne = gen.maskFitness(grid, 18); assertTrue(fitOne < fitEmpty); }