From 948730d7be378e00a66dd34409397a6ab962901f Mon Sep 17 00:00:00 2001 From: mike Date: Sun, 18 Jan 2026 04:11:43 +0100 Subject: [PATCH] introduce bitloops --- src/main/java/puzzle/Export.java | 4 +- src/main/java/puzzle/Masker.java | 78 ++++++++-------- src/main/java/puzzle/SwedishGenerator.java | 50 ++++++++-- src/test/java/puzzle/ConnectivityTest.java | 103 +++++++++++---------- src/test/java/puzzle/CornerClueTest.java | 50 ++++++++++ 5 files changed, 186 insertions(+), 99 deletions(-) create mode 100644 src/test/java/puzzle/CornerClueTest.java diff --git a/src/main/java/puzzle/Export.java b/src/main/java/puzzle/Export.java index b95e237..59ac210 100644 --- a/src/main/java/puzzle/Export.java +++ b/src/main/java/puzzle/Export.java @@ -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(); } diff --git a/src/main/java/puzzle/Masker.java b/src/main/java/puzzle/Masker.java index 42bcb1a..8e77342 100644 --- a/src/main/java/puzzle/Masker.java +++ b/src/main/java/puzzle/Masker.java @@ -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; } } } diff --git a/src/main/java/puzzle/SwedishGenerator.java b/src/main/java/puzzle/SwedishGenerator.java index f979414..b27a437 100644 --- a/src/main/java/puzzle/SwedishGenerator.java +++ b/src/main/java/puzzle/SwedishGenerator.java @@ -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 rand(T[] p) { return p[(int) (((nextU32() & 0xFFFFFFFFL) % ((long) p.length /*- 0L*/ /*+ 1L*/)))]; } public T rand(List p) { return p.get((int) (((nextU32() & 0xFFFFFFFFL) % ((long) p.size() /*- 0L*/ /*+ 1L*/)))); } diff --git a/src/test/java/puzzle/ConnectivityTest.java b/src/test/java/puzzle/ConnectivityTest.java index bcd1da8..8560fa4 100644 --- a/src/test/java/puzzle/ConnectivityTest.java +++ b/src/test/java/puzzle/ConnectivityTest.java @@ -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"); } } diff --git a/src/test/java/puzzle/CornerClueTest.java b/src/test/java/puzzle/CornerClueTest.java new file mode 100644 index 0000000..fc78654 --- /dev/null +++ b/src/test/java/puzzle/CornerClueTest.java @@ -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())); + } +}