From 91722ecc604f654aae9a663dffa8bda1886b72d8 Mon Sep 17 00:00:00 2001 From: mike Date: Fri, 16 Jan 2026 22:46:04 +0100 Subject: [PATCH] introduce bitloops --- src/main/java/puzzle/SwedishGenerator.java | 162 +++++++++--------- src/test/java/puzzle/ExportFormatTest.java | 23 ++- src/test/java/puzzle/MainTest.java | 49 +++--- .../java/puzzle/SwedishGeneratorTest.java | 92 +++++++--- 4 files changed, 202 insertions(+), 124 deletions(-) diff --git a/src/main/java/puzzle/SwedishGenerator.java b/src/main/java/puzzle/SwedishGenerator.java index 7bb017f..79845d5 100644 --- a/src/main/java/puzzle/SwedishGenerator.java +++ b/src/main/java/puzzle/SwedishGenerator.java @@ -179,63 +179,50 @@ public record SwedishGenerator(Rng rng, int[] stack, Clues cache) { long lo, hi, vlo, vhi, rlo, rhi; public static Clues createEmpty() { return new Clues(0, 0, 0, 0, 0, 0); } public Clues deepCopyGrid() { return new Clues(lo, hi, vlo, vhi, rlo, rhi); } - boolean clueless(int idx) { - if (!isClue(idx)) return false; - if ((idx & 64) == 0) { - clearClueLo(idx); - } else { - clearClueHi(idx); - } + boolean cluelessLo(int idx) { + if (!isClueLo(idx)) return false; + clearClueLo(~(1L << idx)); + return true; + } + boolean cluelessHi(int idx) { + if (!isClueHi(idx)) return false; + clearClueHi(~(1L << (idx & 63))); return true; } public boolean hasRoomForClue(long packed) { return (packed) != X && notClue(packed & 0x7FL) && notClue((packed >>> 7) & 0x7FL); } - public void setClue(int ri, byte idx) { - if ((ri & 64) == 0) { - long mask = 1L << ri; - lo |= mask; - if ((idx & 1) != 0) vlo |= mask; - else vlo &= ~mask; - if ((idx & 2) != 0) rlo |= mask; - else rlo &= ~mask; - } else { - long mask = 1L << (ri & 63); - hi |= mask; - if ((idx & 1) != 0) vhi |= mask; - else vhi &= ~mask; - if ((idx & 2) != 0) rhi |= mask; - else rhi &= ~mask; - } + public void setClueLo(long mask, byte idx) { + lo |= mask; + if ((idx & 1) != 0) vlo |= mask; + else vlo &= ~mask; + if ((idx & 2) != 0) rlo |= mask; + else rlo &= ~mask; + } + public void setClueHi(long mask, byte idx) { + hi |= mask; + if ((idx & 1) != 0) vhi |= mask; + else vhi &= ~mask; + if ((idx & 2) != 0) rhi |= mask; + else rhi &= ~mask; } public byte digitAtRevLo(int idx) { return (byte) (2 | ((vlo >>> idx) & 1L)); } public byte digitAtRevHi(int idx) { return (byte) (2 | ((vhi >>> (idx & 63)) & 1L)); } public byte digitAtDevLo(int idx) { return (byte) ((vlo >>> idx) & 1L); } public byte digitAtDevHi(int idx) { return (byte) ((vhi >>> (idx & 63)) & 1L); } - public byte digitAt(int idx) { - if ((idx & 64) == 0) { - int v = (int) ((vlo >>> idx) & 1L); - int r = (int) ((rlo >>> idx) & 1L); - return (byte) ((r << 1) | v); - } else { - int v = (int) ((vhi >>> (idx & 63)) & 1L); - int r = (int) ((rhi >>> (idx & 63)) & 1L); - return (byte) ((r << 1) | v); - } - } - public void clearClueLo(int idx) { - long mask = ~(1L << idx); + public int digitAtHi(int idx) { return (int) ((((rhi >>> idx) & 1L) << 1) | ((vhi >>> idx) & 1L)); } + public int digitAtLo(int idx) { return (int) ((((rlo >>> idx) & 1L) << 1) | ((vlo >>> idx) & 1L)); } + public void clearClueLo(long mask) { lo &= mask; vlo &= mask; rlo &= mask; } - public void clearClueHi(int idx) { - long mask = ~(1L << (idx & 63)); + public void clearClueHi(long mask) { hi &= mask; vhi &= mask; rhi &= mask; } - public boolean isClue(long index) { return ((index & 64) == 0) ? ((lo >>> index) & 1L) != X : ((hi >>> (index & 63)) & 1L) != X; } - public boolean isClue(int index) { return ((index & 64) == 0) ? ((lo >>> index) & 1L) != X : ((hi >>> (index & 63)) & 1L) != X; } + public boolean isClueLo(int index) { return ((lo >>> index) & 1L) != X; } + public boolean isClueHi(int index) { return ((hi >>> (index & 63)) & 1L) != X; } public boolean notClue(long index) { return ((index & 64) == 0) ? ((lo >>> index) & 1L) == X : ((hi >>> (index & 63)) & 1L) == X; } public boolean notClue(int index) { return ((index & 64) == 0) ? ((lo >>> index) & 1L) == X : ((hi >>> (index & 63)) & 1L) == X; } @@ -501,18 +488,18 @@ public record SwedishGenerator(Rng rng, int[] stack, Clues cache) { long seenLo = X, seenHi = X; // loop over beide helften - for (int base = 0; base <= 64; base += 64) { + for (int base = 0, size, sp, cur; base <= 64; base += 64) { long clueMask = (base == 0) ? lo_cl : hi_cl; long seenMask = (base == 0) ? seenLo : seenHi; // "unseen clues" in deze helft - for (long bits = clueMask & ~seenMask; bits != X; bits &= bits - 1) { + for (long bits = clueMask & ~seenMask, nLo, nHi; bits != X; bits &= bits - 1) { int clueIdx = base | Long.numberOfTrailingZeros(bits); // start nieuwe component - int size = 0; - int sp = 0; - stack[sp++] = clueIdx; + size = 0; + stack[0] = clueIdx; + sp = 1; // mark seen if ((clueIdx & 64) == 0) seenLo |= 1L << clueIdx; @@ -520,12 +507,12 @@ public record SwedishGenerator(Rng rng, int[] stack, Clues cache) { // flood fill / bfs while (sp > 0) { - int cur = stack[--sp]; + cur = stack[--sp]; size++; // neighbors als 2x long masks - long nLo = NBR8_PACKED_LO[cur]; - long nHi = NBR8_PACKED_HI[cur]; + nLo = NBR8_PACKED_LO[cur]; + nHi = NBR8_PACKED_HI[cur]; // filter: alleen clues, en nog niet seen nLo &= lo_cl & ~seenLo; @@ -584,32 +571,51 @@ public record SwedishGenerator(Rng rng, int[] stack, Clues cache) { Clues randomMask(final int clueSize) { var g = Clues.createEmpty(); - for (int placed = 0, guard = 0, idx; placed < clueSize && guard < 4000; guard++) { + for (int placed = 0, guard = 0, ri; placed < clueSize && guard < 4000; guard++) { - idx = rng.randint0_SIZE(); - if (g.isClue(idx)) continue; - var d_idx = rng.randint2bitByte(); - if (g.hasRoomForClue(OFFSETS_D_IDX[Slot.packSlotKey(idx, d_idx)])) { - g.setClue(idx, d_idx); - placed++; + ri = rng.randint0_SIZE(); + if (isLo(ri)) { + if (g.isClueLo(ri)) continue; + var d_idx = rng.randint2bitByte(); + if (g.hasRoomForClue(OFFSETS_D_IDX[Slot.packSlotKey(ri, d_idx)])) { + g.setClueLo(1L << ri, d_idx); + placed++; + } + } else { + if (g.isClueHi(ri)) continue; + var d_idx = rng.randint2bitByte(); + if (g.hasRoomForClue(OFFSETS_D_IDX[Slot.packSlotKey(ri, d_idx)])) { + g.setClueHi(1L << (ri & 63), d_idx); + placed++; + } } + } return g; } - Clues mutate(Clues c) { + static boolean isLo(int n) { return (n & 64) == 0; } + Clues mutate(SwedishGenerator.Clues c) { int ri; var bytes = MUTATE_RI[rng.randint0_SIZE()]; for (var k = 0; k < 4; k++) { ri = bytes[rng.randint0_624()]; - if (!c.clueless(ri)) { - var d_idx = rng.randint2bitByte(); - if (c.hasRoomForClue(OFFSETS_D_IDX[Slot.packSlotKey(ri, d_idx)])) c.setClue(ri, d_idx); + if (isLo(ri)) { + if (!c.cluelessLo(ri)) { + var d_idx = rng.randint2bitByte(); + if (c.hasRoomForClue(OFFSETS_D_IDX[Slot.packSlotKey(ri, d_idx)])) c.setClueLo(1L << ri, d_idx); + } + } else { + if (!c.cluelessHi(ri)) { + var d_idx = rng.randint2bitByte(); + if (c.hasRoomForClue(OFFSETS_D_IDX[Slot.packSlotKey(ri, d_idx)])) c.setClueHi(1L << (ri & 63), d_idx); + } } + } return c; } Clues crossover(Clues a, Clues other) { - var out = a.deepCopyGrid(); + var theta = rng.nextFloat() * Math.PI; var nc = Math.cos(theta); var nr = Math.sin(theta); @@ -622,20 +628,20 @@ public record SwedishGenerator(Rng rng, int[] stack, Clues cache) { else maskHi |= (1L << (i - 64)); } } - - out.lo = (out.lo & ~maskLo) | (other.lo & maskLo); - out.hi = (out.hi & ~maskHi) | (other.hi & maskHi); - out.vlo = (out.vlo & ~maskLo) | (other.vlo & maskLo); - out.vhi = (out.vhi & ~maskHi) | (other.vhi & maskHi); - out.rlo = (out.rlo & ~maskLo) | (other.rlo & maskLo); - out.rhi = (out.rhi & ~maskHi) | (other.rhi & maskHi); + var out = new Clues( + (a.lo & ~maskLo) | (other.lo & maskLo), + (a.hi & ~maskHi) | (other.hi & maskHi), + (a.vlo & ~maskLo) | (other.vlo & maskLo), + (a.vhi & ~maskHi) | (other.vhi & maskHi), + (a.rlo & ~maskLo) | (other.rlo & maskLo), + (a.rhi & ~maskHi) | (other.rhi & maskHi)); for (var lo = out.lo; lo != X; lo &= lo - 1L) clearCluesLo(out, Long.numberOfTrailingZeros(lo)); - for (var hi = out.hi; hi != X; hi &= hi - 1L) clearCluesHi(out, 64 | Long.numberOfTrailingZeros(hi)); + for (var hi = out.hi; hi != X; hi &= hi - 1L) clearCluesHi(out, Long.numberOfTrailingZeros(hi)); return out; } - public static void clearCluesLo(Clues out, int idx) { if (!out.hasRoomForClue(OFFSETS_D_IDX[Slot.packSlotKey(idx, out.digitAt(idx))])) out.clearClueLo(idx); } - public static void clearCluesHi(Clues out, int idx) { if (!out.hasRoomForClue(OFFSETS_D_IDX[Slot.packSlotKey(idx, out.digitAt(idx))])) out.clearClueHi(idx); } + public static void clearCluesLo(Clues out, int idx) { if (!out.hasRoomForClue(OFFSETS_D_IDX[Slot.packSlotKey(idx, out.digitAtLo(idx))])) out.clearClueLo(~(1L << idx)); } + public static void clearCluesHi(Clues out, int idx) { if (!out.hasRoomForClue(OFFSETS_D_IDX[Slot.packSlotKey(64 | idx, out.digitAtHi(idx))])) out.clearClueHi(~(1L << idx)); } Clues hillclimb(Clues start, int clue_size, int limit) { var best = start; @@ -723,26 +729,26 @@ public record SwedishGenerator(Rng rng, int[] stack, Clues cache) { long p = 0; if (Slot.increasing(key)) { for (long b = lo & glo; b != X; b &= b - 1) { - int idx = Long.numberOfTrailingZeros(b); - int i = Long.bitCount(lo & ((1L << idx) - 1)); + int idx = Long.numberOfTrailingZeros(b); + int i = Long.bitCount(lo & ((1L << idx) - 1)); p |= ((long) (i * 26 + g[idx])) << (i << 3); } int offset = Long.bitCount(lo); for (long b = hi & ghi; b != X; b &= b - 1) { - int idx = Long.numberOfTrailingZeros(b); - int i = offset + Long.bitCount(hi & ((1L << idx) - 1)); + int idx = Long.numberOfTrailingZeros(b); + int i = offset + Long.bitCount(hi & ((1L << idx) - 1)); p |= ((long) (i * 26 + g[64 | idx])) << (i << 3); } } else { int offset = Long.bitCount(hi); for (long b = hi & ghi; b != X; b &= b - 1) { - int idx = Long.numberOfTrailingZeros(b); - int i = Long.bitCount(hi & ~((1L << idx) | ((1L << idx) - 1))); + int idx = Long.numberOfTrailingZeros(b); + int i = Long.bitCount(hi & ~((1L << idx) | ((1L << idx) - 1))); p |= ((long) (i * 26 + g[64 | idx])) << (i << 3); } for (long b = lo & glo; b != X; b &= b - 1) { - int idx = Long.numberOfTrailingZeros(b); - int i = offset + Long.bitCount(lo & ~((1L << idx) | ((1L << idx) - 1))); + int idx = Long.numberOfTrailingZeros(b); + int i = offset + Long.bitCount(lo & ~((1L << idx) | ((1L << idx) - 1))); p |= ((long) (i * 26 + g[idx])) << (i << 3); } } diff --git a/src/test/java/puzzle/ExportFormatTest.java b/src/test/java/puzzle/ExportFormatTest.java index 2123c27..000f836 100644 --- a/src/test/java/puzzle/ExportFormatTest.java +++ b/src/test/java/puzzle/ExportFormatTest.java @@ -1,5 +1,6 @@ package puzzle; +import lombok.AllArgsConstructor; import lombok.val; import org.junit.jupiter.api.Test; import puzzle.Export.Clued; @@ -9,11 +10,13 @@ import puzzle.Export.PuzzleResult; import puzzle.Export.Rewards; import puzzle.SwedishGenerator.FillResult; import puzzle.SwedishGenerator.Rng; +import puzzle.SwedishGeneratorTest.Idx; import java.io.IOException; import java.nio.file.Paths; 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 puzzle.ExportFormatTest.Clue.RIGHT; import static puzzle.SwedishGenerator.C; import static puzzle.SwedishGenerator.Clues; import static puzzle.SwedishGenerator.FillStats; @@ -35,19 +38,33 @@ public class ExportFormatTest { static final byte CLUE_UP = 2; static final byte CLUE_LEFT = 3; + @AllArgsConstructor + enum Clue { + DOWN(CLUE_DOWN), + RIGHT(CLUE_RIGHT), + UP(CLUE_UP), + LEFT(CLUE_LEFT); + Clue(byte dir) { + this.dir = dir; + this.clueDir = dir; + } + final byte dir; + final int clueDir; + } + @Test void testExportFormatFromFilled() { var swe = new SwedishGenerator(new Rng(0), new int[STACK_SIZE], Clues.createEmpty()); val clues = Clues.createEmpty(); // Place a RIGHT clue at (0,0) - clues.setClue(0, CLUE_RIGHT); + clues.setClueLo(Idx.IDX_0_0.lo, RIGHT.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.setClue(OFF_0_5, CLUE_LEFT); + clues.setClueLo(Idx.IDX_0_5.lo, CLUE_LEFT); var grid = new Gridded(clues.toGrid()); - var clueMap = new long[300]; + var clueMap = new long[SwedishGenerator.CLUE_INDEX_MAX_SIZE]; // key = (cellIndex << 2) | (direction) var key = Slot.packSlotKey(0, CLUE_RIGHT); var lo = (1L << OFF_0_1) | (1L << OFF_0_2) | (1L << OFF_0_3) | (1L << OFF_0_4); diff --git a/src/test/java/puzzle/MainTest.java b/src/test/java/puzzle/MainTest.java index f9a86fb..457759c 100644 --- a/src/test/java/puzzle/MainTest.java +++ b/src/test/java/puzzle/MainTest.java @@ -18,6 +18,11 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import static puzzle.SwedishGenerator.*; import static puzzle.SwedishGeneratorTest.*; +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.OFF_0_0; import static puzzle.SwedishGeneratorTest.OFF_0_1; import static puzzle.SwedishGeneratorTest.OFF_0_2; @@ -41,7 +46,7 @@ public class MainTest { var clues = Clues.createEmpty(); val key = Slot.packSlotKey(OFF_0_0, CLUE_RIGHT); - clues.setClue(OFF_0_0, CLUE_RIGHT); + clues.setClueLo(IDX_0_0.lo, CLUE_RIGHT); var grid = new Gridded(clues.toGrid()); val g = grid.grid().g; placeWord(grid.grid(), g, key, (1L << OFF_0_1) | (1L << OFF_0_2), 0, AB); @@ -69,7 +74,7 @@ public class MainTest { @Test void testForEachSlot() { var clues = Clues.createEmpty(); - clues.setClue(OFF_0_0, CLUE_RIGHT); + clues.setClueLo(IDX_0_0.lo, CLUE_RIGHT); var count = new AtomicInteger(0); clues.forEachSlot((key, lo, hi) -> { count.incrementAndGet(); @@ -90,7 +95,7 @@ public class MainTest { public void testGridBasics() { var clues = Clues.createEmpty(); val key = Slot.packSlotKey(OFF_2_1, CLUE_UP); - clues.setClue(OFF_2_1, CLUE_UP); + clues.setClueLo(IDX_2_1.lo, CLUE_UP); var grid = new Gridded(clues.toGrid()); // Test set/get @@ -98,47 +103,47 @@ public class MainTest { val arr = grid.stream(clues).collect(Collectors.toMap(LetterAt::index, LetterAt::letter)); Assertions.assertEquals(LETTER_A, arr.get(OFF_1_1)); Assertions.assertEquals(LETTER_Z, arr.get(OFF_0_1)); - Assertions.assertEquals(CLUE_UP, clues.digitAt(OFF_2_1)); + Assertions.assertEquals(CLUE_UP, (byte) clues.digitAtLo(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.isClue(OFF_1_1)); + Assertions.assertFalse(clues.isClueLo(OFF_1_1)); // Test isDigitAt - Assertions.assertFalse(clues.isClue(0)); - Assertions.assertTrue(clues.isClue(OFF_2_1)); - Assertions.assertEquals(CLUE_UP, clues.digitAt(OFF_2_1)); - Assertions.assertFalse(clues.isClue(OFF_2_3)); - Assertions.assertFalse(clues.isClue(OFF_1_1)); + Assertions.assertFalse(clues.isClueLo(OFF_0_0)); + Assertions.assertTrue(clues.isClueLo(OFF_2_1)); + Assertions.assertEquals(CLUE_UP, (byte) clues.digitAtLo(OFF_2_1)); + Assertions.assertFalse(clues.isClueLo(OFF_2_3)); + Assertions.assertFalse(clues.isClueLo(OFF_1_1)); // Test isLettercell Assertions.assertTrue(clues.notClue(OFF_0_0)); // 'A' is letter - Assertions.assertTrue(clues.isClue(OFF_2_1)); // digit + Assertions.assertTrue(clues.isClueLo(OFF_2_1)); // digit Assertions.assertTrue(clues.notClue(OFF_1_1)); // '#' is lettercell } @Test public void testCluesDeepCopy() { var grid = Clues.createEmpty(); - grid.setClue(OFF_0_0, (byte) 1); - grid.setClue(OFF_0_1, (byte) 2); - grid.setClue(OFF_1_0, (byte) 3); - grid.setClue(OFF_1_1, (byte) 0); + grid.setClueLo(IDX_0_0.lo, (byte) 1); + grid.setClueLo(IDX_0_1.lo, (byte) 2); + grid.setClueLo(IDX_1_0.lo, (byte) 3); + grid.setClueLo(IDX_1_1.lo, (byte) 0); var copy = grid.deepCopyGrid(); - Assertions.assertEquals((byte) 1, copy.digitAt(0)); + Assertions.assertEquals(1, copy.digitAtLo(0)); - copy.setClue(0, (byte) 3); - Assertions.assertEquals((byte) 3, copy.digitAt(0)); - Assertions.assertEquals((byte) 1, grid.digitAt(0)); // Original should be unchanged + copy.setClueLo(IDX_0_0.lo, (byte) 3); + Assertions.assertEquals(3, copy.digitAtLo(0)); + Assertions.assertEquals(1, grid.digitAtLo(0)); // Original should be unchanged } @Test public void testMini() { - val idx = OFF_1_1; + val idx = IDX_1_1; var clues = Clues.createEmpty(); - clues.setClue(idx, CLUE_LEFT); - Assertions.assertTrue(clues.isClue(idx)); + clues.setClueLo(idx.lo, CLUE_LEFT); + Assertions.assertTrue(clues.isClueLo(idx.index)); } @Test void testMaskerCreation() { diff --git a/src/test/java/puzzle/SwedishGeneratorTest.java b/src/test/java/puzzle/SwedishGeneratorTest.java index 60a5a3d..455eaaf 100644 --- a/src/test/java/puzzle/SwedishGeneratorTest.java +++ b/src/test/java/puzzle/SwedishGeneratorTest.java @@ -7,11 +7,11 @@ import puzzle.Export.Gridded; import puzzle.Export.IntListDTO; import puzzle.Export.LetterVisit.LetterAt; import java.nio.charset.StandardCharsets; -import java.util.Arrays; import java.util.stream.Collectors; import static org.junit.jupiter.api.Assertions.*; import static puzzle.SwedishGenerator.*; +import static puzzle.SwedishGeneratorTest.Idx.IDX_0_0; public class SwedishGeneratorTest { @@ -67,29 +67,79 @@ public class SwedishGeneratorTest { static final byte CLUE_UP = 2; static final byte CLUE_LEFT = 3; - static final int OFF_1_0 = Grid.offset(1, 0); - static final int OFF_1_1 = Grid.offset(1, 1); - static final int OFF_1_2 = Grid.offset(1, 2); - static final int OFF_2_1 = Grid.offset(2, 1); - static final int OFF_2_3 = Grid.offset(2, 3); - static final int OFF_0_0 = Grid.offset(0, 0); - static final int OFF_0_4 = Grid.offset(0, 4); - static final int OFF_0_5 = Grid.offset(0, 5); - static final int OFF_0_1 = Grid.offset(0, 1); - static final int OFF_0_2 = Grid.offset(0, 2); - static final int OFF_0_3 = Grid.offset(0, 3); - static final int OFF_2_0 = Grid.offset(2, 0); - static final int OFF_2_5 = Grid.offset(2, 5); - static final int OFF_3_5 = Grid.offset(3, 5); - static final int OFF_4_5 = Grid.offset(4, 5); - + static final int OFF_1_0 = Grid.offset(1, 0); + static final int OFF_1_1 = Grid.offset(1, 1); + static final int OFF_1_2 = Grid.offset(1, 2); + static final int OFF_1_3 = Grid.offset(1, 3); + static final int OFF_1_4 = Grid.offset(1, 4); + static final int OFF_1_5 = Grid.offset(1, 5); + static final int OFF_2_1 = Grid.offset(2, 1); + static final int OFF_2_3 = Grid.offset(2, 3); + static final int OFF_2_2 = Grid.offset(2, 2); + static final int OFF_2_4 = Grid.offset(2, 4); + static final int OFF_0_0 = Grid.offset(0, 0); + static final int OFF_0_4 = Grid.offset(0, 4); + static final int OFF_0_5 = Grid.offset(0, 5); + static final int OFF_0_1 = Grid.offset(0, 1); + static final int OFF_0_2 = Grid.offset(0, 2); + static final int OFF_0_3 = Grid.offset(0, 3); + static final int OFF_2_0 = Grid.offset(2, 0); + static final int OFF_2_5 = Grid.offset(2, 5); + static final int OFF_3_5 = Grid.offset(3, 5); + static final int OFF_4_5 = Grid.offset(4, 5); + static final int OFF_3_0 = Grid.offset(3, 0); + static final int OFF_3_1 = Grid.offset(3, 1); + static final int OFF_3_2 = Grid.offset(3, 2); + static final int OFF_3_3 = Grid.offset(3, 3); + static final int OFF_3_4 = Grid.offset(3, 4); 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 grid = new Gridded(createEmpty()); var key = Slot.packSlotKey(OFF_0_0, CLUE_RIGHT); val clues = Clues.createEmpty(); - clues.setClue(OFF_0_0, CLUE_RIGHT); + clues.setClueLo(IDX_0_0.lo, CLUE_RIGHT); 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)); @@ -237,7 +287,7 @@ public class SwedishGeneratorTest { void testForEachSlotAndExtractSlots() { // This should detect a slot starting at 0,1 with length 2 (0,1 and 0,2) var clues = Clues.createEmpty(); - clues.setClue(OFF_0_0, CLUE_RIGHT); + clues.setClueLo(IDX_0_0.lo, CLUE_RIGHT); var dict = new Dict(WORDS2); var slots = extractSlots(clues, dict.index()); assertEquals(1, slots.length); @@ -257,7 +307,7 @@ public class SwedishGeneratorTest { assertTrue(f1 >= 1_000_000_000L); // Add a slot - grid.setClue(OFF_0_0, D_BYTE_2); + grid.setClueLo(IDX_0_0.lo, D_BYTE_2); var f2 = gen.maskFitness(grid, 18); assertTrue(f2 < f1); } @@ -381,7 +431,7 @@ public class SwedishGeneratorTest { // Empty grid: huge penalty var fitEmpty = gen.maskFitness(grid, 18); assertTrue(fitEmpty >= 1_000_000_000L); - grid.setClue(0, D_BYTE_2); // Right from 0,0. Len 2 if 3x3. + grid.setClueLo(IDX_0_0.lo, D_BYTE_2); // Right from 0,0. Len 2 if 3x3. var fitOne = gen.maskFitness(grid, 18); assertTrue(fitOne < fitEmpty); }