introduce bitloops
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user