From 85fa317eec9d82dd74554830a5c7eaaca533dc15 Mon Sep 17 00:00:00 2001 From: mike Date: Tue, 20 Jan 2026 22:48:38 +0100 Subject: [PATCH] introduce bitloops --- src/main/java/puzzle/Export.java | 20 ++++--- src/main/java/puzzle/Masker.java | 40 ++++++-------- src/test/java/puzzle/CornerClueTest.java | 55 +++++++++++-------- src/test/java/puzzle/ExportFormatTest.java | 11 ++-- src/test/java/puzzle/MainTest.java | 41 +++++++------- .../java/puzzle/SwedishGeneratorTest.java | 16 +++--- 6 files changed, 95 insertions(+), 88 deletions(-) diff --git a/src/main/java/puzzle/Export.java b/src/main/java/puzzle/Export.java index 9f854ba..390a7f2 100644 --- a/src/main/java/puzzle/Export.java +++ b/src/main/java/puzzle/Export.java @@ -10,8 +10,8 @@ import puzzle.Masker.Clues; import puzzle.SwedishGenerator.FillResult; import puzzle.SwedishGenerator.Grid; import puzzle.SwedishGenerator.Slotinfo; -import static puzzle.Export.Clue.DOWN; -import static puzzle.Export.Clue.RIGHT; +import static puzzle.Export.Clue.DOWN0; +import static puzzle.Export.Clue.RIGHT1; import static puzzle.Masker.Clues.createEmpty; import static puzzle.SwedishGenerator.R; import static puzzle.SwedishGenerator.Lemma; @@ -45,14 +45,16 @@ public record Export() { static int INDEX(int r, int cols, int c) { return r * cols + c; } @AllArgsConstructor enum Clue { - DOWN(CLUE_DOWN, 'B', 'b'), - RIGHT(CLUE_RIGHT, 'A', 'a'), - UP(CLUE_UP, 'C', 'c'), - LEFT(CLUE_LEFT, 'D', 'd'), + DOWN0(CLUE_DOWN, 'B', 'b'), + RIGHT1(CLUE_RIGHT, 'A', 'a'), + UP2(CLUE_UP, 'C', 'c'), + LEFT3(CLUE_LEFT, 'D', 'd'), + LEFT_TOP4(CLUE_LEFT_TOP, 'E', 'e'), + RIGHT_TOP5(CLUE_RIGHT_TOP, 'F', 'f'), NONE(CLUE_LEFT, '?', '?'); final byte dir; final char slotChar, clueChar; - private static final Clue[] CLUES = new Clue[]{ DOWN, RIGHT, UP, LEFT, NONE, NONE, NONE, NONE, NONE }; + private static final Clue[] CLUES = new Clue[]{ DOWN0, RIGHT1, UP2, LEFT3, LEFT_TOP4, RIGHT_TOP5, NONE, NONE, NONE }; public static Clue from(int dir) { return CLUES[dir]; } } @@ -96,8 +98,8 @@ public record Export() { } public Stream stream() { val stream = Stream.builder(); - for (var l = c.lo & ~c.xlo & ~c.rlo & c.vlo; l != X; l &= l - 1) stream.accept(new ClueAt(Long.numberOfTrailingZeros(l), RIGHT.dir)); - for (var l = c.lo & ~c.xlo & ~c.rlo & ~c.vlo; l != X; l &= l - 1) stream.accept(new ClueAt(Long.numberOfTrailingZeros(l), DOWN.dir)); + for (var l = c.lo & ~c.xlo & ~c.rlo & c.vlo; l != X; l &= l - 1) stream.accept(new ClueAt(Long.numberOfTrailingZeros(l), RIGHT1.dir)); + for (var l = c.lo & ~c.xlo & ~c.rlo & ~c.vlo; l != X; l &= l - 1) stream.accept(new ClueAt(Long.numberOfTrailingZeros(l), DOWN0.dir)); for (var l = c.lo & ~c.xlo & c.rlo & ~c.vlo; l != X; l &= l - 1) stream.accept(new ClueAt(Long.numberOfTrailingZeros(l), CLUE_UP)); for (var l = c.lo & ~c.xlo & c.rlo & c.vlo; l != X; l &= l - 1) stream.accept(new ClueAt(Long.numberOfTrailingZeros(l), CLUE_LEFT)); for (var l = c.lo & c.xlo & ~c.rlo & ~c.vlo; l != X; l &= l - 1) stream.accept(new ClueAt(Long.numberOfTrailingZeros(l), CLUE_LEFT_TOP)); diff --git a/src/main/java/puzzle/Masker.java b/src/main/java/puzzle/Masker.java index 8501caa..61a4672 100644 --- a/src/main/java/puzzle/Masker.java +++ b/src/main/java/puzzle/Masker.java @@ -35,24 +35,24 @@ public final class Masker { return findOffendingClue(c) == -1; } - public int findOffendingClue(Clues grid) { - if (((grid.xlo & grid.rlo) & grid.lo) != X) return numberOfTrailingZeros((grid.xlo & grid.rlo) & grid.lo); - if (((grid.xhi & grid.rhi) & grid.hi) != X) return 64 | numberOfTrailingZeros((grid.xhi & grid.rhi) & grid.hi); + public int findOffendingClue(Clues c) { + if (((c.xlo & c.rlo) & c.lo) != X) return numberOfTrailingZeros((c.xlo & c.rlo) & c.lo); + if (((c.xhi & c.rhi) & c.hi) != X) return 64 | numberOfTrailingZeros((c.xhi & c.rhi) & c.hi); var num = 0; - for (var bits = grid.lo; bits != X; bits &= bits - 1) activeCIdx[num++] = numberOfTrailingZeros(bits); - for (var bits = grid.hi; bits != X; bits &= bits - 1) activeCIdx[num++] = 64 | numberOfTrailingZeros(bits); + for (var bits = c.lo; bits != X; bits &= bits - 1) activeCIdx[num++] = numberOfTrailingZeros(bits); + for (var bits = c.hi; bits != X; bits &= bits - 1) activeCIdx[num++] = 64 | numberOfTrailingZeros(bits); if (num == 0) return -1; var start = rng.randint0_SIZE() % num; var n = 0; for (var i = 0; i < num; i++) { - var idx = activeCIdx[(start + i) % num]; - int dir = grid.getDir(idx); + var idx = activeCIdx[(start + i) % num]; + int dir = c.getDir(idx); var key = Slot.packSlotKey(idx, dir); long sLo = PATH_LO[key], sHi = PATH_HI[key]; - long hLo = sLo & grid.lo, hHi = sHi & grid.hi; + long hLo = sLo & c.lo, hHi = sHi & c.hi; if (Slotinfo.increasing(key)) { if (hLo != X) { sLo &= (1L << numberOfTrailingZeros(hLo)) - 1; @@ -121,26 +121,26 @@ public final class Masker { if (Long.bitCount(rayLo) + Long.bitCount(rayHi) >= MIN_LEN) visitor.visit(key, rayLo, rayHi); } - private static boolean validSlotRev(long lo, long hi, int min, int key) { + private static boolean validSlotRev(long lo, long hi, int key) { var rayLo = PATH_LO[key]; var rayHi = PATH_HI[key]; // only consider clue cells var hitsLo = rayLo & lo; var hitsHi = rayHi & hi; - if (hitsHi != X) return (Long.bitCount(rayHi & -(1L << 63 - numberOfLeadingZeros(hitsHi) << 1)) >= min); - else if (hitsLo != X) return (Long.bitCount(rayLo & -(1L << 63 - numberOfLeadingZeros(hitsLo) << 1)) + Long.bitCount(rayHi) >= min); - else return (Long.bitCount(rayLo) + Long.bitCount(rayHi) >= min); + if (hitsHi != X) return (Long.bitCount(rayHi & -(1L << 63 - numberOfLeadingZeros(hitsHi) << 1)) >= SwedishGenerator.MIN_LEN); + else if (hitsLo != X) return (Long.bitCount(rayLo & -(1L << 63 - numberOfLeadingZeros(hitsLo) << 1)) + Long.bitCount(rayHi) >= SwedishGenerator.MIN_LEN); + else return (Long.bitCount(rayLo) + Long.bitCount(rayHi) >= SwedishGenerator.MIN_LEN); } - private static boolean validSlot(long lo, long hi, int min, int key) { + private static boolean validSlot(long lo, long hi, int key) { var rayLo = PATH_LO[key]; var rayHi = PATH_HI[key]; var hitsLo = rayLo & lo; var hitsHi = rayHi & hi; - if (hitsLo != X) return (Long.bitCount(rayLo & ((1L << numberOfTrailingZeros(hitsLo)) - 1)) >= min); - else if (hitsHi != X) return (Long.bitCount(rayLo) + Long.bitCount(rayHi & ((1L << numberOfTrailingZeros(hitsHi)) - 1)) >= min); - else return (Long.bitCount(rayLo) + Long.bitCount(rayHi) >= min); + if (hitsLo != X) return (Long.bitCount(rayLo & ((1L << numberOfTrailingZeros(hitsLo)) - 1)) >= SwedishGenerator.MIN_LEN); + else if (hitsHi != X) return (Long.bitCount(rayLo) + Long.bitCount(rayHi & ((1L << numberOfTrailingZeros(hitsHi)) - 1)) >= SwedishGenerator.MIN_LEN); + else return (Long.bitCount(rayLo) + Long.bitCount(rayHi) >= SwedishGenerator.MIN_LEN); } private static void processSlot(Clues c, SlotVisitor visitor, int key) { var rayLo = PATH_LO[key]; @@ -198,10 +198,6 @@ public final class Masker { long lo_cl = grid.lo, hi_cl = grid.hi; var penalty = (((long) Math.abs(grid.clueCount() - clueSize)) * 16000L); var hasSlots = false; - /* if (!isValid(grid, 2)) { - throw new RuntimeException("Invalid grid configuration for mask fitness calculation"); - //return 1_000_000_000L; - }*/ var numClues = 0; for (var bits = lo_cl; bits != X; bits &= bits - 1) { @@ -653,8 +649,8 @@ public final class Masker { public static Clues createEmpty() { return new Clues(0, 0, 0, 0, 0, 0, 0, 0); } public boolean hasRoomForClue(int key) { - if (Slotinfo.increasing(key)) if (!validSlot(lo, hi, MIN_LEN, key)) return false; - return validSlotRev(lo, hi, MIN_LEN, key); + if (Slotinfo.increasing(key)) if (!validSlot(lo, hi, key)) return false; + return validSlotRev(lo, hi, key); } public void setClueLo(long mask, byte idx) { diff --git a/src/test/java/puzzle/CornerClueTest.java b/src/test/java/puzzle/CornerClueTest.java index c4d330d..6b1e11d 100644 --- a/src/test/java/puzzle/CornerClueTest.java +++ b/src/test/java/puzzle/CornerClueTest.java @@ -1,6 +1,9 @@ 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; @@ -8,17 +11,36 @@ import static precomp.Const9x8.OFF_0_0; import static precomp.Const9x8.OFF_0_1; import static precomp.Const9x8.OFF_1_0; import static precomp.Const9x8.OFF_1_1; +import static puzzle.CornerClueTest.Cell.r0c1d5; +import static puzzle.CornerClueTest.Cell.r0c0d4; public class CornerClueTest { + enum Cell { + r0c0d4(Clue.LEFT_TOP4, OFF_0_0), + r0c1d5(Clue.RIGHT_TOP5, OFF_0_1); + final public byte d; + final public long mask; + final public int index; + Cell(Clue d, int off) { + this.d = d.dir; + this.mask = 1L << off; + this.index = off; + } + public Clues attach(Clues c) { + c.setClueLo(mask, d); + return c; + } + public Clues attach() { return attach(Clues.createEmpty()); } + public int dir(Clues clues) { return clues.getDir(index); } + } @Test void testCornerDownSlot() { - var clues = Clues.createEmpty(); - // Clue op (0,0), type 4 (Corner Down) - var idx = OFF_0_0; - clues.setClueLo(1L << idx, (byte) 4); - assertEquals(4, clues.getDir(idx)); + var clues = r0c0d4.attach(); + // Clue op (0,0), type 4 (Corner Down) + + assertEquals(Clue.LEFT_TOP4.dir, r0c0d4.dir(clues)); // Controleer of forEachSlot het slot vindt final var found = new boolean[]{ false }; @@ -39,25 +61,16 @@ public class CornerClueTest { @Test void testCornerDownExtraction() { - var clues = Clues.createEmpty(); - clues.setClueLo(1L << OFF_0_0, (byte) 4); - - var dict = DictData.DICT.index(); - var slots = Masker.slots(clues, dict); - + var slots = Masker.slots(r0c0d4.attach(), DictData.DICT.index()); assertEquals(1, slots.length); assertEquals(4, Masker.Slot.dir(slots[0].key())); } @Test void testCornerDownLeftSlot() { - var clues = Clues.createEmpty(); - // Clue op (0,1), type 5 (Corner Down Left) - // Should result in word starting at (0,0) going down. - var idx = OFF_0_1; - clues.setClueLo(1L << idx, (byte) 5); + var clues = r0c1d5.attach(); - assertEquals(5, clues.getDir(idx)); + assertEquals(Clue.RIGHT_TOP5.dir, r0c1d5.dir(clues)); // Controleer of forEachSlot het slot vindt final var found = new boolean[]{ false }; @@ -78,13 +91,9 @@ public class CornerClueTest { @Test void testCornerDownLeftExtraction() { - var clues = Clues.createEmpty(); - clues.setClueLo(1L << OFF_0_1, (byte) 5); - - var dict = DictData.DICT.index(); - var slots = Masker.slots(clues, dict); + var slots = Masker.slots(r0c1d5.attach(), DictData.DICT.index()); assertEquals(1, slots.length); - assertEquals(5, Masker.Slot.dir(slots[0].key())); + assertEquals(Clue.RIGHT_TOP5.dir, Masker.Slot.dir(slots[0].key())); } } diff --git a/src/test/java/puzzle/ExportFormatTest.java b/src/test/java/puzzle/ExportFormatTest.java index 2c1654e..31ee1e5 100644 --- a/src/test/java/puzzle/ExportFormatTest.java +++ b/src/test/java/puzzle/ExportFormatTest.java @@ -2,7 +2,6 @@ package puzzle; import module java.base; import lombok.val; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import puzzle.Export.Clued; import puzzle.Export.Gridded; @@ -21,8 +20,8 @@ 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.LEFT; -import static puzzle.Export.Clue.RIGHT; +import static puzzle.Export.Clue.LEFT3; +import static puzzle.Export.Clue.RIGHT1; import static puzzle.SwedishGenerator.C; import static puzzle.Masker.Clues; import static puzzle.SwedishGenerator.FillStats; @@ -37,14 +36,14 @@ public class ExportFormatTest { void testExportFormatFromFilled() { val clues = Clues.createEmpty(); // Place a RIGHT clue at (0,0) - clues.setClueLo(Idx.IDX_0_0.lo, RIGHT.dir); + 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, LEFT.dir); + clues.setClueLo(Idx.IDX_0_5.lo, LEFT3.dir); var grid = new Gridded(clues); // key = (cellIndex << 2) | (direction) - var key = Slot.packSlotKey(0, RIGHT.dir); + var key = Slot.packSlotKey(0, RIGHT1.dir); 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 a5927dd..fe915be 100644 --- a/src/test/java/puzzle/MainTest.java +++ b/src/test/java/puzzle/MainTest.java @@ -4,6 +4,7 @@ import module java.base; import lombok.val; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import precomp.Neighbors9x8.rci; import puzzle.Export.ClueAt; import puzzle.Export.Clued; import puzzle.Export.Gridded; @@ -23,10 +24,10 @@ 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.DOWN; -import static puzzle.Export.Clue.LEFT; -import static puzzle.Export.Clue.RIGHT; -import static puzzle.Export.Clue.UP; +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 puzzle.SwedishGenerator.Lemma; import static puzzle.SwedishGenerator.Slotinfo; import static puzzle.SwedishGenerator.fillMask; @@ -70,11 +71,11 @@ public class MainTest { assertEquals(1, slots.length); var s = slots[0]; assertEquals(8, Masker.Slot.length(s.lo(), s.hi())); - var cells = Gridded.cellWalk((byte) s.key(), s.lo(), s.hi()).toArray(); - assertEquals(0, Masker.IT[cells[0]].r()); - assertEquals(1, Masker.IT[cells[0]].c()); - assertEquals(0, Masker.IT[cells[1]].r()); - assertEquals(2, Masker.IT[cells[1]].c()); + var cells = Gridded.cellWalk((byte) s.key(), s.lo(), s.hi()).mapToObj(c-> Masker.IT[c]).toArray(rci[]::new); + assertEquals(0, cells[0].r()); + assertEquals(1, cells[0].c()); + assertEquals(0, cells[1].r()); + assertEquals(2, cells[1].c()); } @Test @@ -125,10 +126,10 @@ public class MainTest { Assertions.assertEquals(CLUE_UP, clueMap.get(OFF_2_1)); // Test isLetterAt - Assertions.assertTrue(clues.notClue(OFF_0_0)); - Assertions.assertTrue(clues.notClue(OFF_1_2)); - Assertions.assertTrue(clues.notClue(OFF_2_3)); - Assertions.assertFalse(clues.isClueLo(OFF_1_1)); + Assertions.assertFalse(clueMap.containsKey(OFF_0_0)); + Assertions.assertFalse(clueMap.containsKey(OFF_1_2)); + Assertions.assertFalse(clueMap.containsKey(OFF_2_3)); + Assertions.assertFalse(clueMap.containsKey(OFF_1_1)); // Test isDigitAt Assertions.assertFalse(clues.isClueLo(OFF_0_0)); @@ -146,19 +147,19 @@ public class MainTest { @Test public void testCluesDeepCopy() { var clues = new Clued(Masker.Clues.createEmpty()); - clues.setClueLo(IDX_0_0.lo, RIGHT.dir); - clues.setClueLo(IDX_0_1.lo, UP.dir); - clues.setClueLo(IDX_1_0.lo, LEFT.dir); - clues.setClueLo(IDX_1_1.lo, DOWN.dir); + 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 copy = clues.deepCopyGrid(); var clueMap = clues.stream().collect(Collectors.toMap(ClueAt::index, ClueAt::clue)); Assertions.assertEquals(CLUE_RIGHT, clueMap.get(OFF_0_0)); - copy.setClueLo(IDX_0_0.lo, DOWN.dir); + copy.setClueLo(IDX_0_0.lo, DOWN0.dir); var copied = copy.stream().collect(Collectors.toMap(ClueAt::index, ClueAt::clue)); - Assertions.assertEquals(DOWN.dir, copied.get(OFF_0_0)); - Assertions.assertEquals(RIGHT.dir, clueMap.get(OFF_0_0)); + Assertions.assertEquals(DOWN0.dir, copied.get(OFF_0_0)); + Assertions.assertEquals(RIGHT1.dir, clueMap.get(OFF_0_0)); } @Test public void testMini() { diff --git a/src/test/java/puzzle/SwedishGeneratorTest.java b/src/test/java/puzzle/SwedishGeneratorTest.java index 9e7a00e..f9971f6 100644 --- a/src/test/java/puzzle/SwedishGeneratorTest.java +++ b/src/test/java/puzzle/SwedishGeneratorTest.java @@ -13,13 +13,12 @@ import puzzle.Masker.Slot; import static org.junit.jupiter.api.Assertions.*; import static precomp.Const9x8.*; +import static precomp.Const9x8.Cell.r0c0d1; import static puzzle.SwedishGenerator.*; import static puzzle.SwedishGeneratorTest.Idx.IDX_0_0; public class SwedishGeneratorTest { - public static final char C_DASH = '\0'; - public static final byte DASH = (byte) C_DASH; static Grid createEmpty() { return new Grid(new byte[SIZE], X, X); } record Context(long[] bitset) { @@ -137,8 +136,8 @@ public class SwedishGeneratorTest { @Test void testPatternForSlotMixed() { var grid = createEmpty(); - GridBuilder.placeWord(grid, grid.g, Slot.packSlotKey(0, CLUE_RIGHT), 1L << OFF_0_0, 0, Lemma.from("A")); - GridBuilder.placeWord(grid, grid.g, Slot.packSlotKey(0, CLUE_RIGHT), 1L << OFF_2_0, 0, Lemma.from("C")); + GridBuilder.placeWord(grid, grid.g, r0c0d1.slotKey, 1L << OFF_0_0, 0, Lemma.from("A")); + GridBuilder.placeWord(grid, grid.g, r0c0d1.slotKey, 1L << OFF_2_0, 0, Lemma.from("C")); var key = Slot.packSlotKey(OFF_1_0, CLUE_RIGHT); var pattern = patternForSlot(grid.lo, grid.hi, grid.g, key, 7L, 0L); assertEquals(14081L, pattern); @@ -155,7 +154,8 @@ public class SwedishGeneratorTest { @Test void testPatternForSlotSingleLetter() { var grid = createEmpty(); - GridBuilder.placeWord(grid, grid.g, Slot.packSlotKey(0, CLUE_RIGHT), 1L << OFF_0_0, 0, Lemma.from("A")); + //Slot.packSlotKey(0, CLUE_RIGHT) + GridBuilder.placeWord(grid, grid.g, r0c0d1.slotKey, 1L << OFF_0_0, 0, Lemma.from("A")); var key = Slot.packSlotKey(1, CLUE_RIGHT); var pattern = patternForSlot(grid.lo, grid.hi, grid.g, key, 7L, 0L); assertEquals(1L, pattern); @@ -189,7 +189,7 @@ public class SwedishGeneratorTest { void testGrid() { var empty = Clues.createEmpty(); var grid = new Gridded(empty); - GridBuilder.placeWord(grid.grid(), grid.grid().g, Slot.packSlotKey(0, CLUE_RIGHT), 1L << OFF_0_0, 0, Lemma.from("A")); + GridBuilder.placeWord(grid.grid(), grid.grid().g, r0c0d1.slotKey, 1L << OFF_0_0, 0, Lemma.from("A")); val arr = grid.stream(empty).collect(Collectors.toMap(LetterAt::index, LetterAt::letter)); assertEquals(1, arr.size()); assertEquals(LETTER_A, arr.get(OFF_0_0)); @@ -393,8 +393,8 @@ public class SwedishGeneratorTest { grid.grid().lo = low; map = grid.stream(Masker.Clues.createEmpty()).collect(Collectors.toMap(LetterAt::index, LetterAt::letter)); assertEquals(0, map.size()); - assertEquals(DASH, map.getOrDefault(OFF_0_1, DASH)); - assertEquals(DASH, map.getOrDefault(OFF_0_2, DASH)); + assertFalse(map.containsKey(OFF_0_1)); + assertFalse(map.containsKey(OFF_0_2)); } @Test