introduce bitloops
This commit is contained in:
@@ -51,7 +51,7 @@ public record Export() {
|
||||
|
||||
public record Clued(@Delegate Clues c) {
|
||||
|
||||
public Clued deepCopyGrid() { return new Clued(new Clues(c.lo, c.hi, c.vlo, c.vhi, c.rlo, c.rhi)); }
|
||||
public Clued deepCopyGrid() { return new Clued(new Clues(c.lo, c.hi, c.vlo, c.vhi, c.rlo, c.rhi, c.xlo, c.xhi)); }
|
||||
String gridToString() {
|
||||
var sb = new StringBuilder(INIT);
|
||||
forEachSlot((s, _, _) -> {
|
||||
@@ -199,7 +199,7 @@ public record Export() {
|
||||
|
||||
record Placed(long lemma, int slotKey, int[] cells) {
|
||||
|
||||
static final char[] DIRECTION = { Placed.VERTICAL, Placed.HORIZONTAL, Placed.VERTICAL, Placed.HORIZONTAL };
|
||||
static final char[] DIRECTION = { Placed.VERTICAL, Placed.HORIZONTAL, Placed.VERTICAL, Placed.HORIZONTAL, Placed.VERTICAL };
|
||||
public static final char HORIZONTAL = 'h';
|
||||
static final char VERTICAL = 'v';
|
||||
public int arrowCol() { return SwedishGenerator.IT[Slot.clueIndex(slotKey)].c(); }
|
||||
|
||||
@@ -130,9 +130,8 @@ public final class Masker {
|
||||
for (long bits = lo_cl; bits != X; bits &= bits - 1) {
|
||||
long lsb = bits & -bits;
|
||||
int clueIdx = numberOfTrailingZeros(lsb);
|
||||
int v = (grid.vlo & lsb) != 0 ? 1 : 0;
|
||||
int r = (grid.rlo & lsb) != 0 ? 1 : 0;
|
||||
int key = Slot.packSlotKey(clueIdx, (r << 1) | v);
|
||||
int dir = grid.getDir(clueIdx);
|
||||
int 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)) {
|
||||
@@ -173,9 +172,8 @@ public final class Masker {
|
||||
for (long bits = hi_cl; bits != X; bits &= bits - 1) {
|
||||
long lsb = bits & -bits;
|
||||
int clueIdx = numberOfTrailingZeros(lsb);
|
||||
int v = (grid.vhi & lsb) != 0 ? 1 : 0;
|
||||
int r = (grid.rhi & lsb) != 0 ? 1 : 0;
|
||||
int key = Slot.packSlotKey(64 | clueIdx, (r << 1) | v);
|
||||
int dir = grid.getDir(64 | clueIdx);
|
||||
int 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)) {
|
||||
@@ -226,15 +224,6 @@ public final class Masker {
|
||||
// 1. Intersectie
|
||||
if (((activeSLo[i] & activeSLo[j]) | (activeSHi[i] & activeSHi[j])) != 0) {
|
||||
connected = true;
|
||||
} else {
|
||||
// 2. 8-naburigheid van clue cells
|
||||
int ci = activeCIdx[i];
|
||||
int cj = activeCIdx[j];
|
||||
if (cj < 64) {
|
||||
if ((NBR8_PACKED_LO[ci] & (1L << cj)) != 0) connected = true;
|
||||
} else {
|
||||
if ((NBR8_PACKED_HI[ci] & (1L << (cj & 63))) != 0) connected = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (connected) {
|
||||
@@ -282,7 +271,7 @@ public final class Masker {
|
||||
if ((4 - rci.nbrCount()) + bitCount(rci.n1() & lo_cl) + bitCount(rci.n2() & hi_cl) >= 3) penalty += 400;
|
||||
boolean h = (cHLo & (1L << clueIdx)) != X;
|
||||
boolean v = (cVLo & (1L << clueIdx)) != X;
|
||||
if (!h && !v) penalty += 15000;
|
||||
if (!h && !v) penalty += 5000;
|
||||
else if (h && v) { /* ok */ } else if (((h ? cHLo2 : cVLo2) & (1L << clueIdx)) != X) penalty += 600;
|
||||
else penalty += 200;
|
||||
}
|
||||
@@ -293,7 +282,7 @@ public final class Masker {
|
||||
boolean h = (cHHi & (1L << clueIdx)) != X;
|
||||
boolean v = (cVHi & (1L << clueIdx)) != X;
|
||||
if (!h && !v)
|
||||
penalty += 15000;
|
||||
penalty += 5000;
|
||||
else if (h && v) { /* ok */ } else if (((h ? cHHi2 : cVHi2) & (1L << clueIdx)) != X) penalty += 600;
|
||||
else penalty += 200;
|
||||
}
|
||||
@@ -407,7 +396,9 @@ public final class Masker {
|
||||
(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.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);
|
||||
@@ -513,9 +504,9 @@ public final class Masker {
|
||||
@AllArgsConstructor
|
||||
public static class Clues {
|
||||
|
||||
long lo, hi, vlo, vhi, rlo, rhi;
|
||||
long lo, hi, vlo, vhi, rlo, rhi, xlo, xhi;
|
||||
|
||||
public static Clues createEmpty() { return new Clues(0, 0, 0, 0, 0, 0); }
|
||||
public static Clues createEmpty() { return new Clues(0, 0, 0, 0, 0, 0, 0, 0); }
|
||||
public static Clued parse(String s) {
|
||||
var c = createEmpty();
|
||||
var lines = s.split("\n");
|
||||
@@ -523,7 +514,7 @@ public final class Masker {
|
||||
var line = lines[r];
|
||||
for (int col = 0; col < Math.min(line.length(), C); col++) {
|
||||
char ch = line.charAt(col);
|
||||
if (ch >= '0' && ch <= '3') {
|
||||
if (ch >= '0' && ch <= '4') {
|
||||
int idx = Grid.offset(r, col);
|
||||
byte dir = (byte) (ch - '0');
|
||||
if ((idx & 64) == 0) c.setClueLo(1L << idx, dir);
|
||||
@@ -557,6 +548,8 @@ public final class Masker {
|
||||
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;
|
||||
@@ -564,16 +557,20 @@ public final class Masker {
|
||||
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; }
|
||||
@@ -582,8 +579,8 @@ public final class Masker {
|
||||
|
||||
public int clueCount() { return bitCount(lo) + bitCount(hi); }
|
||||
public double similarity(Clues b) {
|
||||
long matchLo = (~(lo ^ b.lo)) & (~lo | (~(vlo ^ b.vlo) & ~(rlo ^ b.rlo)));
|
||||
long matchHi = (~(hi ^ b.hi)) & (~hi | (~(vhi ^ b.vhi) & ~(rhi ^ b.rhi)));
|
||||
long matchLo = (~(lo ^ b.lo)) & (~lo | (~(vlo ^ b.vlo) & ~(rlo ^ b.rlo) & ~(xlo ^ b.xlo)));
|
||||
long matchHi = (~(hi ^ b.hi)) & (~hi | (~(vhi ^ b.vhi) & ~(rhi ^ b.rhi) & ~(xhi ^ b.xhi)));
|
||||
|
||||
return (bitCount(matchLo & MASK_LO) + bitCount(matchHi & MASK_HI)) / SIZED;
|
||||
}
|
||||
@@ -601,14 +598,17 @@ public final class Masker {
|
||||
return v.ok;
|
||||
}
|
||||
public void forEachSlot(SlotVisitor visitor) {
|
||||
for (var l = lo & ~rlo & vlo; l != X; l &= l - 1) processSlot(this, visitor, Slot.packSlotKey(numberOfTrailingZeros(l), 1));
|
||||
for (var l = lo & ~rlo & ~vlo; l != X; l &= l - 1) processSlot(this, visitor, Slot.packSlotKey(numberOfTrailingZeros(l), 0));
|
||||
for (var l = lo & rlo & ~vlo; l != X; l &= l - 1) processSlotRev(this, visitor, Slot.packSlotKey(numberOfTrailingZeros(l), 2));
|
||||
for (var l = lo & rlo & vlo; l != X; l &= l - 1) processSlotRev(this, visitor, Slot.packSlotKey(numberOfTrailingZeros(l), 3));
|
||||
for (var h = hi & ~rhi & vhi; h != X; h &= h - 1) processSlot(this, visitor, Slot.packSlotKey(64 | numberOfTrailingZeros(h), 1));
|
||||
for (var h = hi & ~rhi & ~vhi; h != X; h &= h - 1) processSlot(this, visitor, Slot.packSlotKey(64 | numberOfTrailingZeros(h), 0));
|
||||
for (var h = hi & rhi & ~vhi; h != X; h &= h - 1) processSlotRev(this, visitor, Slot.packSlotKey((64 | numberOfTrailingZeros(h)), 2));
|
||||
for (var h = hi & rhi & vhi; h != X; h &= h - 1) processSlotRev(this, visitor, Slot.packSlotKey((64 | numberOfTrailingZeros(h)), 3));
|
||||
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 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));
|
||||
}
|
||||
public Clues from(Clues best) {
|
||||
lo = best.lo;
|
||||
@@ -617,30 +617,34 @@ public final class Masker {
|
||||
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) {
|
||||
int v = (vlo & (1L << index)) != 0 ? 1 : 0;
|
||||
int r = (rlo & (1L << index)) != 0 ? 1 : 0;
|
||||
return (byte) ((r << 1) | v);
|
||||
int x = (xlo & (1L << index)) != 0 ? 1 : 0;
|
||||
return (byte) ((x << 2) | (r << 1) | v);
|
||||
} else {
|
||||
int v = (vhi & (1L << (index & 63))) != 0 ? 1 : 0;
|
||||
int r = (rhi & (1L << (index & 63))) != 0 ? 1 : 0;
|
||||
return (byte) ((r << 1) | v);
|
||||
int 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 = 2;
|
||||
static Slot from(int key, long lo, long hi, DictEntry entry) { return new Slot(key, lo, hi, 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 & 3; }
|
||||
public static int dir(int key) { return key & 7; }
|
||||
public IntStream walk() { return Gridded.walk((byte) key, lo, hi); }
|
||||
public static boolean horiz(int d) { return (d & 1) != 0; }
|
||||
public static boolean horiz(int d) { return (d & 1) != 0 && (d & 4) == 0; }
|
||||
public static int packSlotKey(int idx, int d) { return (idx << BIT_FOR_DIR) | d; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,12 +76,45 @@ public record SwedishGenerator() {
|
||||
public int count;
|
||||
}
|
||||
|
||||
public static final long[] OFFSETS_D_IDX = Neighbors9x8.OFFSET_D_IDX_0_BASE;
|
||||
public static final long[] OFFSETS_D_IDX;
|
||||
public static final rci[] IT = Neighbors9x8.IT;
|
||||
public static final long[] NBR8_PACKED_LO = Neighbors9x8.NBR8_PACKED_LO;
|
||||
public static final long[] NBR8_PACKED_HI = Neighbors9x8.NBR8_PACKED_HI;
|
||||
public static final long[] PATH_LO = Neighbors9x8.PATH_LO;
|
||||
public static final long[] PATH_HI = Neighbors9x8.PATH_HI;
|
||||
public static final long[] PATH_LO;
|
||||
public static final long[] PATH_HI;
|
||||
|
||||
static {
|
||||
final int NEW_SIZE = SIZE << Masker.Slot.BIT_FOR_DIR;
|
||||
PATH_LO = new long[NEW_SIZE];
|
||||
PATH_HI = new long[NEW_SIZE];
|
||||
OFFSETS_D_IDX = new long[NEW_SIZE];
|
||||
for (int i = 0; i < SIZE; i++) {
|
||||
for (int d = 0; d < 4; d++) {
|
||||
int oldKey = (i << 2) | d;
|
||||
int newKey = (i << Masker.Slot.BIT_FOR_DIR) | d;
|
||||
PATH_LO[newKey] = Neighbors9x8.PATH_LO[oldKey];
|
||||
PATH_HI[newKey] = Neighbors9x8.PATH_HI[oldKey];
|
||||
OFFSETS_D_IDX[newKey] = Neighbors9x8.OFFSET_D_IDX_0_BASE[oldKey];
|
||||
}
|
||||
// Richting 4: Corner Down (Clue op r,c -> Woord op r,c+1 naar beneden)
|
||||
int r = IT[i].r();
|
||||
int c = IT[i].c();
|
||||
int newKey4 = (i << Masker.Slot.BIT_FOR_DIR) | 4;
|
||||
if (c + 1 < C) {
|
||||
long mLo = 0, mHi = 0;
|
||||
for (int ri = r; ri < R; ri++) {
|
||||
int target = Grid.offset(ri, c + 1);
|
||||
if (isLo(target)) mLo |= (1L << target);
|
||||
else mHi |= (1L << (target & 63));
|
||||
}
|
||||
PATH_LO[newKey4] = mLo;
|
||||
PATH_HI[newKey4] = mHi;
|
||||
int first = Grid.offset(r, c + 1);
|
||||
int last = Grid.offset(R - 1, c + 1);
|
||||
OFFSETS_D_IDX[newKey4] = (long)first | ((long)last << 7);
|
||||
}
|
||||
}
|
||||
}
|
||||
public static final Pick PICK_DONE = null;//new Pick(null, null, 0, true);
|
||||
public static final Pick PICK_NOT_DONE = new Pick(null, null, 0);
|
||||
|
||||
@@ -112,13 +145,10 @@ public record SwedishGenerator() {
|
||||
return y;
|
||||
}
|
||||
public byte randint2bitByte() {
|
||||
var b = (byte) (nextU32() & 3);
|
||||
if (b == 3) {
|
||||
return 1;
|
||||
}if (b == 4) {
|
||||
return 2;
|
||||
}
|
||||
return b;
|
||||
int r = nextU32() & 7;
|
||||
if (r < 4) return (byte) r;
|
||||
if (r == 4) return (byte) 4;
|
||||
return (byte) (r % 4);
|
||||
}
|
||||
public <T> T rand(T[] p) { return p[(int) (((nextU32() & 0xFFFFFFFFL) % ((long) p.length /*- 0L*/ /*+ 1L*/)))]; }
|
||||
public <T> T rand(List<T> p) { return p.get((int) (((nextU32() & 0xFFFFFFFFL) % ((long) p.size() /*- 0L*/ /*+ 1L*/)))); }
|
||||
|
||||
@@ -13,78 +13,81 @@ public class ConnectivityTest {
|
||||
Rng rng = new Rng(42);
|
||||
Masker masker = new Masker(rng, new int[STACK_SIZE], Clues.createEmpty());
|
||||
|
||||
// 1. Maak een masker met één component van clues (bijv. 3 clues naast elkaar)
|
||||
// 1. Maak een masker met één component van clues (bijv. 2 clues die elkaar kruisen)
|
||||
Clues singleComp = Clues.createEmpty();
|
||||
// Gebruik offsets die dicht bij elkaar liggen
|
||||
int off1 = SwedishGenerator.Grid.offset(1, 1);
|
||||
int off2 = SwedishGenerator.Grid.offset(1, 2);
|
||||
int off3 = SwedishGenerator.Grid.offset(2, 1);
|
||||
// Clue 1: (0,0) Right. Slot: (0,1), (0,2), (0,3)
|
||||
// Clue 2: (1,2) Up. Slot: (0,2)
|
||||
// Ze zijn NIET 8-naburig, maar wel verbonden via slot op (0,2)
|
||||
singleComp.setClueLo(1L << SwedishGenerator.Grid.offset(0,0), (byte)1);
|
||||
singleComp.setClueLo(1L << SwedishGenerator.Grid.offset(2,2), (byte)2); // Up van (2,2) naar (1,2), (0,2)
|
||||
|
||||
singleComp.setClueLo(1L << off1, (byte)1); // Right
|
||||
singleComp.setClueLo(1L << off2, (byte)1); // Right
|
||||
singleComp.setClueLo(1L << off3, (byte)0); // Down
|
||||
|
||||
long fitnessSingle = masker.maskFitness(singleComp, 3);
|
||||
long fitnessSingle = masker.maskFitness(singleComp, 2);
|
||||
|
||||
// 2. Maak een masker met twee eilandjes van clues
|
||||
Clues twoIslands = Clues.createEmpty();
|
||||
int offA1 = SwedishGenerator.Grid.offset(1, 1);
|
||||
int offB1 = SwedishGenerator.Grid.offset(6, 6); // Ver weg
|
||||
|
||||
// We moeten zorgen dat ze elk minstens 1 slot vormen om door isValid(2) te komen
|
||||
twoIslands.setClueLo(1L << offA1, (byte)1);
|
||||
twoIslands.setClueLo(1L << offB1, (byte)1);
|
||||
twoIslands.setClueLo(1L << SwedishGenerator.Grid.offset(0,0), (byte)1);
|
||||
twoIslands.setClueLo( SwedishGenerator.Grid.offset(7,7) < 64 ? 1L << SwedishGenerator.Grid.offset(7,7) : 0, (byte)1);
|
||||
// Voor de zekerheid checken we of offset(7,7) in lo of hi zit
|
||||
int off77 = SwedishGenerator.Grid.offset(7,7);
|
||||
if (off77 < 64) twoIslands.setClueLo(1L << off77, (byte)1);
|
||||
else twoIslands.setClueHi(1L << (off77 - 64), (byte)1);
|
||||
|
||||
long fitnessIslands = masker.maskFitness(twoIslands, 2);
|
||||
|
||||
System.out.println("[DEBUG_LOG] Fitness single component: " + fitnessSingle);
|
||||
System.out.println("[DEBUG_LOG] Fitness two islands: " + fitnessIslands);
|
||||
|
||||
// De eilandjes moeten een hogere penalty hebben (als clueCount gelijk is)
|
||||
Clues twoIslands3 = Clues.createEmpty();
|
||||
twoIslands3.setClueLo(1L << offA1, (byte)1);
|
||||
twoIslands3.setClueLo(1L << offB1, (byte)1);
|
||||
int offB2 = SwedishGenerator.Grid.offset(6, 7);
|
||||
twoIslands3.setClueLo(1L << offB2, (byte)1);
|
||||
|
||||
long fitnessIslands3 = masker.maskFitness(twoIslands3, 3);
|
||||
System.out.println("[DEBUG_LOG] Fitness three clues in two islands: " + fitnessIslands3);
|
||||
|
||||
assertTrue(fitnessIslands3 > fitnessSingle, "Islands should have higher penalty than single component");
|
||||
assertTrue(fitnessIslands > fitnessSingle + 10000, "Islands should have much higher penalty");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIntersectionConnectivity() {
|
||||
void testPhysicalAdjacency() {
|
||||
Rng rng = new Rng(42);
|
||||
Masker masker = new Masker(rng, new int[STACK_SIZE], Clues.createEmpty());
|
||||
|
||||
// Test of slots die elkaar kruisen als verbonden worden beschouwd,
|
||||
// zelfs als de clues niet 8-naburig zijn.
|
||||
Clues crossing = Clues.createEmpty();
|
||||
// Clue 1: (0,0) naar rechts. Slot op (0,1), (0,2), (0,3)
|
||||
// Clue 2: (1,2) omhoog. Slot op (0,2)
|
||||
// Ze kruisen op (0,2)
|
||||
crossing.setClueLo(1L << SwedishGenerator.Grid.offset(0,0), (byte)1); // Right
|
||||
crossing.setClueLo(1L << SwedishGenerator.Grid.offset(1,2), (byte)2); // Up
|
||||
Clues clues = Clues.createEmpty();
|
||||
// Twee clues naast elkaar, maar slots kruisen niet.
|
||||
// Clue 1: (1,1) Right. Slot (1,2), (1,3), (1,4)
|
||||
// Clue 2: (2,1) Right. Slot (2,2), (2,3), (2,4)
|
||||
clues.setClueLo(1L << SwedishGenerator.Grid.offset(1,1), (byte)1);
|
||||
clues.setClueLo(1L << SwedishGenerator.Grid.offset(2,1), (byte)1);
|
||||
|
||||
// Deze twee clues zijn niet 8-naburig (0,0 en 1,2)
|
||||
// Maar hun slots kruisen op (0,2)
|
||||
long fitness = masker.maskFitness(clues, 2);
|
||||
|
||||
long fitness = masker.maskFitness(crossing, 2);
|
||||
System.out.println("[DEBUG_LOG] Fitness crossing: " + fitness);
|
||||
// Als 8-naburigheid NIET MEER meetelt, moet de penalty hoog zijn.
|
||||
System.out.println("[DEBUG_LOG] Fitness physically adjacent: " + fitness);
|
||||
assertTrue(fitness > 20000, "Should have island penalty even if physically adjacent");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCornerClueConnectivity() {
|
||||
Rng rng = new Rng(42);
|
||||
Masker masker = new Masker(rng, new int[STACK_SIZE], Clues.createEmpty());
|
||||
|
||||
// Als ze als verbonden worden gezien, is er 1 component.
|
||||
// Penalty voor connectiviteit zou 0 moeten zijn (bovenop andere penalties).
|
||||
// Als we een derde clue ver weg toevoegen, moet de penalty significant stijgen.
|
||||
Clues clues = Clues.createEmpty();
|
||||
// Clue A: (2,0) Right. Slot: (2,1), (2,2), (2,3), ...
|
||||
// Clue B: (1,2) Corner Down. Word starts at (1,3) en gaat omlaag: (1,3), (2,3), (3,3)...
|
||||
// Ze kruisen op (2,3).
|
||||
clues.setClueLo(1L << SwedishGenerator.Grid.offset(2,0), (byte)1); // Right
|
||||
clues.setClueLo(1L << SwedishGenerator.Grid.offset(1,2), (byte)4); // Corner Down
|
||||
|
||||
Clues crossingPlusIsland = Clues.createEmpty();
|
||||
crossingPlusIsland.setClueLo(1L << SwedishGenerator.Grid.offset(0,0), (byte)1);
|
||||
crossingPlusIsland.setClueLo(1L << SwedishGenerator.Grid.offset(1,2), (byte)2);
|
||||
crossingPlusIsland.setClueLo(1L << SwedishGenerator.Grid.offset(7,7), (byte)1);
|
||||
long fitness = masker.maskFitness(clues, 2);
|
||||
System.out.println("[DEBUG_LOG] Fitness corner clue connected: " + fitness);
|
||||
|
||||
long fitnessIsland = masker.maskFitness(crossingPlusIsland, 3);
|
||||
System.out.println("[DEBUG_LOG] Fitness crossing plus island: " + fitnessIsland);
|
||||
// Als ze verbonden zijn, is de penalty voor eilandjes 0.
|
||||
// We vergelijken met een island scenario (2 clues die elkaar NIET raken)
|
||||
Clues island = Clues.createEmpty();
|
||||
int offA = SwedishGenerator.Grid.offset(2,0);
|
||||
if (offA < 64) island.setClueLo(1L << offA, (byte)1);
|
||||
else island.setClueHi(1L << (offA - 64), (byte)1);
|
||||
|
||||
assertTrue(fitnessIsland > fitness + 10000, "Island should add significant penalty");
|
||||
int offB = SwedishGenerator.Grid.offset(7,7);
|
||||
if (offB < 64) island.setClueLo(1L << offB, (byte)1);
|
||||
else island.setClueHi(1L << (offB - 64), (byte)1);
|
||||
|
||||
long fitnessIsland = masker.maskFitness(island, 2);
|
||||
System.out.println("[DEBUG_LOG] Fitness island: " + fitnessIsland);
|
||||
|
||||
assertTrue(fitnessIsland > fitness + 20000, "Island should add significant penalty compared to connected corner clue");
|
||||
}
|
||||
}
|
||||
|
||||
50
src/test/java/puzzle/CornerClueTest.java
Normal file
50
src/test/java/puzzle/CornerClueTest.java
Normal file
@@ -0,0 +1,50 @@
|
||||
package puzzle;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import puzzle.Masker.Clues;
|
||||
import puzzle.SwedishGenerator.DictEntry;
|
||||
import puzzle.SwedishGenerator.Slotinfo;
|
||||
|
||||
public class CornerClueTest {
|
||||
@Test
|
||||
void testCornerDownSlot() {
|
||||
Clues clues = Clues.createEmpty();
|
||||
// Clue op (0,0), type 4 (Corner Down)
|
||||
int idx = SwedishGenerator.Grid.offset(0,0);
|
||||
clues.setClueLo(1L << idx, (byte)4);
|
||||
|
||||
assertEquals(4, clues.getDir(idx));
|
||||
|
||||
// Controleer of forEachSlot het slot vindt
|
||||
final boolean[] found = {false};
|
||||
clues.forEachSlot((key, lo, hi) -> {
|
||||
if (Masker.Slot.dir(key) == 4) {
|
||||
found[0] = true;
|
||||
// Woord zou moeten starten op (0,1)
|
||||
int startIdx = SwedishGenerator.Grid.offset(0, 1);
|
||||
assertTrue((lo & (1L << startIdx)) != 0, "Slot should start at (0,1)");
|
||||
// En omlaag gaan
|
||||
int secondIdx = SwedishGenerator.Grid.offset(1, 1);
|
||||
assertTrue((lo & (1L << secondIdx)) != 0, "Slot should continue to (1,1)");
|
||||
|
||||
// Lengte van het slot zou 8 moeten zijn (van rij 0 t/m 7 in kolom 1)
|
||||
assertEquals(8, Masker.Slot.length(lo, hi));
|
||||
}
|
||||
});
|
||||
assertTrue(found[0], "Corner Down slot should be found");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCornerDownExtraction() {
|
||||
Clues clues = Clues.createEmpty();
|
||||
int idx = SwedishGenerator.Grid.offset(0,0);
|
||||
clues.setClueLo(1L << idx, (byte)4);
|
||||
|
||||
DictEntry[] dict = DictData.DICT.index();
|
||||
Slotinfo[] slots = Masker.slots(clues, dict);
|
||||
|
||||
assertEquals(1, slots.length);
|
||||
assertEquals(4, Masker.Slot.dir(slots[0].key()));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user