diff --git a/src/main/java/puzzle/Export.java b/src/main/java/puzzle/Export.java index d6ac995..7a2529d 100644 --- a/src/main/java/puzzle/Export.java +++ b/src/main/java/puzzle/Export.java @@ -5,6 +5,7 @@ import lombok.experimental.Accessors; import lombok.experimental.Delegate; import lombok.val; import puzzle.Export.Gridded.Replacar.Cell; +import puzzle.Export.LetterVisit.LetterAt; import puzzle.SwedishGenerator.Clues; import puzzle.SwedishGenerator.DictEntry; import puzzle.SwedishGenerator.FillResult; @@ -13,7 +14,9 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.function.IntSupplier; +import java.util.stream.Collectors; import java.util.stream.IntStream; +import java.util.stream.Stream; import static puzzle.SwedishGenerator.R; import static puzzle.SwedishGenerator.Lemma; import static puzzle.SwedishGenerator.Slot; @@ -38,30 +41,46 @@ public record Export() { } } + static final String INIT = IntStream.range(0, R).mapToObj(l_ -> " ").collect(Collectors.joining("\n")); + public record Clued(@Delegate Clues mask) { String gridToString() { - var sb = new StringBuilder(); - for (var r = 0; r < R; r++) { - if (r > 0) sb.append('\n'); - for (var c = 0; c < C; c++) { - val idx = Grid.offset(r, c); - if (mask.isClue(idx)) - sb.append((char) (mask.digitAt(idx) | 48)); - else { - sb.append(' '); - } - } - } + var sb = new StringBuilder(INIT); + forEachSlot((s, l, a) -> { + val idx = Slot.clueIndex(s); + val r = idx & 7; + val c = idx >>> 3; + val dir = Slot.dir(s); + sb.setCharAt(r * (C + 1) + c, (char) (dir | 48)); + }); return sb.toString(); } } + @FunctionalInterface + interface LetterVisit { + + record LetterAt(int index, byte letter) { + + static LetterAt from(int index, byte[] bytes) { return new LetterAt(index, bytes[index]); } + } + + void visit(int index, byte letter); + default void visit(int index, byte[] letters) { visit(index, letters[index]); } + } + record Gridded(@Delegate Grid grid) { - public boolean lisLetterAt(int pos) { - if ((pos & 64) == 0) - return lisLetterAtLo(pos); - return lisLetterAtHi(pos); + + public Stream stream(Clues clues) { + val stream = Stream.builder(); + for (var l = grid.lo & ~clues.lo; l != SwedishGenerator.X; l &= l - 1) stream.accept(LetterAt.from(Long.numberOfTrailingZeros(l), grid.g)); + for (var h = grid.hi & ~clues.hi; h != SwedishGenerator.X; h &= h - 1) stream.accept(LetterAt.from(64 | Long.numberOfTrailingZeros(h), grid.g)); + return stream.build(); + } + public void forEachLetter(Clues clues, LetterVisit visitor) { + for (var l = grid.lo & ~clues.lo; l != SwedishGenerator.X; l &= l - 1) visitor.visit(Long.numberOfTrailingZeros(l), grid.g); + for (var h = grid.hi & ~clues.hi; h != SwedishGenerator.X; h &= h - 1) visitor.visit(64 | Long.numberOfTrailingZeros(h), grid.g); } public static IntStream walk(byte base, long lo, long hi) { if (Slot.increasing(base)) { @@ -116,19 +135,19 @@ public record Export() { return (char) (64 | b); } String gridToString(Clues clues) { - var sb = new StringBuilder(); - for (var r = 0; r < R; r++) { - if (r > 0) sb.append('\n'); - for (var c = 0; c < C; c++) { - var offset = Grid.offset(r, c); - if (clues.isClue(offset)) - sb.append((char) (48 | clues.digitAt(offset))); - else if (lisLetterAt(offset)) - sb.append((char) (64 | grid.letter32At(offset))); - else - sb.append(' '); - } - } + var sb = new StringBuilder(INIT); + clues.forEachSlot((s, l, a) -> { + val idx = Slot.clueIndex(s); + val r = idx & 7; + val c = idx >>> 3; + val dir = Slot.dir(s); + sb.setCharAt(r * (C + 1) + c, (char) (dir | 48)); + }); + forEachLetter(clues, (idx, letter) -> { + val r = idx & 7; + val c = idx >>> 3; + sb.setCharAt(r * (C + 1) + c, (char) (letter | 64)); + }); return sb.toString(); } public String renderHuman(Clues clues) { @@ -141,6 +160,21 @@ public record Export() { char replace(Cell c); } public String[] exportGrid(Clues clues, Replacar clueChar, char emptyFallback) { + var sb = new StringBuilder(INIT); + clues.forEachSlot((s, l, a) -> { + val idx = Slot.clueIndex(s); + val r = idx & 7; + val c = idx >>> 3; + val dir = Slot.dir(s); + sb.setCharAt(r * (C + 1) + c, clueChar.replace(new Cell(grid, clues, idx, (byte) (dir | 48)))); + }); + forEachLetter(clues, (idx, letter) -> { + val r = idx & 7; + val c = idx >>> 3; + sb.setCharAt(r * (C + 1) + c, (char) (letter | 64)); + }); + return sb.toString().replaceAll(" ", "" + emptyFallback).split("\n"); + /* var out = new String[R]; for (var r = 0; r < R; r++) { var sb = new StringBuilder(C); @@ -156,7 +190,7 @@ public record Export() { } out[r] = sb.toString(); } - return out; + return out;*/ } } @@ -194,7 +228,7 @@ public record Export() { public record ExportedPuzzle(String[] grid, WordOut[] words, int difficulty, Rewards rewards) { } - public record PuzzleResult(Clued mask, FillResult filled) { + public record PuzzleResult(Clued clues, FillResult filled) { Placed extractPlacedFromSlot(Slot s, long lemma) { return new Placed(lemma, s.key(), s.walk().toArray()); } public ExportedPuzzle exportFormatFromFilled(int difficulty, Rewards rewards) { @@ -202,7 +236,7 @@ public record Export() { var placed = new ArrayList(); var clueMap = filled().clueMap(); val entries = new DictEntry[10]; - mask.forEachSlot((int key, long lo, long hi) -> { + clues.forEachSlot((int key, long lo, long hi) -> { var word = clueMap[key]; if (word != 0L) { placed.add(extractPlacedFromSlot(Slot.from(key, lo, hi, entries[Slot.length(lo, hi)]), word)); @@ -213,7 +247,7 @@ public record Export() { // If nothing placed: return full grid mapped to letters/# only if (placed.isEmpty()) { - return new ExportedPuzzle(g.exportGrid(mask.mask, _ -> '#', '#'), new WordOut[0], difficulty, rewards); + return new ExportedPuzzle(g.exportGrid(clues.mask, _ -> '#', '#'), new WordOut[0], difficulty, rewards); } // 2) bounding box around all word cells + arrow cells, with 1-cell margin @@ -237,13 +271,17 @@ public record Export() { // 3) map of only used letter cells (everything else becomes '#') var letterAt = new HashMap(); - for (var p : placed) { + g.forEachLetter(clues.mask(), (idx, letter) -> { + if (letter == 0) return; + letterAt.put(idx, (char) (64 | letter)); + }); + /* for (var p : placed) { for (var c : p.cells) { - if (mask.notClue(c) && g.lisLetterAt(c)) { + if (clues.notClue(c) && g.lisLetterAt(c)) { letterAt.put(c, (char) (64 | g.letter32At(c))); } } - } + }*/ // 4) render gridv2 over cropped bounds (out-of-bounds become '#') var gridv2 = new String[Math.max(0, maxR - minR + 1)]; diff --git a/src/main/java/puzzle/Main.java b/src/main/java/puzzle/Main.java index 2aaa5bd..f166eb9 100644 --- a/src/main/java/puzzle/Main.java +++ b/src/main/java/puzzle/Main.java @@ -93,13 +93,13 @@ public class Main { info(String.format(Locale.ROOT, "simplicity : %.2f", res.filled().stats().simplicity)); section("Mask"); - System.out.print(indentLines(res.mask().gridToString(), " ")); + System.out.print(indentLines(res.clues().gridToString(), " ")); section("Grid (raw)"); - System.out.print(indentLines(res.filled().grid().gridToString(res.mask().mask()), " ")); + System.out.print(indentLines(res.filled().grid().gridToString(res.clues().mask()), " ")); section("Grid (human)"); - System.out.print(indentLines(res.filled().grid().renderHuman(res.mask().mask()), " ")); + System.out.print(indentLines(res.filled().grid().renderHuman(res.clues().mask()), " ")); var exported = res.exportFormatFromFilled(1, new Rewards(50, 2, 1)); diff --git a/src/main/java/puzzle/SwedishGenerator.java b/src/main/java/puzzle/SwedishGenerator.java index 8cc6ed2..f298a5c 100644 --- a/src/main/java/puzzle/SwedishGenerator.java +++ b/src/main/java/puzzle/SwedishGenerator.java @@ -194,7 +194,11 @@ public record SwedishGenerator(Rng rng, int[] stack, Clues cache) { public Clues deepCopyGrid() { return new Clues(lo, hi, vlo, vhi, rlo, rhi); } boolean clueless(int idx) { if (!isClue(idx)) return false; - clearClue(idx); + if ((idx & 64) == 0) { + clearClueLo(idx); + }else{ + clearClueHi(idx); + } return true; } public boolean hasRoomForClue(long packed) { return (packed) != X && notClue(packed & 0x7FL) && notClue((packed >>> 7) & 0x7FL); } @@ -216,6 +220,10 @@ public record SwedishGenerator(Rng rng, int[] stack, Clues cache) { else rhi &= ~mask; } } + public byte digitAtRevLo(int idx) { return (byte) (2 | ((vlo >>> idx) & 1L)); } + public byte digitAtRevHi(int idx) { return (byte) (2 | ((vhi >>> (idx & 63)) & 1L)); } + public byte digitAtDevLo(int idx) { return (byte) ((vlo >>> idx) & 1L); } + public byte digitAtDevHi(int idx) { return (byte) ((vhi >>> (idx & 63)) & 1L); } public byte digitAt(int idx) { if ((idx & 64) == 0) { int v = (int) ((vlo >>> idx) & 1L); @@ -227,18 +235,17 @@ public record SwedishGenerator(Rng rng, int[] stack, Clues cache) { return (byte) ((r << 1) | v); } } - public void clearClue(int idx) { - if ((idx & 64) == 0) { - long mask = ~(1L << idx); - lo &= mask; - vlo &= mask; - rlo &= mask; - } else { - long mask = ~(1L << (idx & 63)); - hi &= mask; - vhi &= mask; - rhi &= mask; - } + public void clearClueLo(int idx) { + long mask = ~(1L << idx); + lo &= mask; + vlo &= mask; + rlo &= mask; + } + public void clearClueHi(int idx) { + long mask = ~(1L << (idx & 63)); + hi &= mask; + vhi &= mask; + rhi &= mask; } public boolean isClue(long index) { return ((index & 64) == 0) ? ((lo >>> index) & 1L) != X : ((hi >>> (index & 63)) & 1L) != X; } public boolean isClue(int index) { return ((index & 64) == 0) ? ((lo >>> index) & 1L) != X : ((hi >>> (index & 63)) & 1L) != X; } @@ -254,8 +261,14 @@ public record SwedishGenerator(Rng rng, int[] stack, Clues cache) { } public Grid toGrid() { return new Grid(new byte[SIZE], lo, hi); } public void forEachSlot(SlotVisitor visitor) { - for (var l = lo; l != X; l &= l - 1) processSlot(this, visitor, Long.numberOfTrailingZeros(l)); - for (var h = hi; h != X; h &= h - 1) processSlot(this, visitor, 64 | Long.numberOfTrailingZeros(h)); + for (var l = lo & ~rlo & vlo; l != X; l &= l - 1) processSlot(this, visitor, Slot.packSlotKey(Long.numberOfTrailingZeros(l), 1)); + for (var l = lo & ~rlo & ~vlo; l != X; l &= l - 1) processSlot(this, visitor, Slot.packSlotKey(Long.numberOfTrailingZeros(l), 0)); + for (var l = lo & rlo & ~vlo; l != X; l &= l - 1) processSlotRev(this, visitor, Slot.packSlotKey(Long.numberOfTrailingZeros(l), 2)); + for (var l = lo & rlo & vlo; l != X; l &= l - 1) processSlotRev(this, visitor, Slot.packSlotKey(Long.numberOfTrailingZeros(l), 3)); + for (var h = hi & ~rhi & vlo; h != X; h &= h - 1) processSlot(this, visitor, Slot.packSlotKey(64 | Long.numberOfTrailingZeros(h), 1)); + for (var h = hi & ~rhi & ~vlo; h != X; h &= h - 1) processSlot(this, visitor, Slot.packSlotKey(64 | Long.numberOfTrailingZeros(h), 0)); + for (var h = hi & rhi & ~vhi; h != X; h &= h - 1) processSlotRev(this, visitor, Slot.packSlotKey((64 | Long.numberOfTrailingZeros(h)), 2)); + for (var h = hi & rhi & vhi; h != X; h &= h - 1) processSlotRev(this, visitor, Slot.packSlotKey((64 | Long.numberOfTrailingZeros(h)), 3)); } public Clues from(Clues best) { lo = best.lo; @@ -275,17 +288,9 @@ public record SwedishGenerator(Rng rng, int[] stack, Clues cache) { public long lo, hi; static int offset(int r, int c) { return r | (c << 3); } /// the pos will never target a clue - public byte letter32At(int pos) { return g[pos]; } - public boolean lisLetterAtLo(int pos) { return (lo & (1L << pos)) != X; } - public boolean lisLetterAtHi(int pos) { return (hi & (1L << (pos & 63))) != X; } void setLetterLo(int idx, byte ch) { lo |= (1L << idx); - g[idx] = ch; - } - void setLetterHi(int idx, byte ch) { - hi |= (1L << (idx & 63)); - g[idx] = ch; } } @@ -382,43 +387,43 @@ public record SwedishGenerator(Rng rng, int[] stack, Clues cache) { public static boolean horiz(int d) { return (d & 1) != 0; } public static int packSlotKey(int idx, int d) { return (idx << BIT_FOR_DIR) | d; } } - - private static void processSlot(Clues grid, SlotVisitor visitor, int idx) { - int key = Slot.packSlotKey(idx, grid.digitAt(idx)); // 0..3 - + // slice ray to stop before first clue, depending on direction monotonicity + // right/down => increasing indices; up/left => decreasing indices + // first clue is highest index among hits (hi first, then lo) + private static void processSlotRev(Clues c, SlotVisitor visitor, int key) { long rayLo = PATH_LO[key]; long rayHi = PATH_HI[key]; - // only consider clue cells - long hitsLo = rayLo & grid.lo; - long hitsHi = rayHi & grid.hi; + long hitsLo = rayLo & c.lo; + long hitsHi = rayHi & c.hi; - // slice ray to stop before first clue, depending on direction monotonicity - // right/down => increasing indices; up/left => decreasing indices + if (hitsHi != X) { + int msb = 63 - Long.numberOfLeadingZeros(hitsHi); + long stop = 1L << msb; + rayHi &= ~((stop << 1) - 1); // keep bits > stop + rayLo = 0; // lo indices are below stop + } else if (hitsLo != X) { + int msb = 63 - Long.numberOfLeadingZeros(hitsLo); + long stop = 1L << msb; + rayLo &= ~((stop << 1) - 1); + } - if (Slot.increasing(key)) { - // first clue is lowest index among hits (lo first, then hi) - if (hitsLo != X) { - long stop = 1L << Long.numberOfTrailingZeros(hitsLo); - rayLo &= (stop - 1); - rayHi = 0; // any hi is beyond the stop - } else if (hitsHi != X) { - long stop = 1L << Long.numberOfTrailingZeros(hitsHi); - // keep all lo (lo indices are < any hi index), but cut hi below stop - rayHi &= (stop - 1); - } - } else { - // first clue is highest index among hits (hi first, then lo) - if (hitsHi != X) { - int msb = 63 - Long.numberOfLeadingZeros(hitsHi); - long stop = 1L << msb; - rayHi &= ~((stop << 1) - 1); // keep bits > stop - rayLo = 0; // lo indices are below stop - } else if (hitsLo != X) { - int msb = 63 - Long.numberOfLeadingZeros(hitsLo); - long stop = 1L << msb; - rayLo &= ~((stop << 1) - 1); - } + visitor.visit(key, rayLo, rayHi); + } + private static void processSlot(Clues c, SwedishGenerator.SlotVisitor visitor, int key) { + long rayLo = PATH_LO[key]; + long rayHi = PATH_HI[key]; + long hitsLo = rayLo & c.lo; + long hitsHi = rayHi & c.hi; + + if (hitsLo != X) { + long stop = 1L << Long.numberOfTrailingZeros(hitsLo); + rayLo &= (stop - 1); + rayHi = 0; // any hi is beyond the stop + } else if (hitsHi != X) { + long stop = 1L << Long.numberOfTrailingZeros(hitsHi); + // keep all lo (lo indices are < any hi index), but cut hi below stop + rayHi &= (stop - 1); } visitor.visit(key, rayLo, rayHi); @@ -646,11 +651,12 @@ public record SwedishGenerator(Rng rng, int[] stack, Clues cache) { out.rlo = (out.rlo & ~maskLo) | (other.rlo & maskLo); out.rhi = (out.rhi & ~maskHi) | (other.rhi & maskHi); - for (var lo = out.lo; lo != X; lo &= lo - 1L) clearClues(out, Long.numberOfTrailingZeros(lo)); - for (var hi = out.hi; hi != X; hi &= hi - 1L) clearClues(out, 64 | Long.numberOfTrailingZeros(hi)); + for (var lo = out.lo; lo != X; lo &= lo - 1L) clearCluesLo(out, Long.numberOfTrailingZeros(lo)); + for (var hi = out.hi; hi != X; hi &= hi - 1L) clearCluesHi(out, 64 | Long.numberOfTrailingZeros(hi)); return out; } - public static void clearClues(Clues out, int idx) { if (!out.hasRoomForClue(OFFSETS_D_IDX[Slot.packSlotKey(idx, out.digitAt(idx))])) out.clearClue(idx); } + public static void clearCluesLo(Clues out, int idx) { if (!out.hasRoomForClue(OFFSETS_D_IDX[Slot.packSlotKey(idx, out.digitAt(idx))])) out.clearClueLo(idx); } + public static void clearCluesHi(Clues out, int idx) { if (!out.hasRoomForClue(OFFSETS_D_IDX[Slot.packSlotKey(idx, out.digitAt(idx))])) out.clearClueHi(idx); } Clues hillclimb(Clues start, int clue_size, int limit) { var best = start; diff --git a/src/test/java/puzzle/ExportFormatTest.java b/src/test/java/puzzle/ExportFormatTest.java index 97af5e8..4ef2fc7 100644 --- a/src/test/java/puzzle/ExportFormatTest.java +++ b/src/test/java/puzzle/ExportFormatTest.java @@ -5,20 +5,28 @@ import org.junit.jupiter.api.Test; import puzzle.Export.Clued; import puzzle.Export.Gridded; import puzzle.Export.Placed; -import puzzle.Export.Rewards; import puzzle.Export.PuzzleResult; +import puzzle.Export.Rewards; import puzzle.SwedishGenerator.FillResult; -import puzzle.SwedishGenerator.Grid; import puzzle.SwedishGenerator.Rng; - import java.io.IOException; import java.nio.file.Paths; - -import static org.junit.jupiter.api.Assertions.*; -import static puzzle.MainTest.*; -import static puzzle.MainTest.LETTER_E; -import static puzzle.MainTest.LETTER_T; -import static puzzle.SwedishGenerator.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static puzzle.SwedishGenerator.C; +import static puzzle.SwedishGenerator.Clues; +import static puzzle.SwedishGenerator.FillStats; +import static puzzle.SwedishGenerator.R; +import static puzzle.SwedishGenerator.STACK_SIZE; +import static puzzle.SwedishGenerator.Slot; +import static puzzle.SwedishGenerator.placeWord; +import static puzzle.SwedishGeneratorTest.OFF_0_1; +import static puzzle.SwedishGeneratorTest.OFF_0_2; +import static puzzle.SwedishGeneratorTest.OFF_0_3; +import static puzzle.SwedishGeneratorTest.OFF_0_4; +import static puzzle.SwedishGeneratorTest.OFF_0_5; +import static puzzle.SwedishGeneratorTest.TEST; public class ExportFormatTest { @@ -26,7 +34,7 @@ public class ExportFormatTest { static final byte CLUE_RIGHT = 1; static final byte CLUE_UP = 2; static final byte CLUE_LEFT = 3; - + @Test void testExportFormatFromFilled() { var swe = new SwedishGenerator(new Rng(0), new int[STACK_SIZE], Clues.createEmpty()); @@ -36,21 +44,17 @@ public class ExportFormatTest { clues.setClue(0, CLUE_RIGHT); // This creates a slot starting at (0,1) // Terminate the slot at (0,5) with another digit to avoid it extending to MAX_WORD_LENGTH - clues.setClue(Grid.offset(0, 5), CLUE_LEFT); - var grid = clues.toGrid(); + clues.setClue(OFF_0_5, CLUE_LEFT); + var grid = new Gridded(clues.toGrid()); var clueMap = new long[300]; // key = (cellIndex << 2) | (direction) - var key = (0) | (CLUE_RIGHT); - clueMap[key] = SwedishGeneratorTest.TEST; + var key = Slot.packSlotKey(0, CLUE_RIGHT); + var lo = (1L << OFF_0_1) | (1L << OFF_0_2) | (1L << OFF_0_3) | (1L << OFF_0_4); + clueMap[key] = TEST; + assertTrue(placeWord(grid.grid(), key, lo, 0L, TEST)); - // Manually fill the grid letters for "TEST" at (0,1), (0,2), (0,3), (0,4) - grid.setLetterLo(OFF_0_1, LETTER_T); - grid.setLetterLo(OFF_0_2, LETTER_E); - grid.setLetterLo(OFF_0_3, LETTER_S); - grid.setLetterLo(OFF_0_4, LETTER_T); - - var fillResult = new FillResult(true, new Gridded(grid), clueMap, new FillStats(0, 0, 0, 0)); + var fillResult = new FillResult(true, grid, clueMap, new FillStats(0, 0, 0, 0)); var puzzleResult = new PuzzleResult(new Clued(clues), fillResult); var rewards = new Rewards(10, 5, 1); diff --git a/src/test/java/puzzle/MainTest.java b/src/test/java/puzzle/MainTest.java index 940a714..531d0c7 100644 --- a/src/test/java/puzzle/MainTest.java +++ b/src/test/java/puzzle/MainTest.java @@ -4,38 +4,27 @@ import lombok.val; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import puzzle.Export.Clued; +import puzzle.Export.Gridded; +import puzzle.Export.LetterVisit.LetterAt; import puzzle.Export.PuzzleResult; import puzzle.Export.Rewards; import puzzle.Main.Opts; import puzzle.SwedishGenerator.Rng; import puzzle.SwedishGenerator.Slot; import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import static puzzle.SwedishGenerator.*; +import static puzzle.SwedishGeneratorTest.*; +import static puzzle.SwedishGeneratorTest.OFF_0_0; +import static puzzle.SwedishGeneratorTest.OFF_0_1; +import static puzzle.SwedishGeneratorTest.OFF_0_2; public class MainTest { - static final byte LETTER_A = (byte) 'A'; - static final byte LETTER_B = (byte) 'B'; - static final byte LETTER_Z = (byte) 'Z'; - static final byte LETTER_T = (byte) 'T'; - static final byte LETTER_E = (byte) 'E'; - static final byte LETTER_S = (byte) 'S'; - static final byte CLUE_DOWN = 0; - static final byte CLUE_RIGHT = 1; - static final byte CLUE_UP = 2; - static final byte CLUE_LEFT = 3; - - static final int OFF_0_0 = Grid.offset(0, 0); - static final int OFF_0_1 = Grid.offset(0, 1); - static final int OFF_0_2 = Grid.offset(0, 2); - static final int OFF_0_3 = Grid.offset(0, 3); - static final int OFF_0_4 = Grid.offset(0, 4); - static final int OFF_1_1 = Grid.offset(1, 1); - static final int OFF_1_2 = Grid.offset(1, 2); - static final int OFF_2_3 = Grid.offset(2, 3); + static final Opts opts = new Main.Opts() {{ this.seed = 12348; this.clueSize = 4; @@ -52,10 +41,11 @@ public class MainTest { void testExtractSlots() { var clues = Clues.createEmpty(); + val key = Slot.packSlotKey(OFF_0_0, CLUE_RIGHT); clues.setClue(OFF_0_0, CLUE_RIGHT); - var grid = clues.toGrid(); - grid.setLetterLo(OFF_0_1, LETTER_A); - grid.setLetterLo(OFF_0_2, LETTER_B); + var grid = new Gridded(clues.toGrid()); + + placeWord(grid.grid(), key, (1L << OFF_0_1) | (1L << OFF_0_2), 0, AB); var slots = extractSlots(clues, dict.index()); assertEquals(1, slots.length); @@ -100,40 +90,33 @@ public class MainTest { @Test public void testGridBasics() { var clues = Clues.createEmpty(); - clues.setClue(OFF_1_2, CLUE_UP); - var grid = clues.toGrid(); + val key = Slot.packSlotKey(OFF_2_1, CLUE_UP); + clues.setClue(OFF_2_1, CLUE_UP); + var grid = new Gridded(clues.toGrid()); // Test set/get - grid.setLetterLo(OFF_0_0, LETTER_A); - grid.setLetterLo(OFF_2_3, LETTER_Z); - - Assertions.assertEquals(LETTER_A, grid.letter32At(OFF_0_0)); - Assertions.assertEquals(CLUE_UP, clues.digitAt(OFF_1_2)); - Assertions.assertEquals(LETTER_Z, grid.letter32At(OFF_2_3)); - Assertions.assertFalse(grid.lisLetterAtLo(OFF_1_1)); - - // Verify letter mask - Assertions.assertTrue((grid.lo & (1L << OFF_0_0)) != 0); - Assertions.assertTrue((grid.lo & (1L << OFF_2_3)) != 0); - Assertions.assertTrue((grid.lo & (1L << OFF_1_2)) != 0); // Clue also in lo - assertEquals(0, (grid.lo & (1L << OFF_1_1))); // Empty letter cell + placeWord(grid.grid(), key, (1L << OFF_1_1) | (1L << OFF_0_1), 0, AZ); + val arr = grid.stream(clues).collect(Collectors.toMap(LetterAt::index, LetterAt::letter)); + Assertions.assertEquals(LETTER_A, arr.get(OFF_1_1)); + Assertions.assertEquals(LETTER_Z, arr.get(OFF_0_1)); + Assertions.assertEquals(CLUE_UP, clues.digitAt(OFF_2_1)); // Test isLetterAt Assertions.assertTrue(clues.notClue(OFF_0_0)); - Assertions.assertFalse(clues.notClue(OFF_1_2)); + Assertions.assertTrue(clues.notClue(OFF_1_2)); Assertions.assertTrue(clues.notClue(OFF_2_3)); Assertions.assertFalse(clues.isClue(OFF_1_1)); // Test isDigitAt Assertions.assertFalse(clues.isClue(0)); - Assertions.assertTrue(clues.isClue(OFF_1_2)); - Assertions.assertEquals(CLUE_UP, clues.digitAt(OFF_1_2)); + Assertions.assertTrue(clues.isClue(OFF_2_1)); + Assertions.assertEquals(CLUE_UP, clues.digitAt(OFF_2_1)); Assertions.assertFalse(clues.isClue(OFF_2_3)); Assertions.assertFalse(clues.isClue(OFF_1_1)); // Test isLettercell Assertions.assertTrue(clues.notClue(OFF_0_0)); // 'A' is letter - Assertions.assertTrue(clues.isClue(OFF_1_2)); // digit + Assertions.assertTrue(clues.isClue(OFF_2_1)); // digit Assertions.assertTrue(clues.notClue(OFF_1_1)); // '#' is lettercell } @Test @@ -192,7 +175,7 @@ public class MainTest { Assertions.assertEquals("SLEDE", Lemma.asWord(filled.clueMap()[282])); Assertions.assertEquals(-1L, filled.grid().grid().lo); Assertions.assertEquals(255L, filled.grid().grid().hi); - + filled.grid().gridToString(mask); var aa = new PuzzleResult(new Clued(mask), filled).exportFormatFromFilled(1, new Rewards(1, 1, 1)); } @@ -209,7 +192,7 @@ public class MainTest { System.out.println("[DEBUG_LOG] Simplicity: " + res.filled().stats().simplicity); System.out.println("[DEBUG_LOG] ClueMap Size: " + res.filled().wordCount()); System.out.println("[DEBUG_LOG] Grid:"); - System.out.println(res.filled().grid().renderHuman(res.mask().mask())); + System.out.println(res.filled().grid().renderHuman(res.clues().mask())); break; } } diff --git a/src/test/java/puzzle/SwedishGeneratorTest.java b/src/test/java/puzzle/SwedishGeneratorTest.java index 89b4537..c3cb4dd 100644 --- a/src/test/java/puzzle/SwedishGeneratorTest.java +++ b/src/test/java/puzzle/SwedishGeneratorTest.java @@ -5,8 +5,10 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import puzzle.Export.Gridded; import puzzle.Export.IntListDTO; +import puzzle.Export.LetterVisit.LetterAt; import java.nio.charset.StandardCharsets; import java.util.Arrays; +import java.util.stream.Collectors; import static org.junit.jupiter.api.Assertions.*; import static puzzle.SwedishGenerator.*; @@ -54,6 +56,7 @@ public class SwedishGeneratorTest { static final long ABC = Lemma.from(26, "ABC"); static final long ABD = Lemma.from(27, "ABD"); static final long AZ = Lemma.from(28, "AZ"); + static final long AB = Lemma.from(29, "AB"); static final byte LETTER_A = ((byte) 'A') & 31; static final byte LETTER_B = ((byte) 'B') & 31; static final byte LETTER_C = ((byte) 'C') & 31; @@ -64,13 +67,18 @@ public class SwedishGeneratorTest { static final byte CLUE_UP = 2; static final byte CLUE_LEFT = 3; - static final int OFF_0_0 = Grid.offset(0, 0); - static final int OFF_0_1 = Grid.offset(0, 1); - static final int OFF_0_2 = Grid.offset(0, 2); + static final int OFF_1_0 = Grid.offset(1, 0); static final int OFF_1_1 = Grid.offset(1, 1); static final int OFF_1_2 = Grid.offset(1, 2); - static final int OFF_2_0 = Grid.offset(2, 0); + static final int OFF_2_1 = Grid.offset(2, 1); static final int OFF_2_3 = Grid.offset(2, 3); + static final int OFF_0_0 = Grid.offset(0, 0); + static final int OFF_0_4 = Grid.offset(0, 4); + static final int OFF_0_5 = Grid.offset(0, 5); + static final int OFF_0_1 = Grid.offset(0, 1); + static final int OFF_0_2 = Grid.offset(0, 2); + static final int OFF_0_3 = Grid.offset(0, 3); + static final int OFF_2_0 = Grid.offset(2, 0); static final int OFF_2_5 = Grid.offset(2, 5); static final int OFF_3_5 = Grid.offset(3, 5); static final int OFF_4_5 = Grid.offset(4, 5); @@ -78,21 +86,23 @@ public class SwedishGeneratorTest { static final byte D_BYTE_2 = CLUE_RIGHT; @Test void testPatternForSlotAllLetters() { - var grid = createEmpty(); - grid.setLetterLo(0, LETTER_A); - grid.setLetterLo(1, LETTER_B); - grid.setLetterLo(2, LETTER_C); - var key = Slot.packSlotKey(18, CLUE_RIGHT); - var pattern = patternForSlot(grid, key, 7L, 0L); - assertEquals(1L | (28L << 8) | (55L << 16), pattern); + var grid = new Gridded(createEmpty()); + var key = Slot.packSlotKey(OFF_0_0, CLUE_RIGHT); + val clues = Clues.createEmpty(); + clues.setClue(OFF_0_0, CLUE_RIGHT); + placeWord(grid.grid(), 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)); + assertEquals(LETTER_A, map.get(OFF_0_1)); + assertEquals(LETTER_B, map.get(OFF_0_2)); + assertEquals(LETTER_C, map.get(OFF_0_3)); } @Test void testPatternForSlotMixed() { var grid = createEmpty(); grid.setLetterLo(OFF_0_0, LETTER_A); - grid.setLetterLo(2, LETTER_C); - var key = Slot.packSlotKey(1, CLUE_RIGHT); + grid.setLetterLo(OFF_2_0, LETTER_C); + var key = Slot.packSlotKey(OFF_1_0, CLUE_RIGHT); var pattern = patternForSlot(grid, key, 7L, 0L); assertEquals(1L | (0L) | (55L << 16), pattern); } @@ -133,9 +143,11 @@ public class SwedishGeneratorTest { @Test void testGrid() { - var grid = createEmpty(); - grid.setLetterLo(OFF_0_0, LETTER_A); - assertEquals(LETTER_A, grid.letter32At(OFF_0_0)); + var grid = new Gridded(createEmpty()); + grid.grid().setLetterLo(OFF_0_0, LETTER_A); + val arr = grid.stream(Clues.createEmpty()).collect(Collectors.toMap(LetterAt::index, LetterAt::letter)); + assertEquals(1, arr.size()); + assertEquals(LETTER_A, arr.get(OFF_0_0)); } @Test @@ -270,7 +282,7 @@ public class SwedishGeneratorTest { @Test void testPlaceWord() { - var grid = createEmpty(); + var grid = new Gridded(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); @@ -278,51 +290,55 @@ public class SwedishGeneratorTest { var w1 = ABC; // 1. Successful placement in empty grid - assertTrue(placeWord(grid, key, lo, hi, w1)); - assertEquals(LETTER_A, grid.letter32At(OFF_0_0)); - assertEquals(LETTER_B, grid.letter32At(OFF_0_1)); - assertEquals(LETTER_C, grid.letter32At(OFF_0_2)); + assertTrue(placeWord(grid.grid(), key, lo, hi, w1)); + var map = grid.stream(Clues.createEmpty()).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)); + assertEquals(LETTER_C, map.get(OFF_0_2)); // 2. Successful placement with partial overlap (same characters) - assertTrue(placeWord(grid, key, lo, hi, w1)); - + assertTrue(placeWord(grid.grid(), key, lo, hi, w1)); // 3. Conflict: place "ABD" where "ABC" is - var w2 = ABD; - assertFalse(placeWord(grid, key, lo, hi, w2)); + assertFalse(placeWord(grid.grid(), key, lo, hi, ABD)); // Verify grid is unchanged (still "ABC") - assertEquals(LETTER_A, grid.letter32At(OFF_0_0)); - assertEquals(LETTER_B, grid.letter32At(OFF_0_1)); - assertEquals(LETTER_C, grid.letter32At(OFF_0_2)); + map = grid.stream(Clues.createEmpty()).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)); + assertEquals(LETTER_C, map.get(OFF_0_2)); // 4. Partial placement then conflict (rollback) - grid = createEmpty(); - grid.setLetterLo(OFF_0_2, LETTER_X); // Conflict at the end - assertFalse(placeWord(grid, key, lo, hi, w1)); - // Verify grid is still empty (except for 'X') - assertFalse(grid.lisLetterAtLo(OFF_0_0)); - assertFalse(grid.lisLetterAtLo(OFF_0_1)); - assertEquals(LETTER_X, grid.letter32At(OFF_0_2)); + grid = new Gridded(createEmpty()); + grid.grid().setLetterLo(OFF_0_2, LETTER_X); // Conflict at the end + assertFalse(placeWord(grid.grid(), key, lo, hi, w1)); + map = grid.stream(Clues.createEmpty()).collect(Collectors.toMap(LetterAt::index, LetterAt::letter)); + assertEquals(1, map.size()); + assertEquals(LETTER_X, map.get(OFF_0_2)); } @Test void testBacktrackingHelpers() { - var grid = createEmpty(); + var grid = new Gridded(createEmpty()); // Slot at 0,1 length 2 var key = Slot.packSlotKey(0, CLUE_RIGHT); var lo = (1L << OFF_0_1) | (1L << OFF_0_2); var w = AZ; - val low = grid.lo; - val top = grid.hi; - var placed = placeWord(grid, key, lo, 0L, w); + val low = grid.grid().lo; + val top = grid.grid().hi; + var placed = placeWord(grid.grid(), key, lo, 0L, w); assertTrue(placed); - assertEquals(LETTER_A, grid.letter32At(OFF_0_1)); - assertEquals(LETTER_Z, grid.letter32At(OFF_0_2)); + var map = grid.stream(Clues.createEmpty()).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.hi = top; - grid.lo = low; - //grid.undoPlace(undoBuffer[0], undoBuffer[1]); - assertFalse(grid.lisLetterAtLo(OFF_0_1)); - assertFalse(grid.lisLetterAtLo(OFF_0_2)); + grid.grid().hi = top; + grid.grid().lo = low; + map = grid.stream(Clues.createEmpty()).collect(Collectors.toMap(LetterAt::index, LetterAt::letter)); + assertEquals(0, map.size()); + assertEquals(DASH, map.getOrDefault(OFF_0_1, DASH)); + assertEquals(DASH, map.getOrDefault(OFF_0_2, DASH)); } @Test