introduce bitloops

This commit is contained in:
mike
2026-01-20 03:43:02 +01:00
parent 28f448d178
commit 0dcfbebcb0
3 changed files with 263 additions and 180 deletions

View File

@@ -1,117 +1,168 @@
package puzzle;
import module java.base;
import lombok.val;
import org.junit.jupiter.api.Test;
import puzzle.Export.Clued;
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));
}
@Test
void testValidRandomMask() {
Rng rng = new Rng(42);
Masker masker = new Masker(rng, new int[STACK_SIZE], Clues.createEmpty());
for (int i = 0; i < 200; i++) {
for (int j = 19; j < 24; j++) {
var clues = masker.randomMask(j);
assertTrue(clues.isValid(MIN_LEN), "Mask should be valid for length \n" + new Clued(clues).gridToString());
}
}
}
@Test
void testValidMutate() {
Rng rng = new Rng(42);
var cache = Clues.createEmpty();
Masker masker = new Masker(rng, new int[STACK_SIZE], cache);
double sim = 0.0;
double simCount = 0.0;
for (int i = 0; i < 200; i++) {
for (int j = 19; j < 24; j++) {
var clues = masker.randomMask(j);
val orig = cache.from(clues);
simCount++;
masker.mutate(clues);
sim += orig.similarity(clues);
assertTrue(clues.isValid(MIN_LEN), "Mask should be valid for length \n" + new Clued(clues).gridToString());
}
}
System.out.println("Average similarity: " + sim / simCount);
}
@Test
void testCross() {
Rng rng = new Rng(42);
var cache = Clues.createEmpty();
Masker masker = new Masker(rng, new int[STACK_SIZE], cache);
double sim = 0.0;
double simCount = 0.0;
for (int i = 0; i < 200; i++) {
for (int j = 19; j < 24; j++) {
var clues = masker.randomMask(j);
var clues2 = masker.randomMask(j);
simCount++;
var cross = masker.crossover(clues, clues2);
sim += Math.max(cross.similarity(clues), cross.similarity(clues2));
assertTrue(cross.isValid(MIN_LEN), "Mask should be valid for length \n" + new Clued(cross).gridToString());
}
}
System.out.println("Average similarity: " + sim / simCount);
}
@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));
}
}