package puzzle; import module java.base; import anno.GenerateShapedCopies; import anno.Shaped; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.experimental.Accessors; import lombok.val; import precomp.Neighbors9x8; import gen.rci; import static java.lang.Long.*; import static puzzle.SwedishGenerator.*; @GenerateShapedCopies( packageName = "gen", className = "Masker", shapes = { "precomp.Neighbors9x8", "precomp.Neighbors4x3" } ) public final class Masker { @Shaped public static final int SIZE = Neighbors9x8.SIZE; @Shaped public static final rci[] IT = Neighbors9x8.IT; @Shaped public static final long[] PATH_LO = Neighbors9x8.PATH_LO; @Shaped public static final long[] PATH_HI = Neighbors9x8.PATH_HI; @Shaped public static final long MASK_LO = Neighbors9x8.MASK_LO; @Shaped public static final long MASK_HI = Neighbors9x8.MASK_HI;//(1L << (SIZE - 64)) - 1; @Shaped public static final int MIN_LEN = Neighbors9x8.MIN_LEN;//Config.MIN_LEN; @Shaped public static final int C = Neighbors9x8.C; @Shaped public static final int R = Neighbors9x8.R; @Shaped public static final double SIZED = Neighbors9x8.SIZED;// ~18 @Shaped private static final long[] NBR_LO = Neighbors9x8.NBR_LO; @Shaped private static final long[] NBR_HI = Neighbors9x8.NBR_HI; private static final boolean VERBOSE = false; private final int[] activeCIdx = new int[SIZE]; private final long[] activeSLo = new long[SIZE]; private final long[] activeSHi = new long[SIZE]; private final long[] adjLo = new long[SIZE]; private final long[] adjHi = new long[SIZE]; private final int[] rCount = new int[R]; private final int[] cCount = new int[C]; private final Rng rng; private final int[] stack; private final Clues cache; public static final int STACK_SIZE = 128; public Masker(Rng rng, int[] stack, Clues cache) { this.rng = rng; this.stack = stack; this.cache = cache; } public static boolean isLo(int n) { return (n & 64) == 0; } public boolean isValid(Clues c) { return findOffendingClue(c) == -1; } public int findOffendingClue(Clues c) { if (((c.xlo & c.rlo) & c.lo) != X) return numberOfTrailingZeros((c.xlo & c.rlo) & c.lo); if (((c.xhi & c.rhi) & c.hi) != X) return 64 | numberOfTrailingZeros((c.xhi & c.rhi) & c.hi); var num = 0; for (var bits = c.lo; bits != X; bits &= bits - 1) activeCIdx[num++] = numberOfTrailingZeros(bits); for (var bits = c.hi; bits != X; bits &= bits - 1) activeCIdx[num++] = 64 | numberOfTrailingZeros(bits); if (num == 0) return -1; var start = rng.randint0_SIZE() % num; var n = 0; for (var i = 0; i < num; i++) { var idx = activeCIdx[(start + i) % num]; int dir = c.getDir(idx); var key = Slot.packSlotKey(idx, dir); long sLo = PATH_LO[key], sHi = PATH_HI[key]; long hLo = sLo & c.lo, hHi = sHi & c.hi; if (Slotinfo.increasing(key)) { if (hLo != X) { sLo &= (1L << numberOfTrailingZeros(hLo)) - 1; sHi = 0; } else if (hHi != X) { sHi &= (1L << numberOfTrailingZeros(hHi)) - 1; } } else { if (hHi != X) { sHi &= -(1L << (63 - numberOfLeadingZeros(hHi)) << 1); sLo = 0; } else if (hLo != X) { sLo &= -(1L << (63 - numberOfLeadingZeros(hLo)) << 1); } } if (bitCount(sLo) + bitCount(sHi) < MIN_LEN) return idx; for (var j = 0; j < n; j++) if (bitCount(sLo & activeSLo[j]) + bitCount(sHi & activeSHi[j]) > 1) return idx; activeSLo[n] = sLo; activeSHi[n] = sHi; n++; } return -1; } public void cleanup(Clues c) { var guard = 0; while (guard++ < 50) { var offending = findOffendingClue(c); if (offending == -1) break; if ((offending & 64) == 0) c.clearClueLo(~(1L << offending)); else c.clearClueHi(~(1L << (offending & 63))); } } public static final int[][] MUTATE_RI = new int[Neighbors9x8.SIZE][625]; static { for (var i = 0; i < Neighbors9x8.SIZE; i++) { var k = 0; for (var dr1 = -2; dr1 <= 2; dr1++) for (var dr2 = -2; dr2 <= 2; dr2++) for (var dc1 = -2; dc1 <= 2; dc1++) for (var dc2 = -2; dc2 <= 2; dc2++) { val ti = IT[i]; MUTATE_RI[i][k++] = offset(clamp(ti.r() + dr1 + dr2, 0, R - 1), 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) { var rayLo = PATH_LO[key]; var rayHi = PATH_HI[key]; // only consider clue cells var hitsLo = rayLo & c.lo; var hitsHi = rayHi & c.hi; if (hitsHi != X) { var msb = 63 - numberOfLeadingZeros(hitsHi); var stop = 1L << msb; rayHi &= -(stop << 1); // keep bits > stop rayLo = 0; // lo indices are below stop } else if (hitsLo != X) { var msb = 63 - numberOfLeadingZeros(hitsLo); var stop = 1L << msb; rayLo &= -(stop << 1); } if (Long.bitCount(rayLo) + Long.bitCount(rayHi) >= MIN_LEN) visitor.visit(key, rayLo, rayHi); } private static boolean validSlotRev(long lo, long hi, int key) { var rayLo = PATH_LO[key]; var rayHi = PATH_HI[key]; // only consider clue cells var hitsLo = rayLo & lo; var hitsHi = rayHi & hi; if (hitsHi != X) return (Long.bitCount(rayHi & -(1L << 63 - numberOfLeadingZeros(hitsHi) << 1)) >= MIN_LEN); else if (hitsLo != X) return (Long.bitCount(rayLo & -(1L << 63 - numberOfLeadingZeros(hitsLo) << 1)) + Long.bitCount(rayHi) >= MIN_LEN); else return (Long.bitCount(rayLo) + Long.bitCount(rayHi) >= MIN_LEN); } private static boolean validSlot(long lo, long hi, int key) { var rayLo = PATH_LO[key]; var rayHi = PATH_HI[key]; var hitsLo = rayLo & lo; var hitsHi = rayHi & hi; if (hitsLo != X) return (Long.bitCount(rayLo & ((1L << numberOfTrailingZeros(hitsLo)) - 1)) >= MIN_LEN); else if (hitsHi != X) return (Long.bitCount(rayLo) + Long.bitCount(rayHi & ((1L << numberOfTrailingZeros(hitsHi)) - 1)) >= MIN_LEN); else return (Long.bitCount(rayLo) + Long.bitCount(rayHi) >= MIN_LEN); } private static void processSlot(Clues c, SlotVisitor visitor, int key) { var rayLo = PATH_LO[key]; var rayHi = PATH_HI[key]; var hitsLo = rayLo & c.lo; var hitsHi = rayHi & c.hi; if (hitsLo != X) { var stop = 1L << numberOfTrailingZeros(hitsLo); rayLo &= (stop - 1); rayHi = 0; } else if (hitsHi != X) { var stop = 1L << numberOfTrailingZeros(hitsHi); rayHi &= (stop - 1); } if (Long.bitCount(rayLo) + Long.bitCount(rayHi) >= MIN_LEN) visitor.visit(key, rayLo, rayHi); } public static Slot[] extractSlots(Clues c, DictEntry[] index, DictEntry[] rev) { var slots = new ArrayList(c.clueCount()); c.forEachSlot((key, lo, hi) -> slots.add(Slot.from(key, lo, hi, Slotinfo.increasing(key) ? index[Slot.length(lo, hi)] : rev[Slot.length(lo, hi)]))); return slots.toArray(Slot[]::new); } public static Slotinfo[] slots(Clues mask, Dict d) { return slots(mask, d.index(), d.reversed()); } public static Slotinfo[] slots(Clues mask, DictEntry[] index, DictEntry[] rev) { var slots = extractSlots(mask, index, rev); return scoreSlots(slots); } public static Slotinfo[] scoreSlots(Slot[] slots) { val count = new byte[SIZE]; var slotInfo = new Slotinfo[slots.length]; for (var s : slots) { for (var b = s.lo; b != X; b &= b - 1) count[numberOfTrailingZeros(b)]++; for (var b = s.hi; b != X; b &= b - 1) count[64 | numberOfTrailingZeros(b)]++; } for (var i = 0; i < slots.length; i++) { var slot = slots[i]; slotInfo[i] = new Slotinfo(slot.key, slot.lo, slot.hi, slotScore(count, slot.lo, slot.hi), new puzzle.SwedishGenerator.Assign(), slot.entry, Math.min(slot.entry.words().length, MAX_TRIES_PER_SLOT)); } return slotInfo; } public static int slotScore(byte[] count, long lo, long hi) { var cross = 0; for (var b = lo; b != X; b &= b - 1) cross += (count[numberOfTrailingZeros(b)] - 1); for (var b = hi; b != X; b &= b - 1) cross += (count[64 | numberOfTrailingZeros(b)] - 1); return cross * 10 + Slot.length(lo, hi); } public static int clamp(int x, int a, int b) { return Math.max(a, Math.min(b, x)); } public static int offset(int r, int c) { return r | (c << 3); } public long maskFitness(final Clues grid, int clueSize) { long cHLo = 0L, cHHi = 0L, cVLo = 0L, cVHi = 0L; long cHLo2 = 0L, cHHi2 = 0L, cVLo2 = 0L, cVHi2 = 0L; long lo_cl = grid.lo, hi_cl = grid.hi; var penalty = (((long) Math.abs(grid.clueCount() - clueSize)) * 16000L); var hasSlots = false; var numClues = 0; for (var bits = lo_cl; bits != X; bits &= bits - 1) { var lsb = bits & -bits; var clueIdx = numberOfTrailingZeros(lsb); int dir = grid.getDir(clueIdx); var key = Slot.packSlotKey(clueIdx, dir); 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) { var msb = 63 - numberOfLeadingZeros(hHi); rHi &= -(1L << msb << 1); rLo = 0; } else if (hLo != X) { var msb = 63 - numberOfLeadingZeros(hLo); rLo &= -(1L << msb << 1); } activeCIdx[numClues] = clueIdx; activeSLo[numClues] = rLo; activeSHi[numClues] = rHi; numClues++; if ((rLo | rHi) != X) { hasSlots = true; if (Slot.horiz(key)) { cHLo2 |= (cHLo & rLo); cHHi2 |= (cHHi & rHi); cHLo |= rLo; cHHi |= rHi; } else { cVLo2 |= (cVLo & rLo); cVHi2 |= (cVHi & rHi); cVLo |= rLo; cVHi |= rHi; } if ((bitCount(rLo) + bitCount(rHi)) < MIN_LEN) penalty += 8000; var wordLen = bitCount(rLo) + bitCount(rHi); if (wordLen > 6) penalty += (wordLen - 6) * 1000L; } else penalty += 25000; } for (var bits = hi_cl; bits != X; bits &= bits - 1) { var lsb = bits & -bits; var clueIdx = numberOfTrailingZeros(lsb); int dir = grid.getDir(64 | clueIdx); var key = Slot.packSlotKey(64 | clueIdx, dir); 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) { var msb = 63 - numberOfLeadingZeros(hHi); rHi &= -(1L << msb << 1); rLo = 0; } else if (hLo != X) { var msb = 63 - numberOfLeadingZeros(hLo); rLo &= -(1L << msb << 1); } activeCIdx[numClues] = 64 | clueIdx; activeSLo[numClues] = rLo; activeSHi[numClues] = rHi; numClues++; if ((rLo | rHi) != X) { hasSlots = true; if (Slot.horiz(key)) { cHLo2 |= (cHLo & rLo); cHHi2 |= (cHHi & rHi); cHLo |= rLo; cHHi |= rHi; } else { cVLo2 |= (cVLo & rLo); cVHi2 |= (cVHi & rHi); cVLo |= rLo; cVHi |= rHi; } if ((bitCount(rLo) + bitCount(rHi)) < MIN_LEN) penalty += 8000; var wordLen = bitCount(rLo) + bitCount(rHi); if (wordLen > 6) penalty += (wordLen - 6) * 1000L; } else penalty += 25000; } if (!hasSlots) return 1_000_000_000L; Arrays.fill(rCount, 0); Arrays.fill(cCount, 0); for (var i = 0; i < numClues; i++) { var idx = activeCIdx[i]; rCount[idx & 7]++; cCount[idx >> 3]++; } for (var rc : rCount) if (rc < 2) penalty += (2 - rc) * 4000L; for (var cc : cCount) if (cc < 2) penalty += (2 - cc) * 4000L; // Connectiviteitscheck for (var i = 0; i < numClues; i++) { adjLo[i] = 0; adjHi[i] = 0; } for (var i = 0; i < numClues; i++) { for (var j = i + 1; j < numClues; j++) { var connected = false; // 1. Intersectie if (((activeSLo[i] & activeSLo[j]) | (activeSHi[i] & activeSHi[j])) != 0) { connected = true; } if (connected) { if (j < 64) adjLo[i] |= (1L << j); else adjHi[i] |= (1L << (j - 64)); if (i < 64) adjLo[j] |= (1L << i); else adjHi[j] |= (1L << (i - 64)); } } } if (numClues > 0) { var maxReached = 0; long totalReachedLo = 0, totalReachedHi = 0; for (var i = 0; i < numClues; i++) { if (i < 64) { if ((totalReachedLo & (1L << i)) != 0) continue; } else { if ((totalReachedHi & (1L << (i - 64))) != 0) continue; } var currentReachedLo = (i < 64) ? (1L << i) : 0; var currentReachedHi = (i >= 64) ? (1L << (i - 64)) : 0; stack[0] = i; var sp = 1; var count = 0; while (sp > 0) { var cur = stack[--sp]; count++; var nLo = adjLo[cur] & ~currentReachedLo; var nHi = adjHi[cur] & ~currentReachedHi; while (nLo != 0) { var lsb = nLo & -nLo; var idx = numberOfTrailingZeros(lsb); currentReachedLo |= lsb; stack[sp++] = idx; nLo &= ~lsb; } while (nHi != 0) { var lsb = nHi & -nHi; var idx = 64 | numberOfTrailingZeros(lsb); currentReachedHi |= lsb; stack[sp++] = idx; nHi &= ~lsb; } } if (count > maxReached) maxReached = count; totalReachedLo |= currentReachedLo; totalReachedHi |= currentReachedHi; } if (maxReached < numClues) { penalty += (numClues - maxReached) * 4000L; penalty += 20000; } } for (var bits = ~lo_cl & MASK_LO; bits != X; bits &= bits - 1) { var clueIdx = numberOfTrailingZeros(bits); var rci = IT[clueIdx]; if ((4 - rci.nbrCount()) + bitCount(rci.n1() & lo_cl) + bitCount(rci.n2() & hi_cl) >= 3) penalty += 400; var h = (cHLo & (1L << clueIdx)) != X; var v = (cVLo & (1L << clueIdx)) != X; if (!h && !v) penalty += 2000; else if (h && v) { /* ok */ } else if (((h ? cHLo2 : cVLo2) & (1L << clueIdx)) != X) penalty += 1000; else penalty += 1000; } for (var bits = ~hi_cl & MASK_HI; bits != X; bits &= bits - 1) { var 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; var h = (cHHi & (1L << clueIdx)) != X; var v = (cVHi & (1L << clueIdx)) != X; if (!h && !v) penalty += 2000; else if (h && v) { /* ok */ } else if (((h ? cHHi2 : cVHi2) & (1L << clueIdx)) != X) penalty += 1000; else penalty += 1000; } long remLo = lo_cl, remHi = hi_cl; while ((remLo | remHi) != X) { long compLo = 0, compHi = 0; if (remLo != X) compLo = lowestOneBit(remLo); else compHi = lowestOneBit(remHi); long lastLo, lastHi; do { lastLo = compLo; lastHi = compHi; long expandedLo = 0, expandedHi = 0; for (var bits = compLo; bits != X; bits &= bits - 1) { var idx = numberOfTrailingZeros(bits); expandedLo |= NBR_LO[idx]; expandedHi |= NBR_HI[idx]; } for (var bits = compHi; bits != X; bits &= bits - 1) { var idx = 64 | numberOfTrailingZeros(bits); expandedLo |= NBR_LO[idx]; expandedHi |= NBR_HI[idx]; } compLo |= expandedLo & lo_cl; compHi |= expandedHi & hi_cl; } while (compLo != lastLo || compHi != lastHi); var s = bitCount(compLo) + bitCount(compHi); if (s >= 2) penalty += (long) (s - 1) * 520; remLo &= ~compLo; remHi &= ~compHi; } 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.randomClueDir(); var key = Slot.packSlotKey(ri, d_idx); if (g.hasRoomForClue(key)) { g.setClueLo(1L << ri, d_idx); if (isValid(g)) placed++; else g.clearClueLo(~(1L << ri)); } } else { if (g.isClueHi(ri)) continue; var d_idx = rng.randomClueDir(); var key = Slot.packSlotKey(ri, d_idx); if (g.hasRoomForClue(key)) { g.setClueHi(1L << (ri & 63), d_idx); if (isValid(g)) placed++; else g.clearClueHi(~(1L << (ri & 63))); } } } return g; } public Clues mutate(Clues c) { var bytes = MUTATE_RI[rng.randint0_SIZE()]; for (int k = 0, ri; k < 6; k++) { ri = bytes[rng.randint0_624()]; if (c.notClue(ri)) { // ADD var d = rng.randomClueDir(); var key = Slot.packSlotKey(ri, d); if (c.hasRoomForClue(key)) { if (isLo(ri)) { c.setClueLo(1L << ri, d); if (!isValid(c)) c.clearClueLo(~(1L << ri)); else continue; } else { c.setClueHi(1L << (ri & 63), d); if (!isValid(c)) c.clearClueHi(~(1L << (ri & 63))); else continue; } } } else { // HAS CLUE var op = rng.randomClueDir(); if (op < 2) { // REMOVE var oldD = c.getDir(ri); if (isLo(ri)) { c.clearClueLo(~(1L << ri)); if (!isValid(c)) c.setClueLo(1L << ri, oldD); else continue; } else { c.clearClueHi(~(1L << (ri & 63))); if (!isValid(c)) c.setClueHi(1L << (ri & 63), oldD); else continue; } } if (op < 4) { // CHANGE DIRECTION var d = rng.randomClueDir(); var key = Slot.packSlotKey(ri, d); if (c.hasRoomForClue(key)) { var oldD = c.getDir(ri); if (isLo(ri)) { c.setClueLo(1L << ri, d); if (!isValid(c)) c.setClueLo(1L << ri, oldD); else continue; } else { c.setClueHi(1L << (ri & 63), d); if (!isValid(c)) c.setClueHi(1L << (ri & 63), oldD); else continue; } } } // MOVE var nri = bytes[rng.randint0_624()]; if (c.notClue(nri)) { var d = c.getDir(ri); var nkey = Slot.packSlotKey(nri, d); if (c.hasRoomForClue(nkey)) { if (isLo(ri)) c.clearClueLo(~(1L << ri)); else c.clearClueHi(~(1L << (ri & 63))); if (isLo(nri)) c.setClueLo(1L << nri, d); else c.setClueHi(1L << (nri & 63), d); if (!isValid(c)) { if (isLo(nri)) c.clearClueLo(~(1L << nri)); else c.clearClueHi(~(1L << (nri & 63))); if (isLo(ri)) c.setClueLo(1L << ri, d); else c.setClueHi(1L << (ri & 63), d); } else continue; } } } } 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) { var 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), (a.xlo & ~maskLo) | (other.xlo & maskLo), (a.xhi & ~maskHi) | (other.xhi & maskHi)); cleanup(c); return c; } 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 (VERBOSE) System.out.println("generateMask init pop: " + popSize + " clueSize: " + clueSize); var pop = new GridAndFit[popSize]; for (var i = 0; i < popSize; i++) { if (Thread.currentThread().isInterrupted()) return null; pop[i] = new GridAndFit(hillclimb(randomMask(clueSize), clueSize, 180)); } for (var gen = 0; gen < gens; gen++) { if (Thread.currentThread().isInterrupted()) break; var children = new GridAndFit[offspring]; var childCount = 0; for (var k = 0; k < offspring; k++) { if (Thread.currentThread().isInterrupted()) break; var p1 = rng.rand(pop); var p2 = rng.rand(pop); var child = crossover(p1.grid, p2.grid); children[k] = new GridAndFit(hillclimb(child, clueSize, 70)); childCount++; } var combined = new GridAndFit[pop.length + childCount]; System.arraycopy(pop, 0, combined, 0, pop.length); System.arraycopy(children, 0, combined, pop.length, childCount); Arrays.sort(combined, Comparator.comparingLong(GridAndFit::fit)); var next = new GridAndFit[popSize]; var nextCount = 0; for (var cand : combined) { if (nextCount >= popSize) break; var unique = true; for (var i = 0; i < nextCount; i++) { if (cand.grid.similarity(next[i].grid) > 0.92) { unique = false; break; } } if (unique) next[nextCount++] = cand; } if (nextCount < popSize) { for (var cand : combined) { if (nextCount >= popSize) break; var alreadyIn = false; for (var i = 0; i < nextCount; i++) { if (cand == next[i]) { alreadyIn = true; break; } } if (!alreadyIn) next[nextCount++] = cand; } } pop = nextCount == popSize ? next : Arrays.copyOf(next, nextCount); if (VERBOSE && (gen & 15) == 15) System.out.println(" gen " + gen + "/" + gens + " bestFitness=" + pop[0].fit()); } if (pop.length == 0) return null; var best = pop[0]; for (var i = 1; i < pop.length; i++) { var x = pop[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); } //@formatter:on @AllArgsConstructor @Accessors(fluent = true) public static final class Clues { @Getter long lo, hi, vlo, vhi, rlo, rhi, xlo, xhi; public static Clues createEmpty() { return new Clues(0, 0, 0, 0, 0, 0, 0, 0); } public boolean hasRoomForClue(int key) { if (Slotinfo.increasing(key)) if (!validSlot(lo, hi, key)) return false; return validSlotRev(lo, hi, key); } public Clues setClue(precomp.Const9x8.Cell cell) { if ((cell.index & 64) == 0) setClueLo(cell.mask, cell.d); else setClueHi(cell.mask, cell.d); return this; } 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; if ((idx & 4) != 0) xlo |= mask; else xlo &= ~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; if ((idx & 4) != 0) xhi |= mask; else xhi &= ~mask; } public void clearClueLo(long mask) { lo &= mask; vlo &= mask; rlo &= mask; xlo &= mask; } public void clearClueHi(long mask) { hi &= mask; vhi &= mask; rhi &= mask; xhi &= 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(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) { var matchLo = (~(lo ^ b.lo)) & (~lo | (~(vlo ^ b.vlo) & ~(rlo ^ b.rlo) & ~(xlo ^ b.xlo))); var matchHi = (~(hi ^ b.hi)) & (~hi | (~(vhi ^ b.vhi) & ~(rhi ^ b.rhi) & ~(xhi ^ b.xhi))); 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 & ~xlo & ~rlo & vlo; l != X; l &= l - 1) processSlot(this, visitor, Slot.packSlotKey(numberOfTrailingZeros(l), 1)); for (var l = lo & ~xlo & ~rlo & ~vlo; l != X; l &= l - 1) processSlot(this, visitor, Slot.packSlotKey(numberOfTrailingZeros(l), 0)); for (var l = lo & ~xlo & rlo & ~vlo; l != X; l &= l - 1) processSlotRev(this, visitor, Slot.packSlotKey(numberOfTrailingZeros(l), 2)); for (var l = lo & ~xlo & rlo & vlo; l != X; l &= l - 1) processSlotRev(this, visitor, Slot.packSlotKey(numberOfTrailingZeros(l), 3)); for (var l = lo & xlo & ~rlo & ~vlo; l != X; l &= l - 1) processSlot(this, visitor, Slot.packSlotKey(numberOfTrailingZeros(l), 4)); for (var l = lo & xlo & ~rlo & vlo; l != X; l &= l - 1) processSlot(this, visitor, Slot.packSlotKey(numberOfTrailingZeros(l), 5)); for (var h = hi & ~xhi & ~rhi & vhi; h != X; h &= h - 1) processSlot(this, visitor, Slot.packSlotKey(64 | numberOfTrailingZeros(h), 1)); for (var h = hi & ~xhi & ~rhi & ~vhi; h != X; h &= h - 1) processSlot(this, visitor, Slot.packSlotKey(64 | numberOfTrailingZeros(h), 0)); for (var h = hi & ~xhi & rhi & ~vhi; h != X; h &= h - 1) processSlotRev(this, visitor, Slot.packSlotKey((64 | numberOfTrailingZeros(h)), 2)); for (var h = hi & ~xhi & rhi & vhi; h != X; h &= h - 1) processSlotRev(this, visitor, Slot.packSlotKey((64 | numberOfTrailingZeros(h)), 3)); for (var h = hi & xhi & ~rhi & ~vhi; h != X; h &= h - 1) processSlot(this, visitor, Slot.packSlotKey(64 | numberOfTrailingZeros(h), 4)); for (var h = hi & xhi & ~rhi & vhi; h != X; h &= h - 1) processSlot(this, visitor, Slot.packSlotKey(64 | numberOfTrailingZeros(h), 5)); } public Clues from(Clues best) { lo = best.lo; hi = best.hi; vlo = best.vlo; vhi = best.vhi; rlo = best.rlo; rhi = best.rhi; xlo = best.xlo; xhi = best.xhi; return this; } public byte getDir(int index) { if ((index & 64) == 0) { var v = (vlo & (1L << index)) != 0 ? 1 : 0; var r = (rlo & (1L << index)) != 0 ? 1 : 0; var x = (xlo & (1L << index)) != 0 ? 1 : 0; return (byte) ((x << 2) | (r << 1) | v); } else { var v = (vhi & (1L << (index & 63))) != 0 ? 1 : 0; var r = (rhi & (1L << (index & 63))) != 0 ? 1 : 0; var x = (xhi & (1L << (index & 63))) != 0 ? 1 : 0; return (byte) ((x << 2) | (r << 1) | v); } } } public record Slot(int key, long lo, long hi, DictEntry entry) { static final int BIT_FOR_DIR = 3; public 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 & 7; } public static boolean horiz(int d) { return (d == 1) || (d == 3); } public static int packSlotKey(int idx, int d) { return (idx << BIT_FOR_DIR) | d; } } }