From 92a736aa0a276d77f8f89c59b21ac9148894a27c Mon Sep 17 00:00:00 2001 From: mike Date: Wed, 21 Jan 2026 00:56:18 +0100 Subject: [PATCH] introduce bitloops --- src/main/java/puzzle/Export.java | 28 ++++---- src/main/java/puzzle/Main.java | 2 +- src/main/java/puzzle/Masker.java | 67 ++++++++++--------- src/main/java/puzzle/SwedishGenerator.java | 4 +- src/test/java/puzzle/MainTest.java | 35 +++++----- src/test/java/puzzle/MarkerTest.java | 22 +++--- .../java/puzzle/SwedishGeneratorTest.java | 60 ++++++++--------- 7 files changed, 108 insertions(+), 110 deletions(-) diff --git a/src/main/java/puzzle/Export.java b/src/main/java/puzzle/Export.java index 5d6d971..6c0e565 100644 --- a/src/main/java/puzzle/Export.java +++ b/src/main/java/puzzle/Export.java @@ -70,15 +70,10 @@ public record Export() { public record Clued(@Delegate Clues c) { - public static Clues create(Cell... cell) { - var empty = createEmpty(); - for (var cell1 : cell) empty.setClue(cell1); - return empty; - } - public static Clues of(Cell... cells) { + public static Clued of(Cell... cells) { var c = createEmpty(); for (var cell : cells) c.setClue(cell); - return c; + return new Clued(c); } public Clued deepCopyGrid() { return new Clued(new Clues(c.lo, c.hi, c.vlo, c.vhi, c.rlo, c.rhi, c.xlo, c.xhi)); } public static Clued parse(String s) { @@ -125,6 +120,9 @@ public record Export() { return stream.build(); } + public Slotinfo[] slots() { + return Masker.slots(c, DictData.DICT.index()); + } } @FunctionalInterface @@ -143,13 +141,15 @@ public record Export() { default void visit(int index, byte[] letters) { visit(index, letters[index]); } } - record Gridded(@Delegate Grid grid, Clues cl) { + record Gridded(@Delegate Grid grid, Clues cl) + implements Stream { public Gridded(Clues clues) { this(clues.toGrid(), clues); } - public Stream stream(Clues clues) { + public Gridded(Clued clues) { this(clues.toGrid(), clues.c); } + public @Delegate Stream stream() { val stream = Stream.builder(); - for (var l = grid.lo & ~clues.lo; l != X; l &= l - 1) stream.accept(LetterAt.from(Long.numberOfTrailingZeros(l), grid.g)); - for (var h = grid.hi & ~clues.hi & 0xFF; h != X; h &= h - 1) stream.accept(LetterAt.from(64 | Long.numberOfTrailingZeros(h), grid.g)); + for (var l = grid.lo & ~cl.lo; l != X; l &= l - 1) stream.accept(LetterAt.from(Long.numberOfTrailingZeros(l), grid.g)); + for (var h = grid.hi & ~cl.hi & 0xFF; h != X; h &= h - 1) stream.accept(LetterAt.from(64 | Long.numberOfTrailingZeros(h), grid.g)); return stream.build(); } String gridToString(Clues clues) { @@ -161,7 +161,7 @@ public record Export() { val dir = Slot.dir(s); sb.setCharAt(r * (C + 1) + c, (char) (dir | 48)); }); - stream(clues).forEach((l) -> sb.setCharAt(l.index(C + 1), l.human())); + stream().forEach((l) -> sb.setCharAt(l.index(C + 1), l.human())); return sb.toString(); } public String[] exportGrid(Clues clues, Replacar clueChar, char emptyFallback) { @@ -173,7 +173,7 @@ public record Export() { val dir = Slot.dir(s); sb.setCharAt(r * (C + 1) + c, clueChar.replace(new Rell(grid, clues, idx, (byte) (dir | 48)))); }); - stream(clues).forEach((l) -> sb.setCharAt(l.index(C + 1), l.human())); + stream().forEach((l) -> sb.setCharAt(l.index(C + 1), l.human())); return sb.toString().replaceAll(" ", "" + emptyFallback).split("\n"); } public static IntStream cellWalk(byte base, long lo, long hi) { @@ -291,7 +291,7 @@ public record Export() { } // 3) map of only used letter cells (everything else becomes '#') - var map = grid.stream(clues.c()).collect(Collectors.toMap(LetterAt::index, LetterAt::human)); + var map = grid.stream().collect(Collectors.toMap(LetterAt::index, LetterAt::human)); // 4) render gridv2 over cropped bounds (out-of-bounds become '#') var gridv2 = new String[Math.max(0, maxR - minR + 1)]; for (int r = minR, i = 0; r <= maxR; r++, i++) { diff --git a/src/main/java/puzzle/Main.java b/src/main/java/puzzle/Main.java index 9ec3d32..7eb20d0 100644 --- a/src/main/java/puzzle/Main.java +++ b/src/main/java/puzzle/Main.java @@ -96,7 +96,7 @@ public class Main { section("Words"); printWordsTable(exported.words()); - section("Gridv2"); + section("Grid"); for (var row : exported.grid()) System.out.println(" " + row); var theme = "algemeen"; diff --git a/src/main/java/puzzle/Masker.java b/src/main/java/puzzle/Masker.java index 326cc7b..d1df52f 100644 --- a/src/main/java/puzzle/Masker.java +++ b/src/main/java/puzzle/Masker.java @@ -2,6 +2,8 @@ package puzzle; import module java.base; import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.experimental.Accessors; import lombok.val; import precomp.Neighbors9x8; import precomp.Neighbors9x8.rci; @@ -15,11 +17,11 @@ public final class Masker { private final Rng rng; private final int[] stack; private final Clues cache; - private final int[] activeCIdx = new int[SwedishGenerator.SIZE]; - private final long[] activeSLo = new long[SwedishGenerator.SIZE]; - private final long[] activeSHi = new long[SwedishGenerator.SIZE]; - private final long[] adjLo = new long[SwedishGenerator.SIZE]; - private final long[] adjHi = new long[SwedishGenerator.SIZE]; + private final int[] activeCIdx = new int[Neighbors9x8.SIZE]; + private final long[] activeSLo = new long[Neighbors9x8.SIZE]; + private final long[] activeSHi = new long[Neighbors9x8.SIZE]; + private final long[] adjLo = new long[Neighbors9x8.SIZE]; + private final long[] adjHi = new long[Neighbors9x8.SIZE]; private final int[] rCount = new int[8]; private final int[] cCount = new int[9]; private static final long[] NBR_LO = Neighbors9x8.NBR_LO; @@ -133,20 +135,20 @@ public final class Masker { else return (Long.bitCount(rayLo) + Long.bitCount(rayHi) >= SwedishGenerator.MIN_LEN); } private static boolean validSlot(long lo, long hi, int key) { - var rayLo = PATH_LO[key]; - var rayHi = PATH_HI[key]; - var hitsLo = rayLo & lo; - var hitsHi = rayHi & hi; + var rayLo = PATH_LO[key]; + var rayHi = PATH_HI[key]; + var hitsLo = rayLo & lo; + var hitsHi = rayHi & hi; if (hitsLo != X) return (Long.bitCount(rayLo & ((1L << numberOfTrailingZeros(hitsLo)) - 1)) >= SwedishGenerator.MIN_LEN); else if (hitsHi != X) return (Long.bitCount(rayLo) + Long.bitCount(rayHi & ((1L << numberOfTrailingZeros(hitsHi)) - 1)) >= SwedishGenerator.MIN_LEN); else return (Long.bitCount(rayLo) + Long.bitCount(rayHi) >= SwedishGenerator.MIN_LEN); } private static void processSlot(Clues c, SlotVisitor visitor, int key) { - var rayLo = PATH_LO[key]; - var rayHi = PATH_HI[key]; - var hitsLo = rayLo & c.lo; - var hitsHi = rayHi & c.hi; + var rayLo = PATH_LO[key]; + var rayHi = PATH_HI[key]; + var hitsLo = rayLo & c.lo; + var hitsHi = rayHi & c.hi; if (hitsLo != X) { var stop = 1L << numberOfTrailingZeros(hitsLo); @@ -159,9 +161,9 @@ public final class Masker { if (Long.bitCount(rayLo) + Long.bitCount(rayHi) >= MIN_LEN) visitor.visit(key, rayLo, rayHi); } - public static Slot[] extractSlots(Clues grid, DictEntry[] index) { - var slots = new ArrayList(grid.clueCount()); - grid.forEachSlot((key, lo, hi) -> slots.add(Slot.from(key, lo, hi, index[Slot.length(lo, hi)]))); + public static Slot[] extractSlots(Clues c, DictEntry[] index) { + var slots = new ArrayList(c.clueCount()); + c.forEachSlot((key, lo, hi) -> slots.add(Slot.from(key, lo, hi, index[Slot.length(lo, hi)]))); return slots.toArray(Slot[]::new); } public static Slotinfo[] slots(Clues mask, DictEntry[] index) { @@ -193,19 +195,19 @@ public final class Masker { public long maskFitness(final Clues grid, int clueSize) { - long cHLo = 0L, cHHi = 0L, cVLo = 0L, cVHi = 0L; - long cHLo2 = 0L, cHHi2 = 0L, cVLo2 = 0L, cVHi2 = 0L; + long cHLo = 0L, cHHi = 0L, cVLo = 0L, cVHi = 0L; + long cHLo2 = 0L, cHHi2 = 0L, cVLo2 = 0L, cVHi2 = 0L; long lo_cl = grid.lo, hi_cl = grid.hi; var penalty = (((long) Math.abs(grid.clueCount() - clueSize)) * 16000L); var hasSlots = false; var numClues = 0; for (var bits = lo_cl; bits != X; bits &= bits - 1) { - var lsb = bits & -bits; - var clueIdx = numberOfTrailingZeros(lsb); + var lsb = bits & -bits; + var clueIdx = numberOfTrailingZeros(lsb); int dir = grid.getDir(clueIdx); - var key = Slot.packSlotKey(clueIdx, dir); - long rLo = PATH_LO[key], rHi = PATH_HI[key]; + var key = Slot.packSlotKey(clueIdx, dir); + long rLo = PATH_LO[key], rHi = PATH_HI[key]; long hLo = rLo & lo_cl, hHi = rHi & hi_cl; if (Slotinfo.increasing(key)) { if (hLo != X) { @@ -245,11 +247,11 @@ public final class Masker { } else penalty += 25000; } for (var bits = hi_cl; bits != X; bits &= bits - 1) { - var lsb = bits & -bits; - var clueIdx = numberOfTrailingZeros(lsb); + var lsb = bits & -bits; + var clueIdx = numberOfTrailingZeros(lsb); int dir = grid.getDir(64 | clueIdx); - var key = Slot.packSlotKey(64 | clueIdx, dir); - long rLo = PATH_LO[key], rHi = PATH_HI[key]; + var key = Slot.packSlotKey(64 | clueIdx, dir); + long rLo = PATH_LO[key], rHi = PATH_HI[key]; long hLo = rLo & lo_cl, hHi = rHi & hi_cl; if (Slotinfo.increasing(key)) { if (hLo != X) { @@ -588,9 +590,9 @@ public final class Masker { var childCount = 0; for (var k = 0; k < offspring; k++) { if (Thread.currentThread().isInterrupted()) break; - var p1 = rng.rand(pop); - var p2 = rng.rand(pop); - var child = crossover(p1.grid, p2.grid); + var p1 = rng.rand(pop); + var p2 = rng.rand(pop); + var child = crossover(p1.grid, p2.grid); children[k] = new GridAndFit(hillclimb(child, clueSize, 70)); childCount++; } @@ -641,11 +643,14 @@ public final class Masker { } //@formatter:off @FunctionalInterface public interface SlotVisitor { void visit(int key, long lo, long hi); } + sealed interface BitPop permits Clues { long hi(); long lo(); } //@formatter:on @AllArgsConstructor - public static class Clues { + @Accessors(fluent = true) + public static final class Clues + implements BitPop { - long lo, hi, vlo, vhi, rlo, rhi, xlo, xhi; + @Getter long lo, hi, vlo, vhi, rlo, rhi, xlo, xhi; public static Clues createEmpty() { return new Clues(0, 0, 0, 0, 0, 0, 0, 0); } public boolean hasRoomForClue(int key) { diff --git a/src/main/java/puzzle/SwedishGenerator.java b/src/main/java/puzzle/SwedishGenerator.java index 6b0bcfc..cf6d1db 100644 --- a/src/main/java/puzzle/SwedishGenerator.java +++ b/src/main/java/puzzle/SwedishGenerator.java @@ -39,7 +39,7 @@ public record SwedishGenerator() { public static final long MASK_HI = Neighbors9x8.MASK_HI;//(1L << (SIZE - 64)) - 1; public static final int MAX_WORD_LENGTH = Config.PUZZLE_ROWS; public static final int MAX_WORD_LENGTH_PLUS_ONE = MAX_WORD_LENGTH + 1; - public static final int MIN_LEN = 2;//Neighbors9x8.MIN_LEN;//Config.MIN_LEN; + public static final int MIN_LEN = Neighbors9x8.MIN_LEN;//Config.MIN_LEN; public static final int MAX_TRIES_PER_SLOT = 500;//Config.MAX_TRIES_PER_SLOT; public static final int STACK_SIZE = 128; public static final long RANGE_0_SIZE = Neighbors9x8.RANGE_0_SIZE;// (long) SIZE_MIN_1 - 0L + 1L @@ -117,7 +117,7 @@ public record SwedishGenerator() { static int unpackIndex(long w) { return (int) (w >>> 40); } static int unpackShardIndex(long w) { return (int) (w >>> 43); } static int unpackSize(long w) { return (int) (w >>> 40) & 7; } - static int unpackLetters(long w) { return (int) (w & LETTER_MASK); } + static int unpackLetters(long w) { return (int) (w & LETTER_MASK); } static long pack43(long w) { return w & INDEX_MASK; } diff --git a/src/test/java/puzzle/MainTest.java b/src/test/java/puzzle/MainTest.java index 3e8a761..7820a24 100644 --- a/src/test/java/puzzle/MainTest.java +++ b/src/test/java/puzzle/MainTest.java @@ -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)"); diff --git a/src/test/java/puzzle/MarkerTest.java b/src/test/java/puzzle/MarkerTest.java index 3f116ed..b081b19 100644 --- a/src/test/java/puzzle/MarkerTest.java +++ b/src/test/java/puzzle/MarkerTest.java @@ -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); diff --git a/src/test/java/puzzle/SwedishGeneratorTest.java b/src/test/java/puzzle/SwedishGeneratorTest.java index ad50341..cd8233e 100644 --- a/src/test/java/puzzle/SwedishGeneratorTest.java +++ b/src/test/java/puzzle/SwedishGeneratorTest.java @@ -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));