introduce bitloops

This commit is contained in:
mike
2026-01-20 21:19:39 +01:00
parent ddce9addb5
commit b66437bb70
16 changed files with 502 additions and 564 deletions

View File

@@ -15,9 +15,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.function.LongConsumer;
import static java.nio.charset.StandardCharsets.US_ASCII;
import static puzzle.SwedishGenerator.*;
public class BuildClueAndSimpelIndex {
public static void main(String[] args) throws Exception {

View File

@@ -1,106 +1,111 @@
package puzzle;
import module java.base;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
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 puzzle.SwedishGenerator.STACK_SIZE;
public class ConnectivityTest {
@Test
void testConnectivityPenalty() {
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. 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 << Masker.offset(0, 0), (byte)1);
singleComp.setClueLo(1L << Masker.offset(2, 2), (byte)2); // Up van (2,2) naar (1,2), (0,2)
long fitnessSingle = masker.maskFitness(singleComp, 2);
// 2. Maak een masker met twee eilandjes van clues
Clues twoIslands = Clues.createEmpty();
twoIslands.setClueLo(1L << Masker.offset(0, 0), (byte)1);
twoIslands.setClueLo(Masker.offset(7, 7) < 64 ? 1L << Masker.offset(7, 7) : 0, (byte)1);
// Voor de zekerheid checken we of offset(7,7) in lo of hi zit
int off77 = Masker.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);
assertTrue(fitnessIslands > fitnessSingle + 10000, "Islands should have much higher penalty");
}
@Test
void testPhysicalAdjacency() {
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 << Masker.offset(1, 1), (byte)1);
clues.setClueLo(1L << Masker.offset(2, 1), (byte)1);
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 << Masker.offset(1, 1), (byte)1);
clues2.setClueLo(1L << Masker.offset(3, 1), (byte)1);
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
void testCornerClueConnectivity() {
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 << Masker.offset(2, 0), (byte)1); // Right
clues.setClueLo(1L << Masker.offset(1, 2), (byte)4); // Corner Down
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 = Masker.offset(2, 0);
if (offA < 64) island.setClueLo(1L << offA, (byte)1);
else island.setClueHi(1L << (offA - 64), (byte)1);
int offB = Masker.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");
}
@Test
void testConnectivityPenalty() {
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. 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)
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);
long fitnessIslands = masker.maskFitness(twoIslands, 2);
System.out.println("[DEBUG_LOG] Fitness single component: " + fitnessSingle);
System.out.println("[DEBUG_LOG] Fitness two islands: " + fitnessIslands);
assertTrue(fitnessIslands > fitnessSingle + 10000, "Islands should have much higher penalty");
}
@Test
void testPhysicalAdjacency() {
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);
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);
// 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
void testCornerClueConnectivity() {
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
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);
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

