757 lines
30 KiB
Java
757 lines
30 KiB
Java
package puzzle;
|
|
|
|
import module java.base;
|
|
import lombok.AllArgsConstructor;
|
|
import lombok.val;
|
|
import precomp.Neighbors9x8;
|
|
import precomp.Neighbors9x8.rci;
|
|
|
|
import static java.lang.Long.*;
|
|
import static puzzle.SwedishGenerator.*;
|
|
|
|
public final class Masker {
|
|
|
|
public static final rci[] IT = Neighbors9x8.IT;
|
|
private final Rng rng;
|
|
private final int[] stack;
|
|
private final Clues cache;
|
|
private final int[] activeCIdx = new int[SwedishGenerator.SIZE];
|
|
private final long[] activeSLo = new long[SwedishGenerator.SIZE];
|
|
private final long[] activeSHi = new long[SwedishGenerator.SIZE];
|
|
private final long[] adjLo = new long[SwedishGenerator.SIZE];
|
|
private final long[] adjHi = new long[SwedishGenerator.SIZE];
|
|
private final int[] rCount = new int[8];
|
|
private final int[] cCount = new int[9];
|
|
private static final long[] NBR_LO = Neighbors9x8.NBR_LO;
|
|
private static final long[] NBR_HI = Neighbors9x8.NBR_HI;
|
|
|
|
public Masker(Rng rng, int[] stack, Clues cache) {
|
|
this.rng = rng;
|
|
this.stack = stack;
|
|
this.cache = cache;
|
|
}
|
|
|
|
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[SwedishGenerator.SIZE][625];
|
|
|
|
static {
|
|
for (var i = 0; i < SwedishGenerator.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)) >= SwedishGenerator.MIN_LEN);
|
|
else if (hitsLo != X) return (Long.bitCount(rayLo & -(1L << 63 - numberOfLeadingZeros(hitsLo) << 1)) + Long.bitCount(rayHi) >= SwedishGenerator.MIN_LEN);
|
|
else return (Long.bitCount(rayLo) + Long.bitCount(rayHi) >= SwedishGenerator.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)) >= SwedishGenerator.MIN_LEN);
|
|
else if (hitsHi != X) return (Long.bitCount(rayLo) + Long.bitCount(rayHi & ((1L << numberOfTrailingZeros(hitsHi)) - 1)) >= SwedishGenerator.MIN_LEN);
|
|
else return (Long.bitCount(rayLo) + Long.bitCount(rayHi) >= SwedishGenerator.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 grid, DictEntry[] index) {
|
|
var slots = new ArrayList<Slot>(grid.clueCount());
|
|
grid.forEachSlot((key, lo, hi) -> slots.add(Slot.from(key, lo, hi, index[Slot.length(lo, hi)])));
|
|
return slots.toArray(Slot[]::new);
|
|
}
|
|
public static Slotinfo[] slots(Clues mask, DictEntry[] index) {
|
|
var slots = Masker.extractSlots(mask, index);
|
|
return Masker.scoreSlots(slots);
|
|
}
|
|
public static Slotinfo[] scoreSlots(Slot[] slots) {
|
|
val count = new byte[SwedishGenerator.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 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 (Main.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 (Main.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
|
|
public static class Clues {
|
|
|
|
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) 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[SwedishGenerator.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; }
|
|
}
|
|
}
|