introduce bitloops

This commit is contained in:
mike
2026-01-18 04:11:43 +01:00
parent b026ebfbd2
commit 948730d7be
5 changed files with 186 additions and 99 deletions

View File

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

View File

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

View File

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

View File

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

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