From 19f235ae59cfab7e840ec0413ca8fd20c91fdf1b Mon Sep 17 00:00:00 2001 From: mike Date: Wed, 14 Jan 2026 03:30:19 +0100 Subject: [PATCH] introduce bitloops --- src/main/java/puzzle/Export.java | 23 ++- src/main/java/puzzle/Main.java | 4 +- src/main/java/puzzle/SwedishGenerator.java | 143 ++++++++---------- src/test/java/puzzle/ExportFormatTest.java | 12 +- src/test/java/puzzle/MainTest.java | 48 +++--- .../java/puzzle/SwedishGeneratorTest.java | 35 +---- 6 files changed, 123 insertions(+), 142 deletions(-) diff --git a/src/main/java/puzzle/Export.java b/src/main/java/puzzle/Export.java index eb79ee2..cf3ce54 100644 --- a/src/main/java/puzzle/Export.java +++ b/src/main/java/puzzle/Export.java @@ -3,7 +3,9 @@ package puzzle; import lombok.Getter; import lombok.experimental.Accessors; import lombok.experimental.Delegate; +import lombok.val; import puzzle.Export.Gridded.Replacar.Cell; +import puzzle.SwedishGenerator.Clues; import puzzle.SwedishGenerator.Dict; import puzzle.SwedishGenerator.FillResult; import puzzle.SwedishGenerator.Grid; @@ -36,6 +38,25 @@ public record Export() { } } + public record Clued(Clues mask) { + + String gridToString() { + var sb = new StringBuilder(); + for (var r = 0; r < R; r++) { + if (r > 0) sb.append('\n'); + for (var c = 0; c < C; c++) { + val idx = Grid.offset(r, c); + if (mask.isClue(idx)) + sb.append((char) mask.digitAt(idx)); + else { + sb.append(' '); + } + } + } + return sb.toString(); + } + } + record Gridded(@Delegate Grid grid) { static boolean isLetter(byte b) { return (b & SwedishGenerator.B64) != SwedishGenerator.B0; } @@ -161,7 +182,7 @@ public record Export() { public record ExportedPuzzle(String[] grid, WordOut[] words, int difficulty, Rewards rewards) { } - public record PuzzleResult(SwedishGenerator swe, Dict dict, Gridded mask, FillResult filled) { + public record PuzzleResult(SwedishGenerator swe, Dict dict, Clued mask, FillResult filled) { boolean inBounds(int idx) { return idx >= 0 && idx < SwedishGenerator.SIZE; } Placed extractPlacedFromSlot(Slot s, long lemma) { return new Placed(lemma, s.key(), s.walk().toArray()); } diff --git a/src/main/java/puzzle/Main.java b/src/main/java/puzzle/Main.java index 0a04b3f..a22b750 100644 --- a/src/main/java/puzzle/Main.java +++ b/src/main/java/puzzle/Main.java @@ -359,7 +359,7 @@ public class Main { val stack = new int[STACK_SIZE]; var swe = new SwedishGenerator(rng, stack); var mask = swe.generateMask(opts.pop, opts.gens, Math.max(opts.pop, (int) Math.floor(opts.pop * 1.5))); - var filled = fillMask(rng, mask, dict.index()); + var filled = fillMask(rng, mask.toGrid(), dict.index()); TOTAL_NODES.addAndGet(filled.stats().nodes); TOTAL_BACKTRACKS.addAndGet(filled.stats().backtracks); @@ -380,7 +380,7 @@ public class Main { ); if (filled.ok() && (opts.minSimplicity <= 0 || filled.stats().simplicity >= opts.minSimplicity)) { - return new PuzzleResult(swe, dict, new Gridded(mask), filled); + return new PuzzleResult(swe, dict, new Clued(mask), filled); } if (opts.verbose && filled.ok()) { diff --git a/src/main/java/puzzle/SwedishGenerator.java b/src/main/java/puzzle/SwedishGenerator.java index a606a06..ab7a94c 100644 --- a/src/main/java/puzzle/SwedishGenerator.java +++ b/src/main/java/puzzle/SwedishGenerator.java @@ -203,13 +203,17 @@ public record SwedishGenerator(Rng rng, int[] stack) { 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; + 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; + if ((idx & 1) != 0) vhi |= mask; + else vhi &= ~mask; + if ((idx & 2) != 0) rhi |= mask; + else rhi &= ~mask; } } public byte digitAt(int idx) { @@ -270,43 +274,22 @@ public record SwedishGenerator(Rng rng, int[] stack) { } } - static class Grid { + static record Grid(byte[] g, long lo, long hi) { - final byte[] g; - long lo, hi; - - public Grid(byte[] g) { this(g, 0, 0); } - public Grid(byte[] g, long lo, long hi) { - this.g = g; - this.lo = lo; - this.hi = hi; - } - static Grid createEmpty() { return new Grid(new byte[SIZE], X, X); } - int digitAt(int index) { return g[index]; } - public static int r(int offset) { return offset & 7; } - public static int c(int offset) { return offset >>> 3; } - static int offset(int r, int c) { return r | (c << 3); } - Grid deepCopyGrid() { return new Grid(g.clone(), lo, hi); } - public byte byteAt(int pos) { return g[pos]; } - - void setClue(int idx, byte ch) { - g[idx] = ch; - if ((idx & 64) == 0) lo |= (1L << idx); - else hi |= (1L << (idx & 63)); - } + public Grid(byte[] g) { this(g, 0, 0); } + static Grid createEmpty() { return new Grid(new byte[SIZE], X, X); } + int digitAt(int index) { return g[index]; } + public static int r(int offset) { return offset & 7; } + public static int c(int offset) { return offset >>> 3; } + static int offset(int r, int c) { return r | (c << 3); } + public byte byteAt(int pos) { return g[pos]; } void setLetter(int idx, byte ch) { g[idx] = ch; } void clearletter(int idx) { g[idx] = DASH; } - void clearClue(int idx) { - g[idx] = DASH; - if ((idx & 64) == 0) lo &= ~(1L << idx); - else hi &= ~(1L << (idx & 63)); - } - boolean isClue(long index) { return ((index & 64) == 0) ? ((lo >>> index) & 1L) != X : ((hi >>> (index & 63)) & 1L) != X; } - boolean isClue(int index) { return ((index & 64) == 0) ? ((lo >>> index) & 1L) != X : ((hi >>> (index & 63)) & 1L) != X; } - boolean notClue(long index) { return ((index & 64) == 0) ? ((lo >>> index) & 1L) == X : ((hi >>> (index & 63)) & 1L) == X; } - boolean notClue(int index) { return ((index & 64) == 0) ? ((lo >>> index) & 1L) == X : ((hi >>> (index & 63)) & 1L) == X; } - - int clueCount() { return Long.bitCount(lo) + Long.bitCount(hi); } + boolean isClue(long index) { return ((index & 64) == 0) ? ((lo >>> index) & 1L) != X : ((hi >>> (index & 63)) & 1L) != X; } + boolean isClue(int index) { return ((index & 64) == 0) ? ((lo >>> index) & 1L) != X : ((hi >>> (index & 63)) & 1L) != X; } + boolean notClue(long index) { return ((index & 64) == 0) ? ((lo >>> index) & 1L) == X : ((hi >>> (index & 63)) & 1L) == X; } + boolean notClue(int index) { return ((index & 64) == 0) ? ((lo >>> index) & 1L) == X : ((hi >>> (index & 63)) & 1L) == X; } + int clueCount() { return Long.bitCount(lo) + Long.bitCount(hi); } void forEachSlot(SlotVisitor visitor) { for (var l = lo; l != X; l &= l - 1) processSlot(this, visitor, Long.numberOfTrailingZeros(l)); @@ -477,19 +460,19 @@ public record SwedishGenerator(Rng rng, int[] stack) { for (int i = 0; i < 65; i += 64) { for (long bits = (i == 0 ? lo_cl : hi_cl); bits != X; bits &= bits - 1) { - int clueIdx = i | Long.numberOfTrailingZeros(bits); - int key = Slot.packSlotDir(clueIdx, grid.digitAt(clueIdx)); - /// long clueBits = (i == 0 ? lo_cl : hi_cl); - /// long vBits = (i == 0 ? grid.vlo : grid.vhi); - /// long rBits = (i == 0 ? grid.rlo : grid.rhi); - /// for (long bits = clueBits; bits != X; bits &= bits - 1) { - /// long lsb = bits & -bits; - /// int clueIdx = i | Long.numberOfTrailingZeros(lsb); - /// int v = (vBits & lsb) != 0 ? 1 : 0; - /// int r = (rBits & lsb) != 0 ? 1 : 0; - /// int key = Slot.packSlotDir(clueIdx, (r << 1) | v); - long rLo = PATH_LO[key], rHi = PATH_HI[key]; - long hLo = rLo & lo_cl, hHi = rHi & hi_cl; + int clueIdx = i | Long.numberOfTrailingZeros(bits); + int key = Slot.packSlotDir(clueIdx, grid.digitAt(clueIdx)); + /// long clueBits = (i == 0 ? lo_cl : hi_cl); + /// long vBits = (i == 0 ? grid.vlo : grid.vhi); + /// long rBits = (i == 0 ? grid.rlo : grid.rhi); + /// for (long bits = clueBits; bits != X; bits &= bits - 1) { + /// long lsb = bits & -bits; + /// int clueIdx = i | Long.numberOfTrailingZeros(lsb); + /// int v = (vBits & lsb) != 0 ? 1 : 0; + /// int r = (rBits & lsb) != 0 ? 1 : 0; + /// int key = Slot.packSlotDir(clueIdx, (r << 1) | v); + long rLo = PATH_LO[key], rHi = PATH_HI[key]; + long hLo = rLo & lo_cl, hHi = rHi & hi_cl; if (Slot.increasing(key)) { if (hLo != 0) { rLo &= ((1L << Long.numberOfTrailingZeros(hLo)) - 1); @@ -606,7 +589,7 @@ public record SwedishGenerator(Rng rng, int[] stack) { return penalty; } - SwedishGenerator.Clues randomMask() { + Clues randomMask() { var g = Clues.createEmpty(); for (int placed = 0, guard = 0, idx; placed < TARGET_CLUES && guard < 4000; guard++) { idx = rng.randint(0, SIZE_MIN_1); @@ -619,18 +602,18 @@ public record SwedishGenerator(Rng rng, int[] stack) { } return g; } - Clues mutate(Clues grid) { - var g = grid.deepCopyGrid(); + Clues mutate(Clues clues) { + var c = clues.deepCopyGrid(); int ri; var bytes = MUTATE_RI[rng.randint(0, SIZE_MIN_1)]; for (var k = 0; k < 4; k++) { ri = bytes[rng.randint(0, 624)]; - if (!g.clueless(ri)) { + if (!c.clueless(ri)) { var d_idx = rng.randint2bitByte(); - if (g.hasRoomForClue(OFFSETS_D_IDX[Slot.packSlotDir(ri, d_idx)])) g.setClue(ri, d_idx); + if (c.hasRoomForClue(OFFSETS_D_IDX[Slot.packSlotDir(ri, d_idx)])) c.setClue(ri, d_idx); } } - return g; + return c; } Clues crossover(Clues a, Clues other) { var out = a.deepCopyGrid(); @@ -652,21 +635,21 @@ public record SwedishGenerator(Rng rng, int[] stack) { } } - // long maskLo = 0, maskHi = 0; - // for (var rci : IT) { - // if ((rci.cross_r()) * nc + (rci.cross_c()) * nr < 0) { - // int i = rci.i(); - // if (i < 64) maskLo |= (1L << i); - // 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); + // long maskLo = 0, maskHi = 0; + // for (var rci : IT) { + // if ((rci.cross_r()) * nc + (rci.cross_c()) * nr < 0) { + // int i = rci.i(); + // if (i < 64) maskLo |= (1L << i); + // 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); for (var lo = out.lo; lo != X; lo &= lo - 1L) clearClues(out, Long.numberOfTrailingZeros(lo)); for (var hi = out.hi; hi != X; hi &= hi - 1L) clearClues(out, 64 | Long.numberOfTrailingZeros(hi)); @@ -693,7 +676,7 @@ public record SwedishGenerator(Rng rng, int[] stack) { return best; } - public SwedishGenerator.Grid generateMask(int popSize, int gens, int pairs) { + public Clues generateMask(int popSize, int gens, int pairs) { class GridAndFit { Clues grid; @@ -745,10 +728,10 @@ public record SwedishGenerator(Rng rng, int[] stack) { var x = pop.get(i); if (x.fit() < best.fit()) best = x; } - return best.grid.toGrid(); + return best.grid; } - static long patternForSlot(Grid grid, SwedishGenerator.Slot s) { + static long patternForSlot(Grid grid, Slot s) { if ((s.lo & ~grid.lo) == 0 && (s.hi & ~grid.hi) == 0) return 0; long p = 0; if (s.increasing()) { @@ -855,7 +838,7 @@ public record SwedishGenerator(Rng rng, int[] stack) { return true; } - static int[] candidateInfoForPattern(long[] res, long pattern, SwedishGenerator.DictEntry entry, int lenb) { + static int[] candidateInfoForPattern(long[] res, long pattern, DictEntry entry, int lenb) { int numLongs = entry.numlong; boolean first = true; @@ -927,10 +910,10 @@ public record SwedishGenerator(Rng rng, int[] stack) { for (int i = 0; i < slots.length; i++) slotScores[i] = slotScore(count, slots[i]); } - public static SwedishGenerator.FillResult fillMask(SwedishGenerator.Rng rng, Grid mask, DictEntry[] dictIndex) { + public static FillResult fillMask(Rng rng, Grid mask, DictEntry[] dictIndex) { val multiThreaded = Thread.currentThread().getName().contains("pool"); val NO_LOG = (!Main.VERBOSE || multiThreaded); - val grid = mask.deepCopyGrid(); + val grid = mask; val used = new Bit1029(); val assigned = new long[CLUE_INDEX_MAX_SIZE]; val bitset = new long[2500]; @@ -971,7 +954,7 @@ public record SwedishGenerator(Rng rng, int[] stack) { System.out.print("\r" + Strings.padRight(msg, 120)); System.out.flush(); } - SwedishGenerator.Pick chooseMRV() { + Pick chooseMRV() { Slot best = null; for (int i = 0, count, count2 = -1, bestScore = -1, n = TOTAL; i < n; i++) { var s = slots[i]; @@ -1079,7 +1062,7 @@ public record SwedishGenerator(Rng rng, int[] stack) { var ok = solver.backtrack(0); // final progress line - var res = new FillResult(ok, new Gridded(grid), assigned, new SwedishGenerator.FillStats(solver.nodes, solver.backtracks, (System.currentTimeMillis() - t0) / 1000.0, solver.lastMRV)); + var res = new FillResult(ok, new Gridded(grid), assigned, new FillStats(solver.nodes, solver.backtracks, (System.currentTimeMillis() - t0) / 1000.0, solver.lastMRV)); if (!multiThreaded) { System.out.print("\r" + Strings.padRight("", 120) + "\r"); System.out.flush(); diff --git a/src/test/java/puzzle/ExportFormatTest.java b/src/test/java/puzzle/ExportFormatTest.java index 0ca19cf..eb0db2b 100644 --- a/src/test/java/puzzle/ExportFormatTest.java +++ b/src/test/java/puzzle/ExportFormatTest.java @@ -1,5 +1,6 @@ package puzzle; +import lombok.val; import org.junit.jupiter.api.Test; import puzzle.Export.Gridded; import puzzle.Export.Placed; @@ -27,11 +28,15 @@ public class ExportFormatTest { @Test void testExportFormatFromFilled() { var swe = new SwedishGenerator(new Rng(0), new int[STACK_SIZE]); - var grid = Grid.createEmpty(); + val clues = Clues.createEmpty(); // Place a RIGHT clue at (0,0) - grid.setClue(0, CLUE_RIGHT); + clues.setClue(0, CLUE_RIGHT); // 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(Grid.offset(0, 5), CLUE_LEFT); + var grid = clues.toGrid(); + var clueMap = new long[300]; // key = (cellIndex << 2) | (direction) @@ -43,8 +48,7 @@ public class ExportFormatTest { grid.setLetter(Grid.offset(0, 2), (byte) 'E'); grid.setLetter(Grid.offset(0, 3), (byte) 'S'); grid.setLetter(Grid.offset(0, 4), (byte) 'T'); - // Terminate the slot at (0,5) with another digit to avoid it extending to MAX_WORD_LENGTH - grid.setClue(Grid.offset(0, 5), CLUE_LEFT); + var fillResult = new FillResult(true, new Gridded(grid), clueMap, new FillStats(0, 0, 0, 0)); var puzzleResult = new PuzzleResult(swe, null, null, fillResult); diff --git a/src/test/java/puzzle/MainTest.java b/src/test/java/puzzle/MainTest.java index 2aaf9b1..dcdfd0b 100644 --- a/src/test/java/puzzle/MainTest.java +++ b/src/test/java/puzzle/MainTest.java @@ -33,11 +33,10 @@ public class MainTest { @Test void testExtractSlots() { - var grid = Grid.createEmpty(); - // Set up digits on the grid to create slots. - // CLUE_RIGHT at OFF_0_0 -> slot at (0,1), (0,2) - grid.setClue(OFF_0_0, CLUE_RIGHT); + var clues = Clues.createEmpty(); + clues.setClue(OFF_0_0, CLUE_RIGHT); + var grid = clues.toGrid(); grid.setLetter(OFF_0_1, LETTER_A); grid.setLetter(OFF_0_2, LETTER_B); @@ -63,8 +62,9 @@ public class MainTest { @Test void testForEachSlot() { - var grid = Grid.createEmpty(); - grid.setClue(OFF_0_0, CLUE_RIGHT); // right + var clues = Clues.createEmpty(); + clues.setClue(OFF_0_0, CLUE_RIGHT); + var grid = clues.toGrid(); var count = new AtomicInteger(0); grid.forEachSlot((key, lo, hi) -> { @@ -84,11 +84,12 @@ public class MainTest { } @Test public void testGridBasics() { - var grid = Grid.createEmpty(); + var clues = Clues.createEmpty(); + clues.setClue(OFF_1_2, CLUE_UP); + var grid = clues.toGrid(); // Test set/get grid.setLetter(OFF_0_0, LETTER_A); - grid.setClue(OFF_1_2, CLUE_UP); grid.setLetter(OFF_2_3, LETTER_Z); Assertions.assertEquals(LETTER_A, grid.byteAt(OFF_0_0)); @@ -114,28 +115,27 @@ public class MainTest { Assertions.assertTrue(grid.isClue(OFF_1_2)); // digit Assertions.assertTrue(grid.notClue(OFF_1_1)); // '#' is lettercell } - @Test - public void testGridDeepCopy() { - var grid = Grid.createEmpty(); - grid.setLetter(Grid.offset(0, 0), (byte) 'A'); - grid.setLetter(Grid.offset(0, 1), (byte) 'B'); - grid.setLetter(Grid.offset(1, 0), (byte) 'C'); - grid.setLetter(Grid.offset(1, 1), (byte) 'D'); + public void testCluesDeepCopy() { + var grid = Clues.createEmpty(); + grid.setClue(Grid.offset(0, 0), (byte) 1); + grid.setClue(Grid.offset(0, 1), (byte) 2); + grid.setClue(Grid.offset(1, 0), (byte) 3); + grid.setClue(Grid.offset(1, 1), (byte) 0); var copy = grid.deepCopyGrid(); - Assertions.assertEquals((byte) 'A', copy.byteAt(0)); + Assertions.assertEquals((byte) 1, copy.digitAt(0)); - copy.setLetter(0, (byte) 'X'); - Assertions.assertEquals((byte) 'X', copy.byteAt(0)); - Assertions.assertEquals((byte) 'A', grid.byteAt(0)); // Original should be unchanged + copy.setClue(0, (byte) 3); + Assertions.assertEquals((byte) 3, copy.digitAt(0)); + Assertions.assertEquals((byte) 1, grid.digitAt(0)); // Original should be unchanged } - @Test public void testMini() { - var grid = Grid.createEmpty(); val idx = OFF_1_1; - grid.setClue(idx, CLUE_LEFT); + var clues = Clues.createEmpty(); + clues.setClue(idx, CLUE_LEFT); + var grid = clues.toGrid(); Assertions.assertTrue(grid.isClue(idx)); } @Test @@ -179,8 +179,8 @@ public class MainTest { Assertions.assertEquals(12348, foundSeed, "Found seed changed"); Assertions.assertEquals(18, res.filled().wordCount(), "Number of assigned words changed"); Assertions.assertEquals("SLEDE", Lemma.asWord(res.filled().clueMap()[282])); - Assertions.assertEquals(74732156493031040L, res.filled().grid().grid().lo); - Assertions.assertEquals(193L, res.filled().grid().grid().hi); + Assertions.assertEquals(74732156493031040L, res.filled().grid().grid().lo()); + Assertions.assertEquals(193L, res.filled().grid().grid().hi()); } boolean isLetter(byte b) { return (b & 64) != 0; } @Test diff --git a/src/test/java/puzzle/SwedishGeneratorTest.java b/src/test/java/puzzle/SwedishGeneratorTest.java index c2e3689..6181c65 100644 --- a/src/test/java/puzzle/SwedishGeneratorTest.java +++ b/src/test/java/puzzle/SwedishGeneratorTest.java @@ -91,21 +91,6 @@ public class SwedishGeneratorTest { void testGrid() { var grid = Grid.createEmpty(); grid.setLetter(OFF_0_0, LETTER_A); - grid.setClue(OFF_0_1, CLUE_LEFT); - - assertEquals('A', grid.byteAt(OFF_0_0)); - assertEquals(CLUE_LEFT, grid.digitAt(OFF_0_1)); - assertTrue(grid.notClue(OFF_0_0)); - assertFalse(grid.isClue(OFF_0_0)); - assertTrue(grid.isClue(OFF_0_1)); - assertFalse(grid.notClue(OFF_0_1)); - assertTrue(grid.notClue(OFF_0_0)); - assertFalse(grid.notClue(OFF_0_1)); - - var copy = grid.deepCopyGrid(); - assertEquals('A', copy.byteAt(OFF_0_0)); - copy.setLetter(OFF_0_0, LETTER_B); - assertEquals('B', copy.byteAt(OFF_0_0)); assertEquals('A', grid.byteAt(OFF_0_0)); } @@ -267,11 +252,11 @@ public class SwedishGeneratorTest { @Test void testForEachSlotAndExtractSlots() { - var grid = Grid.createEmpty(); - // 3x3 grid (Config.PUZZLE_ROWS/COLS are 3 in test env) - // Set CLUE_RIGHT at OFF_0_0 - grid.setClue(OFF_0_0, CLUE_RIGHT); // 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); + var grid = clues.toGrid(); + var slots = extractSlots(grid); assertEquals(1, slots.length); @@ -430,20 +415,8 @@ public class SwedishGeneratorTest { // Empty grid: huge penalty var fitEmpty = gen.maskFitness(grid); assertTrue(fitEmpty >= 1_000_000_000L); - - // Grid with one short slot: still high penalty but less than empty grid.setClue(0, D_BYTE_2); // Right from 0,0. Len 2 if 3x3. var fitOne = gen.maskFitness(grid); assertTrue(fitOne < fitEmpty); - - // Test penalty for TARGET_CLUES - // TARGET_CLUES = SIZE >>> 2. For 3x3 it is 9 >>> 2 = 2. - // If we have 1 clue, |1 - 2| * 16000 = 16000 penalty. - // If we add another clue at a distant position. - var grid2 = Grid.createEmpty(); - grid2.setClue(0, D_BYTE_2); - grid2.setClue(Grid.offset(2, 0), D_BYTE_2); - // Now clueCount = 2, penalty should be 0 from target clues. - // But they might have other penalties (short slots, etc.) } } \ No newline at end of file