introduce bitloops

This commit is contained in:
mike
2026-01-22 18:47:04 +01:00
parent a659bd5162
commit 2295a7d97c
71 changed files with 254 additions and 205151 deletions

View File

@@ -1,14 +1,15 @@
package puzzle;
import module java.base;
import anno.DictGen;
import lombok.val;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import precomp.Neighbors9x8.rci;
import puzzle.Export.ClueAt;
import puzzle.Export.Clued;
import puzzle.Export.Gridded;
import puzzle.Export.LetterAt;
import puzzle.Export.Vestigium;
import puzzle.Export.Signa;
import puzzle.Export.Puzzle;
import puzzle.Export.Lettrix;
import puzzle.Export.PuzzleResult;
import puzzle.Export.Rewards;
import puzzle.Main.Opts;
@@ -28,7 +29,14 @@ import static puzzle.SwedishGenerator.Slotinfo;
import static puzzle.SwedishGenerator.fillMask;
import static puzzle.SwedishGeneratorTest.LETTER_A;
import static puzzle.SwedishGeneratorTest.LETTER_Z;
@DictGen(
packageName = "puzzle.dict950",
className = "DictData950",
scv = "/home/mike/dev/puzzle-generator/nl_score_hints_v4.csv",
simpleMax = 950,
minLen = 2,
maxLen = 8
)
public class MainTest {
static final Opts opts = new Main.Opts() {{
@@ -44,8 +52,8 @@ public class MainTest {
}};
@Test
void testExtractSlots() {
var clues = Clued.of(r0c0d1);
var grid = new Gridded(clues);
var clues = Signa.of(r0c0d1);
var grid = new Puzzle(clues);
val g = grid.grid().g;
GridBuilder.placeWord(grid.grid(), g, r0c0d1.slotKey, (1L << OFF_0_1) | (1L << OFF_0_2), 0, AB);
@@ -53,7 +61,7 @@ public class MainTest {
assertEquals(1, slots.length);
var s = slots[0];
assertEquals(8, Masker.Slot.length(s.lo(), s.hi()));
var cells = Gridded.cellWalk((byte) s.key(), s.lo(), s.hi()).mapToObj(c -> Masker.IT[c]).toArray(rci[]::new);
var cells = Puzzle.cellWalk((byte) s.key(), s.lo(), s.hi()).mapToObj(c -> Masker.IT[c]).toArray(rci[]::new);
assertEquals(0, cells[0].r());
assertEquals(1, cells[0].c());
assertEquals(0, cells[1].r());
@@ -72,7 +80,7 @@ public class MainTest {
@Test
void testForEachSlot() {
var count = new AtomicInteger(0);
Clued.of(r0c0d1).forEachSlot((key, lo, hi) -> {
Signa.of(r0c0d1).forEachSlot((key, lo, hi) -> {
count.incrementAndGet();
assertEquals(8, Long.bitCount(lo) + Long.bitCount(hi));
assertEquals(0, Masker.IT[Long.numberOfTrailingZeros(lo)].r());
@@ -91,15 +99,15 @@ public class MainTest {
}
@Test
public void testGridBasics() {
var clues = Clued.of(r2c1d2);
var grid = new Gridded(clues);
var clues = Signa.of(r2c1d2);
var grid = new Puzzle(clues);
// Test set/get
GridBuilder.placeWord(grid.grid(), grid.grid().g, r2c1d2.slotKey, (1L << OFF_1_1) | (1L << OFF_0_1), 0, AZ);
val map = grid.collect(Collectors.toMap(LetterAt::index, LetterAt::letter));
val map = grid.collect(Collectors.toMap(Lettrix::index, Lettrix::letter));
Assertions.assertEquals(LETTER_A, map.get(OFF_1_1));
Assertions.assertEquals(LETTER_Z, map.get(OFF_0_1));
var clueMap = clues.stream().collect(Collectors.toMap(ClueAt::index, ClueAt::clue));
var clueMap = clues.stream().collect(Collectors.toMap(Vestigium::index, Vestigium::clue));
Assertions.assertEquals(1, clueMap.size());
Assertions.assertEquals(UP2.dir, clueMap.get(OFF_2_1));
@@ -112,7 +120,7 @@ public class MainTest {
// Test isDigitAt
Assertions.assertFalse(clues.isClueLo(OFF_0_0));
Assertions.assertTrue(clues.isClueLo(OFF_2_1));
clueMap = clues.stream().collect(Collectors.toMap(ClueAt::index, ClueAt::clue));
clueMap = clues.stream().collect(Collectors.toMap(Vestigium::index, Vestigium::clue));
Assertions.assertEquals(UP2.dir, clueMap.get(OFF_2_1));
Assertions.assertFalse(clues.isClueLo(OFF_2_3));
Assertions.assertFalse(clues.isClueLo(OFF_1_1));
@@ -124,24 +132,24 @@ public class MainTest {
}
@Test
public void testCluesDeepCopy() {
var clues = Clued.of(r0c0d1, r0c1d2, r1c0d3, r1c1d0);
var clues = Signa.of(r0c0d1, r0c1d2, r1c0d3, r1c1d0);
var copy = clues.deepCopyGrid();
var clueMap = clues.stream().collect(Collectors.toMap(ClueAt::index, ClueAt::clue));
var clueMap = clues.stream().collect(Collectors.toMap(Vestigium::index, Vestigium::clue));
Assertions.assertEquals(RIGHT1.dir, clueMap.get(OFF_0_0));
copy.setClue(r0c0d0);
var copied = copy.stream().collect(Collectors.toMap(ClueAt::index, ClueAt::clue));
var copied = copy.stream().collect(Collectors.toMap(Vestigium::index, Vestigium::clue));
Assertions.assertEquals(DOWN0.dir, copied.get(OFF_0_0));
Assertions.assertEquals(RIGHT1.dir, clueMap.get(OFF_0_0));
}
@Test
public void testMini() {
Assertions.assertTrue(Clued.of(r1c1d3).isClueLo(OFF_1_1));
Assertions.assertTrue(Signa.of(r1c1d3).isClueLo(OFF_1_1));
}
@Test
void testFiller2() {
var mask = Clued.of(
var mask = Signa.of(
r0c0d1,
r0c3d0, r0c4d0, r0c5d0, r0c6d0, r0c7d0, r0c8d0,
r1c0d1,
@@ -151,9 +159,9 @@ public class MainTest {
r5c0d3,
r6c0d3,
r7c0d2, r7c1d2, r7c2d2, r7c8d3
);
);
Assertions.assertEquals(20, mask.clueCount());
val map = mask.stream().collect(Collectors.toMap(ClueAt::index, ClueAt::clue));
val map = mask.stream().collect(Collectors.toMap(Vestigium::index, Vestigium::clue));
Assertions.assertEquals(20, map.size());
var slots = mask.slots(DictData950.DICT950);
// var filled = fillMask(rng, slotInfo, grid, false);
@@ -163,7 +171,7 @@ public class MainTest {
void testFiller() {
System.out.println(DictData950.DICT950.index().length);
val rng = new Rng(-343913721);
var mask = Clued.of(
var mask = Signa.of(
r0c3d3, r0c6d3, r0c7d0, r0c8d0,
r1c1d1,
r2c1d1,
@@ -172,7 +180,7 @@ public class MainTest {
r5c1d1,
r6c1d1, r6c8d2,
r7c0d2, r7c1d1, r7c4d2, r7c5d2, r7c8d3
);
);
var slotInfo = mask.slots(DictData950.DICT950);
var grid = Slotinfo.grid(slotInfo);
var filled = fillMask(rng, slotInfo, grid);
@@ -181,7 +189,7 @@ public class MainTest {
Assertions.assertEquals("BEADEMT", Lemma.asWord(slotInfo[0].assign().w, Export.BYTES.get()));
Assertions.assertEquals(-1L, grid.lo);
Assertions.assertEquals(-1L, grid.hi);
var g = new Gridded(grid, mask.c());
var g = new Puzzle(grid, mask.c());
g.gridToString();
var aa = new PuzzleResult(mask, g, slotInfo, filled).exportFormatFromFilled(new Rewards(1, 1, 1));
System.out.println(String.join("\n", aa.grid()));

View File

@@ -2,8 +2,8 @@ package puzzle;
import lombok.val;
import org.junit.jupiter.api.Test;
import puzzle.Export.Clued;
import puzzle.Export.Gridded;
import puzzle.Export.Signa;
import puzzle.Export.Puzzle;
import puzzle.Export.Placed;
import puzzle.Export.PuzzleResult;
import puzzle.Export.Rewards;
@@ -36,7 +36,7 @@ public class MarkerTest {
for (var i = 0; i < 200; i++) {
for (var j = 19; j < 24; j++) {
var clues = masker.randomMask(j);
assertTrue(masker.isValid(clues), "Mask should be valid for length \n" + new Clued(clues).gridToString());
assertTrue(masker.isValid(clues), "Mask should be valid for length \n" + new Signa(clues).gridToString());
}
}
}
@@ -54,7 +54,7 @@ public class MarkerTest {
simCount++;
masker.mutate(clues);
sim += orig.similarity(clues);
assertTrue(masker.isValid(clues), "Mask should be valid for length \n" + new Clued(clues).gridToString());
assertTrue(masker.isValid(clues), "Mask should be valid for length \n" + new Signa(clues).gridToString());
}
}
System.out.println("Average similarity: " + sim / simCount);
@@ -73,21 +73,21 @@ public class MarkerTest {
simCount++;
var cross = masker.crossover(clues, clues2);
sim += Math.max(cross.similarity(clues), cross.similarity(clues2));
assertTrue(masker.isValid(cross), "Mask should be valid for length \n" + new Clued(cross).gridToString());
assertTrue(masker.isValid(cross), "Mask should be valid for length \n" + new Signa(cross).gridToString());
}
}
System.out.println("Average similarity: " + sim / simCount);
}
@Test
void testSimilarity() {
var a = Clued.of(r0c0d1, r2c1d0).c();
var b = Clued.of(r0c0d1, r2c1d0).c();
var a = Signa.of(r0c0d1, r2c1d0).c();
var b = Signa.of(r0c0d1, r2c1d0).c();
// Identity
assertEquals(1.0, a.similarity(b), 0.001);
// Different direction
var c = Clued.of(r0c0d0, r2c1d0);
var c = Signa.of(r0c0d0, r2c1d0);
assertTrue(a.similarity(c.c()) < 1.0);
// Completely different
@@ -103,10 +103,10 @@ public class MarkerTest {
assertTrue(masker.isValid(Clues.createEmpty()));
// Valid clue: Right from (0,0) in 9x8 grid. Length is 8.
assertTrue(masker.isValid(Clued.of(r0c0d1).c()));
assertTrue(masker.isValid(Signa.of(r0c0d1).c()));
// Invalid clue: Right from (0,7) in 9x8 grid. Length is 1 (too short if MIN_LEN >= 2).
assertFalse(masker.isValid(Clued.of(r0c7d1).c()));
assertFalse(masker.isValid(Signa.of(r0c7d1).c()));
}
@Test
@@ -138,18 +138,18 @@ public class MarkerTest {
// Clue 1: (0,0) Right. Slot cells: (0,1), (0,2), (0,3), (0,4), (0,5), (0,6), (0,7), (0,8)
// Clue 2: (1,2) Up. Slot cells: (0,2)
// Intersection is exactly 1 cell (0,2). Valid.
assertTrue(masker.isValid(Clued.of(r0c0d1, r2c2d2).c()));
assertTrue(masker.isValid(Signa.of(r0c0d1, r2c2d2).c()));
// Clue 3: (1,1) Right. Slot cells: (1,2), (1,3), ...
// No intersection with Clue 1 or 2. Valid.
assertTrue(masker.isValid(Clued.of(r0c0d1, r2c2d2, r1c1d1).c()));
assertTrue(masker.isValid(Signa.of(r0c0d1, r2c2d2, r1c1d1).c()));
// 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).
assertFalse(masker.isValid(Clued.of(r0c0d4, r0c2d5).c()));
assertFalse(masker.isValid(Signa.of(r0c0d4, r0c2d5).c()));
}
@Test
@@ -233,7 +233,7 @@ public class MarkerTest {
}
@Test
void testCornerDownSlot() {
var clues = Clued.of(r0c0d4);
var clues = Signa.of(r0c0d4);
// Clue op (0,0), type 4 (Corner Down)
assertEquals(r0c0d4.d, clues.getDir(r0c0d4.index));
@@ -257,14 +257,14 @@ public class MarkerTest {
@Test
void testCornerDownExtraction() {
var slots = Masker.slots(Clued.of(r0c0d4).c(), DictData950.DICT950.index());
var slots = Masker.slots(Signa.of(r0c0d4).c(), DictData950.DICT950.index());
assertEquals(1, slots.length);
assertEquals(r0c0d4.d, Masker.Slot.dir(slots[0].key()));
}
@Test
void testCornerDownLeftSlot() {
var clues = Clued.of(r0c1d5);
var clues = Signa.of(r0c1d5);
assertEquals(r0c1d5.d, clues.getDir(r0c1d5.index));
@@ -287,15 +287,15 @@ public class MarkerTest {
@Test
void testCornerDownLeftExtraction() {
var slots = Clued.of(r0c1d5).slots(DictData950.DICT950);
var slots = Signa.of(r0c1d5).slots(DictData950.DICT950);
assertEquals(1, slots.length);
assertEquals(r0c1d5.d, Masker.Slot.dir(slots[0].key()));
}
@Test
void testExportFormatFromFilled() {
val clues = Clued.of(r0c0d1, r0c5d3);
var grid = new Gridded(clues);
val clues = Signa.of(r0c0d1, r0c5d3);
var grid = new Puzzle(clues);
// key = (cellIndex << 2) | (direction)
var key = r0c0d1.slotKey;
@@ -346,7 +346,7 @@ public class MarkerTest {
var grid = SwedishGeneratorTest.createEmpty();
val clues = Clues.createEmpty();
var fillResult = new FillResult(true, 0, 0, 0, 0, new FillStats());
var puzzleResult = new PuzzleResult(new Clued(clues), new Gridded(grid, clues), new Slotinfo[0], fillResult);
var puzzleResult = new PuzzleResult(new Signa(clues), new Puzzle(grid, clues), new Slotinfo[0], fillResult);
var exported = puzzleResult.exportFormatFromFilled(new Rewards(0, 0, 0));
@@ -362,19 +362,20 @@ public class MarkerTest {
@Test
void testShardToClue() {
var bytes = Export.BYTES.get();
for (var length = 2; length <= 8; length++) {
val entry = DictData950.DICT950.index()[length];
if (entry == null) continue;
val words = entry.words();
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()));
assertTrue(clueRec.simpel() >= 0);
assertTrue(clueRec.clues().length > 0);
val rec = Meta.lookupSilent(wordVal);
assertNotNull(rec);
assertEquals(Lemma.asWord(wordVal, bytes), Lemma.asWord(rec.w(), bytes));
assertTrue(rec.simpel() >= 0);
assertTrue(rec.clues().length > 0);
}
}
}
@@ -382,13 +383,14 @@ public class MarkerTest {
@Test
void testSpecificWords() {
// These words are known to be in the CSV and likely in the dictionary
var bytes = Export.BYTES.get();
var testWords = new String[]{ "EEN", "NAAR", "IEDEREEN" };
for (var wStr : testWords) {
var w = Lemma.from(wStr);
val clueRec = Meta.lookup(w);
val clueRec = Meta.lookupSilent(w);
assertNotNull(clueRec);
assertEquals(wStr, Lemma.asWord(clueRec.w(), Export.BYTES.get()));
assertEquals(wStr, Lemma.asWord(clueRec.w(), bytes));
// Check some expected complexity values (from CSV head output, column 3)
if (wStr.equals("EEN")) {
assertEquals(451, clueRec.simpel());

View File

@@ -1,11 +1,13 @@
package puzzle;
import module java.base;
import anno.DictGen;
import anno.Dictionaries;
import lombok.val;
import org.junit.jupiter.api.Test;
import puzzle.Export.Clue;
import puzzle.Export.Clued;
import puzzle.Export.Gridded;
import puzzle.Export.Signa;
import puzzle.Export.Puzzle;
import puzzle.Masker.Clues;
import puzzle.SwedishGenerator.Rng;
import puzzle.SwedishGenerator.Slotinfo;
@@ -13,9 +15,24 @@ import puzzle.SwedishGenerator.Slotinfo;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static precomp.Const9x8.Cell.*;
import static puzzle.SwedishGenerator.fillMask;
import static puzzle.dict800.DictData.DICT800;
import static puzzle.dict900.DictData.DICT900;
import static puzzle.dict800.DictData800.DICT800;
import static puzzle.dict900.DictData900.DICT900;
@Dictionaries({ @DictGen(
packageName = "puzzle.dict900",
className = "DictData900",
scv = "/home/mike/dev/puzzle-generator/nl_score_hints_v4.csv",
simpleMax = 900,
minLen = 2,
maxLen = 8
), @DictGen(
packageName = "puzzle.dict800",
className = "DictData800",
scv = "/home/mike/dev/puzzle-generator/nl_score_hints_v4.csv",
simpleMax = 800,
minLen = 2,
maxLen = 8
) })
public class PerformanceTest {
void main() {
@@ -73,7 +90,7 @@ public class PerformanceTest {
void testIncrementalComplexity() {
// Use the complex mask from Main.java
var mask = Clued.of(
var mask = Signa.of(
r0c0d1, r0c5d0, r0c6d0, r0c7d0, r0c8d0,
r1c0d1,
r2c0d0, r2c1d0, r2c3d0, r2c4d1,
@@ -82,7 +99,7 @@ public class PerformanceTest {
r5c2d2, r5c4d1,
r6c2d1,
r7c0d2, r7c1d2, r7c2d1, r7c7d2, r7c8d2
);
);
val allSlots = Masker.slots(mask.c(), DICT900.index());
//mask.toGrid()
System.out.println("[DEBUG_LOG] \n--- Incremental Complexity Test ---");
@@ -103,7 +120,7 @@ public class PerformanceTest {
val rng = new Rng(42);
// A single horizontal slot at (0,0)
val mask = Clued.of(r0c0d1);
val mask = Signa.of(r0c0d1);
val slots = Masker.slots(mask.c(), DICT800.index());
System.out.println("[DEBUG_LOG] \n--- Single Slot Resolution ---");
@@ -160,7 +177,7 @@ public class PerformanceTest {
var slotChar = dir.slotChar;
display[cr][cc] = clueChar;
Gridded.cellWalk((byte) slot.key(), slot.lo(), slot.hi()).forEach(idx -> {
Puzzle.cellWalk((byte) slot.key(), slot.lo(), slot.hi()).forEach(idx -> {
var r = Masker.IT[idx].r();
var c = Masker.IT[idx].c();
if (display[r][c] == ' ' || (display[r][c] >= 'A' && display[r][c] <= 'D')) {

View File

@@ -0,0 +1,115 @@
package puzzle;
import module java.base;
import module java.sql;
public final class ScoreHintsTask {
public static void main(String[] args) throws Exception {
Class.forName("org.sqlite.JDBC");
try (Connection conn = DriverManager.getConnection("jdbc:sqlite:tools/hint/hint.sqlite")) {
updateCrossScores(conn, ScoreHintsTask::crossabilityScore, 1000);
}
}
static final Map<Character, Integer> LETTER_WEIGHT = Map.ofEntries(
Map.entry('E', 10), Map.entry('N', 9), Map.entry('A', 9), Map.entry('R', 8),
Map.entry('I', 8), Map.entry('O', 7), Map.entry('S', 7), Map.entry('T', 7),
Map.entry('D', 6), Map.entry('L', 6), Map.entry('K', 5), Map.entry('M', 5),
Map.entry('U', 5), Map.entry('P', 4), Map.entry('G', 4), Map.entry('H', 4),
Map.entry('V', 4), Map.entry('B', 3), Map.entry('W', 3),
Map.entry('C', 2), Map.entry('F', 2), Map.entry('Z', 2),
Map.entry('J', 1), Map.entry('Y', 1), Map.entry('Q', 0), Map.entry('X', 0)
);
static boolean isVowel(char ch) {
return ch == 'A' || ch == 'E' || ch == 'I' || ch == 'O' || ch == 'U';
}
static int crossabilityScore(String w) {
var score = 0;
var vowels = 0;
for (var i = 0; i < w.length(); i++) {
var ch = w.charAt(i);
score += LETTER_WEIGHT.getOrDefault(ch, 2);
if (isVowel(ch)) vowels++;
}
var ratio = vowels / (double) w.length();
if (ratio >= 0.35 && ratio <= 0.65) score += 8;
if (w.indexOf('Q') >= 0 || w.indexOf('X') >= 0) score -= 6;
if (w.indexOf('Y') >= 0 || w.indexOf('J') >= 0) score -= 2;
return score;
}
/**
* Updates hints.cross_score by computing a score from hints.word.
*
* @param conn open JDBC connection (PostgreSQL)
* @param scoreFn callback: scoreFn.applyAsInt(word)
* @param batchSize e.g. 1000
*/
public static void updateCrossScores(
Connection conn,
ToIntFunction<String> scoreFn,
int batchSize
) throws SQLException {
// Use a transaction for speed + consistency
final boolean prevAutoCommit = conn.getAutoCommit();
conn.setAutoCommit(false);
// Server-side cursor behavior in pgjdbc requires autoCommit=false + fetchSize>0
final String selectSql =
"SELECT id, puzzle_norm " +
"FROM hints " +
"WHERE puzzle_norm IS NOT NULL"; // optionally add: " AND cross_score IS NULL"
final String updateSql =
"UPDATE hints SET cross_score = ? WHERE id = ?";
try (PreparedStatement psSel = conn.prepareStatement(selectSql);
PreparedStatement psUpd = conn.prepareStatement(updateSql)) {
psSel.setFetchSize(batchSize);
int pending = 0;
try (ResultSet rs = psSel.executeQuery()) {
while (rs.next()) {
long id = rs.getLong("id");
String word = rs.getString("puzzle_norm");
int score;
try {
score = scoreFn.applyAsInt(word);
} catch (RuntimeException ex) {
ex.printStackTrace();
// If scoring fails, decide your policy: skip or set 0.
// Here: skip row.
continue;
}
psUpd.setInt(1, score);
psUpd.setLong(2, id);
psUpd.addBatch();
pending++;
if (pending >= batchSize) {
psUpd.executeBatch();
conn.commit();
pending = 0;
}
}
}
if (pending > 0) {
psUpd.executeBatch();
conn.commit();
}
} catch (SQLException e) {
conn.rollback();
throw e;
} finally {
conn.setAutoCommit(prevAutoCommit);
}
}
}

View File

@@ -1,16 +1,17 @@
package puzzle;
import module java.base;
import gen.GenerateDict;
import gen.GenerateNeighbors;
import anno.GenerateNeighbor;
import anno.GenerateNeighbors;
import anno.LemmaGen;
import lombok.val;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import precomp.Neighbors9x8.rci;
import puzzle.DictJavaGeneratorMulti.DictEntryDTO.IntListDTO;
import puzzle.Export.Clued;
import puzzle.Export.Gridded;
import puzzle.Export.LetterAt;
import puzzle.Export.Signa;
import puzzle.Export.Puzzle;
import puzzle.Export.Lettrix;
import puzzle.Masker.Clues;
import puzzle.Masker.Slot;
@@ -37,17 +38,15 @@ import static puzzle.LemmaData.INEREN;
import static puzzle.LemmaData.INERENA;
import static puzzle.LemmaData.INERENAE;
import static puzzle.SwedishGenerator.*;
@GenerateNeighbors(C = 4, R = 3, packageName = "precomp", className = "Neighbors4x3", MIN_LEN = 2)
@GenerateDict(
@GenerateNeighbors(@GenerateNeighbor(C = 4, R = 3, packageName = "precomp", className = "Neighbors4x3", MIN_LEN = 2))
@LemmaGen(
packageName = "puzzle",
className = "LemmaData",
scv = "nl_score_hints_v4.csv",
words = {
"EEN", "NAAR", "IEDEREEN", "A", "C", "X", "TEST", "IN", "INE", "INER", "INEREN", "INERENA", "INERENAE",
"APPLE", "AXE", "ABC", "ABD", "AZ", "AB",
"AT", "CAT", "DOGS", "APPLY", "BANAN", "BANANA", "BANANAS", "BANANASS"
},
simpleMax=900,
minLen = 2,
maxLen = 8
)
@@ -105,9 +104,9 @@ public class SwedishGeneratorTest {
@Test
void testPatternForSlotAllLetters() {
var grid = new Gridded(Clued.of(r0c0d1));
var grid = new Puzzle(Signa.of(r0c0d1));
GridBuilder.placeWord(grid.grid(), grid.grid().g, r0c0d1.slotKey, (1L << OFF_0_1) | (1L << OFF_0_2) | (1L << OFF_0_3), 0L, ABC);
val map = grid.collect(Collectors.toMap(LetterAt::index, LetterAt::letter));
val map = grid.collect(Collectors.toMap(Lettrix::index, Lettrix::letter));
assertEquals(LETTER_A, map.get(OFF_0_1));
assertEquals(LETTER_B, map.get(OFF_0_2));
assertEquals(LETTER_C, map.get(OFF_0_3));
@@ -167,9 +166,9 @@ public class SwedishGeneratorTest {
@Test
void testGrid() {
var grid = new Gridded(Clues.createEmpty());
var grid = new Puzzle(Clues.createEmpty());
GridBuilder.placeWord(grid.grid(), grid.grid().g, r0c0d1.slotKey, 1L << OFF_0_0, 0, WORD_A);
val arr = grid.collect(Collectors.toMap(LetterAt::index, LetterAt::letter));
val arr = grid.collect(Collectors.toMap(Lettrix::index, Lettrix::letter));
assertEquals(1, arr.size());
assertEquals(LETTER_A, arr.get(OFF_0_0));
}
@@ -229,7 +228,7 @@ public class SwedishGeneratorTest {
assertEquals(OFF_2_3, Slot.clueIndex(key));
assertEquals(CLUE_DOWN, Slot.dir(key));
assertFalse(Slot.horiz(key));
var cells = Gridded.cellWalk((byte) key, lo, 0L).mapToObj(i -> Masker.IT[i]).toArray(rci[]::new);
var cells = Puzzle.cellWalk((byte) key, lo, 0L).mapToObj(i -> Masker.IT[i]).toArray(rci[]::new);
assertEquals(2, cells[0].r());
assertEquals(3, cells[1].r());
assertEquals(4, cells[2].r());
@@ -268,7 +267,7 @@ public class SwedishGeneratorTest {
void testForEachSlotAndExtractSlots() {
// This should detect a slot starting at 0,1 with length 2 (0,1 and 0,2)
var dict = DictJavaGeneratorMulti.Dicts.makeDict(WORDS2);
var slots = Masker.extractSlots(Clued.of(r0c0d1).c(), dict.index());
var slots = Masker.extractSlots(Signa.of(r0c0d1).c(), dict.index());
assertEquals(1, slots.length);
var s = slots[0];
@@ -296,10 +295,10 @@ public class SwedishGeneratorTest {
var rng = new Rng(42);
var gen = new Masker(rng, new int[Masker.STACK_SIZE], Masker.Clues.createEmpty());
var c1 = new Clued(gen.randomMask(18));
var c1 = new Signa(gen.randomMask(18));
assertNotNull(c1);
var g2 = new Clued(gen.mutate(c1.deepCopyGrid().c()));
var g2 = new Signa(gen.mutate(c1.deepCopyGrid().c()));
assertNotNull(g2);
assertNotSame(c1.c(), g2.c());
@@ -311,7 +310,7 @@ public class SwedishGeneratorTest {
@Test
void testPlaceWord() {
var grid = new Gridded(Clues.createEmpty());
var grid = new Puzzle(Clues.createEmpty());
// Slot at OFF_0_0 length 3, horizontal (right)
var key = Slot.packSlotKey(0, CLUE_RIGHT);
var lo = (1L << OFF_0_0) | (1L << OFF_0_1) | (1L << OFF_0_2);
@@ -321,7 +320,7 @@ public class SwedishGeneratorTest {
// 1. Successful placement in empty grid
assertTrue(GridBuilder.placeWord(grid.grid(), grid.grid().g, key, lo, hi, w1));
var map = grid.collect(Collectors.toMap(LetterAt::index, LetterAt::letter));
var map = grid.collect(Collectors.toMap(Lettrix::index, Lettrix::letter));
assertEquals(3, map.size());
assertEquals(LETTER_A, map.get(OFF_0_0));
assertEquals(LETTER_B, map.get(OFF_0_1));
@@ -332,24 +331,24 @@ public class SwedishGeneratorTest {
// 3. Conflict: place "ABD" where "ABC" is
assertFalse(GridBuilder.placeWord(grid.grid(), grid.grid().g, key, lo, hi, ABD));
// Verify grid is unchanged (still "ABC")
map = grid.collect(Collectors.toMap(LetterAt::index, LetterAt::letter));
map = grid.collect(Collectors.toMap(Lettrix::index, Lettrix::letter));
assertEquals(3, map.size());
assertEquals(LETTER_A, map.get(OFF_0_0));
assertEquals(LETTER_B, map.get(OFF_0_1));
assertEquals(LETTER_C, map.get(OFF_0_2));
// 4. Partial placement then conflict (rollback)
grid = new Gridded(Clues.createEmpty());
grid = new Puzzle(Clues.createEmpty());
GridBuilder.placeWord(grid.grid(), grid.grid().g, Slot.packSlotKey(0, CLUE_RIGHT), 1L << OFF_0_2, 0, WORD_X); // Conflict at the end
assertFalse(GridBuilder.placeWord(grid.grid(), grid.grid().g, key, lo, hi, w1));
map = grid.stream().collect(Collectors.toMap(LetterAt::index, LetterAt::letter));
map = grid.stream().collect(Collectors.toMap(Lettrix::index, Lettrix::letter));
assertEquals(1, map.size());
assertEquals(LETTER_X, map.get(OFF_0_2));
}
@Test
void testBacktrackingHelpers() {
var grid = new Gridded(Clues.createEmpty());
var grid = new Puzzle(Clues.createEmpty());
// Slot at 0,1 length 2
var key = Slot.packSlotKey(0, CLUE_RIGHT);
var lo = (1L << OFF_0_1) | (1L << OFF_0_2);
@@ -359,14 +358,14 @@ public class SwedishGeneratorTest {
var placed = GridBuilder.placeWord(grid.grid(), grid.grid().g, key, lo, 0L, w);
assertTrue(placed);
var map = grid.collect(Collectors.toMap(LetterAt::index, LetterAt::letter));
var map = grid.collect(Collectors.toMap(Lettrix::index, Lettrix::letter));
assertEquals(2, map.size());
assertEquals(LETTER_A, map.get(OFF_0_1));
assertEquals(LETTER_Z, map.get(OFF_0_2));
grid.grid().hi = top;
grid.grid().lo = low;
map = grid.collect(Collectors.toMap(LetterAt::index, LetterAt::letter));
map = grid.collect(Collectors.toMap(Lettrix::index, Lettrix::letter));
assertEquals(0, map.size());
assertFalse(map.containsKey(OFF_0_1));
assertFalse(map.containsKey(OFF_0_2));