introduce bitloops
This commit is contained in:
@@ -41,6 +41,7 @@ public record Export() {
|
||||
static final byte CLUE_UP = 2;
|
||||
static final byte CLUE_LEFT = 3;
|
||||
static final byte CLUE_LEFT_TOP = 4;
|
||||
static final byte CLUE_RIGHT_TOP = 5;
|
||||
static int HI(int in) { return in | 64; }
|
||||
static char LETTER(int in) { return (char) (in | 64); }
|
||||
static char CLUE_CHAR(int s) { return (char) (s | 48); }
|
||||
@@ -100,13 +101,15 @@ public record Export() {
|
||||
for (var l = c.lo & ~c.xlo & ~c.rlo & ~c.vlo; l != X; l &= l - 1) stream.accept(new ClueAt(Long.numberOfTrailingZeros(l), DOWN.dir));
|
||||
for (var l = c.lo & ~c.xlo & c.rlo & ~c.vlo; l != X; l &= l - 1) stream.accept(new ClueAt(Long.numberOfTrailingZeros(l), CLUE_UP));
|
||||
for (var l = c.lo & ~c.xlo & c.rlo & c.vlo; l != X; l &= l - 1) stream.accept(new ClueAt(Long.numberOfTrailingZeros(l), CLUE_LEFT));
|
||||
for (var l = c.lo & c.xlo; l != X; l &= l - 1) stream.accept(new ClueAt(Long.numberOfTrailingZeros(l), CLUE_LEFT_TOP));
|
||||
for (var l = c.lo & c.xlo & ~c.rlo & ~c.vlo; l != X; l &= l - 1) stream.accept(new ClueAt(Long.numberOfTrailingZeros(l), CLUE_LEFT_TOP));
|
||||
for (var l = c.lo & c.xlo & ~c.rlo & c.vlo; l != X; l &= l - 1) stream.accept(new ClueAt(Long.numberOfTrailingZeros(l), CLUE_RIGHT_TOP));
|
||||
|
||||
for (var h = c.hi & ~c.xhi & ~c.rhi & c.vhi; h != X; h &= h - 1) stream.accept(new ClueAt(HI(Long.numberOfTrailingZeros(h)), CLUE_RIGHT));
|
||||
for (var h = c.hi & ~c.xhi & ~c.rhi & ~c.vhi; h != X; h &= h - 1) stream.accept(new ClueAt(HI(Long.numberOfTrailingZeros(h)), CLUE_DOWN));
|
||||
for (var h = c.hi & ~c.xhi & c.rhi & ~c.vhi; h != X; h &= h - 1) stream.accept(new ClueAt(HI(Long.numberOfTrailingZeros(h)), CLUE_UP));
|
||||
for (var h = c.hi & ~c.xhi & c.rhi & c.vhi; h != X; h &= h - 1) stream.accept(new ClueAt(HI(Long.numberOfTrailingZeros(h)), CLUE_LEFT));
|
||||
for (var h = c.hi & c.xhi; h != X; h &= h - 1) stream.accept(new ClueAt(HI(Long.numberOfTrailingZeros(h)), CLUE_LEFT_TOP));
|
||||
for (var h = c.hi & c.xhi & ~c.rhi & ~c.vhi; h != X; h &= h - 1) stream.accept(new ClueAt(HI(Long.numberOfTrailingZeros(h)), CLUE_LEFT_TOP));
|
||||
for (var h = c.hi & c.xhi & ~c.rhi & c.vhi; h != X; h &= h - 1) stream.accept(new ClueAt(HI(Long.numberOfTrailingZeros(h)), CLUE_RIGHT_TOP));
|
||||
|
||||
return stream.build();
|
||||
}
|
||||
@@ -223,7 +226,7 @@ public record Export() {
|
||||
|
||||
public static final char HORIZONTAL = 'h';
|
||||
static final char VERTICAL = 'v';
|
||||
static final char[] DIRECTION = { Placed.VERTICAL, Placed.HORIZONTAL, Placed.VERTICAL, Placed.HORIZONTAL, Placed.VERTICAL };
|
||||
static final char[] DIRECTION = { Placed.VERTICAL, Placed.HORIZONTAL, Placed.VERTICAL, Placed.HORIZONTAL, Placed.VERTICAL, Placed.VERTICAL };
|
||||
|
||||
public int arrowCol() { return Masker.IT[Slot.clueIndex(slotKey)].c(); }
|
||||
public int arrowRow() { return Masker.IT[Slot.clueIndex(slotKey)].r(); }
|
||||
|
||||
@@ -34,7 +34,7 @@ public class Main {
|
||||
@NoArgsConstructor
|
||||
public static class Opts {
|
||||
|
||||
static int SSIZE = 24;
|
||||
static int SSIZE = 23;
|
||||
public int seed = (int) (System.nanoTime() ^ System.currentTimeMillis());
|
||||
public int clueSize = SSIZE;
|
||||
public int pop = SSIZE * 2;
|
||||
|
||||
@@ -27,6 +27,20 @@ public final class Masker {
|
||||
this.cache = cache;
|
||||
}
|
||||
|
||||
public boolean isValid(Clues c, int minLen) {
|
||||
return c.isValid(minLen, activeSLo, activeSHi);
|
||||
}
|
||||
|
||||
public void cleanup(Clues c, int minLen) {
|
||||
int guard = 0;
|
||||
while (guard++ < 50) {
|
||||
int offending = c.findOffendingClue(minLen, activeSLo, activeSHi);
|
||||
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 {
|
||||
@@ -105,7 +119,7 @@ public final class Masker {
|
||||
}
|
||||
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, Objects.requireNonNull(index[Slot.length(lo, hi)]))));
|
||||
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) {
|
||||
@@ -121,7 +135,8 @@ public final class Masker {
|
||||
}
|
||||
for (int 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);
|
||||
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;
|
||||
}
|
||||
@@ -141,7 +156,7 @@ public final class Masker {
|
||||
long lo_cl = grid.lo, hi_cl = grid.hi;
|
||||
long penalty = (((long) Math.abs(grid.clueCount() - clueSize)) * 16000L);
|
||||
boolean hasSlots = false;
|
||||
if (!grid.isValid(2)) return 1_000_000_000L;
|
||||
if (!isValid(grid, 2)) return 1_000_000_000L;
|
||||
|
||||
int numClues = 0;
|
||||
for (long bits = lo_cl; bits != X; bits &= bits - 1) {
|
||||
@@ -315,20 +330,20 @@ public final class Masker {
|
||||
ri = rng.randint0_SIZE();
|
||||
if (isLo(ri)) {
|
||||
if (g.isClueLo(ri)) continue;
|
||||
var d_idx = rng.randint2bitByte();
|
||||
var d_idx = rng.randomClueDir();
|
||||
int key = Slot.packSlotKey(ri, d_idx);
|
||||
if (g.hasRoomForClue(key, OFFSETS_D_IDX[key])) {
|
||||
if (g.hasRoomForClue(key)) {
|
||||
g.setClueLo(1L << ri, d_idx);
|
||||
if (g.isValid(MIN_LEN)) placed++;
|
||||
if (isValid(g, MIN_LEN)) placed++;
|
||||
else g.clearClueLo(~(1L << ri));
|
||||
}
|
||||
} else {
|
||||
if (g.isClueHi(ri)) continue;
|
||||
var d_idx = rng.randint2bitByte();
|
||||
var d_idx = rng.randomClueDir();
|
||||
int key = Slot.packSlotKey(ri, d_idx);
|
||||
if (g.hasRoomForClue(key, OFFSETS_D_IDX[key])) {
|
||||
if (g.hasRoomForClue(key)) {
|
||||
g.setClueHi(1L << (ri & 63), d_idx);
|
||||
if (g.isValid(MIN_LEN)) placed++;
|
||||
if (isValid(g, MIN_LEN)) placed++;
|
||||
else g.clearClueHi(~(1L << (ri & 63)));
|
||||
}
|
||||
}
|
||||
@@ -342,33 +357,33 @@ public final class Masker {
|
||||
for (int k = 0, ri; k < 4; k++) {
|
||||
ri = bytes[rng.randint0_624()];
|
||||
if (c.notClue(ri)) { // ADD
|
||||
byte d = rng.randint2bitByte();
|
||||
byte d = rng.randomClueDir();
|
||||
int key = Slot.packSlotKey(ri, d);
|
||||
if (c.hasRoomForClue(key, OFFSETS_D_IDX[key])) {
|
||||
if (c.hasRoomForClue(key)) {
|
||||
if (isLo(ri)) {
|
||||
c.setClueLo(1L << ri, d);
|
||||
if (!c.isValid(MIN_LEN)) c.clearClueLo(~(1L << ri));
|
||||
if (!isValid(c, MIN_LEN)) c.clearClueLo(~(1L << ri));
|
||||
} else {
|
||||
c.setClueHi(1L << (ri & 63), d);
|
||||
if (!c.isValid(MIN_LEN)) c.clearClueHi(~(1L << (ri & 63)));
|
||||
if (!isValid(c, MIN_LEN)) c.clearClueHi(~(1L << (ri & 63)));
|
||||
}
|
||||
}
|
||||
} else { // HAS CLUE
|
||||
int op = rng.randint(10);
|
||||
var op = rng.randomClueDir();
|
||||
if (op < 2) { // REMOVE
|
||||
if (isLo(ri)) c.clearClueLo(~(1L << ri));
|
||||
else c.clearClueHi(~(1L << (ri & 63)));
|
||||
} else if (op < 5) { // CHANGE DIRECTION
|
||||
byte d = rng.randint2bitByte();
|
||||
byte d = rng.randomClueDir();
|
||||
int key = Slot.packSlotKey(ri, d);
|
||||
if (c.hasRoomForClue(key, OFFSETS_D_IDX[key])) {
|
||||
if (c.hasRoomForClue(key)) {
|
||||
byte oldD = c.getDir(ri);
|
||||
if (isLo(ri)) {
|
||||
c.setClueLo(1L << ri, d);
|
||||
if (!c.isValid(MIN_LEN)) c.setClueLo(1L << ri, oldD);
|
||||
if (!isValid(c, MIN_LEN)) c.setClueLo(1L << ri, oldD);
|
||||
} else {
|
||||
c.setClueHi(1L << (ri & 63), d);
|
||||
if (!c.isValid(MIN_LEN)) c.setClueHi(1L << (ri & 63), oldD);
|
||||
if (!isValid(c, MIN_LEN)) c.setClueHi(1L << (ri & 63), oldD);
|
||||
}
|
||||
}
|
||||
} else { // MOVE
|
||||
@@ -376,12 +391,12 @@ public final class Masker {
|
||||
if (c.notClue(nri)) {
|
||||
byte d = c.getDir(ri);
|
||||
int nkey = Slot.packSlotKey(nri, d);
|
||||
if (c.hasRoomForClue(nkey, OFFSETS_D_IDX[nkey])) {
|
||||
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 (!c.isValid(MIN_LEN)) {
|
||||
if (!isValid(c, MIN_LEN)) {
|
||||
if (isLo(nri)) c.clearClueLo(~(1L << nri));
|
||||
else c.clearClueHi(~(1L << (nri & 63)));
|
||||
if (isLo(ri)) c.setClueLo(1L << ri, d);
|
||||
@@ -417,30 +432,10 @@ public final class Masker {
|
||||
(a.rhi & ~maskHi) | (other.rhi & maskHi),
|
||||
(a.xlo & ~maskLo) | (other.xlo & maskLo),
|
||||
(a.xhi & ~maskHi) | (other.xhi & maskHi));
|
||||
int guard = 0;
|
||||
while (!c.isValid(MIN_LEN) && guard++ < 3) {
|
||||
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);
|
||||
}
|
||||
cleanup(c, MIN_LEN);
|
||||
return c;
|
||||
}
|
||||
|
||||
public static void clearCluesLo(Clues out, int idx, int d) {
|
||||
int key = Slot.packSlotKey(idx, d);
|
||||
if (!out.hasRoomForClue(key, OFFSETS_D_IDX[key])) out.clearClueLo(~(1L << idx));
|
||||
}
|
||||
|
||||
public static void clearCluesHi(Clues out, int idx, int d) {
|
||||
int key = Slot.packSlotKey(64 | idx, d);
|
||||
if (!out.hasRoomForClue(key, OFFSETS_D_IDX[key])) out.clearClueHi(~(1L << idx));
|
||||
}
|
||||
|
||||
public Clues hillclimb(Clues start, int clue_size, int limit) {
|
||||
var best = start;
|
||||
var bestF = maskFitness(best, clue_size);
|
||||
@@ -482,8 +477,8 @@ public final class Masker {
|
||||
|
||||
for (int gen = 0; gen < gens; gen++) {
|
||||
if (Thread.currentThread().isInterrupted()) break;
|
||||
GridAndFit[] children = new GridAndFit[offspring];
|
||||
int childCount = 0;
|
||||
GridAndFit[] children = new GridAndFit[offspring];
|
||||
int childCount = 0;
|
||||
for (int k = 0; k < offspring; k++) {
|
||||
if (Thread.currentThread().isInterrupted()) break;
|
||||
GridAndFit p1 = rng.rand(pop);
|
||||
@@ -498,19 +493,34 @@ public final class Masker {
|
||||
System.arraycopy(children, 0, combined, pop.length, childCount);
|
||||
Arrays.sort(combined, Comparator.comparingLong(GridAndFit::fit));
|
||||
|
||||
GridAndFit[] next = new GridAndFit[offspring];
|
||||
int nextCount = 0;
|
||||
GridAndFit[] next = new GridAndFit[popSize];
|
||||
int nextCount = 0;
|
||||
for (GridAndFit cand : combined) {
|
||||
if (nextCount >= offspring) break;
|
||||
boolean ok = true;
|
||||
for (int i = 0; i < nextCount; i++)
|
||||
if (nextCount >= popSize) break;
|
||||
boolean unique = true;
|
||||
for (int i = 0; i < nextCount; i++) {
|
||||
if (cand.grid.similarity(next[i].grid) > 0.92) {
|
||||
ok = false;
|
||||
unique = false;
|
||||
break;
|
||||
}
|
||||
if (ok) next[nextCount++] = cand;
|
||||
}
|
||||
if (unique) next[nextCount++] = cand;
|
||||
}
|
||||
pop = nextCount == offspring ? next : Arrays.copyOf(next, nextCount);
|
||||
|
||||
if (nextCount < popSize) {
|
||||
for (GridAndFit cand : combined) {
|
||||
if (nextCount >= popSize) break;
|
||||
boolean alreadyIn = false;
|
||||
for (int 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());
|
||||
}
|
||||
@@ -531,18 +541,7 @@ public final class Masker {
|
||||
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 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(int key, long packed) {
|
||||
if (packed == X || !notClue(packed & 0x7FL) || !notClue((packed >>> 7) & 0x7FL)) return false;
|
||||
public boolean hasRoomForClue(int key) {
|
||||
if (Slotinfo.increasing(key)) if (!validSlot(lo, hi, MIN_LEN, key)) return false;
|
||||
return validSlotRev(lo, hi, MIN_LEN, key);
|
||||
}
|
||||
@@ -579,7 +578,6 @@ public final class Masker {
|
||||
}
|
||||
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); }
|
||||
@@ -591,18 +589,50 @@ public final class Masker {
|
||||
}
|
||||
public Grid toGrid() { return new Grid(new byte[SwedishGenerator.SIZE], lo, hi); }
|
||||
public boolean isValid(int minLen) {
|
||||
for (var l = lo & ~xlo & ~rlo & vlo; l != X; l &= l - 1) if (!validSlot(lo, hi, minLen, Slot.packSlotKey(numberOfTrailingZeros(l), 1))) return false;
|
||||
for (var l = lo & ~xlo & ~rlo & ~vlo; l != X; l &= l - 1) if (!validSlot(lo, hi, minLen, Slot.packSlotKey(numberOfTrailingZeros(l), 0))) return false;
|
||||
for (var l = lo & ~xlo & rlo & ~vlo; l != X; l &= l - 1) if (!validSlotRev(lo, hi, minLen, Slot.packSlotKey(numberOfTrailingZeros(l), 2))) return false;
|
||||
for (var l = lo & ~xlo & rlo & vlo; l != X; l &= l - 1) if (!validSlotRev(lo, hi, minLen, Slot.packSlotKey(numberOfTrailingZeros(l), 3))) return false;
|
||||
for (var l = lo & xlo & ~rlo & ~vlo; l != X; l &= l - 1) if (!validSlot(lo, hi, minLen, Slot.packSlotKey(numberOfTrailingZeros(l), 4))) return false;
|
||||
|
||||
for (var h = hi & ~xhi & ~rhi & vhi; h != X; h &= h - 1) if (!validSlot(lo, hi, minLen, Slot.packSlotKey(64 | numberOfTrailingZeros(h), 1))) return false;
|
||||
for (var h = hi & ~xhi & ~rhi & ~vhi; h != X; h &= h - 1) if (!validSlot(lo, hi, minLen, Slot.packSlotKey(64 | numberOfTrailingZeros(h), 0))) return false;
|
||||
for (var h = hi & ~xhi & rhi & ~vhi; h != X; h &= h - 1) if (!validSlotRev(lo, hi, minLen, Slot.packSlotKey((64 | numberOfTrailingZeros(h)), 2))) return false;
|
||||
for (var h = hi & ~xhi & rhi & vhi; h != X; h &= h - 1) if (!validSlotRev(lo, hi, minLen, Slot.packSlotKey((64 | numberOfTrailingZeros(h)), 3))) return false;
|
||||
for (var h = hi & xhi & ~rhi & ~vhi; h != X; h &= h - 1) if (!validSlot(lo, hi, minLen, Slot.packSlotKey(64 | numberOfTrailingZeros(h), 4))) return false;
|
||||
return true;
|
||||
return findOffendingClue(minLen, new long[SwedishGenerator.SIZE], new long[SwedishGenerator.SIZE]) == -1;
|
||||
}
|
||||
public boolean isValid(int minLen, long[] slo, long[] shi) {
|
||||
return findOffendingClue(minLen, slo, shi) == -1;
|
||||
}
|
||||
public int findOffendingClue(int minLen, long[] slo, long[] shi) {
|
||||
if (((xlo & rlo) & lo) != X) return numberOfTrailingZeros((xlo & rlo) & lo);
|
||||
if (((xhi & rhi) & hi) != X) return 64 | numberOfTrailingZeros((xhi & rhi) & hi);
|
||||
int n = 0;
|
||||
for (long bits = lo; bits != X; bits &= bits - 1) {
|
||||
int idx = numberOfTrailingZeros(bits);
|
||||
int dir = getDir(idx);
|
||||
int key = Slot.packSlotKey(idx, dir);
|
||||
long sLo = PATH_LO[key], sHi = PATH_HI[key];
|
||||
long hLo = sLo & lo, hHi = sHi & 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) < minLen) return idx;
|
||||
for (int i = 0; i < n; i++) if (bitCount(sLo & slo[i]) + bitCount(sHi & shi[i]) > 1) return idx;
|
||||
slo[n] = sLo; shi[n] = sHi; n++;
|
||||
}
|
||||
for (long bits = hi; bits != X; bits &= bits - 1) {
|
||||
int idx = 64 | numberOfTrailingZeros(bits);
|
||||
int dir = getDir(idx);
|
||||
int key = Slot.packSlotKey(idx, dir);
|
||||
long sLo = PATH_LO[key], sHi = PATH_HI[key];
|
||||
long hLo = sLo & lo, hHi = sHi & 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) < minLen) return idx;
|
||||
for (int i = 0; i < n; i++) if (bitCount(sLo & slo[i]) + bitCount(sHi & shi[i]) > 1) return idx;
|
||||
slo[n] = sLo; shi[n] = sHi; n++;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
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));
|
||||
@@ -610,12 +640,14 @@ public final class Masker {
|
||||
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;
|
||||
@@ -650,7 +682,7 @@ public final class Masker {
|
||||
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) != 0 && (d & 4) == 0; }
|
||||
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; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,7 +84,7 @@ public record SwedishGenerator() {
|
||||
x = y;
|
||||
return y;
|
||||
}
|
||||
static final byte[] BYTE = new byte[]{ 0, 1, 2, 3/*,4, 5*/ };
|
||||
static final byte[] BYTE = new byte[]{ 0, 1, 2, 3, 4, 5 };
|
||||
public byte randomClueDir() { return rand(BYTE); }
|
||||
public <T> T rand(T[] p) { return p[(int) (((nextU32() & 0xFFFFFFFFL) % ((long) p.length)))]; }
|
||||
public byte rand(byte[] p) { return p[(int) (((nextU32() & 0xFFFFFFFFL) % ((long) p.length)))]; }
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
package puzzle;
|
||||
|
||||
import gen.GenerateNeighbors;
|
||||
@GenerateNeighbors(C = 9, R = 8, packageName = "precomp", className = "Neighbors9x8", MIN_LEN = 3)
|
||||
@GenerateNeighbors(C = 9, R = 8, packageName = "precomp", className = "Neighbors9x8", MIN_LEN = 2)
|
||||
public final class Trigger { }
|
||||
|
||||
@@ -11,6 +11,7 @@ import puzzle.Export.LetterVisit.LetterAt;
|
||||
import puzzle.Export.PuzzleResult;
|
||||
import puzzle.Export.Rewards;
|
||||
import puzzle.Main.Opts;
|
||||
import puzzle.Masker.Slot;
|
||||
import puzzle.SwedishGenerator.Rng;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
@@ -20,6 +21,7 @@ import static puzzle.Export.Clue.DOWN;
|
||||
import static puzzle.Export.Clue.LEFT;
|
||||
import static puzzle.Export.Clue.RIGHT;
|
||||
import static puzzle.Export.Clue.UP;
|
||||
import static puzzle.Masker.Slot;
|
||||
import static puzzle.SwedishGenerator.Dict;
|
||||
import static puzzle.SwedishGenerator.Lemma;
|
||||
import static puzzle.SwedishGenerator.Slotinfo;
|
||||
@@ -216,8 +218,8 @@ public class MainTest {
|
||||
var grid = Slotinfo.grid(slotInfo);
|
||||
var filled = fillMask(rng, slotInfo, grid);
|
||||
Assertions.assertTrue(filled.ok(), "Puzzle generation failed (not ok)");
|
||||
Assertions.assertEquals(13, Slotinfo.wordCount(0, slotInfo), "Number of assigned words changed");
|
||||
Assertions.assertEquals("WAANZIN", Lemma.asWord(slotInfo[0].assign().w, Export.BYTES.get()));
|
||||
Assertions.assertEquals(17, Slotinfo.wordCount(0, slotInfo), "Number of assigned words changed");
|
||||
Assertions.assertEquals("VREEMDS", Lemma.asWord(slotInfo[0].assign().w, Export.BYTES.get()));
|
||||
Assertions.assertEquals(-1L, grid.lo);
|
||||
Assertions.assertEquals(-1L, grid.hi);
|
||||
var g = new Gridded(grid, mask.c());
|
||||
|
||||
117
src/test/java/puzzle/MaskerCluesTest.java
Normal file
117
src/test/java/puzzle/MaskerCluesTest.java
Normal file
@@ -0,0 +1,117 @@
|
||||
package puzzle;
|
||||
|
||||
import module java.base;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static puzzle.Masker.Clues;
|
||||
import static puzzle.SwedishGenerator.*;
|
||||
import static puzzle.Masker.Slot;
|
||||
|
||||
public class MaskerCluesTest {
|
||||
|
||||
@Test
|
||||
void testSimilarity() {
|
||||
Clues a = Clues.createEmpty();
|
||||
a.setClueLo(1L << 0, (byte) 1);
|
||||
a.setClueLo(1L << 10, (byte) 0);
|
||||
|
||||
Clues b = Clues.createEmpty();
|
||||
b.setClueLo(1L << 0, (byte) 1);
|
||||
b.setClueLo(1L << 10, (byte) 0);
|
||||
|
||||
// Identity
|
||||
assertEquals(1.0, a.similarity(b), 0.001);
|
||||
|
||||
// Different direction
|
||||
Clues c = Clues.createEmpty();
|
||||
c.setClueLo(1L << 0, (byte) 0);
|
||||
c.setClueLo(1L << 10, (byte) 0);
|
||||
assertTrue(a.similarity(c) < 1.0);
|
||||
|
||||
// Completely different
|
||||
Clues d = Clues.createEmpty();
|
||||
// Matching empty cells count towards similarity.
|
||||
// a has 2 clues, d has 0. They match on 70 empty cells.
|
||||
assertEquals(70.0 / 72.0, a.similarity(d), 0.001);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIsValid() {
|
||||
Clues g = Clues.createEmpty();
|
||||
assertTrue(g.isValid(MIN_LEN));
|
||||
|
||||
// Valid clue: Right from (0,0) in 9x8 grid. Length is 8.
|
||||
g.setClueLo(1L << Masker.offset(0, 0), (byte) 1);
|
||||
assertTrue(g.isValid(MIN_LEN));
|
||||
|
||||
// Invalid clue: Right from (0,7) in 9x8 grid. Length is 1 (too short if MIN_LEN >= 2).
|
||||
Clues g2 = Clues.createEmpty();
|
||||
g2.setClueLo(1L << Masker.offset(0, 7), (byte) 1);
|
||||
assertFalse(g2.isValid(MIN_LEN));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testHasRoomForClue() {
|
||||
Clues g = Clues.createEmpty();
|
||||
|
||||
// Room for Right clue at (0,0) (length 8)
|
||||
assertTrue(g.hasRoomForClue(Slot.packSlotKey(Masker.offset(0, 0), 1)));
|
||||
|
||||
// No room for Right clue at (0,8) (length 0 < MIN_LEN)
|
||||
assertFalse(g.hasRoomForClue(Slot.packSlotKey(Masker.offset(0, 8), 1)));
|
||||
|
||||
// Blocked room
|
||||
// Let's place a clue that leaves only 1 cell for another clue.
|
||||
g.setClueLo(1L << Masker.offset(0, 2), (byte) 1);
|
||||
// Now Right at (0,0) only has (0,1) available -> length 1 < MIN_LEN (which is 2)
|
||||
assertFalse(g.hasRoomForClue(Slot.packSlotKey(Masker.offset(0, 0), 1)));
|
||||
|
||||
// But enough room
|
||||
g.clearClueLo(0L);
|
||||
g.setClueLo(1L << Masker.offset(0, 3), (byte) 1);
|
||||
// Now Right at (0,0) has (0,1), (0,2) -> length 2 == MIN_LEN
|
||||
assertTrue(g.hasRoomForClue(Slot.packSlotKey(Masker.offset(0, 0), 1)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIntersectionConstraint() {
|
||||
Clues g = Clues.createEmpty();
|
||||
|
||||
// Clue 1: (0,0) Right. Slot cells: (0,1), (0,2), (0,3), (0,4), (0,5), (0,6), (0,7), (0,8)
|
||||
g.setClueLo(1L << Masker.offset(0, 0), (byte) 1);
|
||||
|
||||
// Clue 2: (1,2) Up. Slot cells: (0,2)
|
||||
// Intersection is exactly 1 cell (0,2). Valid.
|
||||
g.setClueLo(1L << Masker.offset(2, 2), (byte) 2);
|
||||
assertTrue(g.isValid(MIN_LEN));
|
||||
|
||||
// Clue 3: (1,1) Right. Slot cells: (1,2), (1,3), ...
|
||||
// No intersection with Clue 1 or 2. Valid.
|
||||
g.setClueLo(1L << Masker.offset(1, 1), (byte) 1);
|
||||
assertTrue(g.isValid(MIN_LEN));
|
||||
|
||||
// Now create a violation: two slots sharing 2 cells.
|
||||
// We can do this with Corner Down and another clue.
|
||||
// Clue A: (0,0) Corner Down. Starts at (0,1) goes down: (0,1), (1,1), (2,1), (3,1), ...
|
||||
// Clue B: (0,2) Corner Down Left. Starts at (0,1) goes down: (0,1), (1,1), (2,1), ...
|
||||
// They share MANY cells starting from (0,1).
|
||||
|
||||
Clues g3 = Clues.createEmpty();
|
||||
g3.setClueLo(1L << Masker.offset(0, 0), (byte) 4); // Corner Down
|
||||
g3.setClueLo(1L << Masker.offset(0, 2), (byte) 5); // Corner Down Left
|
||||
assertFalse(g3.isValid(MIN_LEN));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testInvalidDirectionBits() {
|
||||
Clues g = Clues.createEmpty();
|
||||
// Dir 6 (x=1, r=1, v=0) is invalid
|
||||
g.setClueLo(1L << 0, (byte) 6);
|
||||
assertFalse(g.isValid(MIN_LEN));
|
||||
|
||||
// Dir 7 (x=1, r=1, v=1) is invalid
|
||||
Clues g2 = Clues.createEmpty();
|
||||
g2.setClueLo(1L << 0, (byte) 7);
|
||||
assertFalse(g2.isValid(MIN_LEN));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user