From 47b33af09d5c71e61b6b564e09eb0d2c3314344d Mon Sep 17 00:00:00 2001 From: mike Date: Sat, 17 Jan 2026 04:18:35 +0100 Subject: [PATCH] introduce bitloops --- src/main/java/puzzle/Export.java | 15 +- src/main/java/puzzle/LongArrayList.java | 2 - src/main/java/puzzle/Main.java | 17 +- src/main/java/puzzle/Masker.java | 520 ++++++++++++++ src/main/java/puzzle/SwedishGenerator.java | 637 ++---------------- src/test/java/puzzle/ExportFormatTest.java | 17 +- src/test/java/puzzle/MainTest.java | 62 +- .../java/puzzle/SwedishGeneratorTest.java | 86 +-- 8 files changed, 680 insertions(+), 676 deletions(-) create mode 100644 src/main/java/puzzle/Masker.java diff --git a/src/main/java/puzzle/Export.java b/src/main/java/puzzle/Export.java index 882b01a..34eb736 100644 --- a/src/main/java/puzzle/Export.java +++ b/src/main/java/puzzle/Export.java @@ -6,7 +6,7 @@ import lombok.experimental.Delegate; import lombok.val; import puzzle.Export.Gridded.Replacar.Cell; import puzzle.Export.LetterVisit.LetterAt; -import puzzle.SwedishGenerator.Clues; +import puzzle.Masker.Clues; import puzzle.SwedishGenerator.FillResult; import puzzle.SwedishGenerator.Grid; import puzzle.SwedishGenerator.Slotinfo; @@ -19,7 +19,7 @@ import java.util.stream.IntStream; import java.util.stream.Stream; import static puzzle.SwedishGenerator.R; import static puzzle.SwedishGenerator.Lemma; -import static puzzle.SwedishGenerator.Slot; +import static puzzle.Masker.Slot; import static puzzle.SwedishGenerator.C; import static puzzle.SwedishGenerator.X; @@ -100,7 +100,7 @@ public record Export() { for (var h = grid.hi & ~clues.hi; h != X; h &= h - 1) visitor.visit(64 | Long.numberOfTrailingZeros(h), grid.g); } public static IntStream walk(byte base, long lo, long hi) { - if (Slot.increasing(base)) { + if (Slotinfo.increasing(base)) { return IntStream.concat( IntStream.generate(new IntSupplier() { @@ -209,7 +209,7 @@ public record Export() { public int arrowRow() { return SwedishGenerator.IT[Slot.clueIndex(slotKey)].r(); } public int startRow() { return SwedishGenerator.IT[cells[0]].r(); } public int startCol() { return SwedishGenerator.IT[cells[0]].c(); } - public boolean isReversed() { return !Slot.increasing(slotKey); } + public boolean isReversed() { return !Slotinfo.increasing(slotKey); } public char direction() { return DIRECTION[Slot.dir(slotKey)]; } } @@ -224,10 +224,9 @@ public record Export() { public record ExportedPuzzle(String[] grid, WordOut[] words, int difficulty, Rewards rewards) { } - public record PuzzleResult(Clued clues, Slotinfo[] slots, FillResult filled) { + public record PuzzleResult(Clued clues, Gridded grid, Slotinfo[] slots, FillResult filled) { public ExportedPuzzle exportFormatFromFilled(int difficulty, Rewards rewards) { - var g = filled().grid(); var placed = new ArrayList(); for (var slot : slots) { placed.add(new Placed(slot.assign().w, slot.key(), Gridded.walk((byte) slot.key(), slot.lo(), slot.hi()).toArray())); @@ -235,7 +234,7 @@ public record Export() { // If nothing placed: return full grid mapped to letters/# only if (placed.isEmpty()) { - return new ExportedPuzzle(g.exportGrid(clues.c, _ -> '#', '#'), new WordOut[0], difficulty, rewards); + return new ExportedPuzzle(grid.exportGrid(clues.c, _ -> '#', '#'), new WordOut[0], difficulty, rewards); } // 2) bounding box around all word cells + arrow cells, with 1-cell margin @@ -259,7 +258,7 @@ public record Export() { // 3) map of only used letter cells (everything else becomes '#') var letterAt = new HashMap(); - g.forEachLetter(clues.c(), (idx, letter) -> { + grid.forEachLetter(clues.c(), (idx, letter) -> { if (letter == 0) return; letterAt.put(idx, (char) (64 | letter)); }); diff --git a/src/main/java/puzzle/LongArrayList.java b/src/main/java/puzzle/LongArrayList.java index 15f1b49..7dacb58 100644 --- a/src/main/java/puzzle/LongArrayList.java +++ b/src/main/java/puzzle/LongArrayList.java @@ -6,8 +6,6 @@ public final class LongArrayList { private long[] a; private int size; - public LongArrayList() { this(16); } - public LongArrayList(int initialCapacity) { if (initialCapacity < 0) throw new IllegalArgumentException(); a = new long[initialCapacity]; diff --git a/src/main/java/puzzle/Main.java b/src/main/java/puzzle/Main.java index dd29b71..65ea77c 100644 --- a/src/main/java/puzzle/Main.java +++ b/src/main/java/puzzle/Main.java @@ -104,10 +104,10 @@ public class Main { System.out.print(indentLines(res.clues().gridToString(), " ")); section("Grid (raw)"); - System.out.print(indentLines(res.filled().grid().gridToString(res.clues().c()), " ")); + System.out.print(indentLines(res.grid().gridToString(res.clues().c()), " ")); section("Grid (human)"); - System.out.print(indentLines(res.filled().grid().renderHuman(res.clues().c()), " ")); + System.out.print(indentLines(res.grid().renderHuman(res.clues().c()), " ")); var exported = res.exportFormatFromFilled(1, new Rewards(50, 2, 1)); @@ -384,13 +384,14 @@ public class Main { static PuzzleResult _attempt(Rng rng, Dict dict, Opts opts) { long t0 = System.currentTimeMillis(); TOTAL_ATTEMPTS.incrementAndGet(); - var swe = new SwedishGenerator(rng, new int[STACK_SIZE], Clues.createEmpty()); - var mask = swe.generateMask(opts.clueSize, opts.pop, opts.gens, opts.offspring); + var masker = new Masker(rng, new int[STACK_SIZE], Masker.Clues.createEmpty()); + var mask = masker.generateMask(opts.clueSize, opts.pop, opts.gens, opts.offspring); if (mask == null) return null; val multiThreaded = Thread.currentThread().getName().contains("pool"); - var slots = extractSlots(mask, dict.index()); - val slotInfo = scoreSlots(new int[slots.length], slots); - var filled = fillMask(rng, slotInfo, mask.toGrid(), multiThreaded); + var slots = Masker.extractSlots(mask, dict.index()); + val slotInfo = Masker.scoreSlots(new int[slots.length], slots); + var grid = mask.toGrid(); + var filled = fillMask(rng, slotInfo, grid, multiThreaded); TOTAL_NODES.addAndGet(filled.stats().nodes); TOTAL_BACKTRACKS.addAndGet(filled.stats().backtracks); @@ -412,7 +413,7 @@ public class Main { ); if (filled.ok() && (opts.minSimplicity <= 0 || filled.stats().simplicity >= opts.minSimplicity)) { - return new PuzzleResult(new Clued(mask), slotInfo, filled); + return new PuzzleResult(new Clued(mask), new Gridded(grid), slotInfo, filled); } if (opts.verbose && filled.ok()) { diff --git a/src/main/java/puzzle/Masker.java b/src/main/java/puzzle/Masker.java new file mode 100644 index 0000000..e0a6f61 --- /dev/null +++ b/src/main/java/puzzle/Masker.java @@ -0,0 +1,520 @@ +package puzzle; + +import lombok.AllArgsConstructor; +import lombok.val; +import puzzle.Export.Gridded; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.stream.IntStream; + +import static java.lang.Long.*; +import static puzzle.SwedishGenerator.*; + +public record Masker(Rng rng, int[] stack, Clues cache) { + + public static final int[][] MUTATE_RI = new int[SwedishGenerator.SIZE][625]; + + static { + for (int i = 0; i < SwedishGenerator.SIZE; i++) { + int k = 0; + for (int dr1 = -2; dr1 <= 2; dr1++) + for (int dr2 = -2; dr2 <= 2; dr2++) + for (int dc1 = -2; dc1 <= 2; dc1++) + for (int dc2 = -2; dc2 <= 2; dc2++) { + val ti = IT[i]; + MUTATE_RI[i][k++] = Grid.offset(SwedishGenerator.clamp(ti.r() + dr1 + dr2, 0, R - 1), + SwedishGenerator.clamp(ti.c() + dc1 + dc2, 0, C - 1)); + } + } + } + // slice ray to stop before first clue, depending on direction monotonicity + // right/down => increasing indices; up/left => decreasing indices + // first clue is highest index among hits (hi first, then lo) + private static void processSlotRev(Clues c, SlotVisitor visitor, int key) { + long rayLo = PATH_LO[key]; + long rayHi = PATH_HI[key]; + // only consider clue cells + long hitsLo = rayLo & c.lo; + long hitsHi = rayHi & c.hi; + + if (hitsHi != X) { + int msb = 63 - numberOfLeadingZeros(hitsHi); + long stop = 1L << msb; + rayHi &= -(stop << 1); // keep bits > stop + rayLo = 0; // lo indices are below stop + } else if (hitsLo != X) { + int msb = 63 - numberOfLeadingZeros(hitsLo); + long stop = 1L << msb; + rayLo &= -(stop << 1); + } + + visitor.visit(key, rayLo, rayHi); + } + private static void processSlot(Clues c, SlotVisitor visitor, int key) { + long rayLo = PATH_LO[key]; + long rayHi = PATH_HI[key]; + long hitsLo = rayLo & c.lo; + long hitsHi = rayHi & c.hi; + + if (hitsLo != X) { + long stop = 1L << numberOfTrailingZeros(hitsLo); + rayLo &= (stop - 1); + rayHi = 0; // any hi is beyond the stop + } else if (hitsHi != X) { + long stop = 1L << numberOfTrailingZeros(hitsHi); + // keep all lo (lo indices are < any hi index), but cut hi below stop + rayHi &= (stop - 1); + } + + visitor.visit(key, rayLo, rayHi); + } + public static Slot[] extractSlots(Clues grid, DictEntry[] index) { + var slots = new Slot[grid.clueCount()]; + int[] N = new int[]{ 0 }; + grid.forEachSlot((key, lo, hi) -> slots[N[0]++] = Slot.from(key, lo, hi, index[Slot.length(lo, hi)])); + return slots; + } + public static Slotinfo[] scoreSlots(int[] slotScores, Slot[] slots) { + val count = new byte[SwedishGenerator.SIZE]; + Slotinfo[] slotInfo = new Slotinfo[slots.length]; + for (var s : slots) { + for (long b = s.lo; b != X; b &= b - 1) count[numberOfTrailingZeros(b)]++; + for (long b = s.hi; b != X; b &= b - 1) count[64 | numberOfTrailingZeros(b)]++; + } + for (int i = 0; i < slots.length; i++) { + var slot = slots[i]; + slotScores[i] = slotScore(count, slot.lo, slot.hi); + slotInfo[i] = new Slotinfo(slot.key, slot.lo, slot.hi, slotScores[i], new Assign(), slot.entry); + } + return slotInfo; + } + public static int slotScore(byte[] count, long lo, long hi) { + int cross = 0; + for (long b = lo; b != X; b &= b - 1) cross += (count[numberOfTrailingZeros(b)] - 1); + for (long b = hi; b != X; b &= b - 1) cross += (count[64 | numberOfTrailingZeros(b)] - 1); + return cross * 10 + Slot.length(lo, hi); + } + + public long maskFitness(final Clues grid, int clueSize) { + + long cHLo = 0L, cHHi = 0L, cVLo = 0L, cVHi = 0L; + long lo_cl = grid.lo, hi_cl = grid.hi; + long penalty = (((long) Math.abs(grid.clueCount() - clueSize)) * 16000L); + boolean hasSlots = false; + + for (long bits = lo_cl; bits != X; bits &= bits - 1) { + long lsb = bits & -bits; + int clueIdx = numberOfTrailingZeros(lsb); + int v = (grid.vlo & lsb) != 0 ? 1 : 0; + int r = (grid.rlo & lsb) != 0 ? 1 : 0; + int key = Slot.packSlotKey(clueIdx, (r << 1) | v); + long rLo = PATH_LO[key], rHi = PATH_HI[key]; + long hLo = rLo & lo_cl, hHi = rHi & hi_cl; + if (Slotinfo.increasing(key)) { + if (hLo != X) { + rLo &= ((1L << numberOfTrailingZeros(hLo)) - 1); + rHi = 0; + } else if (hHi != X) { rHi &= ((1L << numberOfTrailingZeros(hHi)) - 1); } + } else { + if (hHi != X) { + int msb = 63 - numberOfLeadingZeros(hHi); + rHi &= -(1L << msb << 1); + rLo = 0; + } else if (hLo != X) { + int msb = 63 - numberOfLeadingZeros(hLo); + rLo &= -(1L << msb << 1); + } + } + if ((rLo | rHi) != X) { + hasSlots = true; + if (Slot.horiz(key)) { + cHLo |= rLo; + cHHi |= rHi; + } else { + cVLo |= rLo; + cVHi |= rHi; + } + if ((bitCount(rLo) + bitCount(rHi)) < MIN_LEN) penalty += 8000; + } else { + penalty += 25000; + } + } + for (long bits = hi_cl; bits != X; bits &= bits - 1) { + long lsb = bits & -bits; + int clueIdx = numberOfTrailingZeros(lsb); + int v = (grid.vhi & lsb) != 0 ? 1 : 0; + int r = (grid.rhi & lsb) != 0 ? 1 : 0; + int key = Slot.packSlotKey(64 | clueIdx, (r << 1) | v); + long rLo = PATH_LO[key], rHi = PATH_HI[key]; + long hLo = rLo & lo_cl, hHi = rHi & hi_cl; + if (Slotinfo.increasing(key)) { + if (hLo != X) { + rLo &= ((1L << numberOfTrailingZeros(hLo)) - 1); + rHi = 0; + } else if (hHi != X) { rHi &= ((1L << numberOfTrailingZeros(hHi)) - 1); } + } else { + if (hHi != X) { + int msb = 63 - numberOfLeadingZeros(hHi); + rHi &= -(1L << msb << 1); + rLo = 0; + } else if (hLo != X) { + int msb = 63 - numberOfLeadingZeros(hLo); + rLo &= -(1L << msb << 1); + } + } + if ((rLo | rHi) != X) { + hasSlots = true; + if (Slot.horiz(key)) { + cHLo |= rLo; + cHHi |= rHi; + } else { + cVLo |= rLo; + cVHi |= rHi; + } + if ((bitCount(rLo) + bitCount(rHi)) < MIN_LEN) penalty += 8000; + } else { + penalty += 25000; + } + } + + if (!hasSlots) return 1_000_000_000L; + long seenLo = X, seenHi = X; + +// loop over beide helften + 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, nLo, nHi; bits != X; bits &= bits - 1) { + int clueIdx = base | numberOfTrailingZeros(bits); + + // start nieuwe component + size = 0; + stack[0] = clueIdx; + sp = 1; + + // mark seen + if ((clueIdx & 64) == 0) seenLo |= 1L << clueIdx; + else seenHi |= 1L << (clueIdx & 63); + + // flood fill / bfs + while (sp > 0) { + cur = stack[--sp]; + size++; + + // neighbors als 2x long masks + nLo = NBR8_PACKED_LO[cur]; + nHi = NBR8_PACKED_HI[cur]; + + // filter: alleen clues, en nog niet seen + nLo &= lo_cl & ~seenLo; + nHi &= hi_cl & ~seenHi; + + // push lo-neighbors + while (nLo != X) { + long lsb = nLo & -nLo; + int nidx = numberOfTrailingZeros(nLo); // 0..63 + seenLo |= lsb; + + stack[sp++] = nidx; + + nLo &= nLo - 1; + } + + // push hi-neighbors + while (nHi != X) { + long lsb = nHi & -nHi; + int nidx = 64 | numberOfTrailingZeros(nHi); // 64..127 + seenHi |= lsb; + + stack[sp++] = nidx; + + nHi &= nHi - 1; + } + } + + if (size >= 2) penalty += (size - 1L) * 120L; + } + } + + for (long bits = ~lo_cl & MASK_LO; bits != X; bits &= bits - 1) { + int clueIdx = numberOfTrailingZeros(bits); + var rci = IT[clueIdx]; + if ((4 - rci.nbrCount()) + bitCount(rci.n1() & lo_cl) + bitCount(rci.n2() & hi_cl) >= 3) penalty += 400; + boolean h = (cHLo & (1L << clueIdx)) != X; + boolean v = (cVLo & (1L << clueIdx)) != X; + if (!h && !v) penalty += 1500; + else if (h && v) { /* ok */ } else if (h | v) penalty += 200; + else penalty += 600; + } + for (long bits = ~hi_cl & MASK_HI; bits != X; bits &= bits - 1) { + int clueIdx = numberOfTrailingZeros(bits); + var rci = IT[64 | clueIdx]; + if ((4 - rci.nbrCount()) + bitCount(rci.n1() & lo_cl) + bitCount(rci.n2() & hi_cl) >= 3) penalty += 400; + boolean h = (cHHi & (1L << clueIdx)) != X; + boolean v = (cVHi & (1L << clueIdx)) != X; + if (!h && !v) penalty += 1500; + else if (h && v) { /* ok */ } else if (h | v) penalty += 200; + else penalty += 600; + } + + return penalty; + } + + public Clues randomMask(final int clueSize) { + var g = Clues.createEmpty(); + for (int placed = 0, guard = 0, ri; placed < clueSize && guard < 4000; guard++) { + + 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; + } + + public Clues mutate(Clues c) { + var bytes = MUTATE_RI[rng.randint0_SIZE()]; + for (int k = 0, ri; k < 4; k++) { + ri = bytes[rng.randint0_624()]; + 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; + } + + public Clues crossover(Clues a, Clues other) { + + var theta = rng.nextFloat() * Math.PI; + var nc = Math.cos(theta); + var nr = Math.sin(theta); + + 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) == 0) maskLo |= (1L << i); + else maskHi |= (1L << (i - 64)); + } + } + var c = 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 l = c.lo & ~c.rlo & ~c.vlo; l != X; l &= l - 1) clearCluesLo(c, numberOfTrailingZeros(l), 0); + for (var l = c.lo & ~c.rlo & c.vlo; l != X; l &= l - 1) clearCluesLo(c, numberOfTrailingZeros(l), 1); + for (var l = c.lo & c.rlo & ~c.vlo; l != X; l &= l - 1) clearCluesLo(c, numberOfTrailingZeros(l), 2); + for (var l = c.lo & c.rlo & c.vlo; l != X; l &= l - 1) clearCluesLo(c, numberOfTrailingZeros(l), 3); + for (var h = c.hi & ~c.rhi & ~c.vhi; h != X; h &= h - 1) clearCluesHi(c, numberOfTrailingZeros(h), 0); + for (var h = c.hi & ~c.rhi & c.vhi; h != X; h &= h - 1) clearCluesHi(c, numberOfTrailingZeros(h), 1); + for (var h = c.hi & c.rhi & ~c.vhi; h != X; h &= h - 1) clearCluesHi(c, (numberOfTrailingZeros(h)), 2); + for (var h = c.hi & c.rhi & ~c.vhi; h != X; h &= h - 1) clearCluesHi(c, (numberOfTrailingZeros(h)), 3); + return c; + } + + public static void clearCluesLo(Clues out, int idx, int d) { if (!out.hasRoomForClue(OFFSETS_D_IDX[Slot.packSlotKey(idx, d)])) out.clearClueLo(~(1L << idx)); } + + public static void clearCluesHi(Clues out, int idx, int d) { if (!out.hasRoomForClue(OFFSETS_D_IDX[Slot.packSlotKey(64 | idx, d)])) out.clearClueHi(~(1L << idx)); } + + public Clues hillclimb(Clues start, int clue_size, int limit) { + var best = start; + var bestF = maskFitness(best, clue_size); + var fails = 0; + + while (fails < limit) { + cache.from(best); + var cand = mutate(best); + var f = maskFitness(cand, clue_size); + if (f < bestF) { + best = cand; + bestF = f; + fails = 0; + } else { + best.from(cache); + fails++; + } + } + return best; + } + + public Clues generateMask(int clueSize, int popSize, int gens, int offspring) { + class GridAndFit { + + Clues grid; + long fite = -1; + GridAndFit(Clues grid) { this.grid = grid; } + long fit() { + if (fite == -1) this.fite = maskFitness(grid, clueSize); + return this.fite; + } + } + if (Main.VERBOSE) System.out.println("generateMask init pop: " + popSize + " clueSize: " + clueSize); + var pop = new ArrayList(); + for (var i = 0; i < popSize; i++) { + if (Thread.currentThread().isInterrupted()) return null; + pop.add(new GridAndFit(hillclimb(randomMask(clueSize), clueSize, 180))); + } + + for (var gen = 0; gen < gens; gen++) { + if (Thread.currentThread().isInterrupted()) break; + var children = new ArrayList(); + + for (var k = 0; k < offspring; k++) { + if (Thread.currentThread().isInterrupted()) break; + var p1 = pop.get(rng.randint(pop.size() - 1)); + var p2 = pop.get(rng.randint(pop.size() - 1)); + var child = crossover(p1.grid, p2.grid); + children.add(new GridAndFit(hillclimb(child, clueSize, 70))); + } + + pop.addAll(children); + pop.sort(Comparator.comparingLong(GridAndFit::fit)); + + var next = new ArrayList(); + for (var cand : pop) { + if (next.size() >= offspring) break; + var ok = true; + for (var kept : next) { + if (cand.grid.similarity(kept.grid) > 0.92) { + ok = false; + break; + } + } + if (ok) next.add(cand); + } + pop = next; + + if (Main.VERBOSE && (gen & 15) == 15) System.out.println(" gen " + gen + "/" + gens + " bestFitness=" + pop.get(0).fit()); + } + if (pop.isEmpty()) return null; + GridAndFit best = pop.get(0); + for (int i = 1; i < pop.size(); i++) { + var x = pop.get(i); + if (x.fit() < best.fit()) best = x; + } + return best.grid; + }//@formatter:off + @FunctionalInterface public interface SlotVisitor { void visit(int key, long lo, long hi); } + @AllArgsConstructor + public static class Clues { + + long lo, hi, vlo, vhi, rlo, rhi; + public long lo() { return lo; } + public long hi() { return hi; } + public long vlo() { return vlo; } + public long vhi() { return vhi; } + public long rlo() { return rlo; } + public long rhi() { return rhi; } + + public static Clues createEmpty() { return new Clues(0, 0, 0, 0, 0, 0); } + public boolean cluelessLo(int idx) { + if (!isClueLo(idx)) return false; + clearClueLo(~(1L << idx)); + return true; + } + public 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 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 void clearClueLo(long mask) { + lo &= mask; + vlo &= mask; + rlo &= mask; + } + public void clearClueHi(long mask) { + hi &= mask; + vhi &= mask; + rhi &= mask; + } + 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; } + + public int clueCount() { return bitCount(lo) + bitCount(hi); } + public double similarity(Clues b) { + long matchLo = (~(lo ^ b.lo)) & (~lo | (~(vlo ^ b.vlo) & ~(rlo ^ b.rlo))); + long matchHi = (~(hi ^ b.hi)) & (~hi | (~(vhi ^ b.vhi) & ~(rhi ^ b.rhi))); + + return (bitCount(matchLo & MASK_LO) + bitCount(matchHi & MASK_HI)) / SIZED; + } + public Grid toGrid() { return new Grid(new byte[SwedishGenerator.SIZE], lo, hi); } + public void forEachSlot(SlotVisitor visitor) { + for (var l = lo & ~rlo & vlo; l != X; l &= l - 1) processSlot(this, visitor, Slot.packSlotKey(numberOfTrailingZeros(l), 1)); + for (var l = lo & ~rlo & ~vlo; l != X; l &= l - 1) processSlot(this, visitor, Slot.packSlotKey(numberOfTrailingZeros(l), 0)); + for (var l = lo & rlo & ~vlo; l != X; l &= l - 1) processSlotRev(this, visitor, Slot.packSlotKey(numberOfTrailingZeros(l), 2)); + for (var l = lo & rlo & vlo; l != X; l &= l - 1) processSlotRev(this, visitor, Slot.packSlotKey(numberOfTrailingZeros(l), 3)); + for (var h = hi & ~rhi & vhi; h != X; h &= h - 1) processSlot(this, visitor, Slot.packSlotKey(64 | numberOfTrailingZeros(h), 1)); + for (var h = hi & ~rhi & ~vhi; h != X; h &= h - 1) processSlot(this, visitor, Slot.packSlotKey(64 | numberOfTrailingZeros(h), 0)); + for (var h = hi & rhi & ~vhi; h != X; h &= h - 1) processSlotRev(this, visitor, Slot.packSlotKey((64 | numberOfTrailingZeros(h)), 2)); + for (var h = hi & rhi & vhi; h != X; h &= h - 1) processSlotRev(this, visitor, Slot.packSlotKey((64 | numberOfTrailingZeros(h)), 3)); + } + public Clues from(Clues best) { + lo = best.lo; + hi = best.hi; + vlo = best.vlo; + vhi = best.vhi; + rlo = best.rlo; + rhi = best.rhi; + return this; + } + } + + static record Slot(int key, long lo, long hi, DictEntry entry) { + + static final int BIT_FOR_DIR = 2; + static Slot from(int key, long lo, long hi, DictEntry entry) { return new Slot(key, lo, hi, entry); } + public static int length(long lo, long hi) { return bitCount(lo) + bitCount(hi); } + public static int clueIndex(int key) { return key >>> BIT_FOR_DIR; } + public static int dir(int key) { return key & 3; } + public IntStream walk() { return Gridded.walk((byte) key, lo, hi); } + public static boolean horiz(int d) { return (d & 1) != 0; } + public static int packSlotKey(int idx, int d) { return (idx << BIT_FOR_DIR) | d; } + } +} diff --git a/src/main/java/puzzle/SwedishGenerator.java b/src/main/java/puzzle/SwedishGenerator.java index c7c35bc..17a0b43 100644 --- a/src/main/java/puzzle/SwedishGenerator.java +++ b/src/main/java/puzzle/SwedishGenerator.java @@ -17,11 +17,8 @@ import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; -import java.util.ArrayList; import java.util.Arrays; -import java.util.Comparator; import java.util.Locale; -import java.util.stream.IntStream; import static java.lang.Long.*; import static java.lang.Long.numberOfTrailingZeros; import static java.nio.charset.StandardCharsets.US_ASCII; @@ -43,77 +40,48 @@ import static java.nio.charset.StandardCharsets.US_ASCII; * java SwedishGenerator [--seed N] [--pop N] [--gens N] [--tries N] [--words word-list.txt] */ @SuppressWarnings("ALL") -public record SwedishGenerator(Rng rng, int[] stack, Clues cache) { +public class SwedishGenerator { - record CandidateInfo(int[] indices, int count) { } - - //@formatter:off - @FunctionalInterface interface SlotVisitor { void visit(int key, long lo, long hi); } - //@formatter:on - static final long GT_1_OFFSET_53_BIT = 0x3E00000000000000L; - static final long X = 0L; - static final int LOG_EVERY_MS = 200; - static final int BAR_LEN = 22; - static final int C = Config.PUZZLE_COLS; - static final double CROSS_R = (C - 1) / 2.0; - static final int R = Config.PUZZLE_ROWS; - static final double CROSS_C = (R - 1) / 2.0; - static final int SIZE = C * R;// ~18 - static final int SIZE_MIN_1 = SIZE - 1;// ~18 - static final double SIZED = (double) SIZE;// ~18 - static final long MASK_LO = (SIZE >= 64) ? -1L : (1L << SIZE) - 1; - static final long MASK_HI = (SIZE <= 64) ? 0L : (SIZE >= 128 ? -1L : (1L << (SIZE - 64)) - 1); - static final int MAX_WORD_LENGTH = C <= R ? C : R; - 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; - static final long RANGE_0_SIZE = (long) SIZE_MIN_1 - 0L + 1L; - static final long RANGE_0_624 = 624L - 0L + 1L; - //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)); } + public static final long GT_1_OFFSET_53_BIT = 0x3E00000000000000L; + public static final long X = 0L; + public static final int LOG_EVERY_MS = 200; + public static final int BAR_LEN = 22; + public static final int C = Config.PUZZLE_COLS; + public static final double CROSS_R = (C - 1) / 2.0; + public static final int R = Config.PUZZLE_ROWS; + public static final double CROSS_C = (R - 1) / 2.0; + public static final int SIZE = C * R;// ~18 + public static final int SIZE_MIN_1 = SIZE - 1;// ~18 + public static final double SIZED = (double) SIZE;// ~18 + public static final long MASK_LO = (SIZE >= 64) ? -1L : (1L << SIZE) - 1; + public static final long MASK_HI = (SIZE <= 64) ? 0L : (SIZE >= 128 ? -1L : (1L << (SIZE - 64)) - 1); + public static final int MAX_WORD_LENGTH = C <= R ? C : R; + public static final int MAX_WORD_LENGTH_PLUS_ONE = MAX_WORD_LENGTH + 1; + public static final int MIN_LEN = Config.MIN_LEN; + public static final int MAX_TRIES_PER_SLOT = Config.MAX_TRIES_PER_SLOT; + public static final int STACK_SIZE = 64; + public static final char C_DASH = '\0'; + public static final byte DASH = (byte) C_DASH; + public static final long RANGE_0_SIZE = (long) SIZE_MIN_1 - 0L + 1L; + public static final long RANGE_0_624 = 624L - 0L + 1L; + public static final int CLUE_INDEX_MAX_SIZE = (288 | 3) + 1; + public static int clamp(int x, int a, int b) { return Math.max(a, Math.min(b, x)); } @AllArgsConstructor - static class Pick { + public static class Pick { - Slotinfo slot; - int[] indices; - int count; + public Slotinfo slot; + public int[] indices; + public int count; } - // 0b11 - //0b00 - // 0b01 - // 0b10 - - static final byte B0 = (byte) 0; - static final byte B64 = (byte) 64; - static final long[] OFFSETS_D_IDX = Neighbors9x8.OFFSET_D_IDX_0_BASE; - static final rci[] IT = Neighbors9x8.IT; - static final int[][] MUTATE_RI = new int[SIZE][625]; - static final long[] NBR8_PACKED_LO = Neighbors9x8.NBR8_PACKED_LO; - static final long[] NBR8_PACKED_HI = Neighbors9x8.NBR8_PACKED_HI; - static final long[] PATH_LO = Neighbors9x8.PATH_LO; - static final long[] PATH_HI = Neighbors9x8.PATH_HI; - - static { - for (int i = 0; i < SIZE; i++) { - int k = 0; - for (int dr1 = -2; dr1 <= 2; dr1++) - for (int dr2 = -2; dr2 <= 2; dr2++) - for (int dc1 = -2; dc1 <= 2; dc1++) - for (int dc2 = -2; dc2 <= 2; dc2++) { - val ti = IT[i]; - MUTATE_RI[i][k++] = Grid.offset(clamp(ti.r() + dr1 + dr2, 0, R - 1), - clamp(ti.c() + dc1 + dc2, 0, C - 1)); - } - } - } - - static final Pick PICK_DONE = null;//new Pick(null, null, 0, true); - static final Pick PICK_NOT_DONE = new Pick(null, null, 0); + public static final long[] OFFSETS_D_IDX = Neighbors9x8.OFFSET_D_IDX_0_BASE; + public static final rci[] IT = Neighbors9x8.IT; + public static final long[] NBR8_PACKED_LO = Neighbors9x8.NBR8_PACKED_LO; + public static final long[] NBR8_PACKED_HI = Neighbors9x8.NBR8_PACKED_HI; + public static final long[] PATH_LO = Neighbors9x8.PATH_LO; + public static final long[] PATH_HI = Neighbors9x8.PATH_HI; + public static final Pick PICK_DONE = null;//new Pick(null, null, 0, true); + public static final Pick PICK_NOT_DONE = new Pick(null, null, 0); @RequiredArgsConstructor @Getter @@ -128,7 +96,7 @@ public record SwedishGenerator(Rng rng, int[] stack, Clues cache) { } public static record FillResult(boolean ok, - Gridded grid, + @Delegate FillStats stats) { static public long calcSimpel(Slotinfo[] slots) { @@ -145,15 +113,15 @@ public record SwedishGenerator(Rng rng, int[] stack, Clues cache) { } } - static final class Rng { + public static final class Rng { @Getter private int x; - Rng(int seed) { + public Rng(int seed) { var s = seed; if (s == 0) s = 1; this.x = s; } - int nextU32() { + public int nextU32() { var y = x; y ^= (y << 13); y ^= (y >>> 17); @@ -161,99 +129,24 @@ public record SwedishGenerator(Rng rng, int[] stack, Clues cache) { x = y; return y; } - int randint2bit() { return nextU32() & 3; } - byte randint2bitByte() { return (byte) (nextU32() & 3); } - int randint(int max) { return (int) (((nextU32() & 0xFFFFFFFFL) % ((long) max - 0L + 1L))); } - int randint0_SIZE() { return (int) (((nextU32() & 0xFFFFFFFFL) % RANGE_0_SIZE)); } - int randint0_624() { return (int) (((nextU32() & 0xFFFFFFFFL) % RANGE_0_624)); } - double nextFloat() { return (nextU32() & 0xFFFFFFFFL) / 4294967295.0; } - int biasedIndexPow3(int N) { return (int) (((Math.min(nextU32(), Math.min(nextU32(), nextU32())) & 0xFFFFFFFFL) * (long) N) >>> 32); } + public int randint2bit() { return nextU32() & 3; } + public byte randint2bitByte() { return (byte) (nextU32() & 3); } + public int randint(int max) { return (int) (((nextU32() & 0xFFFFFFFFL) % ((long) max - 0L + 1L))); } + public int randint0_SIZE() { return (int) (((nextU32() & 0xFFFFFFFFL) % RANGE_0_SIZE)); } + public int randint0_624() { return (int) (((nextU32() & 0xFFFFFFFFL) % RANGE_0_624)); } + public double nextFloat() { return (nextU32() & 0xFFFFFFFFL) / 4294967295.0; } + public int biasedIndexPow3(int N) { return (int) (((Math.min(nextU32(), Math.min(nextU32(), nextU32())) & 0xFFFFFFFFL) * (long) N) >>> 32); } } @AllArgsConstructor - static class Clues { + public static class Grid { - long lo, hi, vlo, vhi, rlo, rhi; - public static Clues createEmpty() { return new Clues(0, 0, 0, 0, 0, 0); } - 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 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; - } - void clearClueLo(long mask) { - lo &= mask; - vlo &= mask; - rlo &= mask; - } - void clearClueHi(long mask) { - hi &= mask; - vhi &= mask; - rhi &= mask; - } - 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; } - - public int clueCount() { return bitCount(lo) + bitCount(hi); } - public double similarity(Clues b) { - long matchLo = (~(lo ^ b.lo)) & (~lo | (~(vlo ^ b.vlo) & ~(rlo ^ b.rlo))); - long matchHi = (~(hi ^ b.hi)) & (~hi | (~(vhi ^ b.vhi) & ~(rhi ^ b.rhi))); - - return (bitCount(matchLo & MASK_LO) + bitCount(matchHi & MASK_HI)) / SIZED; - } - public Grid toGrid() { return new Grid(new byte[SIZE], lo, hi); } - public void forEachSlot(SlotVisitor visitor) { - for (var l = lo & ~rlo & vlo; l != X; l &= l - 1) processSlot(this, visitor, Slot.packSlotKey(numberOfTrailingZeros(l), 1)); - for (var l = lo & ~rlo & ~vlo; l != X; l &= l - 1) processSlot(this, visitor, Slot.packSlotKey(numberOfTrailingZeros(l), 0)); - for (var l = lo & rlo & ~vlo; l != X; l &= l - 1) processSlotRev(this, visitor, Slot.packSlotKey(numberOfTrailingZeros(l), 2)); - for (var l = lo & rlo & vlo; l != X; l &= l - 1) processSlotRev(this, visitor, Slot.packSlotKey(numberOfTrailingZeros(l), 3)); - for (var h = hi & ~rhi & vhi; h != X; h &= h - 1) processSlot(this, visitor, Slot.packSlotKey(64 | numberOfTrailingZeros(h), 1)); - for (var h = hi & ~rhi & ~vhi; h != X; h &= h - 1) processSlot(this, visitor, Slot.packSlotKey(64 | numberOfTrailingZeros(h), 0)); - for (var h = hi & rhi & ~vhi; h != X; h &= h - 1) processSlotRev(this, visitor, Slot.packSlotKey((64 | numberOfTrailingZeros(h)), 2)); - for (var h = hi & rhi & vhi; h != X; h &= h - 1) processSlotRev(this, visitor, Slot.packSlotKey((64 | numberOfTrailingZeros(h)), 3)); - } - public Clues from(Clues best) { - lo = best.lo; - hi = best.hi; - vlo = best.vlo; - vhi = best.vhi; - rlo = best.rlo; - rhi = best.rhi; - return this; - } + public final byte[] g; + public long lo, hi; + public static int offset(int r, int c) { return r | (c << 3); } } - @AllArgsConstructor - static class Grid { - - final byte[] g; - public long lo, hi; - static int offset(int r, int c) { return r | (c << 3); } - } - - static record DictEntry(long[] words, long[][] posBitsets, int length, int numlong) { } + public static record DictEntry(long[] words, long[][] posBitsets, int length, int numlong) { } public static interface Lemma { @@ -343,399 +236,15 @@ public record SwedishGenerator(Rng rng, int[] stack, Clues cache) { for (var n = 1; n < arr.length; n++) if (arr[n].assign.w != X) k++; return k; } + public static boolean increasing(int dir) { return (dir & 2) == 0; } } - static record Slot(int key, long lo, long hi, DictEntry entry) { - - static final int BIT_FOR_DIR = 2; - static Slot from(int key, long lo, long hi, DictEntry entry) { return new Slot(key, lo, hi, entry); } - public static int length(long lo, long hi) { return bitCount(lo) + bitCount(hi); } - public static int clueIndex(int key) { return key >>> BIT_FOR_DIR; } - public static int dir(int key) { return key & 3; } - public static boolean increasing(int dir) { return (dir & 2) == 0; } - public IntStream walk() { return Gridded.walk((byte) key, lo, hi); } - public static boolean horiz(int d) { return (d & 1) != 0; } - public static int packSlotKey(int idx, int d) { return (idx << BIT_FOR_DIR) | d; } - } - // slice ray to stop before first clue, depending on direction monotonicity - // right/down => increasing indices; up/left => decreasing indices - // first clue is highest index among hits (hi first, then lo) - private static void processSlotRev(Clues c, SlotVisitor visitor, int key) { - long rayLo = PATH_LO[key]; - long rayHi = PATH_HI[key]; - // only consider clue cells - long hitsLo = rayLo & c.lo; - long hitsHi = rayHi & c.hi; - - if (hitsHi != X) { - int msb = 63 - numberOfLeadingZeros(hitsHi); - long stop = 1L << msb; - rayHi &= ~((stop << 1) - 1); // keep bits > stop - rayLo = 0; // lo indices are below stop - } else if (hitsLo != X) { - int msb = 63 - numberOfLeadingZeros(hitsLo); - long stop = 1L << msb; - rayLo &= ~((stop << 1) - 1); - } - - visitor.visit(key, rayLo, rayHi); - } - private static void processSlot(Clues c, SlotVisitor visitor, int key) { - long rayLo = PATH_LO[key]; - long rayHi = PATH_HI[key]; - long hitsLo = rayLo & c.lo; - long hitsHi = rayHi & c.hi; - - if (hitsLo != X) { - long stop = 1L << numberOfTrailingZeros(hitsLo); - rayLo &= (stop - 1); - rayHi = 0; // any hi is beyond the stop - } else if (hitsHi != X) { - long stop = 1L << numberOfTrailingZeros(hitsHi); - // keep all lo (lo indices are < any hi index), but cut hi below stop - rayHi &= (stop - 1); - } - - visitor.visit(key, rayLo, rayHi); - } - - static Slot[] extractSlots(Clues grid, DictEntry[] index) { - var slots = new Slot[grid.clueCount()]; - int[] N = new int[]{ 0 }; - grid.forEachSlot((key, lo, hi) -> slots[N[0]++] = Slot.from(key, lo, hi, index[Slot.length(lo, hi)])); - return slots; - } - - /// does not modify the grid - long maskFitness(final Clues grid, int clueSize) { - - long cHLo = 0L, cHHi = 0L, cVLo = 0L, cVHi = 0L; - long lo_cl = grid.lo, hi_cl = grid.hi; - long penalty = (((long) Math.abs(grid.clueCount() - clueSize)) * 16000L); - boolean hasSlots = false; - - for (long bits = lo_cl; bits != X; bits &= bits - 1) { - long lsb = bits & -bits; - int clueIdx = numberOfTrailingZeros(lsb); - int v = (grid.vlo & lsb) != 0 ? 1 : 0; - int r = (grid.rlo & lsb) != 0 ? 1 : 0; - int key = Slot.packSlotKey(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 != X) { - rLo &= ((1L << numberOfTrailingZeros(hLo)) - 1); - rHi = 0; - } else if (hHi != X) { rHi &= ((1L << numberOfTrailingZeros(hHi)) - 1); } - } else { - if (hHi != X) { - int msb = 63 - numberOfLeadingZeros(hHi); - rHi &= ~((1L << msb << 1) - 1); - rLo = 0; - } else if (hLo != X) { - int msb = 63 - numberOfLeadingZeros(hLo); - rLo &= ~((1L << msb << 1) - 1); - } - } - if ((rLo | rHi) != X) { - hasSlots = true; - if (Slot.horiz(key)) { - cHLo |= rLo; - cHHi |= rHi; - } else { - cVLo |= rLo; - cVHi |= rHi; - } - if ((bitCount(rLo) + bitCount(rHi)) < MIN_LEN) penalty += 8000; - } else { - penalty += 25000; - } - } - for (long bits = hi_cl; bits != X; bits &= bits - 1) { - long lsb = bits & -bits; - int clueIdx = numberOfTrailingZeros(lsb); - int v = (grid.vhi & lsb) != 0 ? 1 : 0; - int r = (grid.rhi & lsb) != 0 ? 1 : 0; - int key = Slot.packSlotKey(64 | 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 != X) { - rLo &= ((1L << numberOfTrailingZeros(hLo)) - 1); - rHi = 0; - } else if (hHi != X) { rHi &= ((1L << numberOfTrailingZeros(hHi)) - 1); } - } else { - if (hHi != X) { - int msb = 63 - numberOfLeadingZeros(hHi); - rHi &= ~((1L << msb << 1) - 1); - rLo = 0; - } else if (hLo != X) { - int msb = 63 - numberOfLeadingZeros(hLo); - rLo &= ~((1L << msb << 1) - 1); - } - } - if ((rLo | rHi) != X) { - hasSlots = true; - if (Slot.horiz(key)) { - cHLo |= rLo; - cHHi |= rHi; - } else { - cVLo |= rLo; - cVHi |= rHi; - } - if ((bitCount(rLo) + bitCount(rHi)) < MIN_LEN) penalty += 8000; - } else { - penalty += 25000; - } - } - - if (!hasSlots) return 1_000_000_000L; - long seenLo = X, seenHi = X; - -// loop over beide helften - 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, nLo, nHi; bits != X; bits &= bits - 1) { - int clueIdx = base | numberOfTrailingZeros(bits); - - // start nieuwe component - size = 0; - stack[0] = clueIdx; - sp = 1; - - // mark seen - if ((clueIdx & 64) == 0) seenLo |= 1L << clueIdx; - else seenHi |= 1L << (clueIdx & 63); - - // flood fill / bfs - while (sp > 0) { - cur = stack[--sp]; - size++; - - // neighbors als 2x long masks - nLo = NBR8_PACKED_LO[cur]; - nHi = NBR8_PACKED_HI[cur]; - - // filter: alleen clues, en nog niet seen - nLo &= lo_cl & ~seenLo; - nHi &= hi_cl & ~seenHi; - - // push lo-neighbors - while (nLo != X) { - long lsb = nLo & -nLo; - int nidx = numberOfTrailingZeros(nLo); // 0..63 - seenLo |= lsb; - - stack[sp++] = nidx; - - nLo &= nLo - 1; - } - - // push hi-neighbors - while (nHi != X) { - long lsb = nHi & -nHi; - int nidx = 64 | numberOfTrailingZeros(nHi); // 64..127 - seenHi |= lsb; - - stack[sp++] = nidx; - - nHi &= nHi - 1; - } - } - - if (size >= 2) penalty += (size - 1L) * 120L; - } - } - - for (long bits = ~lo_cl; bits != X; bits &= bits - 1) { - int clueIdx = numberOfTrailingZeros(bits); - var rci = IT[clueIdx]; - if ((4 - rci.nbrCount()) + bitCount(rci.n1() & lo_cl) + bitCount(rci.n2() & hi_cl) >= 3) penalty += 400; - boolean h = (cHLo & (1L << clueIdx)) != X; - boolean v = (cVLo & (1L << clueIdx)) != X; - if (!h && !v) penalty += 1500; - else if (h && v) { /* ok */ } else if (h | v) penalty += 200; - else penalty += 600; - } - for (long bits = ~hi_cl & 0xFFL; bits != X; bits &= bits - 1) { - int clueIdx = numberOfTrailingZeros(bits); - var rci = IT[64 | clueIdx]; - if ((4 - rci.nbrCount()) + bitCount(rci.n1() & lo_cl) + bitCount(rci.n2() & hi_cl) >= 3) penalty += 400; - boolean h = (cHHi & (1L << clueIdx)) != X; - boolean v = (cVHi & (1L << clueIdx)) != X; - if (!h && !v) penalty += 1500; - else if (h && v) { /* ok */ } else if (h | v) penalty += 200; - else penalty += 600; - } - - return penalty; - } - - Clues randomMask(final int clueSize) { - var g = Clues.createEmpty(); - for (int placed = 0, guard = 0, ri; placed < clueSize && guard < 4000; guard++) { - - 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; - } - static boolean isLo(int n) { return (n & 64) == 0; } - Clues mutate(Clues c) { - var bytes = MUTATE_RI[rng.randint0_SIZE()]; - for (int k = 0, ri; k < 4; k++) { - ri = bytes[rng.randint0_624()]; - 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 theta = rng.nextFloat() * Math.PI; - var nc = Math.cos(theta); - var nr = Math.sin(theta); - - 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) == 0) maskLo |= (1L << i); - else maskHi |= (1L << (i - 64)); - } - } - var c = 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 l = c.lo & ~c.rlo & ~c.vlo; l != X; l &= l - 1) clearCluesLo(c, numberOfTrailingZeros(l), 0); - for (var l = c.lo & ~c.rlo & c.vlo; l != X; l &= l - 1) clearCluesLo(c, numberOfTrailingZeros(l), 1); - for (var l = c.lo & c.rlo & ~c.vlo; l != X; l &= l - 1) clearCluesLo(c, numberOfTrailingZeros(l), 2); - for (var l = c.lo & c.rlo & c.vlo; l != X; l &= l - 1) clearCluesLo(c, numberOfTrailingZeros(l), 3); - for (var h = c.hi & ~c.rhi & ~c.vhi; h != X; h &= h - 1) clearCluesHi(c, numberOfTrailingZeros(h), 0); - for (var h = c.hi & ~c.rhi & c.vhi; h != X; h &= h - 1) clearCluesHi(c, numberOfTrailingZeros(h), 1); - for (var h = c.hi & c.rhi & ~c.vhi; h != X; h &= h - 1) clearCluesHi(c, (numberOfTrailingZeros(h)), 2); - for (var h = c.hi & c.rhi & ~c.vhi; h != X; h &= h - 1) clearCluesHi(c, (numberOfTrailingZeros(h)), 3); - return c; - } - public static void clearCluesLo(Clues out, int idx, int d) { if (!out.hasRoomForClue(OFFSETS_D_IDX[Slot.packSlotKey(idx, d)])) out.clearClueLo(~(1L << idx)); } - public static void clearCluesHi(Clues out, int idx, int d) { if (!out.hasRoomForClue(OFFSETS_D_IDX[Slot.packSlotKey(64 | idx, d)])) out.clearClueHi(~(1L << idx)); } - - Clues hillclimb(Clues start, int clue_size, int limit) { - var best = start; - var bestF = maskFitness(best, clue_size); - var fails = 0; - - while (fails < limit) { - cache.from(best); - var cand = mutate(best); - var f = maskFitness(cand, clue_size); - if (f < bestF) { - best = cand; - bestF = f; - fails = 0; - } else { - best.from(cache); - fails++; - } - } - return best; - } - - public Clues generateMask(int clueSize, int popSize, int gens, int offspring) { - class GridAndFit { - - Clues grid; - long fite = -1; - GridAndFit(Clues grid) { this.grid = grid; } - long fit() { - if (fite == -1) this.fite = maskFitness(grid, clueSize); - return this.fite; - } - } - if (Main.VERBOSE) System.out.println("generateMask init pop: " + popSize + " clueSize: " + clueSize); - var pop = new ArrayList(); - for (var i = 0; i < popSize; i++) { - if (Thread.currentThread().isInterrupted()) return null; - pop.add(new GridAndFit(hillclimb(randomMask(clueSize), clueSize, 180))); - } - - for (var gen = 0; gen < gens; gen++) { - if (Thread.currentThread().isInterrupted()) break; - var children = new ArrayList(); - - for (var k = 0; k < offspring; k++) { - if (Thread.currentThread().isInterrupted()) break; - var p1 = pop.get(rng.randint(pop.size() - 1)); - var p2 = pop.get(rng.randint(pop.size() - 1)); - var child = crossover(p1.grid, p2.grid); - children.add(new GridAndFit(hillclimb(child, clueSize, 70))); - } - - pop.addAll(children); - pop.sort(Comparator.comparingLong(GridAndFit::fit)); - - var next = new ArrayList(); - for (var cand : pop) { - if (next.size() >= offspring) break; - var ok = true; - for (var kept : next) { - if (cand.grid.similarity(kept.grid) > 0.92) { - ok = false; - break; - } - } - if (ok) next.add(cand); - } - pop = next; - - if (Main.VERBOSE && (gen & 15) == 15) System.out.println(" gen " + gen + "/" + gens + " bestFitness=" + pop.get(0).fit()); - } - if (pop.isEmpty()) return null; - GridAndFit best = pop.get(0); - for (int i = 1; i < pop.size(); i++) { - var x = pop.get(i); - if (x.fit() < best.fit()) best = x; - } - return best.grid; - } - static long patternForSlot(final long glo, final long ghi, final byte[] g, final int key, final long lo, final long hi) { + public static boolean isLo(int n) { return (n & 64) == 0; } + public static long patternForSlot(final long glo, final long ghi, final byte[] g, final int key, final long lo, final long hi) { if (((lo & glo) | (hi & ghi)) == X) return 0; long p = 0; int n = 0; - if (Slot.increasing(key)) { + if (Slotinfo.increasing(key)) { for (long b = lo & glo; b != X; b &= b - 1) { int idx = numberOfTrailingZeros(b); int i = bitCount(lo & ((1L << idx) - 1)); @@ -762,15 +271,9 @@ public record SwedishGenerator(Rng rng, int[] stack, Clues cache) { } return p; } - static int slotScore(byte[] count, long lo, long hi) { - int cross = 0; - for (long b = lo; b != X; b &= b - 1) cross += (count[numberOfTrailingZeros(b)] - 1); - for (long b = hi; b != X; b &= b - 1) cross += (count[64 | numberOfTrailingZeros(b)] - 1); - return cross * 10 + Slot.length(lo, hi); - } - static boolean placeWord(final Grid grid, final byte[] g, final int key, final long lo, final long hi, final long w) { + public static boolean placeWord(final Grid grid, final byte[] g, final int key, final long lo, final long hi, final long w) { final long glo = grid.lo, ghi = grid.hi; - if (Slot.increasing(key)) { + if (Slotinfo.increasing(key)) { for (long b = lo & glo; b != X; b &= b - 1) { int idx = numberOfTrailingZeros(b); if (g[idx] != Lemma.byteAt(w, bitCount(lo & ((1L << idx) - 1)))) return false; @@ -823,7 +326,7 @@ public record SwedishGenerator(Rng rng, int[] stack, Clues cache) { } /// pattern cannot be X - static int[] candidateInfoForPattern(long[] res, long pattern, long[][] posBitsets, int numLongs) { + public static int[] candidateInfoForPattern(long[] res, long pattern, long[][] posBitsets, int numLongs) { System.arraycopy(posBitsets[(int) (pattern & 0xFF) - 1], 0, res, 0, numLongs); for (long p = pattern >>> 8; p != X; p >>>= 8) { long[] bs = posBitsets[(int) (p & 0xFF) - 1]; @@ -841,7 +344,7 @@ public record SwedishGenerator(Rng rng, int[] stack, Clues cache) { return indices; } /// pattern cannot be X - static int candidateCountForPattern(final long[] res, final long pattern, final long[][] posBitsets, final int numLongs) { + public static int candidateCountForPattern(final long[] res, final long pattern, final long[][] posBitsets, final int numLongs) { System.arraycopy(posBitsets[(int) (pattern & 0xFF) - 1], 0, res, 0, numLongs); for (long p = pattern >>> 8; p != X; p >>>= 8) { long[] bs = posBitsets[(int) (p & 0xFF) - 1]; @@ -853,20 +356,6 @@ public record SwedishGenerator(Rng rng, int[] stack, Clues cache) { return count; } - static Slotinfo[] scoreSlots(int[] slotScores, Slot[] slots) { - val count = new byte[SIZE]; - Slotinfo[] slotInfo = new Slotinfo[slots.length]; - for (var s : slots) { - for (long b = s.lo; b != X; b &= b - 1) count[numberOfTrailingZeros(b)]++; - for (long b = s.hi; b != X; b &= b - 1) count[64 | numberOfTrailingZeros(b)]++; - } - for (int i = 0; i < slots.length; i++) { - var slot = slots[i]; - slotScores[i] = slotScore(count, slot.lo, slot.hi); - slotInfo[i] = new Slotinfo(slot.key, slot.lo, slot.hi, slotScores[i], new Assign(), slot.entry); - } - return slotInfo; - } public static FillResult fillMask(final Rng rng, final Slotinfo[] slots, final Grid grid, final boolean multiThreaded) { @@ -907,7 +396,7 @@ public record SwedishGenerator(Rng rng, int[] stack, Clues cache) { } boolean placeWord(final int key, final long lo, final long hi, final long w) { int idx; - if (Slot.increasing(key)) { + if (Slotinfo.increasing(key)) { for (long b = lo & glo; b != X; b &= b - 1) if (g[idx = numberOfTrailingZeros(b)] != Lemma.byteAt(w, bitCount(lo & ((1L << idx) - 1)))) return false; int bcLo = bitCount(lo); for (long b = hi & ghi; b != X; b &= b - 1) @@ -1059,7 +548,7 @@ public record SwedishGenerator(Rng rng, int[] stack, Clues cache) { // final progress line grid.lo = solver.glo; grid.hi = solver.ghi; - var res = new FillResult(ok, new Gridded(grid), + var res = new FillResult(ok, new FillStats(solver.nodes, solver.backtracks, (System.currentTimeMillis() - t0) / 1000.0, solver.lastMRV)); if (!multiThreaded) { System.out.print("\r" + Strings.padRight("", 120) + "\r"); diff --git a/src/test/java/puzzle/ExportFormatTest.java b/src/test/java/puzzle/ExportFormatTest.java index e89f0c1..d142811 100644 --- a/src/test/java/puzzle/ExportFormatTest.java +++ b/src/test/java/puzzle/ExportFormatTest.java @@ -10,7 +10,6 @@ import puzzle.Export.PuzzleResult; import puzzle.Export.Rewards; import puzzle.SwedishGenerator.Assign; import puzzle.SwedishGenerator.FillResult; -import puzzle.SwedishGenerator.Rng; import puzzle.SwedishGenerator.Slotinfo; import puzzle.SwedishGeneratorTest.Idx; import java.io.IOException; @@ -20,17 +19,15 @@ 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.Masker.Clues; import static puzzle.SwedishGenerator.FillStats; import static puzzle.SwedishGenerator.R; -import static puzzle.SwedishGenerator.STACK_SIZE; -import static puzzle.SwedishGenerator.Slot; +import static puzzle.Masker.Slot; import static puzzle.SwedishGenerator.placeWord; import static puzzle.SwedishGeneratorTest.OFF_0_1; import static puzzle.SwedishGeneratorTest.OFF_0_2; import static puzzle.SwedishGeneratorTest.OFF_0_3; import static puzzle.SwedishGeneratorTest.OFF_0_4; -import static puzzle.SwedishGeneratorTest.OFF_0_5; import static puzzle.SwedishGeneratorTest.TEST; public class ExportFormatTest { @@ -56,8 +53,6 @@ public class ExportFormatTest { @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.setClueLo(Idx.IDX_0_0.lo, RIGHT.dir); @@ -72,8 +67,8 @@ public class ExportFormatTest { assertTrue(placeWord(grid.grid(), grid.grid().g, key, lo, 0L, TEST)); - var fillResult = new FillResult(true, grid, new FillStats(0, 0, 0, 0)); - var puzzleResult = new PuzzleResult(new Clued(clues), new Slotinfo[]{ + var fillResult = new FillResult(true, new FillStats(0, 0, 0, 0)); + var puzzleResult = new PuzzleResult(new Clued(clues), grid, new Slotinfo[]{ new Slotinfo(key, lo, 0L, 0, new Assign(TEST), null) }, fillResult); @@ -114,8 +109,8 @@ public class ExportFormatTest { void testExportFormatEmpty() { var grid = SwedishGeneratorTest.createEmpty(); val clues = Clues.createEmpty(); - var fillResult = new FillResult(true, new Gridded(grid), new FillStats(0, 0, 0, 0)); - var puzzleResult = new PuzzleResult(new Clued(clues), new Slotinfo[0], fillResult); + var fillResult = new FillResult(true, new FillStats(0, 0, 0, 0)); + var puzzleResult = new PuzzleResult(new Clued(clues), new Gridded(grid), new Slotinfo[0], 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 4a9f46f..5c37ec2 100644 --- a/src/test/java/puzzle/MainTest.java +++ b/src/test/java/puzzle/MainTest.java @@ -10,8 +10,8 @@ import puzzle.Export.LetterVisit.LetterAt; import puzzle.Export.PuzzleResult; import puzzle.Export.Rewards; import puzzle.Main.Opts; +import puzzle.Masker.Clues; import puzzle.SwedishGenerator.Rng; -import puzzle.SwedishGenerator.Slot; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -46,17 +46,17 @@ public class MainTest { @Test void testExtractSlots() { - var clues = Clues.createEmpty(); - val key = Slot.packSlotKey(OFF_0_0, CLUE_RIGHT); + var clues = Masker.Clues.createEmpty(); + val key = Masker.Slot.packSlotKey(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); - var slots = extractSlots(clues, dict.index()); + var slots = Masker.extractSlots(clues, dict.index()); assertEquals(1, slots.length); var s = slots[0]; - assertEquals(8, Slot.length(s.lo(), s.hi())); + assertEquals(8, Masker.Slot.length(s.lo(), s.hi())); var cells = s.walk().toArray(); assertEquals(0, SwedishGenerator.IT[cells[0]].r()); assertEquals(1, SwedishGenerator.IT[cells[0]].c()); @@ -68,14 +68,14 @@ public class MainTest { void testStaticSlotMethods() { // Test static horiz // dir 1 (right) is horizontal - assertTrue(Slot.horiz(1)); + assertTrue(Masker.Slot.horiz(1)); // dir 0 (down) is vertical - assertFalse(Slot.horiz(0)); + assertFalse(Masker.Slot.horiz(0)); } @Test void testForEachSlot() { - var clues = Clues.createEmpty(); + var clues = Masker.Clues.createEmpty(); clues.setClueLo(IDX_0_0.lo, CLUE_RIGHT); var count = new AtomicInteger(0); clues.forEachSlot((key, lo, hi) -> { @@ -88,15 +88,15 @@ public class MainTest { } @Test public void testHoriz() { - assertTrue(Slot.horiz(1)); // Right - assertTrue(Slot.horiz(3)); // Left - assertFalse(Slot.horiz(0)); // Down - assertFalse(Slot.horiz(2)); // Up + assertTrue(Masker.Slot.horiz(1)); // Right + assertTrue(Masker.Slot.horiz(3)); // Left + assertFalse(Masker.Slot.horiz(0)); // Down + assertFalse(Masker.Slot.horiz(2)); // Up } @Test public void testGridBasics() { - var clues = new Clued(Clues.createEmpty()); - val key = Slot.packSlotKey(OFF_2_1, CLUE_UP); + var clues = new Clued(Masker.Clues.createEmpty()); + val key = Masker.Slot.packSlotKey(OFF_2_1, CLUE_UP); clues.setClueLo(IDX_2_1.lo, CLUE_UP); var grid = new Gridded(clues.toGrid()); @@ -130,7 +130,7 @@ public class MainTest { } @Test public void testCluesDeepCopy() { - var clues = new Clued(Clues.createEmpty()); + 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); @@ -148,16 +148,16 @@ public class MainTest { @Test public void testMini() { val idx = IDX_1_1; - var clues = Clues.createEmpty(); + var clues = Masker.Clues.createEmpty(); clues.setClueLo(idx.lo, CLUE_LEFT); Assertions.assertTrue(clues.isClueLo(idx.index)); } @Test void testMaskerCreation() { - var swe = new SwedishGenerator(new Rng(12348), new int[STACK_SIZE], Clues.createEmpty()); - var mask = swe.generateMask(opts.clueSize, opts.pop, opts.gens, opts.offspring); - val clued = new Clued(mask); - val test = clued.gridToString(); + var masker = new Masker(new Rng(12348), new int[STACK_SIZE], Masker.Clues.createEmpty()); + var mask = masker.generateMask(opts.clueSize, opts.pop, opts.gens, opts.offspring); + val clued = new Clued(mask); + val test = clued.gridToString(); val RESULT = "1 \n" + " \n" + " 3\n" + @@ -180,16 +180,18 @@ public class MainTest { 128L, 422762372923520L, 192L); - var slots = extractSlots(mask, dict.index()); - val slotInfo = scoreSlots(new int[slots.length], slots); - var filled = fillMask(rng, slotInfo, mask.toGrid(), false); + var slots = Masker.extractSlots(mask, dict.index()); + val slotInfo = Masker.scoreSlots(new int[slots.length], slots); + var grid = mask.toGrid(); + var filled = fillMask(rng, slotInfo, grid, false); Assertions.assertTrue(filled.ok(), "Puzzle generation failed (not ok)"); Assertions.assertEquals(17, Slotinfo.wordCount(0, slotInfo), "Number of assigned words changed"); Assertions.assertEquals("POENIGE", Lemma.asWord(slotInfo[0].assign().w)); - Assertions.assertEquals(-1L, filled.grid().grid().lo); - Assertions.assertEquals(255L, filled.grid().grid().hi); - filled.grid().gridToString(mask); - var aa = new PuzzleResult(new Clued(mask), slotInfo, filled).exportFormatFromFilled(1, new Rewards(1, 1, 1)); + Assertions.assertEquals(-1L, grid.lo); + Assertions.assertEquals(255L, grid.hi); + var g = new Gridded(grid); + g.gridToString(mask); + var aa = new PuzzleResult(new Clued(mask), g, slotInfo, filled).exportFormatFromFilled(1, new Rewards(1, 1, 1)); } @Test @@ -205,9 +207,9 @@ public class MainTest { System.out.println("[DEBUG_LOG] Simplicity: " + res.filled().stats().simplicity); System.out.println("[DEBUG_LOG] ClueMap Size: " + Slotinfo.wordCount(0, res.slots())); System.out.println("[DEBUG_LOG] Grid:"); - System.out.println(res.filled().grid().renderHuman(res.clues().c())); - System.out.println(res.filled().grid().gridToString(res.clues().c())); - System.out.println(res.filled().grid().renderHuman(res.clues().c())); + System.out.println(res.grid().renderHuman(res.clues().c())); + System.out.println(res.grid().gridToString(res.clues().c())); + System.out.println(res.grid().renderHuman(res.clues().c())); break; } } diff --git a/src/test/java/puzzle/SwedishGeneratorTest.java b/src/test/java/puzzle/SwedishGeneratorTest.java index cdeefca..0ae5687 100644 --- a/src/test/java/puzzle/SwedishGeneratorTest.java +++ b/src/test/java/puzzle/SwedishGeneratorTest.java @@ -138,8 +138,8 @@ public class SwedishGeneratorTest { @Test void testPatternForSlotAllLetters() { var grid = new Gridded(createEmpty()); - var key = Slot.packSlotKey(OFF_0_0, CLUE_RIGHT); - val clues = Clues.createEmpty(); + var key = Masker.Slot.packSlotKey(OFF_0_0, CLUE_RIGHT); + val clues = Masker.Clues.createEmpty(); 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)); @@ -151,9 +151,9 @@ public class SwedishGeneratorTest { @Test void testPatternForSlotMixed() { var grid = createEmpty(); - placeWord(grid, grid.g, Slot.packSlotKey(0, CLUE_RIGHT), 1L << OFF_0_0, 0, Lemma.from(0, "A")); - placeWord(grid, grid.g, Slot.packSlotKey(0, CLUE_RIGHT), 1L << OFF_2_0, 0, Lemma.from(0, "C")); - var key = Slot.packSlotKey(OFF_1_0, CLUE_RIGHT); + placeWord(grid, grid.g, Masker.Slot.packSlotKey(0, CLUE_RIGHT), 1L << OFF_0_0, 0, Lemma.from(0, "A")); + placeWord(grid, grid.g, Masker.Slot.packSlotKey(0, CLUE_RIGHT), 1L << OFF_2_0, 0, Lemma.from(0, "C")); + var key = Masker.Slot.packSlotKey(OFF_1_0, CLUE_RIGHT); var pattern = patternForSlot(grid.lo, grid.hi, grid.g, key, 7L, 0L); assertEquals(14081L, pattern); } @@ -161,7 +161,7 @@ public class SwedishGeneratorTest { @Test void testPatternForSlotAllDashes() { var grid = createEmpty(); - var key = Slot.packSlotKey(1 << Slot.BIT_FOR_DIR, CLUE_RIGHT); + var key = Masker.Slot.packSlotKey(1 << Masker.Slot.BIT_FOR_DIR, CLUE_RIGHT); var pattern = patternForSlot(grid.lo, grid.hi, grid.g, key, 7L, 0L); assertEquals(0L, pattern); } @@ -169,8 +169,8 @@ public class SwedishGeneratorTest { @Test void testPatternForSlotSingleLetter() { var grid = createEmpty(); - placeWord(grid, grid.g, Slot.packSlotKey(0, CLUE_RIGHT), 1L << OFF_0_0, 0, Lemma.from(0, "A")); - var key = Slot.packSlotKey(1, CLUE_RIGHT); + placeWord(grid, grid.g, Masker.Slot.packSlotKey(0, CLUE_RIGHT), 1L << OFF_0_0, 0, Lemma.from(0, "A")); + var key = Masker.Slot.packSlotKey(1, CLUE_RIGHT); var pattern = patternForSlot(grid.lo, grid.hi, grid.g, key, 7L, 0L); assertEquals(1L, pattern); } @@ -195,8 +195,8 @@ public class SwedishGeneratorTest { @Test void testGrid() { var grid = new Gridded(createEmpty()); - placeWord(grid.grid(), grid.grid().g, Slot.packSlotKey(0, CLUE_RIGHT), 1L << OFF_0_0, 0, Lemma.from(0, "A")); - val arr = grid.stream(Clues.createEmpty()).collect(Collectors.toMap(LetterAt::index, LetterAt::letter)); + placeWord(grid.grid(), grid.grid().g, Masker.Slot.packSlotKey(0, CLUE_RIGHT), 1L << OFF_0_0, 0, Lemma.from(0, "A")); + val arr = grid.stream(Masker.Clues.createEmpty()).collect(Collectors.toMap(LetterAt::index, LetterAt::letter)); assertEquals(1, arr.size()); assertEquals(LETTER_A, arr.get(OFF_0_0)); } @@ -231,11 +231,11 @@ public class SwedishGeneratorTest { @Test void testSlot() { - System.out.println("[DEBUG_LOG] Slot.BIT_FOR_DIR = " + Slot.BIT_FOR_DIR); + System.out.println("[DEBUG_LOG] Slot.BIT_FOR_DIR = " + Masker.Slot.BIT_FOR_DIR); // key = (r << 8) | (c << 4) | d var offset = OFF_2_3; System.out.println("[DEBUG_LOG] Grid.offset(2, 3) = " + offset); - var key = Slot.packSlotKey(offset, CLUE_DOWN); + var key = Masker.Slot.packSlotKey(offset, CLUE_DOWN); System.out.println("[DEBUG_LOG] key = " + key); long lo = 0; // pos 0: (2, 5) @@ -245,10 +245,10 @@ public class SwedishGeneratorTest { // pos 2: (4, 5) lo |= 1L << OFF_4_5; - System.out.println("[DEBUG_LOG] s.dir() = " + Slot.dir(key)); - assertEquals(OFF_2_3, Slot.clueIndex(key)); - assertEquals(CLUE_DOWN, Slot.dir(key)); - assertFalse(Slot.horiz(key)); + System.out.println("[DEBUG_LOG] s.dir() = " + Masker.Slot.dir(key)); + assertEquals(OFF_2_3, Masker.Slot.clueIndex(key)); + assertEquals(CLUE_DOWN, Masker.Slot.dir(key)); + assertFalse(Masker.Slot.horiz(key)); var cells = Gridded.walk((byte) key, lo, 0L).toArray(); assertEquals(2, SwedishGenerator.IT[cells[0]].r()); assertEquals(3, SwedishGenerator.IT[cells[1]].r()); @@ -257,8 +257,8 @@ public class SwedishGeneratorTest { assertEquals(5, SwedishGenerator.IT[cells[1]].c()); assertEquals(5, SwedishGenerator.IT[cells[2]].c()); - assertTrue(Slot.horiz(CLUE_RIGHT)); // right - assertFalse(Slot.horiz(CLUE_DOWN)); // down + assertTrue(Masker.Slot.horiz(CLUE_RIGHT)); // right + assertFalse(Masker.Slot.horiz(CLUE_DOWN)); // down } static long packPattern(String s) { @@ -287,22 +287,22 @@ public class SwedishGeneratorTest { @Test void testForEachSlotAndExtractSlots() { // This should detect a slot starting at 0,1 with length 2 (0,1 and 0,2) - var clues = Clues.createEmpty(); + var clues = Masker.Clues.createEmpty(); clues.setClueLo(IDX_0_0.lo, CLUE_RIGHT); var dict = new Dict(WORDS2); - var slots = extractSlots(clues, dict.index()); + var slots = Masker.extractSlots(clues, dict.index()); assertEquals(1, slots.length); var s = slots[0]; - assertTrue(Slot.length(s.lo(), s.hi()) >= 2); - assertEquals(OFF_0_0, Slot.clueIndex(s.key())); - assertEquals(CLUE_RIGHT, Slot.dir(s.key())); + assertTrue(Masker.Slot.length(s.lo(), s.hi()) >= 2); + assertEquals(OFF_0_0, Masker.Slot.clueIndex(s.key())); + assertEquals(CLUE_RIGHT, Masker.Slot.dir(s.key())); } @Test void testMaskFitnessBasic() { - var gen = new SwedishGenerator(new Rng(0), new int[STACK_SIZE], Clues.createEmpty()); - var grid = Clues.createEmpty(); + var gen = new Masker(new Rng(0), new int[STACK_SIZE], Masker.Clues.createEmpty()); + var grid = Masker.Clues.createEmpty(); // Empty grid should have high penalty (no slots) var f1 = gen.maskFitness(grid, 18); assertTrue(f1 >= 1_000_000_000L); @@ -316,7 +316,7 @@ public class SwedishGeneratorTest { @Test void testGeneticAlgorithmComponents() { var rng = new Rng(42); - var gen = new SwedishGenerator(rng, new int[STACK_SIZE], Clues.createEmpty()); + var gen = new Masker(rng, new int[STACK_SIZE], Masker.Clues.createEmpty()); var c1 = new Clued(gen.randomMask(18)); assertNotNull(c1); @@ -335,14 +335,14 @@ public class SwedishGeneratorTest { void testPlaceWord() { var grid = new Gridded(createEmpty()); // Slot at OFF_0_0 length 3, horizontal (right) - var key = Slot.packSlotKey(0, CLUE_RIGHT); + var key = Masker.Slot.packSlotKey(0, CLUE_RIGHT); var lo = (1L << OFF_0_0) | (1L << OFF_0_1) | (1L << OFF_0_2); val hi = 0L; var w1 = ABC; // 1. Successful placement in empty grid assertTrue(placeWord(grid.grid(), grid.grid().g, key, lo, hi, w1)); - var map = grid.stream(Clues.createEmpty()).collect(Collectors.toMap(LetterAt::index, LetterAt::letter)); + var map = grid.stream(Masker.Clues.createEmpty()).collect(Collectors.toMap(LetterAt::index, LetterAt::letter)); assertEquals(3, map.size()); assertEquals(LETTER_A, map.get(OFF_0_0)); assertEquals(LETTER_B, map.get(OFF_0_1)); @@ -353,7 +353,7 @@ public class SwedishGeneratorTest { // 3. Conflict: place "ABD" where "ABC" is assertFalse(placeWord(grid.grid(), grid.grid().g, key, lo, hi, ABD)); // Verify grid is unchanged (still "ABC") - map = grid.stream(Clues.createEmpty()).collect(Collectors.toMap(LetterAt::index, LetterAt::letter)); + map = grid.stream(Masker.Clues.createEmpty()).collect(Collectors.toMap(LetterAt::index, LetterAt::letter)); assertEquals(3, map.size()); assertEquals(LETTER_A, map.get(OFF_0_0)); assertEquals(LETTER_B, map.get(OFF_0_1)); @@ -361,9 +361,9 @@ public class SwedishGeneratorTest { // 4. Partial placement then conflict (rollback) grid = new Gridded(createEmpty()); - placeWord(grid.grid(), grid.grid().g, Slot.packSlotKey(0, CLUE_RIGHT), 1L << OFF_0_2, 0, Lemma.from(0, "X")); // Conflict at the end + placeWord(grid.grid(), grid.grid().g, Masker.Slot.packSlotKey(0, CLUE_RIGHT), 1L << OFF_0_2, 0, Lemma.from(0, "X")); // Conflict at the end assertFalse(placeWord(grid.grid(), grid.grid().g, key, lo, hi, w1)); - map = grid.stream(Clues.createEmpty()).collect(Collectors.toMap(LetterAt::index, LetterAt::letter)); + map = grid.stream(Masker.Clues.createEmpty()).collect(Collectors.toMap(LetterAt::index, LetterAt::letter)); assertEquals(1, map.size()); assertEquals(LETTER_X, map.get(OFF_0_2)); } @@ -372,21 +372,21 @@ public class SwedishGeneratorTest { void testBacktrackingHelpers() { var grid = new Gridded(createEmpty()); // Slot at 0,1 length 2 - var key = Slot.packSlotKey(0, CLUE_RIGHT); + var key = Masker.Slot.packSlotKey(0, CLUE_RIGHT); var lo = (1L << OFF_0_1) | (1L << OFF_0_2); var w = AZ; val low = grid.grid().lo; val top = grid.grid().hi; var placed = placeWord(grid.grid(), grid.grid().g, key, lo, 0L, w); assertTrue(placed); - var map = grid.stream(Clues.createEmpty()).collect(Collectors.toMap(LetterAt::index, LetterAt::letter)); + var map = grid.stream(Masker.Clues.createEmpty()).collect(Collectors.toMap(LetterAt::index, LetterAt::letter)); assertEquals(2, map.size()); assertEquals(LETTER_A, map.get(OFF_0_1)); assertEquals(LETTER_Z, map.get(OFF_0_2)); grid.grid().hi = top; grid.grid().lo = low; - map = grid.stream(Clues.createEmpty()).collect(Collectors.toMap(LetterAt::index, LetterAt::letter)); + 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)); @@ -395,13 +395,13 @@ public class SwedishGeneratorTest { @Test void testInnerWorkings() { // 1. Test Slot.increasing - assertFalse(Slot.increasing(CLUE_LEFT)); // Left - assertTrue(Slot.increasing(CLUE_RIGHT)); // Right - assertTrue(Slot.increasing(CLUE_DOWN)); // Down - assertFalse(Slot.increasing(CLUE_UP)); // Up + assertFalse(Slotinfo.increasing(CLUE_LEFT)); // Left + assertTrue(Slotinfo.increasing(CLUE_RIGHT)); // Right + assertTrue(Slotinfo.increasing(CLUE_DOWN)); // Down + assertFalse(Slotinfo.increasing(CLUE_UP)); // Up - assertTrue(Slot.increasing(Slot.packSlotKey(0, CLUE_RIGHT))); - assertFalse(Slot.increasing(Slot.packSlotKey(0, CLUE_LEFT))); + assertTrue(Slotinfo.increasing(Masker.Slot.packSlotKey(0, CLUE_RIGHT))); + assertFalse(Slotinfo.increasing(Masker.Slot.packSlotKey(0, CLUE_LEFT))); // 2. Test slotScore val counts = new byte[SIZE]; @@ -411,7 +411,7 @@ public class SwedishGeneratorTest { var entry5 = dict.index()[5]; // cross = (counts[1]-1) + (counts[2]-1) = 1 + 2 = 3 // score = 3 * 10 + len(2) = 32 - assertEquals(32, slotScore(counts, (1L << 1) | (1L << 2), 0L)); + assertEquals(32, Masker.slotScore(counts, (1L << 1) | (1L << 2), 0L)); // 3. Test candidateCountForPattern var ctx = Context.get(); @@ -427,8 +427,8 @@ public class SwedishGeneratorTest { @Test void testMaskFitnessDetailed() { - var gen = new SwedishGenerator(new Rng(42), new int[STACK_SIZE], Clues.createEmpty()); - var grid = Clues.createEmpty(); + var gen = new Masker(new Rng(42), new int[STACK_SIZE], Masker.Clues.createEmpty()); + var grid = Masker.Clues.createEmpty(); // Empty grid: huge penalty var fitEmpty = gen.maskFitness(grid, 18); assertTrue(fitEmpty >= 1_000_000_000L);