introduce bitloops

This commit is contained in:
mike
2026-01-20 01:57:21 +01:00
parent 7e5e363a3e
commit d1c448e1cb
7 changed files with 239 additions and 85 deletions

View File

@@ -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(); }

View File

@@ -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;

View File

@@ -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; }
}
}

View File

@@ -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)))]; }

View File

@@ -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 { }

View File

@@ -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());

View 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));
}
}