introduce bitloops
This commit is contained in:
@@ -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()));
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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')) {
|
||||
|
||||
115
src/test/java/puzzle/ScoreHintsTask.java
Normal file
115
src/test/java/puzzle/ScoreHintsTask.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
|
||||
Reference in New Issue
Block a user