diff --git a/src/main/java/puzzle/Export.java b/src/main/java/puzzle/Export.java index b7bfd4c..eb79ee2 100644 --- a/src/main/java/puzzle/Export.java +++ b/src/main/java/puzzle/Export.java @@ -163,17 +163,8 @@ public record Export() { public record PuzzleResult(SwedishGenerator swe, Dict dict, Gridded mask, FillResult filled) { - boolean inBounds(int idx) { return idx >= 0 && idx < SwedishGenerator.SIZE; } - Placed extractPlacedFromSlot(Slot s, long lemma) { - - var cells = s.walk().toArray(); - - return new Placed( - lemma, - s.key(), - cells - ); - } + 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()); } public ExportedPuzzle exportFormatFromFilled(int difficulty, Rewards rewards) { var g = filled().grid(); var placed = new ArrayList(); diff --git a/src/main/java/puzzle/Main.java b/src/main/java/puzzle/Main.java index 3e47b93..a6645bd 100644 --- a/src/main/java/puzzle/Main.java +++ b/src/main/java/puzzle/Main.java @@ -1,6 +1,7 @@ package puzzle; import lombok.Data; +import lombok.val; import puzzle.SwedishGenerator.Rng; import java.io.IOException; @@ -346,7 +347,7 @@ public class Main { static PuzzleResult attempt(Rng rng, Dict dict, Opts opts) { try { - return _attempt(rng, dict, opts); + return _attempt(rng, dict, opts); } catch (Exception e) { e.printStackTrace(); System.err.println("Failed to operate" + e.getMessage()); @@ -356,8 +357,9 @@ public class Main { static PuzzleResult _attempt(Rng rng, Dict dict, Opts opts) { TOTAL_ATTEMPTS.incrementAndGet(); var swe = new SwedishGenerator(rng); - var mask = swe.generateMask(opts.pop, opts.gens, Math.max(opts.pop, (int) Math.floor(opts.pop * 1.5))); - var filled = swe.fillMask(mask, dict.index(), opts.fillTimeout); + val stack = new int[STACK_SIZE]; + var mask = swe.generateMask(stack, opts.pop, opts.gens, Math.max(opts.pop, (int) Math.floor(opts.pop * 1.5))); + var filled = fillMask(rng, mask, dict.index()); TOTAL_NODES.addAndGet(filled.stats().nodes); TOTAL_BACKTRACKS.addAndGet(filled.stats().backtracks); diff --git a/src/main/java/puzzle/SwedishGenerator.java b/src/main/java/puzzle/SwedishGenerator.java index a44b222..0a8c1d7 100644 --- a/src/main/java/puzzle/SwedishGenerator.java +++ b/src/main/java/puzzle/SwedishGenerator.java @@ -1,6 +1,9 @@ package puzzle; import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.experimental.Accessors; +import lombok.experimental.Delegate; import lombok.val; import precomp.Neighbors9x8; import precomp.Neighbors9x8.rci; @@ -59,9 +62,11 @@ public record SwedishGenerator(Rng rng) { static final int MAX_WORD_LENGTH_PLUS_ONE = MAX_WORD_LENGTH + 1; static final int MIN_LEN = Config.MIN_LEN; static final int MAX_TRIES_PER_SLOT = Config.MAX_TRIES_PER_SLOT; + static final int STACK_SIZE = 64; static final char C_DASH = '\0'; static final byte _1 = 49, _9 = 57, A = 65, Z = 90, DASH = (byte) C_DASH; - + //72 << 3; + static final int CLUE_INDEX_MAX_SIZE = (288 | 3) + 1; static int clamp(int x, int a, int b) { return Math.max(a, Math.min(b, x)); } record Pick(Slot slot, CandidateInfo info, boolean done) { } // 0b11 @@ -94,19 +99,22 @@ public record SwedishGenerator(Rng rng) { static final Pick PICK_DONE = new Pick(null, null, true); static final Pick PICK_NOT_DONE = new Pick(null, null, false); + @RequiredArgsConstructor + @Getter + @Accessors(fluent = true) public static final class FillStats { - public long nodes; - public long backtracks; - public double seconds; - public int lastMRV; - public double simplicity; + final public long nodes; + final public long backtracks; + final public double seconds; + final public int lastMRV; + public double simplicity; } public static record FillResult(boolean ok, Gridded grid, long[] clueMap, - FillStats stats) { + @Delegate FillStats stats) { public void calcSimpel() { if (ok) { @@ -131,9 +139,9 @@ public record SwedishGenerator(Rng rng) { } } - static final record Context(int[] stack, long[] undo, long[] bitset) { + static final record Context(long[] bitset) { - public Context() { this(new int[SIZE], new long[128], new long[2500]); } + public Context() { this(new long[2500]); } private static final ThreadLocal CTX = ThreadLocal.withInitial(Context::new); public static Context get() { return CTX.get(); } @@ -381,18 +389,18 @@ public record SwedishGenerator(Rng rng) { } } - if ((rayLo | rayHi) != 0) { - visitor.visit(key, rayLo, rayHi); - } + visitor.visit(key, rayLo, rayHi); + //if ((rayLo | rayHi) == 0L) throw new RuntimeException() } - static ArrayList extractSlots(Grid grid) { - var slots = new ArrayList(32); - grid.forEachSlot((key, lo, hi) -> slots.add(Slot.from(key, lo, hi))); + static Slot[] extractSlots(Grid grid) { + var slots = new Slot[grid.clueCount()]; + int[] N = new int[]{ 0 }; + grid.forEachSlot((key, lo, hi) -> slots[N[0]++] = Slot.from(key, lo, hi)); return slots; } - long maskFitness(Grid grid) { + long maskFitness(Grid grid, int[] stack) { long cHLo = 0L, cHHi = 0L, cVLo = 0L, cVHi = 0L; long lo_cl = grid.lo, hi_cl = grid.hi; @@ -437,8 +445,6 @@ public record SwedishGenerator(Rng rng) { } if (!hasSlots) return 1_000_000_000L; - var ctx = Context.get(); - var stack = ctx.stack; long seenLo = 0L, seenHi = 0L; // loop over beide helften @@ -448,7 +454,7 @@ public record SwedishGenerator(Rng rng) { // "unseen clues" in deze helft for (long bits = clueMask & ~seenMask; bits != 0L; bits &= bits - 1) { - int clueIdx = base + Long.numberOfTrailingZeros(bits); + int clueIdx = base | Long.numberOfTrailingZeros(bits); // start nieuwe component int size = 0; @@ -543,7 +549,7 @@ public record SwedishGenerator(Rng rng) { for (var k = 0; k < 4; k++) { ri = bytes[rng.randint(0, 624)]; if (!g.clueless(ri)) { - var d_idx = rng.randint2bitByte(); + var d_idx = rng.randint2bitByte(); if (g.hasRoomForClue(OFFSETS_D_IDX[Slot.packSlotDir(ri, d_idx)])) g.setClue(ri, d_idx); } } @@ -580,14 +586,14 @@ public record SwedishGenerator(Rng rng) { } public static void clearClues(Grid out, int idx) { if (!out.hasRoomForClue(OFFSETS_D_IDX[Slot.packSlotDir(idx, out.digitAt(idx))])) out.clearClue(idx); } - Grid hillclimb(Grid start, int limit) { + Grid hillclimb(int[] stack, Grid start, int limit) { var best = start; - var bestF = maskFitness(best); + var bestF = maskFitness(best, stack); var fails = 0; while (fails < limit) { var cand = mutate(best); - var f = maskFitness(cand); + var f = maskFitness(cand, stack); if (f < bestF) { best = cand; bestF = f; @@ -599,21 +605,21 @@ public record SwedishGenerator(Rng rng) { return best; } - public Grid generateMask(int popSize, int gens, int pairs) { + public Grid generateMask(int[] stack, int popSize, int gens, int pairs) { class GridAndFit { Grid grid; long fite = -1; GridAndFit(Grid grid) { this.grid = grid; } long fit() { - if (fite == -1) this.fite = maskFitness(grid); + if (fite == -1) this.fite = maskFitness(grid, stack); return this.fite; } } if (Main.VERBOSE) System.out.println("generateMask init pop: " + popSize); var pop = new ArrayList(); for (var i = 0; i < popSize; i++) { - pop.add(new GridAndFit(hillclimb(randomMask(), 180))); + pop.add(new GridAndFit(hillclimb(stack, randomMask(), 180))); } for (var gen = 0; gen < gens; gen++) { @@ -624,7 +630,7 @@ public record SwedishGenerator(Rng rng) { var p1 = pop.get(rng.randint(0, pop.size() - 1)); var p2 = pop.get(rng.randint(0, pop.size() - 1)); var child = crossover(p1.grid, p2.grid); - children.add(new GridAndFit(hillclimb(child, 70))); + children.add(new GridAndFit(hillclimb(stack, child, 70))); } pop.addAll(children); @@ -696,7 +702,7 @@ public record SwedishGenerator(Rng rng) { } return p; } - static int slotScore(int[] count, Slot s) { + static int slotScore(byte[] count, Slot s) { int cross = 0; for (long b = s.lo; b != 0; b &= b - 1) cross += (count[Long.numberOfTrailingZeros(b)] - 1); for (long b = s.hi; b != 0; b &= b - 1) cross += (count[64 | Long.numberOfTrailingZeros(b)] - 1); @@ -761,9 +767,8 @@ public record SwedishGenerator(Rng rng) { return true; } - static CandidateInfo candidateInfoForPattern(Context ctx, long pattern, DictEntry entry, int lenb) { + static CandidateInfo candidateInfoForPattern(long[] res, long pattern, DictEntry entry, int lenb) { int numLongs = entry.numlong; - long[] res = ctx.bitset; boolean first = true; for (long p = pattern; p != 0; ) { @@ -799,15 +804,15 @@ public record SwedishGenerator(Rng rng) { return new CandidateInfo(indices, count); } - static int candidateCountForPattern(Context ctx, long pattern, DictEntry entry, int lenb) { - int numLongs = entry.numlong; - long[] res = ctx.bitset; - boolean first = true; + static int candidateCountForPattern(final long[] res, final long pattern, final DictEntry entry, final int lenb) { + val numLongs = entry.numlong; + val posBitsets = entry.posBitsets; + boolean first = true; for (long p = pattern; p != 0; ) { int combined = (int) (p & 0xFF); if (combined != 0) { - long[] bs = entry.posBitsets[combined - 1]; + long[] bs = posBitsets[combined - 1]; if (first) { System.arraycopy(bs, 0, res, 0, numLongs); first = false; @@ -824,36 +829,40 @@ public record SwedishGenerator(Rng rng) { for (int k = 0; k < numLongs; k++) count += Long.bitCount(res[k]); return count; } - //72 << 3; - static final int BIGG = (288 | 3) + 1; - public FillResult fillMask(Grid mask, DictEntry[] dictIndex, - int timeLimitMs) { - val multiThreaded = Thread.currentThread().getName().contains("pool"); - val grid = mask.deepCopyGrid(); - val used = new Bit1029(); - - long[] assigned = new long[BIGG]; - val ctx = Context.get(); - val count = new int[SIZE]; - - val slots = extractSlots(grid); + + static void scoreSlots(int[] slotScores, Slot[] slots) { + val count = new byte[SIZE]; for (var s : slots) { for (long b = s.lo; b != 0; b &= b - 1) count[Long.numberOfTrailingZeros(b)]++; for (long b = s.hi; b != 0; b &= b - 1) count[64 | Long.numberOfTrailingZeros(b)]++; } + for (int i = 0; i < slots.length; i++) slotScores[i] = slotScore(count, slots[i]); - val slotScores = new int[slots.size()]; - for (int i = 0; i < slots.size(); i++) slotScores[i] = slotScore(count, slots.get(i)); + } + 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 used = new Bit1029(); + val assigned = new long[CLUE_INDEX_MAX_SIZE]; + val bitset = new long[2500]; + val undo = new long[64]; - val t0 = System.currentTimeMillis(); - val stats = new FillStats(); - val TOTAL = slots.size(); + val slots = extractSlots(grid); + val TOTAL = slots.length; + val slotScores = new int[TOTAL]; + + scoreSlots(slotScores, slots); + + val t0 = System.currentTimeMillis(); class Solver { + long nodes; + long backtracks; + int lastMRV; long lastLog = t0; void renderProgress() { - if (!Main.VERBOSE || multiThreaded) return; var now = System.currentTimeMillis(); if ((now - lastLog) < LOG_EVERY_MS) return; lastLog = (now); @@ -869,19 +878,19 @@ public record SwedishGenerator(Rng rng) { var msg = String.format( Locale.ROOT, "%s %d/%d slots | nodes=%d | backtracks=%d | mrv=%d | %s", - bar, done, TOTAL, stats.nodes, stats.backtracks, stats.lastMRV, elapsed + bar, done, TOTAL, nodes, backtracks, lastMRV, elapsed ); System.out.print("\r" + Strings.padRight(msg, 120)); System.out.flush(); } Pick chooseMRV() { Slot best = null; - for (int i = 0, count, count2 = -1, bestScore = -1, n = slots.size(); i < n; i++) { - var s = slots.get(i); + for (int i = 0, count, count2 = -1, bestScore = -1, n = TOTAL; i < n; i++) { + var s = slots[i]; if (assigned[s.key] != X) continue; var pattern = patternForSlot(grid, s); var index = dictIndex[s.length()]; - count = pattern == X ? index.length : candidateCountForPattern(ctx, pattern, index, s.length()); + count = pattern == X ? index.length : candidateCountForPattern(bitset, pattern, index, s.length()); if (count == 0) return PICK_NOT_DONE; if (best == null @@ -898,28 +907,27 @@ public record SwedishGenerator(Rng rng) { var pattern = patternForSlot(grid, best); var index = dictIndex[best.length()]; if (pattern == X) return new Pick(best, index.empty, false); - return new Pick(best, candidateInfoForPattern(ctx, pattern, index, best.length()), false); + return new Pick(best, candidateInfoForPattern(bitset, pattern, index, best.length()), false); } boolean backtrack(int depth) { if (Thread.currentThread().isInterrupted()) return false; - stats.nodes++; + nodes++; - if (timeLimitMs > 0 && (System.currentTimeMillis() - t0) > timeLimitMs) return false; + if (20_000 > 0 && (System.currentTimeMillis() - t0) > 20_000) return false; var pick = chooseMRV(); if (pick.done) return true; if (pick.slot == null) { - stats.backtracks++; + backtracks++; return false; } val info = pick.info; - stats.lastMRV = info.count; - renderProgress(); + lastMRV = info.count; + if (!NO_LOG) renderProgress(); - var s = pick.slot; - var k = s.key; - int patLen = s.length(); - var entry = dictIndex[patLen]; + val s = pick.slot; + val k = s.key; + val entry = dictIndex[s.length()]; if (info.indices != null && info.indices.length > 0) { var idxs = info.indices; @@ -935,7 +943,7 @@ public record SwedishGenerator(Rng rng) { var lemIdx = Lemma.unpackIndex(w); if (used.get(lemIdx)) continue; - if (!placeWord(grid, s, w, ctx.undo, depth)) continue; + if (!placeWord(grid, s, w, undo, depth)) continue; used.set(lemIdx); assigned[k] = w; @@ -944,9 +952,9 @@ public record SwedishGenerator(Rng rng) { assigned[k] = X; used.clear(lemIdx); - grid.undoPlace(ctx.undo[depth << 1], ctx.undo[(depth << 1) | 1]); + grid.undoPlace(undo[depth << 1], undo[(depth << 1) | 1]); } - stats.backtracks++; + backtracks++; return false; } @@ -960,7 +968,7 @@ public record SwedishGenerator(Rng rng) { var lemIdx = Lemma.unpackIndex(w); if (used.get(lemIdx)) continue; - if (!placeWord(grid, s, w, ctx.undo, depth)) continue; + if (!placeWord(grid, s, w, undo, depth)) continue; used.set(lemIdx); assigned[k] = w; @@ -969,33 +977,31 @@ public record SwedishGenerator(Rng rng) { assigned[k] = X; used.clear(lemIdx); - grid.undoPlace(ctx.undo[depth << 1], ctx.undo[(depth << 1) | 1]); + grid.undoPlace(undo[depth << 1], undo[(depth << 1) | 1]); } - stats.backtracks++; + backtracks++; return false; } } // initial render (same feel) var solver = new Solver(); - solver.renderProgress(); + if (!NO_LOG) solver.renderProgress(); var ok = solver.backtrack(0); // final progress line + + 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(); } - - stats.seconds = (System.currentTimeMillis() - t0) / 1000.0; - var res = new FillResult(ok, new Gridded(grid), assigned, stats); - // print a final progress line if (Main.VERBOSE && !multiThreaded) { System.out.println( String.format(Locale.ROOT, "[######################] %d/%d slots | nodes=%d | backtracks=%d | mrv=%d | %.1fs", - res.wordCount(), TOTAL, stats.nodes, stats.backtracks, stats.lastMRV, stats.seconds + res.wordCount(), TOTAL, res.nodes(), res.backtracks(), res.lastMRV(), res.seconds() ) ); } diff --git a/src/test/java/puzzle/ExportFormatTest.java b/src/test/java/puzzle/ExportFormatTest.java index 4e0cdbc..bcbff7d 100644 --- a/src/test/java/puzzle/ExportFormatTest.java +++ b/src/test/java/puzzle/ExportFormatTest.java @@ -35,7 +35,7 @@ public class ExportFormatTest { var clueMap = new long[300]; // key = (cellIndex << 2) | (direction) - var key = (0 << 2) | (CLUE_RIGHT); + var key = (0) | (CLUE_RIGHT); clueMap[key] = Lemma.from("TEST"); // Manually fill the grid letters for "TEST" at (0,1), (0,2), (0,3), (0,4) @@ -44,9 +44,9 @@ public class ExportFormatTest { 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_UP); + grid.setClue(Grid.offset(0, 5), CLUE_LEFT); - var fillResult = new FillResult(true, new Gridded(grid), clueMap, new FillStats()); + var fillResult = new FillResult(true, new Gridded(grid), clueMap, new FillStats(0, 0, 0, 0)); var puzzleResult = new PuzzleResult(swe, null, null, fillResult); var rewards = new Rewards(10, 5, 1); @@ -86,7 +86,7 @@ public class ExportFormatTest { void testExportFormatEmpty() { var swe = new SwedishGenerator(new Rng(0)); var grid = Grid.createEmpty(); - var fillResult = new FillResult(true, new Gridded(grid), new long[300], new FillStats()); + var fillResult = new FillResult(true, new Gridded(grid), new long[300], new FillStats(0, 0, 0, 0)); var puzzleResult = new PuzzleResult(swe, null, null, fillResult); var exported = puzzleResult.exportFormatFromFilled(1, new Rewards(0, 0, 0)); diff --git a/src/test/java/puzzle/MainTest.java b/src/test/java/puzzle/MainTest.java index 8df76d2..2aaf9b1 100644 --- a/src/test/java/puzzle/MainTest.java +++ b/src/test/java/puzzle/MainTest.java @@ -42,8 +42,8 @@ public class MainTest { grid.setLetter(OFF_0_2, LETTER_B); var slots = extractSlots(grid); - assertEquals(1, slots.size()); - var s = slots.getFirst(); + assertEquals(1, slots.length); + var s = slots[0]; assertEquals(8, s.length()); var cells = s.walk().toArray(); assertEquals(0, Grid.r(cells[0])); diff --git a/src/test/java/puzzle/SwedishGeneratorTest.java b/src/test/java/puzzle/SwedishGeneratorTest.java index 9fc22d9..601c821 100644 --- a/src/test/java/puzzle/SwedishGeneratorTest.java +++ b/src/test/java/puzzle/SwedishGeneratorTest.java @@ -1,5 +1,6 @@ package puzzle; +import lombok.val; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import puzzle.Export.IntListDTO; @@ -35,36 +36,36 @@ public class SwedishGeneratorTest { static final byte D_BYTE_2 = CLUE_RIGHT; @Test void testPatternForSlotAllLetters() { - var grid = new Grid(new byte[]{ LETTER_A, LETTER_B, LETTER_C }); - var slot = Slot.from(18 << Slot.BIT_FOR_DIR | (CLUE_RIGHT), 7L, 0L); - long pattern = patternForSlot(grid, slot); + var grid = new Grid(new byte[]{ LETTER_A, LETTER_B, LETTER_C }); + var slot = Slot.from(18 << Slot.BIT_FOR_DIR | (CLUE_RIGHT), 7L, 0L); + var pattern = patternForSlot(grid, slot); assertEquals(1L | (28L << 8) | (55L << 16), pattern); } @Test void testPatternForSlotMixed() { - var grid = new Grid(new byte[]{ LETTER_A, DASH, LETTER_C }); - var slot = Slot.from(1 << Slot.BIT_FOR_DIR | (CLUE_RIGHT), 7L, 0L); - long pattern = patternForSlot(grid, slot); + var grid = new Grid(new byte[]{ LETTER_A, DASH, LETTER_C }); + var slot = Slot.from(1 << Slot.BIT_FOR_DIR | (CLUE_RIGHT), 7L, 0L); + var pattern = patternForSlot(grid, slot); assertEquals(1L | (0L << 8) | (55L << 16), pattern); } @Test void testPatternForSlotAllDashes() { - var grid = new Grid(new byte[]{ DASH, DASH, DASH }); // - - - - var slot = Slot.from(1 << Slot.BIT_FOR_DIR | (CLUE_RIGHT), 7L, 0L); - long pattern = patternForSlot(grid, slot); + var grid = new Grid(new byte[]{ DASH, DASH, DASH }); // - - - + var slot = Slot.from(1 << Slot.BIT_FOR_DIR | (CLUE_RIGHT), 7L, 0L); + var pattern = patternForSlot(grid, slot); assertEquals(0L, pattern); } @Test void testPatternForSlotSingleLetter() { - var grid = new Grid(new byte[]{ LETTER_A, DASH, DASH }); - var slot = Slot.from(1 << Slot.BIT_FOR_DIR | (CLUE_RIGHT), 7L, 0L); - long pattern = patternForSlot(grid, slot); + var grid = new Grid(new byte[]{ LETTER_A, DASH, DASH }); + var slot = Slot.from(1 << Slot.BIT_FOR_DIR | (CLUE_RIGHT), 7L, 0L); + var pattern = patternForSlot(grid, slot); assertEquals(1L, pattern); } @@ -186,17 +187,17 @@ public class SwedishGeneratorTest { static int intersectSorted(int[] a, int aLen, int[] b, int bLen, int[] out) { if (aLen == 0 || bLen == 0) return 0; if (aLen < bLen >>> 4) { - int k = 0; - for (int i = 0; i < aLen; i++) { - int x = a[i]; + var k = 0; + for (var i = 0; i < aLen; i++) { + var x = a[i]; if (Arrays.binarySearch(b, 0, bLen, x) >= 0) out[k++] = x; } return k; } if (bLen < aLen >>> 4) { - int k = 0; - for (int i = 0; i < bLen; i++) { - int y = b[i]; + var k = 0; + for (var i = 0; i < bLen; i++) { + var y = b[i]; if (Arrays.binarySearch(a, 0, aLen, y) >= 0) out[k++] = y; } return k; @@ -233,10 +234,10 @@ public class SwedishGeneratorTest { } static long packPattern(String s) { - long p = 0; - byte[] b = s.getBytes(StandardCharsets.US_ASCII); - for (int i = 0; i < b.length; i++) { - int val = b[i] & 31; + long p = 0; + var b = s.getBytes(StandardCharsets.US_ASCII); + for (var i = 0; i < b.length; i++) { + var val = b[i] & 31; if (val != 0) { p |= (long) (i * 26 + val) << (i << 3); } @@ -258,7 +259,7 @@ public class SwedishGeneratorTest { var dict = new Dict(new long[]{ l0, l1, l2, l3, l3a, l4a, l6a, l7a, l8a }); // Pattern "APP--" for length 5 - var info = candidateInfoForPattern(Context.get(), packPattern("APP"), dict.index()[5], 5); + var info = candidateInfoForPattern(Context.get().bitset(), packPattern("APP"), dict.index()[5], 5); assertEquals(2, info.count()); assertNotNull(info.indices()); @@ -274,8 +275,8 @@ public class SwedishGeneratorTest { // This should detect a slot starting at 0,1 with length 2 (0,1 and 0,2) var slots = extractSlots(grid); - assertEquals(1, slots.size()); - var s = slots.getFirst(); + assertEquals(1, slots.length); + var s = slots[0]; assertTrue(s.length() >= 2); assertEquals(OFF_0_0, s.clueIndex()); @@ -284,19 +285,16 @@ public class SwedishGeneratorTest { @Test void testMaskFitnessBasic() { - var gen = new SwedishGenerator(new Rng(0)); - var grid = Grid.createEmpty(); - var lenCounts = new int[12]; - lenCounts[2] = 10; - lenCounts[8] = 10; // In case MAX_WORD_LENGTH is 8 - + var gen = new SwedishGenerator(new Rng(0)); + var grid = Grid.createEmpty(); + var stack = new int[STACK_SIZE]; // Empty grid should have high penalty (no slots) - var f1 = gen.maskFitness(grid); + var f1 = gen.maskFitness(grid, stack); assertTrue(f1 >= 1_000_000_000L); // Add a slot grid.setClue(OFF_0_0, D_BYTE_2); - var f2 = gen.maskFitness(grid); + var f2 = gen.maskFitness(grid, stack); assertTrue(f2 < f1); } @@ -312,12 +310,10 @@ public class SwedishGeneratorTest { assertNotNull(g2); assertNotSame(g1, g2); - var g3 = gen.crossover(g1, g2); - assertNotNull(g3); + assertNotNull(gen.crossover(g1, g2)); - var lenCounts = new int[12]; - Arrays.fill(lenCounts, 10); - var g4 = gen.hillclimb(g1, 10); + val stack = new int[STACK_SIZE]; + var g4 = gen.hillclimb(stack, g1, 10); assertNotNull(g4); } @@ -395,7 +391,7 @@ public class SwedishGeneratorTest { assertFalse(sDec.increasing()); // 2. Test slotScore - int[] counts = new int[SIZE]; + val counts = new byte[SIZE]; counts[1] = 2; counts[2] = 3; var sScore = Slot.from(0, (1L << 1) | (1L << 2), 0L); @@ -419,29 +415,29 @@ public class SwedishGeneratorTest { var dict = new Dict(words); var entry5 = dict.index()[5]; - var ctx = Context.get(); - long pattern = packPattern("APP"); - assertEquals(2, candidateCountForPattern(ctx, pattern, entry5, 3)); + var ctx = Context.get(); + var pattern = packPattern("APP"); + assertEquals(2, candidateCountForPattern(ctx.bitset(), pattern, entry5, 3)); pattern = packPattern("BAN"); - assertEquals(1, candidateCountForPattern(ctx, pattern, entry5, 3)); + assertEquals(1, candidateCountForPattern(ctx.bitset(), pattern, entry5, 3)); pattern = packPattern("CAT"); - assertEquals(0, candidateCountForPattern(ctx, pattern, entry5, 3)); + assertEquals(0, candidateCountForPattern(ctx.bitset(), pattern, entry5, 3)); } @Test void testMaskFitnessDetailed() { - var gen = new SwedishGenerator(new Rng(42)); - var grid = Grid.createEmpty(); - + var gen = new SwedishGenerator(new Rng(42)); + var grid = Grid.createEmpty(); + val stack = new int[STACK_SIZE]; // Empty grid: huge penalty - long fitEmpty = gen.maskFitness(grid); + var fitEmpty = gen.maskFitness(grid, stack); 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. - long fitOne = gen.maskFitness(grid); + var fitOne = gen.maskFitness(grid, stack); assertTrue(fitOne < fitEmpty); // Test penalty for TARGET_CLUES