From 3cc6570cdc3033e02dc2fca07e215c13cbdbf1b3 Mon Sep 17 00:00:00 2001 From: mike Date: Sat, 24 Jan 2026 00:46:10 +0100 Subject: [PATCH] redo --- .run/Main.run.xml | 2 +- src/main/java/precomp/Mask.java | 17 ++++++ src/main/java/puzzle/Clues.java | 1 + src/main/java/puzzle/Export.java | 53 +++++++++++-------- src/main/java/puzzle/Main.java | 2 +- src/main/java/puzzle/Mask.java | 14 ----- src/main/java/puzzle/Riddle.java | 29 +++++----- src/test/java/puzzle/MainTest.java | 8 +-- src/test/java/puzzle/MarkerTest.java | 12 ++--- .../java/puzzle/SwedishGeneratorTest.java | 20 +++---- src/test/java/puzzle/TestDuplication.java | 2 +- 11 files changed, 88 insertions(+), 72 deletions(-) create mode 100644 src/main/java/precomp/Mask.java delete mode 100644 src/main/java/puzzle/Mask.java diff --git a/.run/Main.run.xml b/.run/Main.run.xml index 686f4cc..3449578 100644 --- a/.run/Main.run.xml +++ b/.run/Main.run.xml @@ -4,7 +4,7 @@ - diff --git a/src/main/java/precomp/Mask.java b/src/main/java/precomp/Mask.java new file mode 100644 index 0000000..1c8fd7e --- /dev/null +++ b/src/main/java/precomp/Mask.java @@ -0,0 +1,17 @@ +package precomp; + +public sealed interface Mask + permits Const9x8.Cell, Const3x4.Cell, Mask.Masker { + + record Masker(long lo, long hi, int index, int r, int c, byte d) + implements precomp.Mask { } + default Mask or(Mask o) { return new Masker(o.lo() | lo(), o.hi() | hi(), 0, 0, 0, (byte) 0); } + default Mask and(Mask o) { return new Masker(o.lo() & lo(), o.hi() & hi(), 0, 0, 0, (byte) 0); } + + long hi(); + long lo(); + int index(); + int r(); + int c(); + byte d(); +} \ No newline at end of file diff --git a/src/main/java/puzzle/Clues.java b/src/main/java/puzzle/Clues.java index 7fb2220..77df5cb 100644 --- a/src/main/java/puzzle/Clues.java +++ b/src/main/java/puzzle/Clues.java @@ -4,6 +4,7 @@ import anno.ConstGen; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.experimental.Accessors; +import precomp.Mask; import static java.lang.Long.bitCount; import static puzzle.SwedishGenerator.X; //@formatter:on diff --git a/src/main/java/puzzle/Export.java b/src/main/java/puzzle/Export.java index 2e3218f..c9b42c4 100644 --- a/src/main/java/puzzle/Export.java +++ b/src/main/java/puzzle/Export.java @@ -6,6 +6,7 @@ import anno.Shaped; import lombok.experimental.Delegate; import lombok.val; import precomp.Const9x8; +import precomp.Mask; import puzzle.Riddle.ClueSign; import puzzle.Riddle.ExportedPuzzle; import puzzle.Riddle.Placed; @@ -17,6 +18,9 @@ import puzzle.SwedishGenerator.Grid; import puzzle.SwedishGenerator.Slotinfo; import static puzzle.Masker.Slot; import static puzzle.SwedishGenerator.X; +import puzzle.SwedishGenerator.Lemma; +import java.util.stream.Stream; +import java.util.Arrays; @GenerateShapedCopies( packageName = "puzzle", @@ -48,14 +52,6 @@ public record Export() { static int INDEX_COL(int idx) { return idx / R; } static int INDEX(int idx, int cols) { return INDEX_ROW(idx) * cols + INDEX_COL(idx); } - record Lettrix(int index, byte letter) { - - public int row() { return INDEX_ROW(index); } - public int col() { return INDEX_COL(index); } - public byte human() { return LETTER(letter); } - static Lettrix from(int index, byte[] bytes) { return new Lettrix(index, bytes[index]); } - public int index(int cols) { return (row() * cols) + col(); } - } public static String gridToString(Clues clues) { val chars = BYTES.get().table(); var signa = new Signa(clues); @@ -65,17 +61,30 @@ public record Export() { return result; } - record Puzzle(@Delegate Grid grid, Clues cl) - implements Stream { + record Puzzle(@Delegate Grid grid, Mask[] cells, Clues cl) + implements Stream { - public Puzzle(Clues clues) { this(new Grid(new byte[SIZE], clues.lo, clues.hi), clues); } - public Puzzle(Signa clues) { this(clues.c()); } - public @Delegate Stream stream() { - val stream = Stream.builder(); - for (var l = grid.lo & MASK_LO & ~cl.lo; l != X; l &= l - 1) stream.accept(Lettrix.from(Long.numberOfTrailingZeros(l), grid.g)); - for (var h = grid.hi & MASK_HI & ~cl.hi; h != X; h &= h - 1) stream.accept(Lettrix.from(HI(Long.numberOfTrailingZeros(h)), grid.g)); + public Puzzle { + for (var l = grid.lo & MASK_LO & ~cl.lo; l != X; l &= l - 1) set(Long.numberOfTrailingZeros(l), cells, grid.g); + for (var h = grid.hi & MASK_HI & ~cl.hi; h != X; h &= h - 1) set(HI(Long.numberOfTrailingZeros(h)), cells, grid.g); + new Signa(cl).forEach(v -> cells[v.index()] = CELLS[v.index() * 33 + 27 + Slot.dir(v.clue())]); + } + static void set(int idx, Mask[] cells, byte[] read) { cells[idx] = CELLS[idx * 33 + read[idx]]; } + public Puzzle(Grid grid, Clues cl) { this(grid, new Mask[grid.g.length], cl); } + public Puzzle(Clues clues) { this(new Grid(new byte[SIZE], clues.lo, clues.hi), new Mask[SIZE], clues); } + public Puzzle(Signa clues) { this(clues.c()); } + public @Delegate Stream stream() { + val stream = Stream.builder(); + for (var l = grid.lo & MASK_LO & ~cl.lo; l != X; l &= l - 1) stream.accept(cells[Long.numberOfTrailingZeros(l)]); + for (var h = grid.hi & MASK_HI & ~cl.hi; h != X; h &= h - 1) stream.accept(cells[HI(Long.numberOfTrailingZeros(h))]); return stream.build(); } + public Puzzle sync() { + for (var l = grid.lo & MASK_LO & ~cl.lo; l != X; l &= l - 1) set(Long.numberOfTrailingZeros(l), cells, grid.g); + for (var h = grid.hi & MASK_HI & ~cl.hi; h != X; h &= h - 1) set(HI(Long.numberOfTrailingZeros(h)), cells, grid.g); + new Signa(cl).forEach(v -> cells[v.index()] = CELLS[v.index() * 33 + 27 + Slot.dir(v.clue())]); + return this; + } } public record PuzzleResult(Signa clues, Puzzle puzzle, Slotinfo[] slots, FillResult filled) { @@ -83,19 +92,19 @@ public record Export() { public String exportGrid(ClueSign clueChar, byte[] template) { var sb = template.clone(); for (var slot : slots) sb[INDEX(Slot.clueIndex(slot.key()), (C + 1))] = clueChar.replace(CLUE_CHAR(Slot.dir(slot.key()))); - puzzle.forEach((l) -> sb[l.index(C + 1)] = l.human()); + puzzle.forEach((l) -> sb[INDEX(l.index(), C + 1)] = LETTER(l.d())); return new String(sb); } public String cluesGridToString() { return gridToString(clues.c()); } public String gridRenderHuman() { return exportGrid(_ -> SPACE, INIT_GRID_OUTPUT_DASH_ARR); } public String gridGridToString() { return exportGrid(d1 -> d1, INIT_GRID_OUTPUT_ARR); } - public ExportedPuzzle exportFormatFromFilled(Rewards rewards, rci[] rcis) { + public ExportedPuzzle exportFormatFromFilled(Rewards rewards) { if (slots.length == 0) { return new ExportedPuzzle(new String(INIT_GRID_OUTPUT_DASH_ARR).split("\n"), new WordOut[0], 1, rewards); } var placed = Arrays.stream(slots) - .map(slot -> new Placed(slot.assign().w, slot.key(), Riddle.cellWalk(slot.key(), slot.lo(), slot.hi()).mapToObj(i -> rcis[i]).toArray(rci[]::new))) + .map(slot -> new Placed(slot.assign().w, slot.key(), Riddle.cellWalk(slot.key(), slot.lo(), slot.hi()).mapToObj(idx-> puzzle.cells[idx]).toArray(Mask[]::new))) .toArray(Placed[]::new); // 2) bounding box around all word cells + arrow cells, with 1-cell margin @@ -120,10 +129,10 @@ public record Export() { for (int i = width; i < template.length; i += width + 1) template[i] = LINE_BREAK; puzzle.forEach(l -> { - int rr = l.row() - MINR; - int cc = l.col() - MINC; + int rr = l.r() - MINR; + int cc = l.c() - MINC; if (rr >= 0 && rr < height && cc >= 0 && cc < width) { - template[rr * (width + 1) + cc] = l.human(); + template[rr * (width + 1) + cc] = LETTER(l.d()); } }); var grid = new String(template).split("\n"); diff --git a/src/main/java/puzzle/Main.java b/src/main/java/puzzle/Main.java index b993116..d530aea 100644 --- a/src/main/java/puzzle/Main.java +++ b/src/main/java/puzzle/Main.java @@ -106,7 +106,7 @@ public class Main { section("Grid (human)"); System.out.print(indentLines(res.gridRenderHuman())); - var exported = res.exportFormatFromFilled(new Rewards(50, 2, 1), Masker.IT); + var exported = res.exportFormatFromFilled(new Rewards(50, 2, 1)); section("Clues"); info("status : generating..."); diff --git a/src/main/java/puzzle/Mask.java b/src/main/java/puzzle/Mask.java deleted file mode 100644 index e7a3e70..0000000 --- a/src/main/java/puzzle/Mask.java +++ /dev/null @@ -1,14 +0,0 @@ -package puzzle; - -public interface Mask { - - record Masker(long lo, long hi,int index,byte d) - implements Mask { } - default Mask or(Mask o) { return new Masker(o.lo() | lo(), o.hi() | hi(), 0, (byte) 0); } - default Mask and(Mask o) { return new Masker(o.lo() & lo(), o.hi() & hi(), 0, (byte) 0); } - - long hi(); - long lo(); - int index(); - byte d(); -} \ No newline at end of file diff --git a/src/main/java/puzzle/Riddle.java b/src/main/java/puzzle/Riddle.java index 883f23a..1c719bc 100644 --- a/src/main/java/puzzle/Riddle.java +++ b/src/main/java/puzzle/Riddle.java @@ -3,6 +3,7 @@ package puzzle; import lombok.AllArgsConstructor; import lombok.experimental.Delegate; import lombok.val; +import precomp.Mask; import puzzle.Masker.Slot; import puzzle.Meta.ShardLem; import puzzle.SwedishGenerator.Lemma; @@ -87,20 +88,6 @@ public class Riddle { public record Vestigium(int index, int clue) { } - record Placed(long lemma, int slotKey, rci[] cells) { - - public static final char HORIZONTAL = 'h'; - static final char VERTICAL = 'v'; - static final char[] DIRECTION = { Placed.VERTICAL, Placed.HORIZONTAL, Placed.VERTICAL, Placed.HORIZONTAL, Placed.VERTICAL, Placed.VERTICAL }; - - public int arrowCol() { return cells[0].c(); } - public int arrowRow() { return cells[0].r(); } - public int startRow() { return cells[1].r(); } - public int startCol() { return cells[1].c(); } - public boolean isReversed() { return !Slotinfo.increasing(slotKey); } - public char direction() { return DIRECTION[Slot.dir(slotKey)]; } - } - public record ExportedPuzzle(String[] grid, WordOut[] words, int difficulty, Rewards rewards) { } public record Rewards(int coins, int stars, int hints) { } @@ -174,4 +161,18 @@ public class Riddle { return stream.build(); } } + + public record Placed(long lemma, int slotKey, Mask[] cells) { + + public static final char HORIZONTAL = 'h'; + static final char VERTICAL = 'v'; + static final char[] DIRECTION = { Placed.VERTICAL, Placed.HORIZONTAL, Placed.VERTICAL, Placed.HORIZONTAL, Placed.VERTICAL, Placed.VERTICAL }; + + public int arrowCol() { return cells[0].c(); } + public int arrowRow() { return cells[0].r(); } + public int startRow() { return cells[1].r(); } + public int startCol() { return cells[1].c(); } + public boolean isReversed() { return !Slotinfo.increasing(slotKey); } + public char direction() { return DIRECTION[Slot.dir(slotKey)]; } + } } diff --git a/src/test/java/puzzle/MainTest.java b/src/test/java/puzzle/MainTest.java index ca6cf4d..2a85af8 100644 --- a/src/test/java/puzzle/MainTest.java +++ b/src/test/java/puzzle/MainTest.java @@ -5,7 +5,7 @@ import anno.DictGen; import lombok.val; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import puzzle.Export.Lettrix; +import precomp.Mask; import puzzle.Export.Puzzle; import puzzle.Export.PuzzleResult; import puzzle.Main.Opts; @@ -148,7 +148,7 @@ public class MainTest { r1c1.or(r0c1).lo(); // 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(Lettrix::index, Lettrix::letter)); + val map = new Puzzle(grid.grid(), clues.c()).collect(Collectors.toMap(Mask::index, Mask::d)); 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(Riddle.Vestigium::index, Riddle.Vestigium::clue)); @@ -233,9 +233,11 @@ public class MainTest { Assertions.assertEquals("BEADEMT", Lemma.asWord(slotInfo[0].assign().w, Export.BYTES.get().wordBytes())); Assertions.assertEquals(74732156493031040L, grid.lo); Assertions.assertEquals(193L, grid.hi); + grid.lo = ~mask.c().lo; + grid.hi = 0xFFL & ~mask.c().hi; var g = new Puzzle(grid, mask.c()); var result = new PuzzleResult(mask, g, slotInfo, filled); - var aa = result.exportFormatFromFilled(new Rewards(1, 1, 1), Masker.IT); + var aa = result.exportFormatFromFilled(new Rewards(1, 1, 1)); result.gridGridToString(); System.out.println(String.join("\n", aa.grid())); diff --git a/src/test/java/puzzle/MarkerTest.java b/src/test/java/puzzle/MarkerTest.java index c10fa49..18fc180 100644 --- a/src/test/java/puzzle/MarkerTest.java +++ b/src/test/java/puzzle/MarkerTest.java @@ -310,22 +310,22 @@ public class MarkerTest { } @Test void testExportFormatFromFilled() { - val clues = Riddle.Signa.of(r0c0d1, r0c5d3); - var grid = new Puzzle(clues); + val clues = Riddle.Signa.of(r0c0d1, r0c5d3); + var puzzle = new Puzzle(clues); // key = (cellIndex << 2) | (direction) var key = r0c0d1.slotKey; var lo = (1L << OFF_0_1) | (1L << OFF_0_2) | (1L << OFF_0_3) | (1L << OFF_0_4); - assertTrue(placeWord(grid.grid(), grid.grid().g, key, lo, 0L, TEST)); + assertTrue(placeWord(puzzle.grid(), puzzle.grid().g, key, lo, 0L, TEST)); var fillResult = new FillResult(true, 0, 0, 0, 0); - var puzzleResult = new PuzzleResult(clues, grid, new Slotinfo[]{ + var puzzleResult = new PuzzleResult(clues, new Puzzle(puzzle.grid(), puzzle.cl()), new Slotinfo[]{ new Slotinfo(key, lo, 0L, 0, new Assign(TEST), null, 0) }, fillResult); var rewards = new Rewards(10, 5, 1); - var exported = puzzleResult.exportFormatFromFilled(rewards, Masker.IT); + var exported = puzzleResult.exportFormatFromFilled(rewards); assertNotNull(exported); assertEquals(709, exported.difficulty()); @@ -364,7 +364,7 @@ public class MarkerTest { var fillResult = new FillResult(true, 0, 0, 0, 0); var puzzleResult = new PuzzleResult(new Signa(clues), new Puzzle(grid, clues), new Slotinfo[0], fillResult); - var exported = puzzleResult.exportFormatFromFilled(new Rewards(0, 0, 0), Masker.IT); + var exported = puzzleResult.exportFormatFromFilled(new Rewards(0, 0, 0)); assertNotNull(exported); assertEquals(0, exported.words().length); diff --git a/src/test/java/puzzle/SwedishGeneratorTest.java b/src/test/java/puzzle/SwedishGeneratorTest.java index cb9184f..4bbf09a 100644 --- a/src/test/java/puzzle/SwedishGeneratorTest.java +++ b/src/test/java/puzzle/SwedishGeneratorTest.java @@ -9,7 +9,7 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import precomp.Neighbors9x8; import puzzle.DictJavaGeneratorMulti.DictEntryDTO.IntListDTO; -import puzzle.Export.Lettrix; +import precomp.Mask; import puzzle.Export.Puzzle; import puzzle.Riddle.Signa; import puzzle.Masker.Slot; @@ -128,7 +128,7 @@ public class SwedishGeneratorTest { void testPatternForSlotAllLetters() { var grid = new Puzzle(Riddle.Signa.of(r0c0d1)); GridBuilder.placeWord(grid.grid(), grid.grid().g, r0c0d1.slotKey, r0c1.or(r0c2).or(r0c3).lo(), X, ABC); - val map = grid.collect(Collectors.toMap(Lettrix::index, Lettrix::letter)); + val map = grid.sync().collect(Collectors.toMap(Mask::index, Mask::d)); assertEquals(LETTER_A, map.get(OFF_0_1)); assertEquals(LETTER_B, map.get(OFF_0_2)); assertEquals(LETTER_C, map.get(OFF_0_3)); @@ -160,9 +160,9 @@ public class SwedishGeneratorTest { @Test void testGrid() { - var grid = new Puzzle(Clues.createEmpty()); - GridBuilder.placeWord(grid.grid(), grid.grid().g, r0c0d1.slotKey, r0c0d0.mask, X, WORD_A); - val arr = grid.collect(Collectors.toMap(Lettrix::index, Lettrix::letter)); + var p = new Puzzle(Clues.createEmpty()); + GridBuilder.placeWord(p.grid(), p.grid().g, r0c0d1.slotKey, r0c0d0.mask, X, WORD_A); + val arr = p.sync().collect(Collectors.toMap(Mask::index, Mask::d)); assertEquals(1, arr.size()); assertEquals(LETTER_A, arr.get(OFF_0_0)); } @@ -286,7 +286,7 @@ public class SwedishGeneratorTest { var grid = new Puzzle(Clues.createEmpty()); assertTrue(GridBuilder.placeWord(grid.grid(), grid.grid().g, r0c0d1.slotKey, r0c0.or(r0c1).or(r0c2).lo(), X, ABC)); - var map = grid.collect(Collectors.toMap(Lettrix::index, Lettrix::letter)); + var map = new Puzzle(grid.grid(), grid.cl()).collect(Collectors.toMap(Mask::index, Mask::d)); assertEquals(3, map.size()); assertEquals(LETTER_A, map.get(OFF_0_0)); assertEquals(LETTER_B, map.get(OFF_0_1)); @@ -297,7 +297,7 @@ public class SwedishGeneratorTest { // 3. Conflict: place "ABD" where "ABC" is assertFalse(GridBuilder.placeWord(grid.grid(), grid.grid().g, r0c0d1.slotKey, r0c0.or(r0c1).or(r0c2).lo(), X, ABD)); // Verify grid is unchanged (still "ABC") - map = grid.collect(Collectors.toMap(Lettrix::index, Lettrix::letter)); + map = new Puzzle(grid.grid(), grid.cl()).collect(Collectors.toMap(Mask::index, Mask::d)); assertEquals(3, map.size()); assertEquals(LETTER_A, map.get(OFF_0_0)); assertEquals(LETTER_B, map.get(OFF_0_1)); @@ -307,7 +307,7 @@ public class SwedishGeneratorTest { grid = new Puzzle(Clues.createEmpty()); GridBuilder.placeWord(grid.grid(), grid.grid().g, r0c0d1.slotKey, 1L << OFF_0_2, 0, WORD_X); // Conflict at the end assertFalse(GridBuilder.placeWord(grid.grid(), grid.grid().g, r0c0d1.slotKey, r0c0.or(r0c1).or(r0c2).lo(), X, ABC)); - map = grid.stream().collect(Collectors.toMap(Lettrix::index, Lettrix::letter)); + map = new Puzzle(grid.grid(), grid.cl()).collect(Collectors.toMap(Mask::index, Mask::d)); assertEquals(1, map.size()); assertEquals(LETTER_X, map.get(OFF_0_2)); } @@ -321,14 +321,14 @@ public class SwedishGeneratorTest { var placed = GridBuilder.placeWord(grid.grid(), grid.grid().g, r0c0d1.slotKey, r0c1.or(r0c2).lo(), X, AZ); assertTrue(placed); - var map = grid.collect(Collectors.toMap(Lettrix::index, Lettrix::letter)); + var map = new Puzzle(grid.grid(), grid.cl()).collect(Collectors.toMap(Mask::index, Mask::d)); 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(Lettrix::index, Lettrix::letter)); + map = grid.collect(Collectors.toMap(Mask::index, Mask::d)); assertEquals(0, map.size()); assertFalse(map.containsKey(OFF_0_1)); assertFalse(map.containsKey(OFF_0_2)); diff --git a/src/test/java/puzzle/TestDuplication.java b/src/test/java/puzzle/TestDuplication.java index f3aee7c..f2a265a 100644 --- a/src/test/java/puzzle/TestDuplication.java +++ b/src/test/java/puzzle/TestDuplication.java @@ -40,7 +40,7 @@ public class TestDuplication { var result = new ExportX_Const3x4.PuzzleResult(new Signa(mask.c()), grid1, slots, filled); if (filled.ok()) { System.out.println(filled); - val res = result.exportFormatFromFilled(new Rewards(0, 0, 0), Masker_Neighbors3x4.IT); + val res = result.exportFormatFromFilled(new Rewards(0, 0, 0)); System.out.println(String.join("\n", res.grid())); } }