introduce bitloops

This commit is contained in:
mike
2026-01-21 00:56:18 +01:00
parent 78f72a024e
commit 92a736aa0a
7 changed files with 108 additions and 110 deletions

View File

@@ -43,17 +43,16 @@ public class MainTest {
}};
@Test
void testExtractSlots() {
var clues = Clued.of(r0c0d1);
val key = r0c0d1.slotKey;
var grid = new Gridded(clues);
val g = grid.grid().g;
GridBuilder.placeWord(grid.grid(), g, key, (1L << OFF_0_1) | (1L << OFF_0_2), 0, AB);
var clues = Clued.of(r0c0d1);
var grid = new Gridded(clues);
val g = grid.grid().g;
GridBuilder.placeWord(grid.grid(), g, r0c0d1.slotKey, (1L << OFF_0_1) | (1L << OFF_0_2), 0, AB);
var slots = Masker.extractSlots(clues, DictData.DICT.index());
var slots = clues.slots();
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 = Gridded.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());
@@ -71,9 +70,8 @@ public class MainTest {
@Test
void testForEachSlot() {
var clues = Clued.of(r0c0d1);
var count = new AtomicInteger(0);
clues.forEachSlot((key, lo, hi) -> {
Clued.of(r0c0d1).forEachSlot((key, lo, hi) -> {
count.incrementAndGet();
assertEquals(8, Long.bitCount(lo) + Long.bitCount(hi));
assertEquals(0, Masker.IT[Long.numberOfTrailingZeros(lo)].r());
@@ -92,13 +90,12 @@ public class MainTest {
}
@Test
public void testGridBasics() {
var clues = new Clued(Clued.of(r2c1d2));
val key = r2c1d2.slotKey;
var grid = new Gridded(clues.c());
var clues = Clued.of(r2c1d2);
var grid = new Gridded(clues);
// Test set/get
GridBuilder.placeWord(grid.grid(), grid.grid().g, key, (1L << OFF_1_1) | (1L << OFF_0_1), 0, AZ);
val map = grid.stream(clues.c()).collect(Collectors.toMap(LetterAt::index, LetterAt::letter));
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));
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));
@@ -126,7 +123,7 @@ public class MainTest {
}
@Test
public void testCluesDeepCopy() {
var clues = new Clued(Clued.of(r0c0d1, r0c1d2, r1c0d3, r1c1d0));
var clues = Clued.of(r0c0d1, r0c1d2, r1c0d3, r1c1d0);
var copy = clues.deepCopyGrid();
var clueMap = clues.stream().collect(Collectors.toMap(ClueAt::index, ClueAt::clue));
@@ -139,12 +136,10 @@ public class MainTest {
}
@Test
public void testMini() {
var clues = Clued.of(r1c1d3);
Assertions.assertTrue(clues.isClueLo(OFF_1_1));
Assertions.assertTrue(Clued.of(r1c1d3).isClueLo(OFF_1_1));
}
@Test
void testFiller2() {
val rng = new Rng(-343913721);
val mask = Clued.parse(
"1 000000\n" +
"1 \n" +
@@ -157,7 +152,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(), DictData.DICT.index());
var slots = mask.slots();
// 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));
}
@@ -173,7 +168,7 @@ public class MainTest {
" 1 \n" +
" 1 2\n" +
"21 22 3");
var slotInfo = Masker.slots(mask.c(), DictData.DICT.index());
var slotInfo = mask.slots();
var grid = Slotinfo.grid(slotInfo);
var filled = fillMask(rng, slotInfo, grid);
Assertions.assertTrue(filled.ok(), "Puzzle generation failed (not ok)");

View File

@@ -79,15 +79,15 @@ public class MarkerTest {
}
@Test
void testSimilarity() {
var a = Clued.of(r0c0d1, r2c1d0);
var b = Clued.of(r0c0d1, r2c1d0);
var a = Clued.of(r0c0d1, r2c1d0).c();
var b = Clued.of(r0c0d1, r2c1d0).c();
// Identity
assertEquals(1.0, a.similarity(b), 0.001);
// Different direction
var c = Clued.of(r0c0d0, r2c1d0);
assertTrue(a.similarity(c) < 1.0);
assertTrue(a.similarity(c.c()) < 1.0);
// Completely different
var d = Clues.createEmpty();
@@ -102,10 +102,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)));
assertTrue(masker.isValid(Clued.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)));
assertFalse(masker.isValid(Clued.of(r0c7d1).c()));
}
@Test
@@ -137,18 +137,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)));
assertTrue(masker.isValid(Clued.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)));
assertTrue(masker.isValid(Clued.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)));
assertFalse(masker.isValid(Clued.of(r0c0d4, r0c2d5).c()));
}
@Test
@@ -256,7 +256,7 @@ public class MarkerTest {
@Test
void testCornerDownExtraction() {
var slots = Masker.slots(Clued.of(r0c0d4), DictData.DICT.index());
var slots = Masker.slots(Clued.of(r0c0d4).c(), DictData.DICT.index());
assertEquals(1, slots.length);
assertEquals(r0c0d4.d, Masker.Slot.dir(slots[0].key()));
}
@@ -286,7 +286,7 @@ public class MarkerTest {
@Test
void testCornerDownLeftExtraction() {
var slots = Masker.slots(Clued.of(r0c1d5), DictData.DICT.index());
var slots = Clued.of(r0c1d5).slots();
assertEquals(1, slots.length);
assertEquals(r0c1d5.d, Masker.Slot.dir(slots[0].key()));
@@ -303,7 +303,7 @@ public class MarkerTest {
assertTrue(placeWord(grid.grid(), grid.grid().g, key, lo, 0L, TEST));
var fillResult = new FillResult(true, 0, 0, 0, 0, new FillStats());
var puzzleResult = new PuzzleResult(new Clued(clues), grid, new Slotinfo[]{
var puzzleResult = new PuzzleResult(clues, grid, new Slotinfo[]{
new Slotinfo(key, lo, 0L, 0, new Assign(TEST), null, 0)
}, fillResult);

View File

@@ -4,6 +4,7 @@ import module java.base;
import lombok.val;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import precomp.Neighbors9x8.rci;
import puzzle.Export.Clued;
import puzzle.Export.Gridded;
import puzzle.DictJavaGeneratorMulti.DictEntryDTO.IntListDTO;
@@ -37,6 +38,9 @@ import static puzzle.SwedishGenerator.*;
public class SwedishGeneratorTest {
public static final long WORD_A = Lemma.from("A");
public static final long WORD_C = Lemma.from("C");
public static final long WORD_X = Lemma.from("X");
static Grid createEmpty() { return new Grid(new byte[SIZE], X, X); }
record Context(long[] bitset) {
@@ -86,11 +90,9 @@ public class SwedishGeneratorTest {
@Test
void testPatternForSlotAllLetters() {
var key = r0c0d1.slotKey;
val clues = Clued.of(r0c0d1);
var grid = new Gridded(clues);
GridBuilder.placeWord(grid.grid(), grid.grid().g, key, (1L << OFF_0_1) | (1L << OFF_0_2) | (1L << OFF_0_3), 0L, ABC);
val map = grid.stream(clues).collect(Collectors.toMap(LetterAt::index, LetterAt::letter));
var grid = new Gridded(Clued.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));
assertEquals(LETTER_A, map.get(OFF_0_1));
assertEquals(LETTER_B, map.get(OFF_0_2));
assertEquals(LETTER_C, map.get(OFF_0_3));
@@ -99,8 +101,8 @@ public class SwedishGeneratorTest {
@Test
void testPatternForSlotMixed() {
var grid = createEmpty();
GridBuilder.placeWord(grid, grid.g, r0c0d1.slotKey, 1L << OFF_0_0, 0, Lemma.from("A"));
GridBuilder.placeWord(grid, grid.g, r0c0d1.slotKey, 1L << OFF_2_0, 0, Lemma.from("C"));
GridBuilder.placeWord(grid, grid.g, r0c0d1.slotKey, 1L << OFF_0_0, 0, WORD_A);
GridBuilder.placeWord(grid, grid.g, r0c0d1.slotKey, 1L << OFF_2_0, 0, WORD_C);
var key = Slot.packSlotKey(OFF_1_0, CLUE_RIGHT);
var pattern = patternForSlot(grid.lo, grid.hi, grid.g, key, 7L, 0L);
assertEquals(14081L, pattern);
@@ -118,7 +120,7 @@ public class SwedishGeneratorTest {
void testPatternForSlotSingleLetter() {
var grid = createEmpty();
//Slot.packSlotKey(0, CLUE_RIGHT)
GridBuilder.placeWord(grid, grid.g, r0c0d1.slotKey, 1L << OFF_0_0, 0, Lemma.from("A"));
GridBuilder.placeWord(grid, grid.g, r0c0d1.slotKey, 1L << OFF_0_0, 0, WORD_A);
var key = Slot.packSlotKey(1, CLUE_RIGHT);
var pattern = patternForSlot(grid.lo, grid.hi, grid.g, key, 7L, 0L);
assertEquals(1L, pattern);
@@ -150,10 +152,9 @@ public class SwedishGeneratorTest {
@Test
void testGrid() {
var empty = Clues.createEmpty();
var grid = new Gridded(empty);
GridBuilder.placeWord(grid.grid(), grid.grid().g, r0c0d1.slotKey, 1L << OFF_0_0, 0, Lemma.from("A"));
val arr = grid.stream(empty).collect(Collectors.toMap(LetterAt::index, LetterAt::letter));
var grid = new Gridded(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));
assertEquals(1, arr.size());
assertEquals(LETTER_A, arr.get(OFF_0_0));
}
@@ -213,13 +214,13 @@ 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).toArray();
assertEquals(2, Masker.IT[cells[0]].r());
assertEquals(3, Masker.IT[cells[1]].r());
assertEquals(4, Masker.IT[cells[2]].r());
assertEquals(5, Masker.IT[cells[0]].c());
assertEquals(5, Masker.IT[cells[1]].c());
assertEquals(5, Masker.IT[cells[2]].c());
var cells = Gridded.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());
assertEquals(5, cells[0].c());
assertEquals(5, cells[1].c());
assertEquals(5, cells[2].c());
assertTrue(Slot.horiz(CLUE_RIGHT)); // right
assertFalse(Slot.horiz(CLUE_DOWN)); // down
@@ -251,9 +252,8 @@ public class SwedishGeneratorTest {
@Test
void testForEachSlotAndExtractSlots() {
// This should detect a slot starting at 0,1 with length 2 (0,1 and 0,2)
var clues = Clued.of(r0c0d1);
var dict = DictJavaGeneratorMulti.Dicts.makeDict(WORDS2);
var slots = Masker.extractSlots(clues, dict.index());
var slots = Masker.extractSlots(Clued.of(r0c0d1).c(), dict.index());
assertEquals(1, slots.length);
var s = slots[0];
@@ -296,8 +296,7 @@ public class SwedishGeneratorTest {
@Test
void testPlaceWord() {
var empty = Clues.createEmpty();
var grid = new Gridded(empty);
var grid = new Gridded(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);
@@ -307,7 +306,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.stream(empty).collect(Collectors.toMap(LetterAt::index, LetterAt::letter));
var map = grid.collect(Collectors.toMap(LetterAt::index, LetterAt::letter));
assertEquals(3, map.size());
assertEquals(LETTER_A, map.get(OFF_0_0));
assertEquals(LETTER_B, map.get(OFF_0_1));
@@ -318,7 +317,7 @@ 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.stream(Masker.Clues.createEmpty()).collect(Collectors.toMap(LetterAt::index, LetterAt::letter));
map = grid.collect(Collectors.toMap(LetterAt::index, LetterAt::letter));
assertEquals(3, map.size());
assertEquals(LETTER_A, map.get(OFF_0_0));
assertEquals(LETTER_B, map.get(OFF_0_1));
@@ -326,17 +325,16 @@ public class SwedishGeneratorTest {
// 4. Partial placement then conflict (rollback)
grid = new Gridded(Clues.createEmpty());
GridBuilder.placeWord(grid.grid(), grid.grid().g, Slot.packSlotKey(0, CLUE_RIGHT), 1L << OFF_0_2, 0, Lemma.from("X")); // Conflict at the end
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(Masker.Clues.createEmpty()).collect(Collectors.toMap(LetterAt::index, LetterAt::letter));
map = grid.stream().collect(Collectors.toMap(LetterAt::index, LetterAt::letter));
assertEquals(1, map.size());
assertEquals(LETTER_X, map.get(OFF_0_2));
}
@Test
void testBacktrackingHelpers() {
var clues = Clues.createEmpty();
var grid = new Gridded(clues);
var grid = new Gridded(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);
@@ -346,14 +344,14 @@ public class SwedishGeneratorTest {
var placed = GridBuilder.placeWord(grid.grid(), grid.grid().g, key, lo, 0L, w);
assertTrue(placed);
var map = grid.stream(clues).collect(Collectors.toMap(LetterAt::index, LetterAt::letter));
var map = grid.collect(Collectors.toMap(LetterAt::index, LetterAt::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.stream(Masker.Clues.createEmpty()).collect(Collectors.toMap(LetterAt::index, LetterAt::letter));
map = grid.collect(Collectors.toMap(LetterAt::index, LetterAt::letter));
assertEquals(0, map.size());
assertFalse(map.containsKey(OFF_0_1));
assertFalse(map.containsKey(OFF_0_2));