From 5d0da1cf6bfce2b69d1ead5e86cfdb2c9b19a284 Mon Sep 17 00:00:00 2001 From: mike Date: Tue, 20 Jan 2026 10:35:10 +0100 Subject: [PATCH] introduce bitloops --- src/main/java/puzzle/Masker.java | 59 +---------------------- src/test/java/puzzle/MaskerCluesTest.java | 32 ++++++------ 2 files changed, 18 insertions(+), 73 deletions(-) diff --git a/src/main/java/puzzle/Masker.java b/src/main/java/puzzle/Masker.java index bc0b25f..242db46 100644 --- a/src/main/java/puzzle/Masker.java +++ b/src/main/java/puzzle/Masker.java @@ -704,64 +704,7 @@ public final class Masker { return (bitCount(matchLo & MASK_LO) + bitCount(matchHi & MASK_HI)) / SIZED; } public Grid toGrid() { return new Grid(new byte[SwedishGenerator.SIZE], lo, hi); } - public boolean isValid(int minLen) { - return findOffendingClue(minLen, new long[SwedishGenerator.SIZE], new long[SwedishGenerator.SIZE]) == -1; - } - public boolean isValid(int minLen, long[] slo, long[] shi) { - return findOffendingClue(minLen, slo, shi) == -1; - } - public int findOffendingClue(int minLen, long[] slo, long[] shi) { - if (((xlo & rlo) & lo) != X) return numberOfTrailingZeros((xlo & rlo) & lo); - if (((xhi & rhi) & hi) != X) return 64 | numberOfTrailingZeros((xhi & rhi) & hi); - int n = 0; - for (long bits = lo; bits != X; bits &= bits - 1) { - int idx = numberOfTrailingZeros(bits); - int dir = getDir(idx); - int key = Slot.packSlotKey(idx, dir); - long sLo = PATH_LO[key], sHi = PATH_HI[key]; - long hLo = sLo & lo, hHi = sHi & hi; - if (Slotinfo.increasing(key)) { - if (hLo != X) { - sLo &= (1L << numberOfTrailingZeros(hLo)) - 1; - sHi = 0; - } else if (hHi != X) { sHi &= (1L << numberOfTrailingZeros(hHi)) - 1; } - } else { - if (hHi != X) { - sHi &= -(1L << (63 - numberOfLeadingZeros(hHi)) << 1); - sLo = 0; - } else if (hLo != X) { sLo &= -(1L << (63 - numberOfLeadingZeros(hLo)) << 1); } - } - if (bitCount(sLo) + bitCount(sHi) < minLen) return idx; - for (int i = 0; i < n; i++) if (bitCount(sLo & slo[i]) + bitCount(sHi & shi[i]) > 1) return idx; - slo[n] = sLo; - shi[n] = sHi; - n++; - } - for (long bits = hi; bits != X; bits &= bits - 1) { - int idx = 64 | numberOfTrailingZeros(bits); - int dir = getDir(idx); - int key = Slot.packSlotKey(idx, dir); - long sLo = PATH_LO[key], sHi = PATH_HI[key]; - long hLo = sLo & lo, hHi = sHi & hi; - if (Slotinfo.increasing(key)) { - if (hLo != X) { - sLo &= (1L << numberOfTrailingZeros(hLo)) - 1; - sHi = 0; - } else if (hHi != X) { sHi &= (1L << numberOfTrailingZeros(hHi)) - 1; } - } else { - if (hHi != X) { - sHi &= -(1L << (63 - numberOfLeadingZeros(hHi)) << 1); - sLo = 0; - } else if (hLo != X) { sLo &= -(1L << (63 - numberOfLeadingZeros(hLo)) << 1); } - } - if (bitCount(sLo) + bitCount(sHi) < minLen) return idx; - for (int i = 0; i < n; i++) if (bitCount(sLo & slo[i]) + bitCount(sHi & shi[i]) > 1) return idx; - slo[n] = sLo; - shi[n] = sHi; - n++; - } - return -1; - } + public void forEachSlot(SlotVisitor visitor) { for (var l = lo & ~xlo & ~rlo & vlo; l != X; l &= l - 1) processSlot(this, visitor, Slot.packSlotKey(numberOfTrailingZeros(l), 1)); for (var l = lo & ~xlo & ~rlo & ~vlo; l != X; l &= l - 1) processSlot(this, visitor, Slot.packSlotKey(numberOfTrailingZeros(l), 0)); diff --git a/src/test/java/puzzle/MaskerCluesTest.java b/src/test/java/puzzle/MaskerCluesTest.java index 26a7978..d37533e 100644 --- a/src/test/java/puzzle/MaskerCluesTest.java +++ b/src/test/java/puzzle/MaskerCluesTest.java @@ -18,7 +18,7 @@ public class MaskerCluesTest { for (int i = 0; i < 200; i++) { for (int j = 19; j < 24; j++) { var clues = masker.randomMask(j); - assertTrue(clues.isValid(MIN_LEN), "Mask should be valid for length \n" + new Clued(clues).gridToString()); + assertTrue(masker.isValid(clues, MIN_LEN), "Mask should be valid for length \n" + new Clued(clues).gridToString()); } } } @@ -36,7 +36,7 @@ public class MaskerCluesTest { simCount++; masker.mutate(clues); sim += orig.similarity(clues); - assertTrue(clues.isValid(MIN_LEN), "Mask should be valid for length \n" + new Clued(clues).gridToString()); + assertTrue(masker.isValid(clues, MIN_LEN), "Mask should be valid for length \n" + new Clued(clues).gridToString()); } } System.out.println("Average similarity: " + sim / simCount); @@ -55,7 +55,7 @@ public class MaskerCluesTest { simCount++; var cross = masker.crossover(clues, clues2); sim += Math.max(cross.similarity(clues), cross.similarity(clues2)); - assertTrue(cross.isValid(MIN_LEN), "Mask should be valid for length \n" + new Clued(cross).gridToString()); + assertTrue(masker.isValid(cross, MIN_LEN), "Mask should be valid for length \n" + new Clued(cross).gridToString()); } } System.out.println("Average similarity: " + sim / simCount); @@ -88,17 +88,18 @@ public class MaskerCluesTest { @Test void testIsValid() { - Clues g = Clues.createEmpty(); - assertTrue(g.isValid(MIN_LEN)); + Masker masker = new Masker(new Rng(42), new int[STACK_SIZE], Clues.createEmpty()); + Clues g = Clues.createEmpty(); + assertTrue(masker.isValid(g, MIN_LEN)); // Valid clue: Right from (0,0) in 9x8 grid. Length is 8. g.setClueLo(1L << Masker.offset(0, 0), (byte) 1); - assertTrue(g.isValid(MIN_LEN)); + assertTrue(masker.isValid(g, MIN_LEN)); // Invalid clue: Right from (0,7) in 9x8 grid. Length is 1 (too short if MIN_LEN >= 2). Clues g2 = Clues.createEmpty(); g2.setClueLo(1L << Masker.offset(0, 7), (byte) 1); - assertFalse(g2.isValid(MIN_LEN)); + assertFalse(masker.isValid(g2, MIN_LEN)); } @Test @@ -126,20 +127,20 @@ public class MaskerCluesTest { @Test void testIntersectionConstraint() { - Clues g = Clues.createEmpty(); - + Clues g = Clues.createEmpty(); + 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) g.setClueLo(1L << Masker.offset(0, 0), (byte) 1); // Clue 2: (1,2) Up. Slot cells: (0,2) // Intersection is exactly 1 cell (0,2). Valid. g.setClueLo(1L << Masker.offset(2, 2), (byte) 2); - assertTrue(g.isValid(MIN_LEN)); + assertTrue(masker.isValid(g, MIN_LEN)); // Clue 3: (1,1) Right. Slot cells: (1,2), (1,3), ... // No intersection with Clue 1 or 2. Valid. g.setClueLo(1L << Masker.offset(1, 1), (byte) 1); - assertTrue(g.isValid(MIN_LEN)); + assertTrue(masker.isValid(g, MIN_LEN)); // Now create a violation: two slots sharing 2 cells. // We can do this with Corner Down and another clue. @@ -150,19 +151,20 @@ public class MaskerCluesTest { Clues g3 = Clues.createEmpty(); g3.setClueLo(1L << Masker.offset(0, 0), (byte) 4); // Corner Down g3.setClueLo(1L << Masker.offset(0, 2), (byte) 5); // Corner Down Left - assertFalse(g3.isValid(MIN_LEN)); + assertFalse(masker.isValid(g3, MIN_LEN)); } @Test void testInvalidDirectionBits() { - Clues g = Clues.createEmpty(); + 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(g.isValid(MIN_LEN)); + assertFalse(masker.isValid(g,MIN_LEN)); // Dir 7 (x=1, r=1, v=1) is invalid Clues g2 = Clues.createEmpty(); g2.setClueLo(1L << 0, (byte) 7); - assertFalse(g2.isValid(MIN_LEN)); + assertFalse(masker.isValid(g2,MIN_LEN)); } }