From 7f15ab8ff19683f82736ef7c22730cde109370cc Mon Sep 17 00:00:00 2001 From: mike Date: Tue, 20 Jan 2026 23:18:29 +0100 Subject: [PATCH] introduce bitloops --- src/main/java/puzzle/Export.java | 14 ++++-- src/main/java/puzzle/Masker.java | 6 +++ src/test/java/puzzle/ConnectivityTest.java | 52 +++++----------------- src/test/java/puzzle/CornerClueTest.java | 46 +++++-------------- src/test/java/puzzle/MaskerCluesTest.java | 50 +++++++-------------- 5 files changed, 55 insertions(+), 113 deletions(-) diff --git a/src/main/java/puzzle/Export.java b/src/main/java/puzzle/Export.java index 390a7f2..5f16cf6 100644 --- a/src/main/java/puzzle/Export.java +++ b/src/main/java/puzzle/Export.java @@ -4,7 +4,8 @@ import module java.base; import lombok.AllArgsConstructor; import lombok.experimental.Delegate; import lombok.val; -import puzzle.Export.Gridded.Replacar.Cell; +import precomp.Const9x8.Cell; +import puzzle.Export.Gridded.Replacar.Rell; import puzzle.Export.LetterVisit.LetterAt; import puzzle.Masker.Clues; import puzzle.SwedishGenerator.FillResult; @@ -69,6 +70,11 @@ public record Export() { public record Clued(@Delegate Clues c) { + public static Clues create(Cell... cell) { + var empty = createEmpty(); + for (var cell1 : cell) empty.setClue(cell1); + return empty; + } public Clued deepCopyGrid() { return new Clued(new Clues(c.lo, c.hi, c.vlo, c.vhi, c.rlo, c.rhi, c.xlo, c.xhi)); } public static Clued parse(String s) { var c = createEmpty(); @@ -160,7 +166,7 @@ public record Export() { val r = idx & 7; val c = idx >>> 3; val dir = Slot.dir(s); - sb.setCharAt(r * (C + 1) + c, clueChar.replace(new Cell(grid, clues, idx, (byte) (dir | 48)))); + sb.setCharAt(r * (C + 1) + c, clueChar.replace(new Rell(grid, clues, idx, (byte) (dir | 48)))); }); stream(clues).forEach((l) -> sb.setCharAt(l.index(C + 1), l.human())); return sb.toString().replaceAll(" ", "" + emptyFallback).split("\n"); @@ -217,8 +223,8 @@ public record Export() { @FunctionalInterface interface Replacar { - record Cell(Grid grid, Clues clues, int index, byte data) { } - char replace(Cell c); + record Rell(Grid grid, Clues clues, int index, byte data) { } + char replace(Rell c); } } diff --git a/src/main/java/puzzle/Masker.java b/src/main/java/puzzle/Masker.java index 61a4672..326cc7b 100644 --- a/src/main/java/puzzle/Masker.java +++ b/src/main/java/puzzle/Masker.java @@ -653,6 +653,12 @@ public final class Masker { return validSlotRev(lo, hi, key); } + public Clues setClue(precomp.Const9x8.Cell cell) { + if (cell.index < 64) setClueLo(cell.mask, cell.d); + else setClueHi(cell.mask, cell.d); + return this; + } + public void setClueLo(long mask, byte idx) { lo |= mask; if ((idx & 1) != 0) vlo |= mask; diff --git a/src/test/java/puzzle/ConnectivityTest.java b/src/test/java/puzzle/ConnectivityTest.java index 605c270..556bb5f 100644 --- a/src/test/java/puzzle/ConnectivityTest.java +++ b/src/test/java/puzzle/ConnectivityTest.java @@ -4,14 +4,8 @@ import org.junit.jupiter.api.Test; import puzzle.Masker.Clues; import puzzle.SwedishGenerator.Rng; import static org.junit.jupiter.api.Assertions.assertTrue; -import static precomp.Const9x8.OFF_0_0; -import static precomp.Const9x8.OFF_1_1; -import static precomp.Const9x8.OFF_1_2; -import static precomp.Const9x8.OFF_2_0; -import static precomp.Const9x8.OFF_2_1; -import static precomp.Const9x8.OFF_2_2; -import static precomp.Const9x8.OFF_3_1; -import static precomp.Const9x8.OFF_7_7; +import static precomp.Const9x8.*; +import static precomp.Const9x8.Cell.*; import static puzzle.SwedishGenerator.STACK_SIZE; public class ConnectivityTest { @@ -22,23 +16,12 @@ public class ConnectivityTest { Masker masker = new Masker(rng, new int[STACK_SIZE], Clues.createEmpty()); // 1. Maak een masker met één component van clues (bijv. 2 clues die elkaar kruisen) - Clues singleComp = Clues.createEmpty(); - // 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 << OFF_0_0, (byte) 1); - singleComp.setClueLo(1L << OFF_2_2, (byte) 2); // Up van (2,2) naar (1,2), (0,2) + Clues singleComp = Clues.createEmpty().setClue(r0c0d1).setClue(r2c2d2); long fitnessSingle = masker.maskFitness(singleComp, 2); // 2. Maak een masker met twee eilandjes van clues - Clues twoIslands = Clues.createEmpty(); - twoIslands.setClueLo(1L << OFF_0_0, (byte) 1); - twoIslands.setClueLo(OFF_7_7 < 64 ? 1L << OFF_7_7 : 0, (byte) 1); - // Voor de zekerheid checken we of offset(7,7) in lo of hi zit - int off77 = OFF_7_7; - if (off77 < 64) twoIslands.setClueLo(1L << off77, (byte) 1); - else twoIslands.setClueHi(1L << (off77 - 64), (byte) 1); + Clues twoIslands = Clues.createEmpty().setClue(r0c0d1).setClue(r7c7d1); long fitnessIslands = masker.maskFitness(twoIslands, 2); @@ -53,28 +36,22 @@ public class ConnectivityTest { Rng rng = new Rng(42); Masker masker = new Masker(rng, new int[STACK_SIZE], Clues.createEmpty()); - 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 << OFF_1_1, (byte) 1); - clues.setClueLo(1L << OFF_2_1, (byte) 1); + Clues clues = Clues.createEmpty().setClue(r1c1d1).setClue(r2c1d1); long fitness = masker.maskFitness(clues, 2); // 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"); - Clues clues2 = 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) - clues2.setClueLo(1L << OFF_1_1, (byte) 1); - clues2.setClueLo(1L << OFF_3_1, (byte) 1); - long fitness2 = new Masker(rng, new int[STACK_SIZE], Clues.createEmpty()).maskFitness(clues2, 2); + Clues clues2 = Clues.createEmpty().setClue(r1c1d1).setClue(r3c1d1); + long fitness2 = new Masker(rng, new int[STACK_SIZE], Clues.createEmpty()).maskFitness(clues2, 2); // Als 8-naburigheid NIET MEER meetelt, moet de penalty hoog zijn. System.out.println("[DEBUG_LOG] Fitness physically adjacent: " + fitness2); assertTrue(fitness2 > 20000, "Should have island penalty even if physically adjacent"); - } @Test @@ -82,26 +59,17 @@ public class ConnectivityTest { Rng rng = new Rng(42); Masker masker = new Masker(rng, new int[STACK_SIZE], Clues.createEmpty()); - 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 << OFF_2_0, (byte) 1); // Right - clues.setClueLo(1L << OFF_1_2, (byte) 4); // Corner Down + Clues clues = Clues.createEmpty().setClue(r2c0d1).setClue(r1c2d4); long fitness = masker.maskFitness(clues, 2); System.out.println("[DEBUG_LOG] Fitness corner clue connected: " + fitness); // 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 = OFF_2_0; - if (offA < 64) island.setClueLo(1L << offA, (byte) 1); - else island.setClueHi(1L << (offA - 64), (byte) 1); - - int offB = OFF_7_7; - if (offB < 64) island.setClueLo(1L << offB, (byte) 1); - else island.setClueHi(1L << (offB - 64), (byte) 1); + Clues island = Clues.createEmpty().setClue(r2c0d1).setClue(r7c7d1); long fitnessIsland = masker.maskFitness(island, 2); System.out.println("[DEBUG_LOG] Fitness island: " + fitnessIsland); diff --git a/src/test/java/puzzle/CornerClueTest.java b/src/test/java/puzzle/CornerClueTest.java index 6b1e11d..906b7d6 100644 --- a/src/test/java/puzzle/CornerClueTest.java +++ b/src/test/java/puzzle/CornerClueTest.java @@ -7,45 +7,23 @@ import puzzle.Export.Clue; import puzzle.Masker.Clues; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -import static precomp.Const9x8.OFF_0_0; -import static precomp.Const9x8.OFF_0_1; -import static precomp.Const9x8.OFF_1_0; -import static precomp.Const9x8.OFF_1_1; -import static puzzle.CornerClueTest.Cell.r0c1d5; -import static puzzle.CornerClueTest.Cell.r0c0d4; +import static precomp.Const9x8.*; +import static precomp.Const9x8.Cell.*; public class CornerClueTest { - enum Cell { - r0c0d4(Clue.LEFT_TOP4, OFF_0_0), - r0c1d5(Clue.RIGHT_TOP5, OFF_0_1); - final public byte d; - final public long mask; - final public int index; - Cell(Clue d, int off) { - this.d = d.dir; - this.mask = 1L << off; - this.index = off; - } - public Clues attach(Clues c) { - c.setClueLo(mask, d); - return c; - } - public Clues attach() { return attach(Clues.createEmpty()); } - public int dir(Clues clues) { return clues.getDir(index); } - } @Test void testCornerDownSlot() { - var clues = r0c0d4.attach(); + var clues = Clues.createEmpty().setClue(r0c0d4); // Clue op (0,0), type 4 (Corner Down) - assertEquals(Clue.LEFT_TOP4.dir, r0c0d4.dir(clues)); + assertEquals(r0c0d4.d, clues.getDir(r0c0d4.index)); // Controleer of forEachSlot het slot vindt final var found = new boolean[]{ false }; clues.forEachSlot((key, lo, hi) -> { - if (Masker.Slot.dir(key) == 4) { + if (key == r0c0d4.slotKey) { found[0] = true; // Woord zou moeten starten op (0,1) assertTrue((lo & (1L << OFF_0_1)) != 0, "Slot should start at (0,1)"); @@ -61,21 +39,21 @@ public class CornerClueTest { @Test void testCornerDownExtraction() { - var slots = Masker.slots(r0c0d4.attach(), DictData.DICT.index()); + var slots = Masker.slots(Clues.createEmpty().setClue(r0c0d4), DictData.DICT.index()); assertEquals(1, slots.length); - assertEquals(4, Masker.Slot.dir(slots[0].key())); + assertEquals(r0c0d4.d, Masker.Slot.dir(slots[0].key())); } @Test void testCornerDownLeftSlot() { - var clues = r0c1d5.attach(); + var clues = Clues.createEmpty().setClue(r0c1d5); - assertEquals(Clue.RIGHT_TOP5.dir, r0c1d5.dir(clues)); + assertEquals(r0c1d5.d, clues.getDir(r0c1d5.index)); // Controleer of forEachSlot het slot vindt final var found = new boolean[]{ false }; clues.forEachSlot((key, lo, hi) -> { - if (Masker.Slot.dir(key) == 5) { + if (key == r0c1d5.slotKey) { found[0] = true; // Woord zou moeten starten op (0,0) assertTrue((lo & (1L << OFF_0_0)) != 0, "Slot should start at (0,0)"); @@ -91,9 +69,9 @@ public class CornerClueTest { @Test void testCornerDownLeftExtraction() { - var slots = Masker.slots(r0c1d5.attach(), DictData.DICT.index()); + var slots = Masker.slots(Clues.createEmpty().setClue(r0c1d5), DictData.DICT.index()); assertEquals(1, slots.length); - assertEquals(Clue.RIGHT_TOP5.dir, Masker.Slot.dir(slots[0].key())); + assertEquals(r0c1d5.d, Masker.Slot.dir(slots[0].key())); } } diff --git a/src/test/java/puzzle/MaskerCluesTest.java b/src/test/java/puzzle/MaskerCluesTest.java index 0189bee..41d7ad0 100644 --- a/src/test/java/puzzle/MaskerCluesTest.java +++ b/src/test/java/puzzle/MaskerCluesTest.java @@ -6,13 +6,8 @@ import puzzle.Export.Clued; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import static precomp.Const9x8.OFF_0_0; -import static precomp.Const9x8.OFF_0_2; -import static precomp.Const9x8.OFF_0_3; -import static precomp.Const9x8.OFF_0_7; -import static precomp.Const9x8.OFF_0_8; -import static precomp.Const9x8.OFF_1_1; -import static precomp.Const9x8.OFF_2_2; +import static precomp.Const9x8.*; +import static precomp.Const9x8.Cell.*; import static puzzle.Masker.Clues; import static puzzle.Masker.Slot; import static puzzle.SwedishGenerator.Rng; @@ -71,21 +66,14 @@ 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); + Clues a = Clued.create(r0c0d1, r2c1d0); + Clues b = Clues.createEmpty().setClue(r0c0d1).setClue(r2c1d0); // 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); + Clues c = Clues.createEmpty().setClue(r0c0d0).setClue(r2c1d0); assertTrue(a.similarity(c) < 1.0); // Completely different @@ -102,12 +90,11 @@ public class MaskerCluesTest { assertTrue(masker.isValid(g)); // Valid clue: Right from (0,0) in 9x8 grid. Length is 8. - g.setClueLo(1L << OFF_0_0, (byte) 1); + g.setClue(r0c0d1); assertTrue(masker.isValid(g)); // 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 << OFF_0_7, (byte) 1); + Clues g2 = Clues.createEmpty().setClue(r0c7d1); assertFalse(masker.isValid(g2)); } @@ -116,39 +103,38 @@ public class MaskerCluesTest { Clues g = Clues.createEmpty(); // Room for Right clue at (0,0) (length 8) - assertTrue(g.hasRoomForClue(Slot.packSlotKey(OFF_0_0, 1))); + assertTrue(g.hasRoomForClue(r0c0d1.slotKey)); // No room for Right clue at (0,8) (length 0 < MIN_LEN) - assertFalse(g.hasRoomForClue(Slot.packSlotKey(OFF_0_8, 1))); + assertFalse(g.hasRoomForClue(r0c8d1.slotKey)); // Blocked room // Let's place a clue that leaves only 1 cell for another clue. - g.setClueLo(1L << OFF_0_2, (byte) 1); + g.setClue(r0c2d1); // Now Right at (0,0) only has (0,1) available -> length 1 < MIN_LEN (which is 2) - assertFalse(g.hasRoomForClue(Slot.packSlotKey(OFF_0_0, 1))); + assertFalse(g.hasRoomForClue(r0c0d1.slotKey)); // But enough room g.clearClueLo(0L); - g.setClueLo(1L << OFF_0_3, (byte) 1); + g.setClue(r0c3d1); // Now Right at (0,0) has (0,1), (0,2) -> length 2 == MIN_LEN - assertTrue(g.hasRoomForClue(Slot.packSlotKey(OFF_0_0, 1))); + assertTrue(g.hasRoomForClue(r0c0d1.slotKey)); } @Test void testIntersectionConstraint() { - Clues g = Clues.createEmpty(); Masker masker = new Masker(new Rng(42), new int[STACK_SIZE], 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 << OFF_0_0, (byte) 1); + Clues g = Clues.createEmpty().setClue(r0c0d1); // Clue 2: (1,2) Up. Slot cells: (0,2) // Intersection is exactly 1 cell (0,2). Valid. - g.setClueLo(1L << OFF_2_2, (byte) 2); + g.setClue(r2c2d2); assertTrue(masker.isValid(g)); // Clue 3: (1,1) Right. Slot cells: (1,2), (1,3), ... // No intersection with Clue 1 or 2. Valid. - g.setClueLo(1L << OFF_1_1, (byte) 1); + g.setClue(r1c1d1); assertTrue(masker.isValid(g)); // Now create a violation: two slots sharing 2 cells. @@ -157,9 +143,7 @@ public class MaskerCluesTest { // 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 << OFF_0_0, (byte) 4); // Corner Down - g3.setClueLo(1L << OFF_0_2, (byte) 5); // Corner Down Left + Clues g3 = Clues.createEmpty().setClue(r0c0d4).setClue(r0c2d5); assertFalse(masker.isValid(g3)); }