@@ -1,93 +1,90 @@
package puzzle;
import module java.base;
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;
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;
public class CornerClueTest {
@Test
void testCornerDownSlot() {
Clues clues = Clues.createEmpty();
// Clue op (0,0), type 4 (Corner Down)
int idx = Masker.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 = Masker.offset(0, 1);
assertTrue((lo & (1L << startIdx)) != 0, "Slot should start at (0,1)");
// En omlaag gaan
int secondIdx = Masker.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 = Masker.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()));
}
@Test
void testCornerDownLeftSlot() {
Clues clues = Clues.createEmpty();
// Clue op (0,1), type 5 (Corner Down Left)
// Should result in word starting at (0,0) going down.
int idx = Masker.offset(0, 1);
clues.setClueLo(1L << idx, (byte)5);
assertEquals(5, clues.getDir(idx));
// Controleer of forEachSlot het slot vindt
final boolean[] found = {false};
clues.forEachSlot((key, lo, hi) -> {
if (Masker.Slot.dir(key) == 5) {
found[0] = true;
// Woord zou moeten starten op (0,0)
int startIdx = Masker.offset(0, 0);
assertTrue((lo & (1L << startIdx)) != 0, "Slot should start at (0,0)");
// En omlaag gaan
int secondIdx = Masker.offset(1, 0);
assertTrue((lo & (1L << secondIdx)) != 0, "Slot should continue to (1,0)");
// Lengte van het slot zou 8 moeten zijn (van rij 0 t/m 7 in kolom 0)
assertEquals(8, Masker.Slot.length(lo, hi));
}
});
assertTrue(found[0], "Corner Down Left slot should be found");
}
@Test
void testCornerDownLeftExtraction() {
Clues clues = Clues.createEmpty();
int idx = Masker.offset(0, 1);
clues.setClueLo(1L << idx, (byte)5);
DictEntry[] dict = DictData.DICT.index();
Slotinfo[] slots = Masker.slots(clues, dict);
assertEquals(1, slots.length);
assertEquals(5, Masker.Slot.dir(slots[0].key()));
}
@Test
void testCornerDownSlot() {
var clues = Clues.createEmpty();
// Clue op (0,0), type 4 (Corner Down)
var idx = OFF_0_0;
clues.setClueLo(1L << idx, (byte) 4);
assertEquals(4, clues.getDir(idx));
// Controleer of forEachSlot het slot vindt
final var found = new boolean[]{ false };
clues.forEachSlot((key, lo, hi) -> {
if (Masker.Slot.dir(key) == 4) {
found[0] = true;
// Woord zou moeten starten op (0,1)
assertTrue((lo & (1L << OFF_0_1)) != 0, "Slot should start at (0,1)");
// En omlaag gaan
assertTrue((lo & (1L << OFF_1_1)) != 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() {
var clues = Clues.createEmpty();
clues.setClueLo(1L << OFF_0_0, (byte) 4);
var dict = DictData.DICT.index();
var slots = Masker.slots(clues, dict);
assertEquals(1, slots.length);
assertEquals(4, Masker.Slot.dir(slots[0].key()));
}
@Test
void testCornerDownLeftSlot() {
var clues = Clues.createEmpty();
// Clue op (0,1), type 5 (Corner Down Left)
// Should result in word starting at (0,0) going down.
var idx = OFF_0_1;
clues.setClueLo(1L << idx, (byte) 5);
assertEquals(5, clues.getDir(idx));
// Controleer of forEachSlot het slot vindt
final var found = new boolean[]{ false };
clues.forEachSlot((key, lo, hi) -> {
if (Masker.Slot.dir(key) == 5) {
found[0] = true;
// Woord zou moeten starten op (0,0)
assertTrue((lo & (1L << OFF_0_0)) != 0, "Slot should start at (0,0)");
// En omlaag gaan
assertTrue((lo & (1L << OFF_1_0)) != 0, "Slot should continue to (1,0)");
// Lengte van het slot zou 8 moeten zijn (van rij 0 t/m 7 in kolom 0)
assertEquals(8, Masker.Slot.length(lo, hi));
}
});
assertTrue(found[0], "Corner Down Left slot should be found");
}
@Test
void testCornerDownLeftExtraction() {
var clues = Clues.createEmpty();
clues.setClueLo(1L << OFF_0_1, (byte) 5);
var dict = DictData.DICT.index();
var slots = Masker.slots(clues, dict);
assertEquals(1, slots.length);
assertEquals(5, Masker.Slot.dir(slots[0].key()));
}
}

View File

@@ -2,6 +2,7 @@ package puzzle;
import module java.base;
import lombok.val;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import puzzle.Export.Clued;
import puzzle.Export.Gridded;
@@ -16,6 +17,10 @@ import puzzle.SwedishGeneratorTest.Idx;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static precomp.Const9x8.OFF_0_1;
import static precomp.Const9x8.OFF_0_2;
import static precomp.Const9x8.OFF_0_3;
import static precomp.Const9x8.OFF_0_4;
import static puzzle.Export.Clue.LEFT;
import static puzzle.Export.Clue.RIGHT;
import static puzzle.SwedishGenerator.C;
@@ -24,10 +29,6 @@ import static puzzle.SwedishGenerator.FillStats;
import static puzzle.SwedishGenerator.R;
import static puzzle.Masker.Slot;
import static puzzle.GridBuilder.placeWord;
import static puzzle.SwedishGeneratorTest.OFF_0_1;
import static puzzle.SwedishGeneratorTest.OFF_0_2;
import static puzzle.SwedishGeneratorTest.OFF_0_3;
import static puzzle.SwedishGeneratorTest.OFF_0_4;
import static puzzle.SwedishGeneratorTest.TEST;
public class ExportFormatTest {
@@ -104,20 +105,17 @@ public class ExportFormatTest {
assertTrue(row.matches("#+"));
}
}
Path shardKey(long word) {
return Path.of("src/main/generated-sources/puzzle").resolve(Lemma.unpackSize(word) + 1 + ".idx");
}
@Test
void testShardToClue() {
for (int length = 2; length <= 8; length++) {
for (var length = 2; length <= 8; length++) {
val entry = DictData.DICT.index()[length];
if (entry == null) continue;
val words = entry.words();
for (int i = 0; i < Math.min(words.length, 5); i++) {
val wordVal = words[i];
val word = Lemma.asWord(wordVal, Export.BYTES.get());
val clueRec = Meta.lookup(wordVal);
for (var i = 0; i < Math.min(words.length, 5); i++) {
val wordVal = words[i];
val word = Lemma.asWord(wordVal, Export.BYTES.get());
val clueRec = Meta.lookup(wordVal);
assertNotNull(clueRec);
assertEquals(word, Lemma.asWord(clueRec.w(), Export.BYTES.get()));
@@ -130,43 +128,28 @@ public class ExportFormatTest {
@Test
void testSpecificWords() {
// These words are known to be in the CSV and likely in the dictionary
String[] testWords = { "EEN", "NAAR", "IEDEREEN" };
for (String wStr : testWords) {
long w = Lemma.from(wStr);
int L = wStr.length();
var entry = DictData.DICT.index()[L];
if (entry == null) continue;
var testWords = new String[]{ "EEN", "NAAR", "IEDEREEN" };
for (var wStr : testWords) {
var w = Lemma.from(wStr);
// Find index of word in entry
int idx = -1;
long[] words = entry.words();
for (int i = 0; i < words.length; i++) {
if (Lemma.asWord(words[i], Export.BYTES.get()).equals(wStr)) {
idx = i;
break;
}
val clueRec = Meta.lookup(w);
assertNotNull(clueRec);
assertEquals(wStr, Lemma.asWord(clueRec.w(), Export.BYTES.get()));
// Check some expected complexity values (from CSV head output, column 3)
if (wStr.equals("EEN")) {
assertEquals(451, clueRec.simpel());
assertEquals("een geheel vormend", clueRec.clues()[0]);
}
if (wStr.equals("NAAR")) {
assertEquals(497, clueRec.simpel());
assertEquals("onaangenaam, vervelend, rot, niet leuk", clueRec.clues()[0]);
}
if (wStr.equals("IEDEREEN")) {
assertEquals(501, clueRec.simpel());
assertEquals("elke persoon", clueRec.clues()[0]);
}
if (idx != -1) {
val clueRec = Meta.lookup(w);
assertNotNull(clueRec);
assertEquals(wStr, Lemma.asWord(clueRec.w(), Export.BYTES.get()));
// Check some expected complexity values (from CSV head output, column 3)
if (wStr.equals("EEN")) {
assertEquals(451, clueRec.simpel());
assertEquals("een geheel vormend", clueRec.clues()[0]);
}
if (wStr.equals("NAAR")) {
assertEquals(497, clueRec.simpel());
assertEquals("onaangenaam, vervelend, rot, niet leuk", clueRec.clues()[0]);
}
if (wStr.equals("IEDEREEN")) {
assertEquals(501, clueRec.simpel());
assertEquals("elke persoon", clueRec.clues()[0]);
}
assertTrue(clueRec.clues().length > 0);
}
assertTrue(clueRec.clues().length > 0);
}
}
}

View File

@@ -16,11 +16,17 @@ import puzzle.SwedishGenerator.Rng;
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_1;
import static precomp.Const9x8.OFF_0_2;
import static precomp.Const9x8.OFF_1_1;
import static precomp.Const9x8.OFF_1_2;
import static precomp.Const9x8.OFF_2_1;
import static precomp.Const9x8.OFF_2_3;
import static puzzle.Export.Clue.DOWN;
import static puzzle.Export.Clue.LEFT;
import static puzzle.Export.Clue.RIGHT;
import static puzzle.Export.Clue.UP;
import static puzzle.SwedishGenerator.Dict;
import static puzzle.SwedishGenerator.Lemma;
import static puzzle.SwedishGenerator.Slotinfo;
import static puzzle.SwedishGenerator.fillMask;
@@ -36,13 +42,6 @@ import static puzzle.SwedishGeneratorTest.Idx.IDX_1_1;
import static puzzle.SwedishGeneratorTest.Idx.IDX_2_1;
import static puzzle.SwedishGeneratorTest.LETTER_A;
import static puzzle.SwedishGeneratorTest.LETTER_Z;
import static puzzle.SwedishGeneratorTest.OFF_0_0;
import static puzzle.SwedishGeneratorTest.OFF_0_1;
import static puzzle.SwedishGeneratorTest.OFF_0_2;
import static puzzle.SwedishGeneratorTest.OFF_1_1;
import static puzzle.SwedishGeneratorTest.OFF_1_2;
import static puzzle.SwedishGeneratorTest.OFF_2_1;
import static puzzle.SwedishGeneratorTest.OFF_2_3;
public class MainTest {
@@ -57,7 +56,6 @@ public class MainTest {
this.tries = 1;
this.verbose = false;
}};
static final Dict dict = DictData.DICT;//loadDict(opts.wordsPath);
@Test
void testExtractSlots() {
@@ -68,7 +66,7 @@ public class MainTest {
val g = grid.grid().g;
GridBuilder.placeWord(grid.grid(), g, key, (1L << OFF_0_1) | (1L << OFF_0_2), 0, AB);
var slots = Masker.extractSlots(clues, dict.index());
var slots = Masker.extractSlots(clues, DictData.DICT.index());
assertEquals(1, slots.length);
var s = slots[0];
assertEquals(8, Masker.Slot.length(s.lo(), s.hi()));
@@ -184,7 +182,7 @@ public class MainTest {
Assertions.assertEquals(20, mask.clueCount());
val map = mask.stream().collect(Collectors.toMap(ClueAt::index, ClueAt::clue));
Assertions.assertEquals(20, map.size());
var slots = Masker.slots(mask.c(), dict.index());
var slots = Masker.slots(mask.c(), DictData.DICT.index());
// var filled = fillMask(rng, slotInfo, grid, false);
// val res = new PuzzleResult(new Clued(mask), new Gridded(grid), slotInfo, filled).exportFormatFromFilled(0, new Rewards(0, 0, 0));
}
@@ -200,7 +198,7 @@ public class MainTest {
" 1 \n" +
" 1 2\n" +
"21 22 3");
var slotInfo = Masker.slots(mask.c(), dict.index());
var slotInfo = Masker.slots(mask.c(), DictData.DICT.index());
var grid = Slotinfo.grid(slotInfo);
var filled = fillMask(rng, slotInfo, grid);
Assertions.assertTrue(filled.ok(), "Puzzle generation failed (not ok)");

View File

@@ -1,13 +1,22 @@
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 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 puzzle.Masker.Clues;
import static puzzle.SwedishGenerator.*;
import static puzzle.Masker.Slot;
import static puzzle.SwedishGenerator.Rng;
import static puzzle.SwedishGenerator.STACK_SIZE;
public class MaskerCluesTest {
@@ -18,7 +27,7 @@ public class MaskerCluesTest {
for (int i = 0; i < 200; i++) {
for (int j = 19; j < 24; j++) {
var clues = masker.randomMask(j);
assertTrue(masker.isValid(clues, MIN_LEN), "Mask should be valid for length \n" + new Clued(clues).gridToString());
assertTrue(masker.isValid(clues), "Mask should be valid for length \n" + new Clued(clues).gridToString());
}
}
}
@@ -36,7 +45,7 @@ public class MaskerCluesTest {
simCount++;
masker.mutate(clues);
sim += orig.similarity(clues);
assertTrue(masker.isValid(clues, MIN_LEN), "Mask should be valid for length \n" + new Clued(clues).gridToString());
assertTrue(masker.isValid(clues), "Mask should be valid for length \n" + new Clued(clues).gridToString());
}
}
System.out.println("Average similarity: " + sim / simCount);
@@ -55,7 +64,7 @@ public class MaskerCluesTest {
simCount++;
var cross = masker.crossover(clues, clues2);
sim += Math.max(cross.similarity(clues), cross.similarity(clues2));
assertTrue(masker.isValid(cross, MIN_LEN), "Mask should be valid for length \n" + new Clued(cross).gridToString());
assertTrue(masker.isValid(cross), "Mask should be valid for length \n" + new Clued(cross).gridToString());
}
}
System.out.println("Average similarity: " + sim / simCount);
@@ -90,16 +99,16 @@ public class MaskerCluesTest {
void testIsValid() {
Masker masker = new Masker(new Rng(42), new int[STACK_SIZE], Clues.createEmpty());
Clues g = Clues.createEmpty();
assertTrue(masker.isValid(g, MIN_LEN));
assertTrue(masker.isValid(g));
// Valid clue: Right from (0,0) in 9x8 grid. Length is 8.
g.setClueLo(1L << Masker.offset(0, 0), (byte) 1);
assertTrue(masker.isValid(g, MIN_LEN));
g.setClueLo(1L << OFF_0_0, (byte) 1);
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 << Masker.offset(0, 7), (byte) 1);
assertFalse(masker.isValid(g2, MIN_LEN));
g2.setClueLo(1L << OFF_0_7, (byte) 1);
assertFalse(masker.isValid(g2));
}
@Test
@@ -107,22 +116,22 @@ public class MaskerCluesTest {
Clues g = Clues.createEmpty();
// Room for Right clue at (0,0) (length 8)
assertTrue(g.hasRoomForClue(Slot.packSlotKey(Masker.offset(0, 0), 1)));
assertTrue(g.hasRoomForClue(Slot.packSlotKey(OFF_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)));
assertFalse(g.hasRoomForClue(Slot.packSlotKey(OFF_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);
g.setClueLo(1L << OFF_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)));
assertFalse(g.hasRoomForClue(Slot.packSlotKey(OFF_0_0, 1)));
// But enough room
g.clearClueLo(0L);
g.setClueLo(1L << Masker.offset(0, 3), (byte) 1);
g.setClueLo(1L << OFF_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)));
assertTrue(g.hasRoomForClue(Slot.packSlotKey(OFF_0_0, 1)));
}
@Test
@@ -130,17 +139,17 @@ public class MaskerCluesTest {
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 << Masker.offset(0, 0), (byte) 1);
g.setClueLo(1L << OFF_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(masker.isValid(g, MIN_LEN));
g.setClueLo(1L << OFF_2_2, (byte) 2);
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 << Masker.offset(1, 1), (byte) 1);
assertTrue(masker.isValid(g, MIN_LEN));
g.setClueLo(1L << OFF_1_1, (byte) 1);
assertTrue(masker.isValid(g));
// Now create a violation: two slots sharing 2 cells.
// We can do this with Corner Down and another clue.
@@ -149,9 +158,9 @@ public class MaskerCluesTest {
// 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(masker.isValid(g3, MIN_LEN));
g3.setClueLo(1L << OFF_0_0, (byte) 4); // Corner Down
g3.setClueLo(1L << OFF_0_2, (byte) 5); // Corner Down Left
assertFalse(masker.isValid(g3));
}
@Test
@@ -160,11 +169,11 @@ public class MaskerCluesTest {
Clues g = Clues.createEmpty();
// Dir 6 (x=1, r=1, v=0) is invalid
g.setClueLo(1L << 0, (byte) 6);
assertFalse(masker.isValid(g,MIN_LEN));
assertFalse(masker.isValid(g));
// Dir 7 (x=1, r=1, v=1) is invalid
Clues g2 = Clues.createEmpty();
g2.setClueLo(1L << 0, (byte) 7);
assertFalse(masker.isValid(g2,MIN_LEN));
assertFalse(masker.isValid(g2));
}
}

View File

@@ -3,6 +3,7 @@ package puzzle;
import module java.base;
import lombok.val;
import org.junit.jupiter.api.Test;
import puzzle.Export.Clue;
import puzzle.Export.Clued;
import puzzle.Export.Gridded;
import puzzle.Masker.Clues;
@@ -12,12 +13,14 @@ import puzzle.SwedishGenerator.Slotinfo;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static puzzle.SwedishGenerator.fillMask;
import static puzzle.dict800.DictData.DICT800;
import static puzzle.dict900.DictData.DICT900;
public class PerformanceTest {
final DictEntry[] EN = DictData.DICT.index();
final DictEntry[] EN = DICT800.index();
void main() {
testPerformance();
testIncrementalComplexity();
}
@Test
void testPerformance() {
@@ -80,7 +83,7 @@ public class PerformanceTest {
" 1 \n" +
"221 22\n";
val mask = Clued.parse(maskStr);
val allSlots = Masker.slots(mask.c(), EN);
val allSlots = Masker.slots(mask.c(), DICT900.index());
//mask.toGrid()
System.out.println("[DEBUG_LOG] \n--- Incremental Complexity Test ---");
System.out.println("[DEBUG_LOG] Full Slot Layout:");
@@ -123,7 +126,9 @@ public class PerformanceTest {
for (Slotinfo s : slots) s.assign().w = 0;
val result = fillMask(rng, slots, Slotinfo.grid(slots));
if (result.ok()) successCount++;
if (result.ok()) {
successCount++;
}
totalNodes += result.nodes();
totalBacktracks += result.backtracks();
}
@@ -132,6 +137,7 @@ public class PerformanceTest {
System.out.printf(Locale.ROOT, "[DEBUG_LOG] %s: %d/%d SUCCESS | avg nodes=%d | avg backtracks=%d | total time=%.3fs%n",
label, successCount, iterations, totalNodes / iterations, totalBacktracks / iterations, totalDuration);
visualizeSlots(slots);
}
private void visualizeSlots(Slotinfo[] slots) {
@@ -141,39 +147,17 @@ public class PerformanceTest {
for (int r = 0; r < R; r++) Arrays.fill(display[r], ' ');
for (Slotinfo slot : slots) {
int key = slot.key();
int dir = Masker.Slot.dir(key);
int clueIdx = Masker.Slot.clueIndex(key);
int key = slot.key();
Clue dir = Clue.from(Masker.Slot.dir(key));
int clueIdx = Masker.Slot.clueIndex(key);
int cr = Masker.IT[clueIdx].r();
int cc = Masker.IT[clueIdx].c();
// User requested: aAAAA for a four letter to RIGHT clue slot.
// SwedishGenerator: 1=RIGHT, 0=DOWN, 2=UP, 3=LEFT
char clueChar;
char slotChar;
switch (dir) {
case 1:
clueChar = 'a';
slotChar = 'A';
break; // RIGHT
case 0:
clueChar = 'b';
slotChar = 'B';
break; // DOWN
case 2:
clueChar = 'c';
slotChar = 'C';
break; // UP
case 3:
clueChar = 'd';
slotChar = 'D';
break; // LEFT
default:
clueChar = '?';
slotChar = '?';
}
char clueChar = dir.clueChar;
char slotChar = dir.slotChar;
display[cr][cc] = clueChar;
Gridded.cellWalk((byte) slot.key(), slot.lo(), slot.hi()).forEach(idx -> {

View File

@@ -12,6 +12,7 @@ import puzzle.Masker.Clues;
import puzzle.Masker.Slot;
import static org.junit.jupiter.api.Assertions.*;
import static precomp.Const9x8.*;
import static puzzle.SwedishGenerator.*;
import static puzzle.SwedishGeneratorTest.Idx.IDX_0_0;
@@ -78,31 +79,6 @@ public class SwedishGeneratorTest {
static final byte CLUE_UP = 2;
static final byte CLUE_LEFT = 3;
static final int OFF_1_0 = Masker.offset(1, 0);
static final int OFF_1_1 = Masker.offset(1, 1);
static final int OFF_1_2 = Masker.offset(1, 2);
static final int OFF_1_3 = Masker.offset(1, 3);
static final int OFF_1_4 = Masker.offset(1, 4);
static final int OFF_1_5 = Masker.offset(1, 5);
static final int OFF_2_1 = Masker.offset(2, 1);
static final int OFF_2_3 = Masker.offset(2, 3);
static final int OFF_2_2 = Masker.offset(2, 2);
static final int OFF_2_4 = Masker.offset(2, 4);
static final int OFF_0_0 = Masker.offset(0, 0);
static final int OFF_0_4 = Masker.offset(0, 4);
static final int OFF_0_5 = Masker.offset(0, 5);
static final int OFF_0_1 = Masker.offset(0, 1);
static final int OFF_0_2 = Masker.offset(0, 2);
static final int OFF_0_3 = Masker.offset(0, 3);
static final int OFF_2_0 = Masker.offset(2, 0);
static final int OFF_2_5 = Masker.offset(2, 5);
static final int OFF_3_5 = Masker.offset(3, 5);
static final int OFF_4_5 = Masker.offset(4, 5);
static final int OFF_3_0 = Masker.offset(3, 0);
static final int OFF_3_1 = Masker.offset(3, 1);
static final int OFF_3_2 = Masker.offset(3, 2);
static final int OFF_3_3 = Masker.offset(3, 3);
static final int OFF_3_4 = Masker.offset(3, 4);
static final byte D_BYTE_2 = CLUE_RIGHT;
enum Idx {