From 1fa112ab655e442eb1b2aa0d0333528e2aed7097 Mon Sep 17 00:00:00 2001 From: mike Date: Mon, 19 Jan 2026 16:31:33 +0100 Subject: [PATCH] introduce bitloops --- .../generated-sources/puzzle/DictData.java | 9 +- .../generated-sources/puzzle/DictDataL3.java | 5 +- src/main/java-templates/puzzle/Config.java | 3 + src/main/java/puzzle/Export.java | 186 ++++++++++-------- src/main/java/puzzle/Main.java | 32 +-- src/main/java/puzzle/Masker.java | 122 ++++++------ src/main/java/puzzle/SwedishGenerator.java | 165 +++++----------- src/test/java/puzzle/ConnectivityTest.java | 22 +-- src/test/java/puzzle/CornerClueTest.java | 8 +- .../java/puzzle/DictJavaGeneratorMulti.java | 2 +- src/test/java/puzzle/ExportFormatTest.java | 42 +--- src/test/java/puzzle/MainTest.java | 38 ++-- src/test/java/puzzle/PerformanceTest.java | 37 ++-- .../java/puzzle/SwedishGeneratorTest.java | 177 ++++++++++------- 14 files changed, 393 insertions(+), 455 deletions(-) diff --git a/src/main/generated-sources/puzzle/DictData.java b/src/main/generated-sources/puzzle/DictData.java index 8eaf405..6ddf87e 100644 --- a/src/main/generated-sources/puzzle/DictData.java +++ b/src/main/generated-sources/puzzle/DictData.java @@ -1,12 +1,13 @@ package puzzle; +import static puzzle.SwedishGenerator.*; public final class DictData { private DictData() {} - public static final SwedishGenerator.Dict DICT = build(); + public static final Dict DICT = build(); - private static SwedishGenerator.Dict build() { - SwedishGenerator.DictEntry[] idx = new SwedishGenerator.DictEntry[SwedishGenerator.MAX_WORD_LENGTH_PLUS_ONE]; + private static Dict build() { + DictEntry[] idx = new DictEntry[MAX_WORD_LENGTH_PLUS_ONE]; idx[2] = DictDataL2.entry(); idx[3] = DictDataL3.entry(); idx[4] = DictDataL4.entry(); @@ -14,6 +15,6 @@ public final class DictData { idx[6] = DictDataL6.entry(); idx[7] = DictDataL7.entry(); idx[8] = DictDataL8.entry(); - return new SwedishGenerator.Dict(idx, 40670); + return new Dict(idx, 40670); } } diff --git a/src/main/generated-sources/puzzle/DictDataL3.java b/src/main/generated-sources/puzzle/DictDataL3.java index dfff1b2..9ab12c2 100644 --- a/src/main/generated-sources/puzzle/DictDataL3.java +++ b/src/main/generated-sources/puzzle/DictDataL3.java @@ -1,5 +1,6 @@ package puzzle; +import static puzzle.SwedishGenerator.*; public final class DictDataL3 { private DictDataL3() {} @@ -17,11 +18,11 @@ public final class DictDataL3 { return DictDataL3P0.get(); } - public static SwedishGenerator.DictEntry entry() { + public static DictEntry entry() { long[] wds = words(); long[] flat = posFlat(); long[][] pos = reshape(flat, ROWS, COLS); - return new SwedishGenerator.DictEntry(wds, pos, wds.length, (wds.length + 63) >>> 6); + return new DictEntry(wds, pos, wds.length, (wds.length + 63) >>> 6); } private static int copy(long[] dst, int at, long[] src) { diff --git a/src/main/java-templates/puzzle/Config.java b/src/main/java-templates/puzzle/Config.java index d6e744e..9973399 100644 --- a/src/main/java-templates/puzzle/Config.java +++ b/src/main/java-templates/puzzle/Config.java @@ -10,4 +10,7 @@ public final class Config { public static final int MAX_LEN = ${MAX_LEN}; public static final int PUZZLE_ROWS = ${PUZZLE_ROWS}; public static final int PUZZLE_COLS = ${PUZZLE_COLS}; + public static final int PUZZLE_SIZE = PUZZLE_ROWS*PUZZLE_COLS; + public static final int MAX_WORD_LENGTH = PUZZLE_ROWS; + public static final int MAX_WORD_LENGTH_MIN_1 = PUZZLE_ROWS-1; } diff --git a/src/main/java/puzzle/Export.java b/src/main/java/puzzle/Export.java index 59ac210..404445f 100644 --- a/src/main/java/puzzle/Export.java +++ b/src/main/java/puzzle/Export.java @@ -1,5 +1,6 @@ package puzzle; +import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.experimental.Accessors; @@ -13,13 +14,14 @@ import puzzle.SwedishGenerator.DictEntry; import puzzle.SwedishGenerator.FillResult; import puzzle.SwedishGenerator.Grid; import puzzle.SwedishGenerator.Slotinfo; -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.Export.Clue.DOWN; +import static puzzle.Export.Clue.RIGHT; +import static puzzle.Masker.Clues.createEmpty; import static puzzle.SwedishGenerator.R; import static puzzle.SwedishGenerator.Lemma; import static puzzle.Masker.Slot; @@ -37,42 +39,78 @@ import static puzzle.SwedishGenerator.X; */ public record Export() { - record Strings() { - - static String padRight(String s, int n) { - if (s.length() >= n) return s; - return s + " ".repeat(n - s.length()); - } + public static final ThreadLocal BYTES = ThreadLocal.withInitial(() -> new byte[SwedishGenerator.MAX_WORD_LENGTH]); + 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 byte CLUE_LEFT_TOP = 4; + static int HI(int in) { return in | 64; } + static char LETTER(int in) { return (char) (in | 64); } + static char CLUE_CHAR(int s) { return (char) (s | 48); } + static int INDEX_ROW(int idx) { return idx & 7; } + static int INDEX_COL(int idx) { return idx >>> 3; } + static int INDEX(int r, int cols, int c) { return r * cols + c; } + @AllArgsConstructor + enum Clue { + DOWN(CLUE_DOWN), + RIGHT(CLUE_RIGHT), + UP(CLUE_UP), + LEFT(CLUE_LEFT); + final byte dir; } - static final String INIT = IntStream.range(0, R).mapToObj(l_ -> " ").collect(Collectors.joining("\n")); + record Strings() { + + static String padRight(String s, int n) { return s.length() >= n ? s : s + " ".repeat(n - s.length()); } + } + + static final String INIT = IntStream.range(0, Config.PUZZLE_ROWS).mapToObj(l_ -> " ").collect(Collectors.joining("\n")); public record ClueAt(int index, int clue) { } public record Clued(@Delegate Clues 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) { + var c = createEmpty(); + var lines = s.split("\n"); + for (int r = 0; r < Math.min(lines.length, R); r++) { + var line = lines[r]; + for (int col = 0; col < Math.min(line.length(), C); col++) { + char ch = line.charAt(col); + if (ch >= '0' && ch <= '4') { + int idx = Masker.offset(r, col); + byte dir = (byte) (ch - '0'); + if ((idx & 64) == 0) c.setClueLo(1L << idx, dir); + else c.setClueHi(1L << (idx & 63), dir); + } + } + } + return new Clued(c); + } String gridToString() { var sb = new StringBuilder(INIT); forEachSlot((s, _, _) -> { 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)); + sb.setCharAt(INDEX(INDEX_ROW(idx), C + 1, INDEX_COL(idx)), CLUE_CHAR(dir)); }); return sb.toString(); } public Stream stream() { val stream = Stream.builder(); - for (var l = c.lo & ~c.rlo & c.vlo; l != X; l &= l - 1) stream.accept(new ClueAt(Long.numberOfTrailingZeros(l), 1)); - for (var l = c.lo & ~c.rlo & ~c.vlo; l != X; l &= l - 1) stream.accept(new ClueAt(Long.numberOfTrailingZeros(l), 0)); - for (var l = c.lo & c.rlo & ~c.vlo; l != X; l &= l - 1) stream.accept(new ClueAt(Long.numberOfTrailingZeros(l), 2)); - for (var l = c.lo & c.rlo & c.vlo; l != X; l &= l - 1) stream.accept(new ClueAt(Long.numberOfTrailingZeros(l), 3)); - for (var h = c.hi & ~c.rhi & c.vhi; h != X; h &= h - 1) stream.accept(new ClueAt(64 | Long.numberOfTrailingZeros(h), 1)); - for (var h = c.hi & ~c.rhi & ~c.vhi; h != X; h &= h - 1) stream.accept(new ClueAt(64 | Long.numberOfTrailingZeros(h), 0)); - for (var h = c.hi & c.rhi & ~c.vhi; h != X; h &= h - 1) stream.accept(new ClueAt((64 | Long.numberOfTrailingZeros(h)), 2)); - for (var h = c.hi & c.rhi & c.vhi; h != X; h &= h - 1) stream.accept(new ClueAt((64 | Long.numberOfTrailingZeros(h)), 3)); + for (var l = c.lo & ~c.xlo & ~c.rlo & c.vlo; l != X; l &= l - 1) stream.accept(new ClueAt(Long.numberOfTrailingZeros(l), RIGHT.dir)); + for (var l = c.lo & ~c.xlo & ~c.rlo & ~c.vlo; l != X; l &= l - 1) stream.accept(new ClueAt(Long.numberOfTrailingZeros(l), DOWN.dir)); + for (var l = c.lo & ~c.xlo & c.rlo & ~c.vlo; l != X; l &= l - 1) stream.accept(new ClueAt(Long.numberOfTrailingZeros(l), CLUE_UP)); + for (var l = c.lo & ~c.xlo & c.rlo & c.vlo; l != X; l &= l - 1) stream.accept(new ClueAt(Long.numberOfTrailingZeros(l), CLUE_LEFT)); + for (var l = c.lo & c.xlo; l != X; l &= l - 1) stream.accept(new ClueAt(Long.numberOfTrailingZeros(l), CLUE_LEFT_TOP)); + + for (var h = c.hi & ~c.xhi & ~c.rhi & c.vhi; h != X; h &= h - 1) stream.accept(new ClueAt(HI(Long.numberOfTrailingZeros(h)), CLUE_RIGHT)); + for (var h = c.hi & ~c.xhi & ~c.rhi & ~c.vhi; h != X; h &= h - 1) stream.accept(new ClueAt(HI(Long.numberOfTrailingZeros(h)), CLUE_DOWN)); + for (var h = c.hi & ~c.xhi & c.rhi & ~c.vhi; h != X; h &= h - 1) stream.accept(new ClueAt(HI(Long.numberOfTrailingZeros(h)), CLUE_UP)); + for (var h = c.hi & ~c.xhi & c.rhi & c.vhi; h != X; h &= h - 1) stream.accept(new ClueAt(HI(Long.numberOfTrailingZeros(h)), CLUE_LEFT)); + for (var h = c.hi & c.xhi; h != X; h &= h - 1) stream.accept(new ClueAt(HI(Long.numberOfTrailingZeros(h)), CLUE_LEFT_TOP)); return stream.build(); } @@ -83,29 +121,51 @@ public record Export() { record LetterAt(int index, byte letter) { - public char human() { - return (char) (letter | 64); - } + public int row() { return INDEX_ROW(index); } + public int col() { return INDEX_COL(index); } + public char human() { return LETTER(letter); } static LetterAt from(int index, byte[] bytes) { return new LetterAt(index, bytes[index]); } + public int index(int cols) { return (row() * cols) + col(); } } void visit(int index, byte letter); default void visit(int index, byte[] letters) { visit(index, letters[index]); } } - record Gridded(@Delegate Grid grid) { + record Gridded(@Delegate Grid grid, Clues cl) { + public Gridded(Clues clues) { this(clues.toGrid(), clues); } public Stream stream(Clues clues) { 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)); return stream.build(); } - public void forEachLetter(Clues clues, LetterVisit visitor) { - for (var l = grid.lo & ~clues.lo; l != X; l &= l - 1) visitor.visit(Long.numberOfTrailingZeros(l), grid.g); - for (var h = grid.hi & ~clues.hi & 0xFF; h != X; h &= h - 1) visitor.visit(64 | Long.numberOfTrailingZeros(h), grid.g); + String gridToString(Clues clues) { + var sb = new StringBuilder(INIT); + clues.forEachSlot((s, _, _) -> { + 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)); + }); + stream(clues).forEach((l) -> sb.setCharAt(l.index(C + 1), l.human())); + return sb.toString(); } - public static IntStream walk(byte base, long lo, long hi) { + 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)))); + }); + stream(clues).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) { if (Slotinfo.increasing(base)) { return IntStream.concat( IntStream.generate(new IntSupplier() { @@ -136,9 +196,8 @@ public record Export() { @Override public int getAsInt() { int msb = 63 - Long.numberOfLeadingZeros(temp); - int res = 64 | msb; temp &= ~(1L << msb); - return res; + return 64 | msb; } }).limit(Long.bitCount(hi)), IntStream.generate(new IntSupplier() { @@ -147,65 +206,33 @@ public record Export() { @Override public int getAsInt() { int msb = 63 - Long.numberOfLeadingZeros(temp); - int res = msb; temp &= ~(1L << msb); - return res; + return msb; } }).limit(Long.bitCount(lo))); } } - String gridToString(Clues clues) { - var sb = new StringBuilder(INIT); - clues.forEachSlot((s, _, _) -> { - 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) { - return String.join("\n", exportGrid(clues, _ -> ' ', '#')); - } + + public String renderHuman(Clues clues) { return String.join("\n", exportGrid(clues, _ -> ' ', '#')); } @FunctionalInterface interface Replacar { record Cell(Grid grid, Clues clues, int index, byte data) { } 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"); - } + } record Placed(long lemma, int slotKey, int[] cells) { - static final char[] DIRECTION = { Placed.VERTICAL, Placed.HORIZONTAL, Placed.VERTICAL, Placed.HORIZONTAL, Placed.VERTICAL }; public static final char HORIZONTAL = 'h'; static final char VERTICAL = 'v'; - public int arrowCol() { return SwedishGenerator.IT[Slot.clueIndex(slotKey)].c(); } - public int arrowRow() { return SwedishGenerator.IT[Slot.clueIndex(slotKey)].r(); } - public int startRow() { return SwedishGenerator.IT[cells[0]].r(); } - public int startCol() { return SwedishGenerator.IT[cells[0]].c(); } + static final char[] DIRECTION = { Placed.VERTICAL, Placed.HORIZONTAL, Placed.VERTICAL, Placed.HORIZONTAL, Placed.VERTICAL }; + + public int arrowCol() { return Masker.IT[Slot.clueIndex(slotKey)].c(); } + public int arrowRow() { return Masker.IT[Slot.clueIndex(slotKey)].r(); } + public int startRow() { return Masker.IT[cells[0]].r(); } + public int startCol() { return Masker.IT[cells[0]].c(); } public boolean isReversed() { return !Slotinfo.increasing(slotKey); } public char direction() { return DIRECTION[Slot.dir(slotKey)]; } } @@ -214,9 +241,9 @@ public record Export() { public record WordOut(String word, int[] cell, int startRow, int startCol, char direction, int arrowRow, int arrowCol, boolean isReversed, int complex, String[] clue) { - public WordOut(long l, int startRow, int startCol, char d, int arrowRow, int arrowCol, boolean isReversed) { - val meta = Meta.readRecord(Meta.shardKey(l), Lemma.unpackShardIndex(l)); - this(Lemma.asWord(l), new int[]{ arrowRow, arrowCol, startRow, startCol }, startRow, startCol, d, arrowRow, arrowCol, isReversed, + public WordOut(long l, int startRow, int startCol, char d, int arrowRow, int arrowCol, boolean isReversed, byte[] bytes) { + val meta = Meta.readRecord(Meta.shardKey(l), Lemma.unpackShardIndex(l)); + this(Lemma.asWord(l, bytes), new int[]{ arrowRow, arrowCol, startRow, startCol }, startRow, startCol, d, arrowRow, arrowCol, isReversed, meta.simpel(), meta.clues()); } } @@ -243,7 +270,7 @@ public record Export() { return new ExportedPuzzle(grid.exportGrid(clues.c, _ -> '#', '#'), new WordOut[0], difficulty, rewards); } - var placed = Arrays.stream(slots).map(slot -> new Placed(slot.assign().w, slot.key(), Gridded.walk((byte) slot.key(), slot.lo(), slot.hi()).toArray())).toArray( + var placed = Arrays.stream(slots).map(slot -> new Placed(slot.assign().w, slot.key(), Gridded.cellWalk((byte) slot.key(), slot.lo(), slot.hi()).toArray())).toArray( Placed[]::new); // 2) bounding box around all word cells + arrow cells, with 1-cell margin @@ -252,7 +279,7 @@ public record Export() { for (var rc : placed) { for (var c : rc.cells) { - val it = SwedishGenerator.IT[c]; + val it = Masker.IT[c]; minR = Math.min(minR, it.r()); minC = Math.min(minC, it.c()); maxR = Math.max(maxR, it.r()); @@ -270,12 +297,13 @@ public record Export() { var gridv2 = new String[Math.max(0, maxR - minR + 1)]; for (int r = minR, i = 0; r <= maxR; r++, i++) { var row = new StringBuilder(Math.max(0, maxC - minC + 1)); - for (var c = minC; c <= maxC; c++) row.append(map.getOrDefault(Grid.offset(r, c), '#')); + for (var c = minC; c <= maxC; c++) row.append(map.getOrDefault(Masker.offset(r, c), '#')); gridv2[i] = row.toString(); } // 5) words output with cropped coordinates int MIN_R = minR, MIN_C = minC; + val bytes = BYTES.get(); var wordsOut = Arrays.stream(placed).map(p -> new WordOut( p.lemma, p.startRow() - MIN_R, @@ -283,7 +311,7 @@ public record Export() { p.direction(), p.arrowRow() - MIN_R, p.arrowCol() - MIN_C, - p.isReversed() + p.isReversed(), bytes )).toArray(WordOut[]::new); return new ExportedPuzzle(gridv2, wordsOut, difficulty, rewards); } diff --git a/src/main/java/puzzle/Main.java b/src/main/java/puzzle/Main.java index 28afa9a..5c4e197 100644 --- a/src/main/java/puzzle/Main.java +++ b/src/main/java/puzzle/Main.java @@ -373,34 +373,6 @@ public class Main { return null; } } - static Clued generateClues() { - String simple = "000 3000\n" + - " 3 \n" + - " 31 \n" + - " 3\n" + - "1 \n" + - "1 \n" + - "1 2\n" + - "1 222 3"; - String sampleComplex = "1 0000\n" + - "1 \n" + - "00 01 \n" + - " 1 \n" + - " 1 \n" + - " 2 1 \n" + - " 1 \n" + - "221 22\n"; - String def = " 30000\n" + - "0 001 \n" + - " 1 \n" + - " 3 \n" + - " 3 \n" + - " 32 \n" + - " 32 2\n" + - "2222 3"; - return Clues.parse(sampleComplex - ); - } static Clues generateNewClues(Rng rng, Opts opts) { var masker = new Masker(rng, new int[STACK_SIZE], Masker.Clues.createEmpty()); return masker.generateMask(opts.clueSize, opts.pop, opts.gens, opts.offspring); @@ -415,7 +387,7 @@ public class Main { val slotInfo = Masker.slots(mask, dict.index()); var grid = Slotinfo.grid(slotInfo);// mask.toGrid(); - var filled = fillMask(rng, slotInfo, grid, (!Main.VERBOSE || multiThreaded)); + var filled = fillMask(rng, slotInfo, grid); if (!multiThreaded) { System.out.print("\r" + Strings.padRight("", 120) + "\r"); @@ -451,7 +423,7 @@ public class Main { System.out.println(Arrays.stream(new Clued(mask).gridToString().split("\n")).map(s -> "\"" + s + "\\n\" +").collect(Collectors.joining("\n"))); } if (filled.ok() && (opts.minSimplicity <= 0 || filled.stats().simplicity >= opts.minSimplicity)) { - return new PuzzleResult(new Clued(mask), new Gridded(grid), slotInfo, filled); + return new PuzzleResult(new Clued(mask), new Gridded(grid, mask), slotInfo, filled); } if (opts.verbose && filled.ok()) { diff --git a/src/main/java/puzzle/Masker.java b/src/main/java/puzzle/Masker.java index 8e77342..8d5cff4 100644 --- a/src/main/java/puzzle/Masker.java +++ b/src/main/java/puzzle/Masker.java @@ -2,24 +2,22 @@ package puzzle; import lombok.AllArgsConstructor; import lombok.val; -import puzzle.Export.Clued; -import puzzle.Export.Gridded; +import precomp.Neighbors9x8; +import precomp.Neighbors9x8.rci; -import java.sql.Array; import java.util.ArrayList; import java.util.Comparator; import java.util.Objects; -import java.util.stream.IntStream; import static java.lang.Long.*; import static puzzle.SwedishGenerator.*; public final class Masker { - private final Rng rng; - private final int[] stack; - private final Clues cache; - + public static final rci[] IT = Neighbors9x8.IT; + 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]; @@ -42,8 +40,8 @@ public final class Masker { for (int dc1 = -2; dc1 <= 2; dc1++) for (int dc2 = -2; dc2 <= 2; dc2++) { val ti = IT[i]; - MUTATE_RI[i][k++] = Grid.offset(SwedishGenerator.clamp(ti.r() + dr1 + dr2, 0, R - 1), - SwedishGenerator.clamp(ti.c() + dc1 + dc2, 0, C - 1)); + MUTATE_RI[i][k++] = offset(clamp(ti.r() + dr1 + dr2, 0, R - 1), + clamp(ti.c() + dc1 + dc2, 0, C - 1)); } } } @@ -70,6 +68,27 @@ public final class Masker { if (Long.bitCount(rayLo) + Long.bitCount(rayHi) >= MIN_LEN) visitor.visit(key, rayLo, rayHi); } + private static boolean validSlotRev(long lo, long hi, int min, int key) { + long rayLo = PATH_LO[key]; + long rayHi = PATH_HI[key]; + // only consider clue cells + long hitsLo = rayLo & lo; + long hitsHi = rayHi & hi; + + if (hitsHi != X) return (Long.bitCount(rayHi & -(1L << 63 - numberOfLeadingZeros(hitsHi) << 1)) >= min); + else if (hitsLo != X) return (Long.bitCount(rayLo & -(1L << 63 - numberOfLeadingZeros(hitsLo) << 1)) + Long.bitCount(rayHi) >= min); + else return (Long.bitCount(rayLo) + Long.bitCount(rayHi) >= min); + } + private static boolean validSlot(long lo, long hi, int min, int key) { + long rayLo = PATH_LO[key]; + long rayHi = PATH_HI[key]; + long hitsLo = rayLo & lo; + long hitsHi = rayHi & hi; + + if (hitsLo != X) return (Long.bitCount(rayLo & ((1L << numberOfTrailingZeros(hitsLo)) - 1)) >= min); + else if (hitsHi != X) return (Long.bitCount(rayLo) + Long.bitCount(rayHi & ((1L << numberOfTrailingZeros(hitsHi)) - 1)) >= min); + else return (Long.bitCount(rayLo) + Long.bitCount(rayHi) >= min); + } private static void processSlot(Clues c, SlotVisitor visitor, int key) { long rayLo = PATH_LO[key]; long rayHi = PATH_HI[key]; @@ -79,10 +98,9 @@ public final class Masker { if (hitsLo != X) { long stop = 1L << numberOfTrailingZeros(hitsLo); rayLo &= (stop - 1); - rayHi = 0; // any hi is beyond the stop + rayHi = 0; } else if (hitsHi != X) { long stop = 1L << numberOfTrailingZeros(hitsHi); - // keep all lo (lo indices are < any hi index), but cut hi below stop rayHi &= (stop - 1); } if (Long.bitCount(rayLo) + Long.bitCount(rayHi) >= MIN_LEN) @@ -116,6 +134,8 @@ public final class Masker { for (long b = hi; b != X; b &= b - 1) cross += (count[64 | numberOfTrailingZeros(b)] - 1); return cross * 10 + Slot.length(lo, hi); } + public static int clamp(int x, int a, int b) { return Math.max(a, Math.min(b, x)); } + public static int offset(int r, int c) { return r | (c << 3); } public long maskFitness(final Clues grid, int clueSize) { @@ -216,7 +236,8 @@ public final class Masker { // Connectiviteitscheck for (int i = 0; i < numClues; i++) { - adjLo[i] = 0; adjHi[i] = 0; + adjLo[i] = 0; + adjHi[i] = 0; } for (int i = 0; i < numClues; i++) { for (int j = i + 1; j < numClues; j++) { @@ -240,22 +261,22 @@ public final class Masker { stack[0] = 0; int sp = 1; while (sp > 0) { - int cur = stack[--sp]; + int cur = stack[--sp]; long nLo = adjLo[cur] & ~reachedLo; long nHi = adjHi[cur] & ~reachedHi; while (nLo != 0) { long lsb = nLo & -nLo; - int idx = numberOfTrailingZeros(lsb); - reachedLo |= lsb; + int idx = numberOfTrailingZeros(lsb); + reachedLo |= lsb; stack[sp++] = idx; - nLo &= ~lsb; + nLo &= ~lsb; } while (nHi != 0) { long lsb = nHi & -nHi; - int idx = 64 | numberOfTrailingZeros(lsb); - reachedHi |= lsb; + int idx = 64 | numberOfTrailingZeros(lsb); + reachedHi |= lsb; stack[sp++] = idx; - nHi &= ~lsb; + nHi &= ~lsb; } } int count = bitCount(reachedLo) + bitCount(reachedHi); @@ -501,29 +522,13 @@ public final class Masker { return best.grid; }//@formatter:off @FunctionalInterface public interface SlotVisitor { void visit(int key, long lo, long hi); } + //@formatter:on @AllArgsConstructor public static class Clues { 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 static Clued parse(String s) { - var c = createEmpty(); - var lines = s.split("\n"); - for (int r = 0; r < Math.min(lines.length, R); r++) { - var line = lines[r]; - for (int col = 0; col < Math.min(line.length(), C); col++) { - char ch = line.charAt(col); - if (ch >= '0' && ch <= '4') { - int idx = Grid.offset(r, col); - byte dir = (byte) (ch - '0'); - if ((idx & 64) == 0) c.setClueLo(1L << idx, dir); - else c.setClueHi(1L << (idx & 63), dir); - } - } - } - return new Clued(c); - } + public boolean cluelessLo(int idx) { if (!isClueLo(idx)) return false; clearClueLo(~(1L << idx)); @@ -536,10 +541,8 @@ public final class Masker { } public boolean hasRoomForClue(int key, long packed) { if (packed == X || !notClue(packed & 0x7FL) || !notClue((packed >>> 7) & 0x7FL)) return false; - final boolean[] res = {false}; - if (Slotinfo.increasing(key)) processSlot(this, (k, lo, hi) -> res[0] = true, key); - else processSlotRev(this, (k, lo, hi) -> res[0] = true, key); - return res[0]; + if (Slotinfo.increasing(key)) if (!validSlot(lo, hi, MIN_LEN, key)) return false; + return validSlotRev(lo, hi, MIN_LEN, key); } public void setClueLo(long mask, byte idx) { @@ -586,16 +589,18 @@ public final class Masker { } public Grid toGrid() { return new Grid(new byte[SwedishGenerator.SIZE], lo, hi); } public boolean isValid(int minLen) { - class ValidationVisitor implements SlotVisitor { - boolean ok = true; - @Override - public void visit(int key, long lo, long hi) { - if (bitCount(lo) + bitCount(hi) < minLen) ok = false; - } - } - ValidationVisitor v = new ValidationVisitor(); - forEachSlot(v); - return v.ok; + for (var l = lo & ~xlo & ~rlo & vlo; l != X; l &= l - 1) if (!validSlot(lo, hi, minLen, Slot.packSlotKey(numberOfTrailingZeros(l), 1))) return false; + for (var l = lo & ~xlo & ~rlo & ~vlo; l != X; l &= l - 1) if (!validSlot(lo, hi, minLen, Slot.packSlotKey(numberOfTrailingZeros(l), 0))) return false; + for (var l = lo & ~xlo & rlo & ~vlo; l != X; l &= l - 1) if (!validSlotRev(lo, hi, minLen, Slot.packSlotKey(numberOfTrailingZeros(l), 2))) return false; + for (var l = lo & ~xlo & rlo & vlo; l != X; l &= l - 1) if (!validSlotRev(lo, hi, minLen, Slot.packSlotKey(numberOfTrailingZeros(l), 3))) return false; + for (var l = lo & xlo & ~rlo & ~vlo; l != X; l &= l - 1) if (!validSlot(lo, hi, minLen, Slot.packSlotKey(numberOfTrailingZeros(l), 4))) return false; + + for (var h = hi & ~xhi & ~rhi & vhi; h != X; h &= h - 1) if (!validSlot(lo, hi, minLen, Slot.packSlotKey(64 | numberOfTrailingZeros(h), 1))) return false; + for (var h = hi & ~xhi & ~rhi & ~vhi; h != X; h &= h - 1) if (!validSlot(lo, hi, minLen, Slot.packSlotKey(64 | numberOfTrailingZeros(h), 0))) return false; + for (var h = hi & ~xhi & rhi & ~vhi; h != X; h &= h - 1) if (!validSlotRev(lo, hi, minLen, Slot.packSlotKey((64 | numberOfTrailingZeros(h)), 2))) return false; + for (var h = hi & ~xhi & rhi & vhi; h != X; h &= h - 1) if (!validSlotRev(lo, hi, minLen, Slot.packSlotKey((64 | numberOfTrailingZeros(h)), 3))) return false; + for (var h = hi & xhi & ~rhi & ~vhi; h != X; h &= h - 1) if (!validSlot(lo, hi, minLen, Slot.packSlotKey(64 | numberOfTrailingZeros(h), 4))) return false; + return true; } public void forEachSlot(SlotVisitor visitor) { for (var l = lo & ~xlo & ~rlo & vlo; l != X; l &= l - 1) processSlot(this, visitor, Slot.packSlotKey(numberOfTrailingZeros(l), 1)); @@ -603,7 +608,7 @@ public final class Masker { for (var l = lo & ~xlo & rlo & ~vlo; l != X; l &= l - 1) processSlotRev(this, visitor, Slot.packSlotKey(numberOfTrailingZeros(l), 2)); for (var l = lo & ~xlo & rlo & vlo; l != X; l &= l - 1) processSlotRev(this, visitor, Slot.packSlotKey(numberOfTrailingZeros(l), 3)); for (var l = lo & xlo & ~rlo & ~vlo; l != X; l &= l - 1) processSlot(this, visitor, Slot.packSlotKey(numberOfTrailingZeros(l), 4)); - + for (var h = hi & ~xhi & ~rhi & vhi; h != X; h &= h - 1) processSlot(this, visitor, Slot.packSlotKey(64 | numberOfTrailingZeros(h), 1)); for (var h = hi & ~xhi & ~rhi & ~vhi; h != X; h &= h - 1) processSlot(this, visitor, Slot.packSlotKey(64 | numberOfTrailingZeros(h), 0)); for (var h = hi & ~xhi & rhi & ~vhi; h != X; h &= h - 1) processSlotRev(this, visitor, Slot.packSlotKey((64 | numberOfTrailingZeros(h)), 2)); @@ -640,11 +645,10 @@ public final class Masker { static final int BIT_FOR_DIR = 3; public static Slot from(int key, long lo, long hi, DictEntry entry) { return new Slot(key, lo, hi, entry); } - public static int length(long lo, long hi) { return bitCount(lo) + bitCount(hi); } - public static int clueIndex(int key) { return key >>> BIT_FOR_DIR; } - public static int dir(int key) { return key & 7; } - public IntStream walk() { return Gridded.walk((byte) key, lo, hi); } - public static boolean horiz(int d) { return (d & 1) != 0 && (d & 4) == 0; } - public static int packSlotKey(int idx, int d) { return (idx << BIT_FOR_DIR) | d; } + public static int length(long lo, long hi) { return bitCount(lo) + bitCount(hi); } + public static int clueIndex(int key) { return key >>> BIT_FOR_DIR; } + public static int dir(int key) { return key & 7; } + public static boolean horiz(int d) { return (d & 1) != 0 && (d & 4) == 0; } + public static int packSlotKey(int idx, int d) { return (idx << BIT_FOR_DIR) | d; } } } diff --git a/src/main/java/puzzle/SwedishGenerator.java b/src/main/java/puzzle/SwedishGenerator.java index f7ed6a0..7e2246a 100644 --- a/src/main/java/puzzle/SwedishGenerator.java +++ b/src/main/java/puzzle/SwedishGenerator.java @@ -1,16 +1,10 @@ package puzzle; import lombok.AllArgsConstructor; -import lombok.Getter; import lombok.NoArgsConstructor; -import lombok.RequiredArgsConstructor; -import lombok.experimental.Accessors; -import lombok.experimental.Delegate; import lombok.val; import precomp.Neighbors9x8; -import precomp.Neighbors9x8.rci; import java.util.List; -import java.util.Locale; import static java.lang.Long.*; import static java.lang.Long.numberOfTrailingZeros; import static java.nio.charset.StandardCharsets.US_ASCII; @@ -38,9 +32,7 @@ public record SwedishGenerator() { public static final int LOG_EVERY_MS = 200; public static final int BAR_LEN = 22; public static final int C = Config.PUZZLE_COLS; - public static final double CROSS_R = (C - 1) / 2.0; public static final int R = Config.PUZZLE_ROWS; - public static final double CROSS_C = (R - 1) / 2.0; public static final int SIZE = C * R;// ~18 public static final int SIZE_MIN_1 = SIZE - 1;// ~18 public static final double SIZED = (double) SIZE;// ~18 @@ -55,7 +47,7 @@ public record SwedishGenerator() { public static final byte DASH = (byte) C_DASH; public static final long RANGE_0_SIZE = (long) SIZE_MIN_1 - 0L + 1L; public static final long RANGE_0_624 = 624L - 0L + 1L; - public static int clamp(int x, int a, int b) { return Math.max(a, Math.min(b, x)); } + public static boolean isLo(int n) { return (n & 64) == 0; } interface Bit1029 { static long[] bit1029() { return new long[2048]; } @@ -64,10 +56,7 @@ public record SwedishGenerator() { static void set(long[] bits, int bitIndex) { bits[wordIndex(bitIndex)] |= 1L << bitIndex; } static void clear(long[] bits, int bitIndex) { bits[wordIndex(bitIndex)] &= ~(1L << bitIndex); } } - static String padRight(String s, int n) { - if (s.length() >= n) return s; - return s + " ".repeat(n - s.length()); - } + @AllArgsConstructor public static class Pick { @@ -76,29 +65,36 @@ public record SwedishGenerator() { public int count; } - public static final long[] OFFSETS_D_IDX=Neighbors9x8.OFFSETS_D_IDX; - public static final rci[] IT = Neighbors9x8.IT; + //@formatter:off + public static record Dict(DictEntry[] index, int length) { } + @AllArgsConstructor @NoArgsConstructor static class Assign { long w; } + public static final class FillStats { public double simplicity; } + //@formatter:on + @AllArgsConstructor + public static class Grid { + + public final byte[] g; + public long lo, hi; + } + + public static record FillResult(boolean ok, long nodes, long backtracks, int lastMRV, long elapsed, FillStats stats) { } + + public static record DictEntry(long[] words, long[][] posBitsets, int length, int numlong) { } + + public static final long[] OFFSETS_D_IDX = Neighbors9x8.OFFSETS_D_IDX; public static final long[] NBR8_PACKED_LO = Neighbors9x8.NBR8_PACKED_LO; public static final long[] NBR8_PACKED_HI = Neighbors9x8.NBR8_PACKED_HI; - public static final long[] PATH_LO= Neighbors9x8.PATH_LO; - public static final long[] PATH_HI= Neighbors9x8.PATH_HI; - public static final Pick PICK_DONE = null;//new Pick(null, null, 0, true); - public static final Pick PICK_NOT_DONE = new Pick(null, null, 0); + public static final long[] PATH_LO = Neighbors9x8.PATH_LO; + public static final long[] PATH_HI = Neighbors9x8.PATH_HI; - @RequiredArgsConstructor - public static final class FillStats { - - public double simplicity; - } + public static final Pick PICK_DONE = null;//new Pick(null, null, 0, true); - public static record FillResult(boolean ok, long nodes, long backtracks, int lastMRV, long elapsed, FillStats stats) { - - } + public static final Pick PICK_NOT_DONE = new Pick(null, null, 0); public static final class Rng { - @Getter private int x; + private int x; public Rng(int seed) { var s = seed; if (s == 0) s = 1; @@ -114,9 +110,9 @@ public record SwedishGenerator() { } public byte randint2bitByte() { int r = nextU32() & 7; - if (r < 4) return (byte) r; if (r == 4) return (byte) 4; - return (byte) (r % 4); + //if (r < 4) return (byte) r; + return (byte) (r & 3); } public T rand(T[] p) { return p[(int) (((nextU32() & 0xFFFFFFFFL) % ((long) p.length /*- 0L*/ /*+ 1L*/)))]; } public T rand(List p) { return p.get((int) (((nextU32() & 0xFFFFFFFFL) % ((long) p.size() /*- 0L*/ /*+ 1L*/)))); } @@ -127,20 +123,6 @@ public record SwedishGenerator() { public int biasedIndexPow3(int N) { return (int) (((Math.min(nextU32(), Math.min(nextU32(), nextU32())) & 0xFFFFFFFFL) * (long) N) >>> 32); } } - @AllArgsConstructor - public static class Grid { - - public final byte[] g; - public long lo, hi; - public static int offset(int r, int c) { return r | (c << 3); } - - public Grid copy() { - return new Grid(g.clone(), lo, hi); - } - } - - public static record DictEntry(long[] words, long[][] posBitsets, int length, int numlong) { } - public static interface Lemma { static final long LETTER_MASK = (1L << 40) - 1; // low 40 bits @@ -148,28 +130,20 @@ public record SwedishGenerator() { static long from(byte[] word) { return packShiftIn(word) | ((long) (word.length - 1) << 40); } static long pack(long w, int shardIndex) { return w | (((long) shardIndex) << 43) | ((long) length0(w)) << 40; } - /* static long pack(byte[] b) { - long w = 0; - for (var i = 0; i < b.length; i++) w |= ((long) b[i] & 31) << (i * 5); - return w; - }*/ static long packShiftIn(byte[] b) { long w = 0; for (int i = b.length - 1; i >= 0; i--) w = (w << 5) | ((long) b[i] & 31); return w; } static public long from(String word) { return packShiftIn(word.getBytes(US_ASCII)) | ((long) (word.length() - 1) << 40); } - static byte byteAt(long word, int idx) { return (byte) ((word >>> ((long) idx * 5L)) & 0b11111L); } + static byte byteAt(long word, int idx) { return (byte) ((word >>> (idx * 5)) & 0b11111L); } static int length0(long word) { return ((63 - numberOfLeadingZeros(word & LETTER_MASK)) / 5); } - static ThreadLocal BYTES = ThreadLocal.withInitial(() -> new byte[MAX_WORD_LENGTH]); - public static String asWord(long word) { - var b = BYTES.get(); + public static String asWord(long word, byte[] bytes) { int bi = 0; - for (long w = word & LETTER_MASK; w != 0; w >>>= 5) { - b[bi++] = (byte) ((w & 31) | 64); // neem laagste 5 bits + bytes[bi++] = (byte) ((w & 31) | 64); // neem laagste 5 bits } - return new String(b, 0, bi, US_ASCII); + return new String(bytes, 0, bi, US_ASCII); } static int unpackIndex(long w) { return (int) (w >>> 40); } static int unpackShardIndex(long w) { return (int) (w >>> 43); } @@ -177,15 +151,6 @@ public record SwedishGenerator() { static int unpackLetters(long w) { return (int) (w & LETTER_MASK); } } - public static record Dict(DictEntry[] index, int length) { } - - @AllArgsConstructor - @NoArgsConstructor - static class Assign { - - long w; - } - public static record Slotinfo(int key, long lo, long hi, int score, Assign assign, DictEntry entry) { public static int wordCount(int k, Slotinfo[] arr) { @@ -203,34 +168,29 @@ public record SwedishGenerator() { } } - public static boolean isLo(int n) { return (n & 64) == 0; } public static long patternForSlot(final long glo, final long ghi, final byte[] g, final int key, final long lo, final long hi) { - if (((lo & glo) | (hi & ghi)) == X) return 0; + if (((lo & glo) | (hi & ghi)) == X) return X; long p = 0; - int n = 0; + int n = 0, offset, idx; if (Slotinfo.increasing(key)) { for (long b = lo & glo; b != X; b &= b - 1) { - int idx = numberOfTrailingZeros(b); - int i = bitCount(lo & ((1L << idx) - 1)); - p |= ((long) (i * 26 + g[idx])) << (n++ << 3); + idx = numberOfTrailingZeros(b); + p |= ((long) (bitCount(lo & ((1L << idx) - 1)) * 26 + g[idx])) << (n++ << 3); } - int offset = bitCount(lo); + offset = bitCount(lo); for (long b = hi & ghi; b != X; b &= b - 1) { - int idx = numberOfTrailingZeros(b); - int i = offset + bitCount(hi & ((1L << idx) - 1)); - p |= ((long) (i * 26 + g[64 | idx])) << (n++ << 3); + idx = numberOfTrailingZeros(b); + p |= ((long) ((offset + bitCount(hi & ((1L << idx) - 1))) * 26 + g[64 | idx])) << (n++ << 3); } } else { - int offset = bitCount(hi); + offset = bitCount(hi); for (long b = hi & ghi; b != X; b &= b - 1) { - int idx = numberOfTrailingZeros(b); - int i = bitCount(hi & ~((1L << idx) | ((1L << idx) - 1))); - p |= ((long) (i * 26 + g[64 | idx])) << (n++ << 3); + idx = numberOfTrailingZeros(b); + p |= ((long) (bitCount(hi & ~((1L << idx) | ((1L << idx) - 1))) * 26 + g[64 | idx])) << (n++ << 3); } for (long b = lo & glo; b != X; b &= b - 1) { - int idx = numberOfTrailingZeros(b); - int i = offset + bitCount(lo & ~((1L << idx) | ((1L << idx) - 1))); - p |= ((long) (i * 26 + g[idx])) << (n++ << 3); + idx = numberOfTrailingZeros(b); + p |= ((long) ((offset + bitCount(lo & ~((1L << idx) | ((1L << idx) - 1)))) * 26 + g[idx])) << (n++ << 3); } } return p; @@ -268,8 +228,7 @@ public record SwedishGenerator() { } public static FillResult fillMask(final Rng rng, final Slotinfo[] slots, - final Grid grid, - final boolean NO_LOG) { + final Grid grid) { val used = new long[2048]; val bitset = new long[2500]; val g = grid.g; @@ -283,27 +242,6 @@ public record SwedishGenerator() { int lastMRV; long lastLog = t0, glo = grid.lo, ghi = grid.hi; Pick current = CARRIER; - void renderProgress() { - var now = System.currentTimeMillis(); - if ((now - lastLog) < LOG_EVERY_MS) return; - lastLog = (now); - var done = 0; - for (var lemma : slots) { - if (lemma.assign.w != X) done++; - } - var pct = (TOTAL == 0) ? 100 : (int) Math.floor((done / (double) TOTAL) * 100); - var filled = Math.min(BAR_LEN, (int) Math.floor((pct / 100.0) * BAR_LEN)); - var bar = "[" + "#".repeat(filled) + "-".repeat(BAR_LEN - filled) + "]"; - var elapsed = String.format(Locale.ROOT, "%.1fs", (now - t0) / 1000.0); - - var msg = String.format( - Locale.ROOT, - "%s %d/%d slots | nodes=%d | backtracks=%d | mrv=%d | %s", - bar, done, TOTAL, nodes, backtracks, lastMRV, elapsed - ); - System.out.print("\r" + padRight(msg, 120)); - System.out.flush(); - } boolean placeWordDec(final long lo, final long hi, final long w) { int idx; int bcHi = bitCount(hi); @@ -358,7 +296,6 @@ public record SwedishGenerator() { if (count <= 1) break; } } - // Re-calculate for the best slot to get actual indices if (best == null) { current = PICK_DONE; return; @@ -383,7 +320,6 @@ public record SwedishGenerator() { } val info = pick.indices; lastMRV = pick.count; - if (!NO_LOG) renderProgress(); val s = pick.slot; val inc = Slotinfo.increasing(s.key); @@ -397,11 +333,8 @@ public record SwedishGenerator() { var tries = Math.min(MAX_TRIES_PER_SLOT, L); for (var t = 0; t < tries; t++) { - //var r = rng.nextFloat(); - //var idxInArray = (int) (r * r * r * (L - 1)); - int idxInArray = rng.biasedIndexPow3(L - 1); - var w = entry.words[idxs[idxInArray/*(int) (r * r * r * (L - 1))*/]]; - var lemIdx = Lemma.unpackIndex(w); + var w = entry.words[idxs[rng.biasedIndexPow3(L - 1)]]; + var lemIdx = Lemma.unpackIndex(w); if (Bit1029.get(used, lemIdx)) continue; low = glo; top = ghi; @@ -427,8 +360,7 @@ public record SwedishGenerator() { var tries = Math.min(MAX_TRIES_PER_SLOT, N); for (var t = 0; t < tries; t++) { - // double r = rng.nextFloat(); - var w = entry.words[rng.biasedIndexPow3(N - 1)/*(int) (r * r * r * (N - 1))*/]; + var w = entry.words[rng.biasedIndexPow3(N - 1)]; var lemIdx = Lemma.unpackIndex(w); if (Bit1029.get(used, lemIdx)) continue; low = glo; @@ -454,11 +386,8 @@ public record SwedishGenerator() { } } - // initial render (same feel) var solver = new Solver(); - if (!NO_LOG) solver.renderProgress(); - var ok = solver.backtrack(0); - // final progress line + var ok = solver.backtrack(0); grid.lo = solver.glo; grid.hi = solver.ghi; diff --git a/src/test/java/puzzle/ConnectivityTest.java b/src/test/java/puzzle/ConnectivityTest.java index 8560fa4..4fd5357 100644 --- a/src/test/java/puzzle/ConnectivityTest.java +++ b/src/test/java/puzzle/ConnectivityTest.java @@ -18,17 +18,17 @@ public class ConnectivityTest { // Clue 1: (0,0) Right. Slot: (0,1), (0,2), (0,3) // Clue 2: (1,2) Up. Slot: (0,2) // Ze zijn NIET 8-naburig, maar wel verbonden via slot op (0,2) - singleComp.setClueLo(1L << SwedishGenerator.Grid.offset(0,0), (byte)1); - singleComp.setClueLo(1L << SwedishGenerator.Grid.offset(2,2), (byte)2); // Up van (2,2) naar (1,2), (0,2) + singleComp.setClueLo(1L << Masker.offset(0, 0), (byte)1); + singleComp.setClueLo(1L << Masker.offset(2, 2), (byte)2); // Up van (2,2) naar (1,2), (0,2) long fitnessSingle = masker.maskFitness(singleComp, 2); // 2. Maak een masker met twee eilandjes van clues Clues twoIslands = Clues.createEmpty(); - twoIslands.setClueLo(1L << SwedishGenerator.Grid.offset(0,0), (byte)1); - twoIslands.setClueLo( SwedishGenerator.Grid.offset(7,7) < 64 ? 1L << SwedishGenerator.Grid.offset(7,7) : 0, (byte)1); + twoIslands.setClueLo(1L << Masker.offset(0, 0), (byte)1); + twoIslands.setClueLo(Masker.offset(7, 7) < 64 ? 1L << Masker.offset(7, 7) : 0, (byte)1); // Voor de zekerheid checken we of offset(7,7) in lo of hi zit - int off77 = SwedishGenerator.Grid.offset(7,7); + int off77 = Masker.offset(7, 7); if (off77 < 64) twoIslands.setClueLo(1L << off77, (byte)1); else twoIslands.setClueHi(1L << (off77 - 64), (byte)1); @@ -49,8 +49,8 @@ public class ConnectivityTest { // Twee clues naast elkaar, maar slots kruisen niet. // Clue 1: (1,1) Right. Slot (1,2), (1,3), (1,4) // Clue 2: (2,1) Right. Slot (2,2), (2,3), (2,4) - clues.setClueLo(1L << SwedishGenerator.Grid.offset(1,1), (byte)1); - clues.setClueLo(1L << SwedishGenerator.Grid.offset(2,1), (byte)1); + clues.setClueLo(1L << Masker.offset(1, 1), (byte)1); + clues.setClueLo(1L << Masker.offset(2, 1), (byte)1); long fitness = masker.maskFitness(clues, 2); @@ -68,8 +68,8 @@ public class ConnectivityTest { // Clue A: (2,0) Right. Slot: (2,1), (2,2), (2,3), ... // Clue B: (1,2) Corner Down. Word starts at (1,3) en gaat omlaag: (1,3), (2,3), (3,3)... // Ze kruisen op (2,3). - clues.setClueLo(1L << SwedishGenerator.Grid.offset(2,0), (byte)1); // Right - clues.setClueLo(1L << SwedishGenerator.Grid.offset(1,2), (byte)4); // Corner Down + clues.setClueLo(1L << Masker.offset(2, 0), (byte)1); // Right + clues.setClueLo(1L << Masker.offset(1, 2), (byte)4); // Corner Down long fitness = masker.maskFitness(clues, 2); System.out.println("[DEBUG_LOG] Fitness corner clue connected: " + fitness); @@ -77,11 +77,11 @@ public class ConnectivityTest { // Als ze verbonden zijn, is de penalty voor eilandjes 0. // We vergelijken met een island scenario (2 clues die elkaar NIET raken) Clues island = Clues.createEmpty(); - int offA = SwedishGenerator.Grid.offset(2,0); + int offA = Masker.offset(2, 0); if (offA < 64) island.setClueLo(1L << offA, (byte)1); else island.setClueHi(1L << (offA - 64), (byte)1); - int offB = SwedishGenerator.Grid.offset(7,7); + int offB = Masker.offset(7, 7); if (offB < 64) island.setClueLo(1L << offB, (byte)1); else island.setClueHi(1L << (offB - 64), (byte)1); diff --git a/src/test/java/puzzle/CornerClueTest.java b/src/test/java/puzzle/CornerClueTest.java index fc78654..d200452 100644 --- a/src/test/java/puzzle/CornerClueTest.java +++ b/src/test/java/puzzle/CornerClueTest.java @@ -11,7 +11,7 @@ public class CornerClueTest { void testCornerDownSlot() { Clues clues = Clues.createEmpty(); // Clue op (0,0), type 4 (Corner Down) - int idx = SwedishGenerator.Grid.offset(0,0); + int idx = Masker.offset(0, 0); clues.setClueLo(1L << idx, (byte)4); assertEquals(4, clues.getDir(idx)); @@ -22,10 +22,10 @@ public class CornerClueTest { if (Masker.Slot.dir(key) == 4) { found[0] = true; // Woord zou moeten starten op (0,1) - int startIdx = SwedishGenerator.Grid.offset(0, 1); + int startIdx = Masker.offset(0, 1); assertTrue((lo & (1L << startIdx)) != 0, "Slot should start at (0,1)"); // En omlaag gaan - int secondIdx = SwedishGenerator.Grid.offset(1, 1); + int secondIdx = Masker.offset(1, 1); assertTrue((lo & (1L << secondIdx)) != 0, "Slot should continue to (1,1)"); // Lengte van het slot zou 8 moeten zijn (van rij 0 t/m 7 in kolom 1) @@ -38,7 +38,7 @@ public class CornerClueTest { @Test void testCornerDownExtraction() { Clues clues = Clues.createEmpty(); - int idx = SwedishGenerator.Grid.offset(0,0); + int idx = Masker.offset(0, 0); clues.setClueLo(1L << idx, (byte)4); DictEntry[] dict = DictData.DICT.index(); diff --git a/src/test/java/puzzle/DictJavaGeneratorMulti.java b/src/test/java/puzzle/DictJavaGeneratorMulti.java index 00da990..a35dcda 100644 --- a/src/test/java/puzzle/DictJavaGeneratorMulti.java +++ b/src/test/java/puzzle/DictJavaGeneratorMulti.java @@ -46,7 +46,7 @@ public final class DictJavaGeneratorMulti { try (var lines = Files.lines(wordsPath, StandardCharsets.UTF_8)) { lines.forEach(line -> { CsvIndexService.lineToLemma(line, w -> { - String word = Lemma.asWord(w); + String word = Lemma.asWord(w, Export.BYTES.get()); String[] clues = CsvIndexService.lineToClue(line); int simpel = CsvIndexService.lineToSimpel(line); diff --git a/src/test/java/puzzle/ExportFormatTest.java b/src/test/java/puzzle/ExportFormatTest.java index 45b6410..247aaec 100644 --- a/src/test/java/puzzle/ExportFormatTest.java +++ b/src/test/java/puzzle/ExportFormatTest.java @@ -1,6 +1,5 @@ package puzzle; -import lombok.AllArgsConstructor; import lombok.val; import org.junit.jupiter.api.Test; import puzzle.Export.Clued; @@ -13,13 +12,11 @@ import puzzle.SwedishGenerator.FillResult; import puzzle.SwedishGenerator.Lemma; import puzzle.SwedishGenerator.Slotinfo; import puzzle.SwedishGeneratorTest.Idx; -import java.io.IOException; -import java.nio.file.Path; -import java.nio.file.Paths; 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.ExportFormatTest.Clue.RIGHT; +import static puzzle.Export.Clue.LEFT; +import static puzzle.Export.Clue.RIGHT; import static puzzle.SwedishGenerator.C; import static puzzle.Masker.Clues; import static puzzle.SwedishGenerator.FillStats; @@ -34,25 +31,6 @@ import static puzzle.SwedishGeneratorTest.TEST; public class ExportFormatTest { - static final byte CLUE_DOWN = 0; - static final byte CLUE_RIGHT = 1; - static final byte CLUE_UP = 2; - static final byte CLUE_LEFT = 3; - - @AllArgsConstructor - enum Clue { - DOWN(CLUE_DOWN), - RIGHT(CLUE_RIGHT), - UP(CLUE_UP), - LEFT(CLUE_LEFT); - Clue(byte dir) { - this.dir = dir; - this.clueDir = dir; - } - final byte dir; - final int clueDir; - } - @Test void testExportFormatFromFilled() { val clues = Clues.createEmpty(); @@ -60,11 +38,11 @@ public class ExportFormatTest { clues.setClueLo(Idx.IDX_0_0.lo, RIGHT.dir); // 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.setClueLo(Idx.IDX_0_5.lo, CLUE_LEFT); - var grid = new Gridded(clues.toGrid()); + clues.setClueLo(Idx.IDX_0_5.lo, LEFT.dir); + var grid = new Gridded(clues); // key = (cellIndex << 2) | (direction) - var key = Slot.packSlotKey(0, CLUE_RIGHT); + var key = Slot.packSlotKey(0, RIGHT.dir); 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)); @@ -112,7 +90,7 @@ public class ExportFormatTest { 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), new Slotinfo[0], fillResult); + var puzzleResult = new PuzzleResult(new Clued(clues), new Gridded(grid, clues), new Slotinfo[0], fillResult); var exported = puzzleResult.exportFormatFromFilled(1, new Rewards(0, 0, 0)); @@ -133,13 +111,13 @@ public class ExportFormatTest { val words = entry.words(); for (int i = 0; i < Math.min(words.length, 5); i++) { val wordVal = words[i]; - val word = Lemma.asWord(wordVal); + val word = Lemma.asWord(wordVal, Export.BYTES.get()); val assigned = new Assign(wordVal); val shard = Meta.shardKey(assigned.w); val clueRec = Meta.readRecord(shard, i); assertNotNull(clueRec); - assertEquals(word, Lemma.asWord(clueRec.w())); + assertEquals(word, Lemma.asWord(clueRec.w(), Export.BYTES.get())); assertTrue(clueRec.simpel() >= 0); assertTrue(clueRec.clues().length > 0); } @@ -160,7 +138,7 @@ public class ExportFormatTest { int idx = -1; long[] words = entry.words(); for (int i = 0; i < words.length; i++) { - if (Lemma.asWord(words[i]).equals(wStr)) { + if (Lemma.asWord(words[i], Export.BYTES.get()).equals(wStr)) { idx = i; break; } @@ -170,7 +148,7 @@ public class ExportFormatTest { val shard = Meta.shardKey(w); val clueRec = Meta.readRecord(shard, idx); assertNotNull(clueRec); - assertEquals(wStr, Lemma.asWord(clueRec.w())); + assertEquals(wStr, Lemma.asWord(clueRec.w(), Export.BYTES.get())); // Check some expected complexity values (from CSV head output, column 3) if (wStr.equals("EEN")) { assertEquals(451, clueRec.simpel()); diff --git a/src/test/java/puzzle/MainTest.java b/src/test/java/puzzle/MainTest.java index 2be0cad..d43bb97 100644 --- a/src/test/java/puzzle/MainTest.java +++ b/src/test/java/puzzle/MainTest.java @@ -10,20 +10,18 @@ import puzzle.Export.LetterVisit.LetterAt; import puzzle.Export.PuzzleResult; import puzzle.Export.Rewards; import puzzle.Main.Opts; -import puzzle.Masker.Clues; import puzzle.SwedishGenerator.Rng; 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.ExportFormatTest.Clue.DOWN; -import static puzzle.ExportFormatTest.Clue.LEFT; -import static puzzle.ExportFormatTest.Clue.RIGHT; -import static puzzle.ExportFormatTest.Clue.UP; +import static puzzle.Export.Clue.DOWN; +import static puzzle.Export.Clue.LEFT; +import static puzzle.Export.Clue.RIGHT; +import static puzzle.Export.Clue.UP; import static puzzle.SwedishGenerator.Dict; import static puzzle.SwedishGenerator.Lemma; -import static puzzle.SwedishGenerator.STACK_SIZE; import static puzzle.SwedishGenerator.Slotinfo; import static puzzle.SwedishGenerator.fillMask; import static puzzle.SwedishGeneratorTest.AB; @@ -66,7 +64,7 @@ public class MainTest { var clues = Masker.Clues.createEmpty(); val key = Masker.Slot.packSlotKey(OFF_0_0, CLUE_RIGHT); clues.setClueLo(IDX_0_0.lo, CLUE_RIGHT); - var grid = new Gridded(clues.toGrid()); + 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); @@ -74,11 +72,11 @@ public class MainTest { assertEquals(1, slots.length); var s = slots[0]; assertEquals(8, Masker.Slot.length(s.lo(), s.hi())); - var cells = s.walk().toArray(); - assertEquals(0, SwedishGenerator.IT[cells[0]].r()); - assertEquals(1, SwedishGenerator.IT[cells[0]].c()); - assertEquals(0, SwedishGenerator.IT[cells[1]].r()); - assertEquals(2, SwedishGenerator.IT[cells[1]].c()); + var cells = Gridded.cellWalk((byte) s.key(), s.lo(), s.hi()).toArray(); + assertEquals(0, Masker.IT[cells[0]].r()); + assertEquals(1, Masker.IT[cells[0]].c()); + assertEquals(0, Masker.IT[cells[1]].r()); + assertEquals(2, Masker.IT[cells[1]].c()); } @Test @@ -98,8 +96,8 @@ public class MainTest { clues.forEachSlot((key, lo, hi) -> { count.incrementAndGet(); assertEquals(8, Long.bitCount(lo) + Long.bitCount(hi)); - assertEquals(0, SwedishGenerator.IT[Long.numberOfTrailingZeros(lo)].r()); - assertEquals(1, SwedishGenerator.IT[Long.numberOfTrailingZeros(lo)].c()); + assertEquals(0, Masker.IT[Long.numberOfTrailingZeros(lo)].r()); + assertEquals(1, Masker.IT[Long.numberOfTrailingZeros(lo)].c()); }); assertEquals(1, count.get()); } @@ -115,7 +113,7 @@ public class MainTest { var clues = new Clued(Masker.Clues.createEmpty()); val key = Masker.Slot.packSlotKey(OFF_2_1, CLUE_UP); clues.setClueLo(IDX_2_1.lo, CLUE_UP); - var grid = new Gridded(clues.toGrid()); + var grid = new Gridded(clues.c()); // Test set/get GridBuilder.placeWord(grid.grid(), grid.grid().g, key, (1L << OFF_1_1) | (1L << OFF_0_1), 0, AZ); @@ -184,7 +182,7 @@ public class MainTest { @Test void testFiller2() { val rng = new Rng(-343913721); - val mask = Clues.parse( + val mask = Clued.parse( "1 000000\n" + "1 \n" + "1 \n" + @@ -203,7 +201,7 @@ public class MainTest { @Test void testFiller() { val rng = new Rng(-343913721); - val mask = Clues.parse( + val mask = Clued.parse( " 3 300\n" + " 1 \n" + " 1 \n" + @@ -214,13 +212,13 @@ public class MainTest { "21 22 3"); var slotInfo = Masker.slots(mask.c(), dict.index()); var grid = Slotinfo.grid(slotInfo); - var filled = fillMask(rng, slotInfo, grid, false); + var filled = fillMask(rng, slotInfo, grid); Assertions.assertTrue(filled.ok(), "Puzzle generation failed (not ok)"); Assertions.assertEquals(13, Slotinfo.wordCount(0, slotInfo), "Number of assigned words changed"); - Assertions.assertEquals("WAANZIN", Lemma.asWord(slotInfo[0].assign().w)); + Assertions.assertEquals("WAANZIN", Lemma.asWord(slotInfo[0].assign().w, Export.BYTES.get())); Assertions.assertEquals(-1L, grid.lo); Assertions.assertEquals(-1L, grid.hi); - var g = new Gridded(grid); + var g = new Gridded(grid, mask.c()); g.gridToString(mask.c()); var aa = new PuzzleResult(mask, g, slotInfo, filled).exportFormatFromFilled(1, new Rewards(1, 1, 1)); System.out.println(String.join("\n", aa.grid())); diff --git a/src/test/java/puzzle/PerformanceTest.java b/src/test/java/puzzle/PerformanceTest.java index d5d526d..c473dd9 100644 --- a/src/test/java/puzzle/PerformanceTest.java +++ b/src/test/java/puzzle/PerformanceTest.java @@ -1,24 +1,22 @@ package puzzle; import lombok.val; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; +import puzzle.Export.Clued; +import puzzle.Export.Gridded; import puzzle.SwedishGenerator.DictEntry; import puzzle.SwedishGenerator.Rng; import puzzle.SwedishGenerator.Slotinfo; -import puzzle.SwedishGenerator.Grid; import java.util.Arrays; -import java.util.Comparator; import java.util.Locale; -import java.util.stream.IntStream; import static org.junit.jupiter.api.Assertions.assertTrue; import static puzzle.SwedishGenerator.fillMask; public class PerformanceTest { - final DictEntry[] EN = DictData.DICT.index(); + final DictEntry[] EN = DictData.DICT.index(); @Test void testPerformance() { val rng = new Rng(42); @@ -45,7 +43,6 @@ public class PerformanceTest { val mask = masker.generateMask(size, 100, 50, 20); val slotInfo = Masker.slots(mask, EN); - val grid = mask.toGrid(); long t0 = System.currentTimeMillis(); // Try to fill multiple times to get a better average @@ -54,7 +51,7 @@ public class PerformanceTest { long totalBacktracks = 0; int successCount = 0; for (int i = 0; i < iterations; i++) { - val result = fillMask(rng, slotInfo, grid.copy(), false); + val result = fillMask(rng, slotInfo, Slotinfo.grid(slotInfo)); if (result.ok()) successCount++; totalNodes += result.nodes(); totalBacktracks += result.backtracks(); @@ -66,7 +63,7 @@ public class PerformanceTest { size, successCount, iterations, totalNodes / iterations, totalBacktracks / iterations, totalDuration); } } - void main() { + void main() { testIncrementalComplexity(); } @Test @@ -81,7 +78,7 @@ public class PerformanceTest { " 2 1 \n" + " 1 \n" + "221 22\n"; - val mask = Masker.Clues.parse(maskStr); + val mask = Clued.parse(maskStr); val allSlots = Masker.slots(mask.c(), EN); //mask.toGrid() System.out.println("[DEBUG_LOG] \n--- Incremental Complexity Test ---"); @@ -90,11 +87,11 @@ public class PerformanceTest { for (int i = 10; i <= allSlots.length; i++) { val subset = Arrays.copyOf(allSlots, i); - // Arrays.sort(subset, Comparator.comparingInt(Slotinfo::score)); + // Arrays.sort(subset, Comparator.comparingInt(Slotinfo::score)); System.out.printf("[DEBUG_LOG] Testing with first %d slots%n of %s", i, allSlots.length); var grid = Slotinfo.grid(subset); visualizeSlots(subset); - measureFill(new Rng(123 + i), subset, grid, "Subset size " + i); + measureFill(new Rng(123 + i), subset, "Subset size " + i); } } @@ -103,18 +100,18 @@ public class PerformanceTest { val rng = new Rng(42); // A single horizontal slot at (0,0) - val mask = Masker.Clues.parse("1 \n"); + val mask = Clued.parse("1 \n"); val slots = Masker.slots(mask.c(), EN); System.out.println("[DEBUG_LOG] \n--- Single Slot Resolution ---"); if (slots.length > 0) { - measureFill(rng, slots, mask.toGrid(), "Single Slot"); + measureFill(rng, slots, "Single Slot"); } else { System.out.println("[DEBUG_LOG] Error: No slots found in mask."); } } - private void measureFill(Rng rng, Slotinfo[] slots, Grid grid, String label) { + private void measureFill(Rng rng, Slotinfo[] slots, String label) { long t0 = System.currentTimeMillis(); int iterations = 1; long totalNodes = 0; @@ -125,7 +122,7 @@ public class PerformanceTest { // Reset assignments for each iteration for (Slotinfo s : slots) s.assign().w = 0; - val result = fillMask(rng, slots, grid.copy(), false); + val result = fillMask(rng, slots, Slotinfo.grid(slots)); if (result.ok()) successCount++; totalNodes += result.nodes(); totalBacktracks += result.backtracks(); @@ -148,8 +145,8 @@ public class PerformanceTest { int dir = Masker.Slot.dir(key); int clueIdx = Masker.Slot.clueIndex(key); - int cr = SwedishGenerator.IT[clueIdx].r(); - int cc = SwedishGenerator.IT[clueIdx].c(); + int cr = Masker.IT[clueIdx].r(); + int cc = Masker.IT[clueIdx].c(); // User requested: aAAAA for a four letter to RIGHT clue slot. // SwedishGenerator: 1=RIGHT, 0=DOWN, 2=UP, 3=LEFT @@ -179,9 +176,9 @@ public class PerformanceTest { display[cr][cc] = clueChar; - Masker.Slot.from(slot.key(), slot.lo(), slot.hi(), null).walk().forEach(idx -> { - int r = SwedishGenerator.IT[idx].r(); - int c = SwedishGenerator.IT[idx].c(); + Gridded.cellWalk((byte) slot.key(), slot.lo(), slot.hi()).forEach(idx -> { + int r = Masker.IT[idx].r(); + int c = Masker.IT[idx].c(); if (display[r][c] == ' ' || (display[r][c] >= 'A' && display[r][c] <= 'D')) { if (display[r][c] != ' ' && display[r][c] != slotChar) { display[r][c] = '+'; // Intersection diff --git a/src/test/java/puzzle/SwedishGeneratorTest.java b/src/test/java/puzzle/SwedishGeneratorTest.java index 2855e0c..5f3f895 100644 --- a/src/test/java/puzzle/SwedishGeneratorTest.java +++ b/src/test/java/puzzle/SwedishGeneratorTest.java @@ -8,6 +8,7 @@ import puzzle.Export.Dicts; import puzzle.Export.Gridded; import puzzle.Export.IntListDTO; import puzzle.Export.LetterVisit.LetterAt; +import puzzle.Masker.Clues; import puzzle.Masker.Slot; import java.nio.charset.StandardCharsets; import java.util.stream.Collectors; @@ -27,74 +28,81 @@ public class SwedishGeneratorTest { public static Context get() { return CTX.get(); } } - static final long TEST = Lemma.from("TEST"); + static final long TEST = Lemma.from("TEST"); + static final long IN = Lemma.from("IN"); + static final long INER = Lemma.from("INER"); + static final long INEREN = Lemma.from("INEREN"); + static final long INERENA = Lemma.from("INERENA"); + static final long INERENAE = Lemma.from("INERENAE"); + static final long APPLE = Lemma.from("APPLE"); + static final long EXE = Lemma.from("AXE"); + static final long ABC = Lemma.from("ABC"); + static final long ABD = Lemma.from("ABD"); + static final long AZ = Lemma.from("AZ"); + static final long AB = Lemma.from("AB"); static final long[] WORDS = new long[]{ Lemma.from("AT"), Lemma.from("CAT"), Lemma.from("DOGS"), - Lemma.from("APPLE"), + APPLE, Lemma.from("APPLY"), Lemma.from("BANAN"), Lemma.from("BANANA"), Lemma.from("BANANAS"), Lemma.from("BANANASS") // length 8 }; - static final long l2a = Lemma.from("IN"); - static final long l4a = Lemma.from("INER"); - static final long l6a = Lemma.from("INEREN"); - static final long l7a = Lemma.from("INERENA"); - static final long l8a = Lemma.from("INERENAE"); - static final long l1 = Lemma.from("APPLE"); - static final long l2 = Lemma.from("AXE"); - static final long[] WORDS2 = new long[]{ Lemma.from("IN"), - Lemma.from("APPLE"), - Lemma.from("APPLY"), - Lemma.from("BANAN"), - Lemma.from("INE"), - Lemma.from("INER"), - Lemma.from("INEREN"), - Lemma.from("INERENA"), - Lemma.from("INERENAE") }; - static final long ABC = Lemma.from("ABC"); - static final long ABD = Lemma.from("ABD"); - static final long AZ = Lemma.from("AZ"); - static final long AB = Lemma.from("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; - static final byte LETTER_X = ((byte) 'X') & 31; - static final byte LETTER_Z = ((byte) 'Z') & 31; - 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 long[] WORDS2 = new long[]{ IN, + APPLE, + Lemma.from("APPLY"), + Lemma.from("BANAN"), + Lemma.from("INE"), + INER, + INEREN, + INERENA, + INERENAE }; - 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_1_3 = Grid.offset(1, 3); - static final int OFF_1_4 = Grid.offset(1, 4); - static final int OFF_1_5 = Grid.offset(1, 5); - static final int OFF_2_1 = Grid.offset(2, 1); - static final int OFF_2_3 = Grid.offset(2, 3); - static final int OFF_2_2 = Grid.offset(2, 2); - static final int OFF_2_4 = Grid.offset(2, 4); - 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); - static final int OFF_3_0 = Grid.offset(3, 0); - static final int OFF_3_1 = Grid.offset(3, 1); - static final int OFF_3_2 = Grid.offset(3, 2); - static final int OFF_3_3 = Grid.offset(3, 3); - static final int OFF_3_4 = Grid.offset(3, 4); + static final byte LETTER_A = ((byte) 'A') & 31; + static final byte LETTER_P = ((byte) 'P') & 31; + static final byte LETTER_L = ((byte) 'L') & 31; + static final byte LETTER_B = ((byte) 'B') & 31; + static final byte LETTER_C = ((byte) 'C') & 31; + static final byte LETTER_E = ((byte) 'E') & 31; + static final byte LETTER_I = ((byte) 'I') & 31; + static final byte LETTER_N = ((byte) 'N') & 31; + static final byte LETTER_X = ((byte) 'X') & 31; + static final byte LETTER_R = ((byte) 'R') & 31; + static final byte LETTER_Z = ((byte) 'Z') & 31; + 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_1_0 = Masker.offset(1, 0); + static final int OFF_1_1 = Masker.offset(1, 1); + static final int OFF_1_2 = Masker.offset(1, 2); + static final int OFF_1_3 = Masker.offset(1, 3); + static final int OFF_1_4 = Masker.offset(1, 4); + static final int OFF_1_5 = Masker.offset(1, 5); + static final int OFF_2_1 = Masker.offset(2, 1); + static final int OFF_2_3 = Masker.offset(2, 3); + static final int OFF_2_2 = Masker.offset(2, 2); + static final int OFF_2_4 = Masker.offset(2, 4); + static final int OFF_0_0 = Masker.offset(0, 0); + static final int OFF_0_4 = Masker.offset(0, 4); + static final int OFF_0_5 = Masker.offset(0, 5); + static final int OFF_0_1 = Masker.offset(0, 1); + static final int OFF_0_2 = Masker.offset(0, 2); + static final int OFF_0_3 = Masker.offset(0, 3); + static final int OFF_2_0 = Masker.offset(2, 0); + static final int OFF_2_5 = Masker.offset(2, 5); + static final int OFF_3_5 = Masker.offset(3, 5); + static final int OFF_4_5 = Masker.offset(4, 5); + static final int OFF_3_0 = Masker.offset(3, 0); + static final int OFF_3_1 = Masker.offset(3, 1); + static final int OFF_3_2 = Masker.offset(3, 2); + static final int OFF_3_3 = Masker.offset(3, 3); + static final int OFF_3_4 = Masker.offset(3, 4); static final byte D_BYTE_2 = CLUE_RIGHT; enum Idx { @@ -139,9 +147,9 @@ public class SwedishGeneratorTest { } @Test void testPatternForSlotAllLetters() { - var grid = new Gridded(createEmpty()); var key = Slot.packSlotKey(OFF_0_0, CLUE_RIGHT); val clues = Masker.Clues.createEmpty(); + var grid = new Gridded(clues); clues.setClueLo(IDX_0_0.lo, CLUE_RIGHT); 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)); @@ -192,13 +200,21 @@ public class SwedishGeneratorTest { var f = rng.nextFloat(); assertTrue(f >= 0.0 && f <= 1.0); } + assertTrue(rng.biasedIndexPow3(100) >= 0 && rng.biasedIndexPow3(100) < 100); + assertTrue(rng.biasedIndexPow3(100) >= 0 && rng.biasedIndexPow3(100) < 100); + assertTrue(rng.biasedIndexPow3(100) >= 0 && rng.biasedIndexPow3(100) < 100); + assertTrue(rng.biasedIndexPow3(100) >= 0 && rng.biasedIndexPow3(100) < 100); + assertTrue(rng.biasedIndexPow3(100) >= 0 && rng.biasedIndexPow3(100) < 100); + assertTrue(rng.biasedIndexPow3(100) >= 0 && rng.biasedIndexPow3(100) < 100); + assertTrue(rng.biasedIndexPow3(100) >= 0 && rng.biasedIndexPow3(100) < 100); } @Test void testGrid() { - var grid = new Gridded(createEmpty()); + var empty = Clues.createEmpty(); + var grid = new Gridded(empty); GridBuilder.placeWord(grid.grid(), grid.grid().g, Slot.packSlotKey(0, CLUE_RIGHT), 1L << OFF_0_0, 0, Lemma.from("A")); - val arr = grid.stream(Masker.Clues.createEmpty()).collect(Collectors.toMap(LetterAt::index, LetterAt::letter)); + val arr = grid.stream(empty).collect(Collectors.toMap(LetterAt::index, LetterAt::letter)); assertEquals(1, arr.size()); assertEquals(LETTER_A, arr.get(OFF_0_0)); } @@ -217,11 +233,18 @@ public class SwedishGeneratorTest { @Test void testLemmaAndDict() { - Assertions.assertEquals(Lemma.packShiftIn("APPLE".getBytes(StandardCharsets.US_ASCII)), Lemma.unpackLetters(l1)); - assertEquals(4, Lemma.unpackSize(l1)); - assertEquals(LETTER_A, Lemma.byteAt(l1, 0)); + Assertions.assertEquals(Lemma.packShiftIn("APPLE".getBytes(StandardCharsets.US_ASCII)), Lemma.unpackLetters(APPLE)); + assertEquals(4, Lemma.unpackSize(APPLE)); + assertEquals(LETTER_I, Lemma.byteAt(INERENAE, 0)); + assertEquals(LETTER_N, Lemma.byteAt(INERENAE, 1)); + assertEquals(LETTER_E, Lemma.byteAt(INERENAE, 2)); + assertEquals(LETTER_R, Lemma.byteAt(INERENAE, 3)); + assertEquals(LETTER_E, Lemma.byteAt(INERENAE, 4)); + assertEquals(LETTER_N, Lemma.byteAt(INERENAE, 5)); + assertEquals(LETTER_A, Lemma.byteAt(INERENAE, 6)); + assertEquals(LETTER_E, Lemma.byteAt(INERENAE, 7)); - var dict = Dicts.makeDict(new long[]{ l1, l2, l2a, l4a, l6a, l7a, l8a }); + var dict = Dicts.makeDict(new long[]{ APPLE, EXE, IN, INER, INEREN, INERENA, INERENAE }); assertEquals(1, dict.index()[3].words().length); assertEquals(1, dict.index()[5].words().length); @@ -251,13 +274,13 @@ public class SwedishGeneratorTest { assertEquals(OFF_2_3, Slot.clueIndex(key)); assertEquals(CLUE_DOWN, Slot.dir(key)); assertFalse(Slot.horiz(key)); - var cells = Gridded.walk((byte) key, lo, 0L).toArray(); - assertEquals(2, SwedishGenerator.IT[cells[0]].r()); - assertEquals(3, SwedishGenerator.IT[cells[1]].r()); - assertEquals(4, SwedishGenerator.IT[cells[2]].r()); - assertEquals(5, SwedishGenerator.IT[cells[0]].c()); - assertEquals(5, SwedishGenerator.IT[cells[1]].c()); - assertEquals(5, SwedishGenerator.IT[cells[2]].c()); + 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()); assertTrue(Slot.horiz(CLUE_RIGHT)); // right assertFalse(Slot.horiz(CLUE_DOWN)); // down @@ -335,7 +358,8 @@ public class SwedishGeneratorTest { @Test void testPlaceWord() { - var grid = new Gridded(createEmpty()); + var empty = Clues.createEmpty(); + var grid = new Gridded(empty); // 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); @@ -344,7 +368,8 @@ 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(Masker.Clues.createEmpty()).collect(Collectors.toMap(LetterAt::index, LetterAt::letter)); + + var map = grid.stream(empty).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)); @@ -362,7 +387,7 @@ public class SwedishGeneratorTest { assertEquals(LETTER_C, map.get(OFF_0_2)); // 4. Partial placement then conflict (rollback) - grid = new Gridded(createEmpty()); + 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 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)); @@ -372,7 +397,8 @@ public class SwedishGeneratorTest { @Test void testBacktrackingHelpers() { - var grid = new Gridded(createEmpty()); + var clues = Clues.createEmpty(); + var grid = new Gridded(clues); // Slot at 0,1 length 2 var key = Slot.packSlotKey(0, CLUE_RIGHT); var lo = (1L << OFF_0_1) | (1L << OFF_0_2); @@ -381,7 +407,8 @@ public class SwedishGeneratorTest { val top = grid.grid().hi; var placed = GridBuilder.placeWord(grid.grid(), grid.grid().g, key, lo, 0L, w); assertTrue(placed); - var map = grid.stream(Masker.Clues.createEmpty()).collect(Collectors.toMap(LetterAt::index, LetterAt::letter)); + + var map = grid.stream(clues).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));