diff --git a/pom.xml b/pom.xml index f0199d7..42e680c 100644 --- a/pom.xml +++ b/pom.xml @@ -61,6 +61,12 @@ 1.18.42 provided + + mike.processor + puzzle-processor + 1.1-SNAPSHOT + provided + @@ -75,9 +81,9 @@ filter-sources - + @@ -87,11 +93,19 @@ 3.13.0 + org.projectlombok lombok 1.18.42 + + + + mike.processor + puzzle-processor + 1.1-SNAPSHOT + 25 25 diff --git a/src/main/java/puzzle/Export.java b/src/main/java/puzzle/Export.java new file mode 100644 index 0000000..5c4cd2d --- /dev/null +++ b/src/main/java/puzzle/Export.java @@ -0,0 +1,222 @@ +package puzzle; + +import lombok.experimental.Delegate; +import puzzle.SwedishGenerator.Dict; +import puzzle.SwedishGenerator.FillResult; +import puzzle.SwedishGenerator.Grid; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import static puzzle.SwedishGenerator.R; +import static puzzle.SwedishGenerator.Lemma; +import static puzzle.SwedishGenerator.Slot; +import static puzzle.SwedishGenerator.C; + +/** + * ExportFormat.java + * + * Direct port of export_format.js: + * - scans filled grid for clue digits '1'..'4' + * - extracts placed words in canonical direction (horizontal=right, vertical=down) + * - crops to bounding box (words + arrow cells) with 1-cell margin + * - outputs gridv2 + words[] (+ difficulty, rewards) + */ +public record Export() { + + record Strings() { + + static String padRight(String s, int n) { + if (s.length() >= n) return s; + return s + " ".repeat(n - s.length()); + } + } + + record Gridded(@Delegate Grid grid) { + + 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++) sb.append((char) grid.byteAt(Grid.offset(r, c))); + } + return sb.toString(); + } + public String renderHuman() { + var sb = new StringBuilder(); + for (var r = 0; r < R; r++) { + if (r > 0) sb.append('\n'); + for (var c = 0; c < C; c++) { + sb.append(grid.isDigitAt(r, c) ? ' ' : (char) grid.byteAt(Grid.offset(r, c))); + } + } + return sb.toString(); + } + } + + static class Bit { + + static long pack(int r, int c) { return (((long) r) << 32) ^ (c & 0xFFFFFFFFL); } + long l1, l2; + public Bit() { + l1 = 0L; + l2 = 0L; + } + public boolean get(int bitIndex) { + if (bitIndex < 64) return (l1 & (1L << bitIndex)) != 0L; + return (l2 & (1L << (bitIndex & 63))) != 0L; + } + public void set(int bitIndex) { + if (bitIndex < 64) this.l1 |= 1L << bitIndex; + else this.l2 |= 1L << (bitIndex & 63); + } + public void clear() { + l1 = 0L; + l2 = 0L; + } + } + + record Bit1029(long[] bits) { + + public Bit1029() { this(new long[1029]); } + static int wordIndex(int bitIndex) { return bitIndex >> 6; } + public boolean get(int bitIndex) { return (this.bits[wordIndex(bitIndex)] & 1L << bitIndex) != 0L; } + public void set(int bitIndex) { bits[wordIndex(bitIndex)] |= 1L << bitIndex; } + public void clear(int bitIndex) { this.bits[wordIndex(bitIndex)] &= ~(1L << bitIndex); } + public void clear() { Arrays.fill(bits, 0L); } + } + + private record Placed(Lemma lemma, int startRow, int startCol, String direction, int arrowRow, int arrowCol, int[] cells, boolean isReversed) { + + static final String HORIZONTAL = "h"; + static final String VERTICAL = "v"; + } + + public record Rewards(int coins, int stars, int hints) { } + + public record WordOut(Lemma lemma, int startRow, int startCol, String direction, int arrowRow, int arrowCol, boolean isReversed, int complex) { + + public String word() { return new String(lemma().word(), java.nio.charset.StandardCharsets.US_ASCII); } + public String[] clue() { return lemma.clue(); } + } + + public record ExportedPuzzle(List gridv2, WordOut[] words, int difficulty, Rewards rewards) { } + + public record PuzzleResult(SwedishGenerator swe, Dict dict, Gridded mask, FillResult filled) { + + boolean inBounds(int r, int c) { return r >= 0 && r < SwedishGenerator.R && c >= 0 && c < SwedishGenerator.C; } + Placed extractPlacedFromSlot(Slot s, Lemma lemma) { + var d = s.dir(); + + var cells = new int[s.len()]; + for (int i = 0, len = s.len(); i < len; i++) cells[i] = s.pos(i); + + String direction; + var isReversed = false; + var startRow = Grid.r(cells[0]); + var startCol = Grid.c(cells[0]); + if (d == 2) { // right -> horizontal + direction = Placed.HORIZONTAL; + } else if (d == 3 || d == 5) { // down or down-bent -> vertical + direction = Placed.VERTICAL; + } else if (d == 4) { // left -> horizontal (REVERSED) + direction = Placed.HORIZONTAL; + isReversed = true; + } else if (d == 1) { // up -> vertical (REVERSED) + direction = Placed.VERTICAL; + isReversed = true; + } else { + return null; + } + + return new Placed( + lemma, + startRow, + startCol, + direction, + s.clueR(), + s.clueC(), + cells, + isReversed + ); + } + public ExportedPuzzle exportFormatFromFilled(int difficulty, Rewards rewards) { + var g = filled().grid(); + var placed = new ArrayList(); + var clueMap = filled().clueMap(); + g.grid().forEachSlot((int key, long packedPos, int len) -> { + var word = clueMap.get(key); + if (word != null) { + var p = extractPlacedFromSlot(Slot.from(key, packedPos, len), word); + if (p != null) placed.add(p); + } + }); + + // If nothing placed: return full grid mapped to letters/# only + if (placed.isEmpty()) { + var gridv2 = new ArrayList(R); + for (var r = 0; r < R; r++) { + var sb = new StringBuilder(C); + for (var c = 0; c < C; c++) { + sb.append(g.isLetterAt(r, c) ? (char) g.byteAt(Grid.offset(r, c)) : '#'); + } + gridv2.add(sb.toString()); + } + return new ExportedPuzzle(gridv2, new WordOut[0], difficulty, rewards); + } + + // 2) bounding box around all word cells + arrow cells, with 1-cell margin + + int minR = Integer.MAX_VALUE, minC = Integer.MAX_VALUE; + int maxR = Integer.MIN_VALUE, maxC = Integer.MIN_VALUE; + + for (var rc : placed) { + for (var c : rc.cells) { + minR = Math.min(minR, Grid.r(c)); + minC = Math.min(minC, Grid.c(c)); + maxR = Math.max(maxR, Grid.r(c)); + maxC = Math.max(maxC, Grid.c(c)); + } + minR = Math.min(minR, rc.arrowRow); + minC = Math.min(minC, rc.arrowCol); + maxR = Math.max(maxR, rc.arrowRow); + maxC = Math.max(maxC, rc.arrowCol); + } + + // 3) map of only used letter cells (everything else becomes '#') + var letterAt = new HashMap(); + for (var p : placed) { + for (var c : p.cells) { + int rr = Grid.r(c), cc = Grid.c(c); + if (inBounds(rr, cc) && g.isLetterAt(rr, cc)) { + letterAt.put(Bit.pack(rr, cc), (char) g.byteAt(Grid.offset(rr, cc))); + } + } + } + + // 4) render gridv2 over cropped bounds (out-of-bounds become '#') + var gridv2 = new ArrayList(Math.max(0, maxR - minR + 1)); + for (var r = minR; r <= maxR; r++) { + var row = new StringBuilder(Math.max(0, maxC - minC + 1)); + for (var c = minC; c <= maxC; c++) { + row.append(letterAt.getOrDefault(Bit.pack(r, c), '#')); + } + gridv2.add(row.toString()); + } + + // 5) words output with cropped coordinates + int MIN_R = minR, MIN_C = minC; + var wordsOut = placed.stream().map(p -> new WordOut( + p.lemma, + p.startRow - MIN_R, + p.startCol - MIN_C, + p.direction, + p.arrowRow - MIN_R, + p.arrowCol - MIN_C, + p.isReversed, + p.lemma.simpel() + )).toArray(WordOut[]::new); + return new ExportedPuzzle(gridv2, wordsOut, difficulty, rewards); + } + } +} diff --git a/src/main/java/puzzle/ExportFormat.java b/src/main/java/puzzle/ExportFormat.java deleted file mode 100644 index 807972e..0000000 --- a/src/main/java/puzzle/ExportFormat.java +++ /dev/null @@ -1,221 +0,0 @@ -package puzzle; - -import lombok.experimental.Delegate; -import puzzle.Main.PuzzleResult; -import puzzle.SwedishGenerator.Grid; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Objects; -import static puzzle.SwedishGenerator.R; -import static puzzle.SwedishGenerator.Lemma; -import static puzzle.SwedishGenerator.Slot; -import static puzzle.SwedishGenerator.C; - -/** - * ExportFormat.java - * - * Direct port of export_format.js: - * - scans filled grid for clue digits '1'..'4' - * - extracts placed words in canonical direction (horizontal=right, vertical=down) - * - crops to bounding box (words + arrow cells) with 1-cell margin - * - outputs gridv2 + words[] (+ difficulty, rewards) - */ -public record ExportFormat() { - - record Gridded(@Delegate Grid grid) { - - 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++) sb.append((char) grid.byteAt(Grid.offset(r, c))); - } - return sb.toString(); - } - public String renderHuman() { - var sb = new StringBuilder(); - for (var r = 0; r < R; r++) { - if (r > 0) sb.append('\n'); - for (var c = 0; c < C; c++) { - sb.append(grid.isDigitAt(r, c) ? ' ' : (char) grid.byteAt(Grid.offset(r, c))); - } - } - return sb.toString(); - } - } - - static class Bit { - - long l1, l2; - public Bit() { - l1 = 0L; - l2 = 0L; - } - public boolean get(int bitIndex) { - if (bitIndex < 64) { - return (l1 & (1L << bitIndex)) != 0L; - } - return (l2 & (1L << (bitIndex & 63))) != 0L; - } - public void set(int bitIndex) { - if (bitIndex < 64) { - this.l1 |= 1L << bitIndex; - } else { - this.l2 |= 1L << (bitIndex & 63); - } - } - public void clear() { - l1 = 0L; - l2 = 0L; - } - } - - record Bit1029(long[] bits) { - - public Bit1029() { this(new long[1029]); } - static int wordIndex(int bitIndex) { return bitIndex >> 6; } - public boolean get(int bitIndex) { return (this.bits[wordIndex(bitIndex)] & 1L << bitIndex) != 0L; } - public void set(int bitIndex) { bits[wordIndex(bitIndex)] |= 1L << bitIndex; } - public void clear(int bitIndex) { this.bits[wordIndex(bitIndex)] &= ~(1L << bitIndex); } - public void clear() { Arrays.fill(bits, 0L); } - } - - static final String HORIZONTAL = "h", VERTICAL = "v"; - private static boolean inBounds(int r, int c) { return r >= 0 && r < SwedishGenerator.R && c >= 0 && c < SwedishGenerator.C; } - - public static ExportedPuzzle exportFormatFromFilled(PuzzleResult puz, int difficulty, Rewards rewards) { - Objects.requireNonNull(puz, "puz"); - var g = puz.filled().grid(); - var placed = new ArrayList(); - var clueMap = puz.filled().clueMap(); - puz.swe().forEachSlot(g.grid(), (int key, long packedPos, int len) -> { - var word = clueMap.get(key); - if (word != null) { - var p = extractPlacedFromSlot(Slot.from(key, packedPos, len), word); - if (p != null) placed.add(p); - } - }); - - // If nothing placed: return full grid mapped to letters/# only - if (placed.isEmpty()) { - var gridv2 = new ArrayList(R); - for (var r = 0; r < R; r++) { - var sb = new StringBuilder(C); - for (var c = 0; c < C; c++) { - sb.append(g.isLetterAt(r, c) ? (char) g.byteAt(Grid.offset(r, c)) : '#'); - } - gridv2.add(sb.toString()); - } - return new ExportedPuzzle(gridv2, new WordOut[0], difficulty, rewards); - } - - // 2) bounding box around all word cells + arrow cells, with 1-cell margin - - int minR = Integer.MAX_VALUE, minC = Integer.MAX_VALUE; - int maxR = Integer.MIN_VALUE, maxC = Integer.MIN_VALUE; - - for (var rc : placed) { - for (var c : rc.cells) { - minR = Math.min(minR, Grid.r(c)); - minC = Math.min(minC, Grid.c(c)); - maxR = Math.max(maxR, Grid.r(c)); - maxC = Math.max(maxC, Grid.c(c)); - } - minR = Math.min(minR, rc.arrowRow); - minC = Math.min(minC, rc.arrowCol); - maxR = Math.max(maxR, rc.arrowRow); - maxC = Math.max(maxC, rc.arrowCol); - } - - // 3) map of only used letter cells (everything else becomes '#') - var letterAt = new HashMap(); - for (var p : placed) { - for (var c : p.cells) { - int rr = Grid.r(c), cc = Grid.c(c); - if (inBounds(rr, cc) && g.isLetterAt(rr, cc)) { - letterAt.put(pack(rr, cc), (char) g.byteAt(Grid.offset(rr, cc))); - } - } - } - - // 4) render gridv2 over cropped bounds (out-of-bounds become '#') - var gridv2 = new ArrayList(Math.max(0, maxR - minR + 1)); - for (var r = minR; r <= maxR; r++) { - var row = new StringBuilder(Math.max(0, maxC - minC + 1)); - for (var c = minC; c <= maxC; c++) { - row.append(letterAt.getOrDefault(pack(r, c), '#')); - } - gridv2.add(row.toString()); - } - - // 5) words output with cropped coordinates - int MIN_R = minR, MIN_C = minC; - var wordsOut = placed.stream().map(p -> new WordOut( - p.lemma, - p.startRow - MIN_R, - p.startCol - MIN_C, - p.direction, - p.arrowRow - MIN_R, - p.arrowCol - MIN_C, - p.isReversed, - p.lemma.simpel() - )).toArray(WordOut[]::new); - return new ExportedPuzzle(gridv2, wordsOut, difficulty, rewards); - } - - /** - * Convert a generator Slot + assigned word into a Placed object for export. - */ - private static Placed extractPlacedFromSlot(Slot s, Lemma lemma) { - var d = s.dir(); - - var cells = new int[s.len()]; - for (int i = 0, len = s.len(); i < len; i++) cells[i] = s.pos(i); - - // Canonicalize: always output right/down - String direction; - var isReversed = false; - var startRow = Grid.r(cells[0]); - var startCol = Grid.c(cells[0]); - if (d == 2) { // right -> horizontal - direction = HORIZONTAL; - } else if (d == 3 || d == 5) { // down or down-bent -> vertical - direction = VERTICAL; - } else if (d == 4) { // left -> horizontal (REVERSED) - direction = HORIZONTAL; - isReversed = true; - } else if (d == 1) { // up -> vertical (REVERSED) - direction = VERTICAL; - isReversed = true; - } else { - return null; - } - - return new Placed( - lemma, - startRow, - startCol, - direction, - s.clueR(), - s.clueC(), - cells, - isReversed - ); - } - - private static long pack(int r, int c) { return (((long) r) << 32) ^ (c & 0xFFFFFFFFL); } - private record Placed(Lemma lemma, int startRow, int startCol, String direction, int arrowRow, int arrowCol, int[] cells, boolean isReversed) { } - - public record Rewards(int coins, int stars, int hints) { } - - public record WordOut(Lemma lemma, int startRow, int startCol, String direction, int arrowRow, int arrowCol, boolean isReversed, int complex) { - - public String word() { return new String(lemma().word(), java.nio.charset.StandardCharsets.US_ASCII); } - public String[] clue() { return lemma.clue(); } - } - - public record ExportedPuzzle(List gridv2, WordOut[] words, int difficulty, Rewards rewards) { } - -} diff --git a/src/main/java/puzzle/Main.java b/src/main/java/puzzle/Main.java index 9c38be8..fa29ed7 100644 --- a/src/main/java/puzzle/Main.java +++ b/src/main/java/puzzle/Main.java @@ -14,30 +14,27 @@ import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicLong; import java.util.stream.Collectors; -import static puzzle.ExportFormat.*; +import static puzzle.Export.*; import static puzzle.SwedishGenerator.*; import static puzzle.SwedishGenerator.loadWords; public class Main { - // ---------------- Top-level generatePuzzle ---------------- - public record PuzzleResult(SwedishGenerator swe, Dict dict, Gridded mask, FillResult filled) { } - - final static String OUT_DIR = envOrDefault("OUT_DIR", "/data/puzzle"); - final static Path PUZZLE_DIR = Paths.get(OUT_DIR, "puzzles"); - static final Path INDEX_FILE = PUZZLE_DIR.resolve("index.json"); - static final OffsetDateTime now = OffsetDateTime.now(ZoneOffset.UTC); - static final String CREATED_AT = now.format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'")); - static final String FILE_ID = CREATED_AT.replace(":", "-") + "_" + System.currentTimeMillis() / 1000; - static final String FILE_NAME = FILE_ID + ".json"; - static final Path OUTPUT_PATH = PUZZLE_DIR.resolve(FILE_NAME); - static final String DATE_STRING = now.toLocalDate().toString(); - - static final AtomicLong TOTAL_NODES = new AtomicLong(0); - static final AtomicLong TOTAL_BACKTRACKS = new AtomicLong(0); - static final AtomicLong TOTAL_ATTEMPTS = new AtomicLong(0); - static final AtomicLong TOTAL_SUCCESS = new AtomicLong(0); - static final AtomicLong TOTAL_SIMPLICITY = new AtomicLong(0); // Scaled by 100 for precision + final static String OUT_DIR = envOrDefault("OUT_DIR", "/data/puzzle"); + final static Path PUZZLE_DIR = Paths.get(OUT_DIR, "puzzles"); + static final Path INDEX_FILE = PUZZLE_DIR.resolve("index.json"); + static final OffsetDateTime now = OffsetDateTime.now(ZoneOffset.UTC); + static final String CREATED_AT = now.format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'")); + static final String FILE_ID = CREATED_AT.replace(":", "-") + "_" + System.currentTimeMillis() / 1000; + static final String FILE_NAME = FILE_ID + ".json"; + static final Path OUTPUT_PATH = PUZZLE_DIR.resolve(FILE_NAME); + static final String DATE_STRING = now.toLocalDate().toString(); + static final boolean VERBOSE = false; + static final AtomicLong TOTAL_NODES = new AtomicLong(0); + static final AtomicLong TOTAL_BACKTRACKS = new AtomicLong(0); + static final AtomicLong TOTAL_ATTEMPTS = new AtomicLong(0); + static final AtomicLong TOTAL_SUCCESS = new AtomicLong(0); + static final AtomicLong TOTAL_SIMPLICITY = new AtomicLong(0); // Scaled by 100 for precision @Data public static class Opts { @@ -52,6 +49,7 @@ public class Main { public boolean reindex = false; public int fillTimeout = 20_000; public boolean verbose = false; + } public void main(String[] args) { @@ -92,7 +90,7 @@ public class Main { section("Grid (human)"); System.out.print(indentLines(res.filled().grid().renderHuman(), " ")); - var exported = exportFormatFromFilled(res, 1, new Rewards(50, 2, 1)); + var exported = res.exportFormatFromFilled(1, new Rewards(50, 2, 1)); section("Clues"); info("status : generating..."); @@ -233,8 +231,6 @@ public class Main { i++; } else if (a.equals("--reindex")) { out.reindex = true; - } else if (a.equals("--verbose")) { - out.verbose = true; } else { throw new IllegalArgumentException("Unknown arg: " + a); } @@ -344,8 +340,8 @@ public class Main { static PuzzleResult attempt(Rng rng, Dict dict, Opts opts) { TOTAL_ATTEMPTS.incrementAndGet(); var swe = new SwedishGenerator(); - var mask = swe.generateMask(rng, dict.lenCounts(), opts.pop, opts.gens, opts.verbose); - var filled = swe.fillMask(rng, mask, dict.index(), 200, opts.fillTimeout, opts.verbose); + var mask = swe.generateMask(rng, dict.lenCounts(), opts.pop, opts.gens); + var filled = new CSP(rng).fillMask(mask, dict.index(), 200, opts.fillTimeout); TOTAL_NODES.addAndGet(filled.stats().nodes); TOTAL_BACKTRACKS.addAndGet(filled.stats().backtracks); @@ -399,7 +395,7 @@ public class Main { sb.append(" ],\n"); sb.append(" \"words\": [\n"); for (var i = 0; i < puzzle.words().length; i++) { - var w = puzzle.words()[i]; + var w = puzzle.words()[i]; sb.append(" {\n"); sb.append(" \"word\": \"").append(escapeJson(w.word())).append("\",\n"); sb.append(" \"clue\": [").append(Arrays.stream(w.clue()).map(ss -> "\"" + escapeJson(ss) + "\"").collect(Collectors.joining(","))).append("],\n"); diff --git a/src/main/java/puzzle/SwedishGenerator.java b/src/main/java/puzzle/SwedishGenerator.java index 8aa9ad4..aabef5e 100644 --- a/src/main/java/puzzle/SwedishGenerator.java +++ b/src/main/java/puzzle/SwedishGenerator.java @@ -3,9 +3,10 @@ package puzzle; import com.google.gson.Gson; import lombok.Getter; import lombok.val; -import puzzle.ExportFormat.Bit; -import puzzle.ExportFormat.Bit1029; -import puzzle.ExportFormat.Gridded; +import puzzle.Export.Bit; +import puzzle.Export.Bit1029; +import puzzle.Export.Gridded; +import puzzle.Export.Strings; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; @@ -16,6 +17,7 @@ import java.util.Comparator; import java.util.HashMap; import java.util.Locale; import java.util.concurrent.atomic.AtomicLong; +import java.util.function.IntConsumer; import java.util.function.Supplier; /** @@ -30,18 +32,6 @@ public record SwedishGenerator() { record CandidateInfo(int[] indices, int count) { } - record nbrs_8(int r, int c) { } - - record nbrs_16(int r, int c, int dr, int dc, int d, byte dbyte) { - - public nbrs_16(int r, int c, int dr, int dc, int d) { - this(r, c, dr, dc, d, (byte) (48 + d)); - } - } - - record rci(int r, int c, int i, long n1, long n2, int nbrCount, long n8_1, long n8_2) { - } - static final int BAR_LEN = 22; static final int C = Config.PUZZLE_COLS; static final double CROSS_Y = (C - 1) / 2.0; @@ -61,8 +51,12 @@ public record SwedishGenerator() { static boolean isLetter(byte b) { return (b & 64) != 0; } static int clamp(int x, int a, int b) { return Math.max(a, Math.min(b, x)); } - public SwedishGenerator() { } - public SwedishGenerator(int[] buff) { this(); } + record nbrs_8(int r, int c) { } + + record nbrs_16(int r, int c, int dr, int dc, int d, byte dbyte) { + + public nbrs_16(int r, int c, int dr, int dc, int d) { this(r, c, dr, dc, d, (byte) (48 + d)); } + } // Directions for '1'..'6' static final nbrs_16[] OFFSETS = new nbrs_16[]{ @@ -91,6 +85,8 @@ public record SwedishGenerator() { new nbrs_8(0, 1) }; + record rci(int r, int c, int i, long n1, long n2, int nbrCount, long n8_1, long n8_2) { } + static final rci[] IT = new rci[SIZE]; static { for (int i = 0; i < SIZE; i++) { @@ -120,6 +116,35 @@ public record SwedishGenerator() { } } + public static final class FillStats { + + public long nodes; + public long backtracks; + public double seconds; + public int lastMRV; + } + + record Pick(Slot slot, CandidateInfo info, boolean done) { } + + static final Pick PICK_DONE = new Pick(null, null, true); + static final Pick PICK_NOT_DONE = new Pick(null, null, false); + + public static record FillResult(boolean ok, + Gridded grid, + HashMap clueMap, + FillStats stats, + double simplicity) { + + public FillResult(boolean ok, Gridded grid, HashMap assigned, FillStats stats) { + double totalSimplicity = 0; + if (ok) { + for (var w : assigned.values()) totalSimplicity += w.simpel; + totalSimplicity = assigned.isEmpty() ? 0 : totalSimplicity / assigned.size(); + } + this(ok, grid, assigned, stats, totalSimplicity); + } + } + static record Context(int[] covH, int[] covV, int[] cellCount, @@ -208,25 +233,15 @@ public record SwedishGenerator() { for (int i = 0; i < SIZE; i++) if (g[i] == b.g[i]) same++; return same / SIZED; } - int clueCount() { - return Long.bitCount(bo[0]) + Long.bitCount(bo[1]); + int clueCount() { return Long.bitCount(bo[0]) + Long.bitCount(bo[1]); } + void forEachSetBit71() { forEachSetBit71(null); } + void forEachSetBit71(IntConsumer consumer) { + for (var lo = bo[0]; lo != 0; lo &= lo - 1) consumer.accept(Long.numberOfTrailingZeros(lo)); + for (var hi = bo[1]; hi != 0; hi &= hi - 1) consumer.accept(64 + Long.numberOfTrailingZeros(hi)); } - void forEachSetBit71(java.util.function.IntConsumer consumer) { - long lo = bo[0], hi = bo[1]; - // low 64 bits - while (lo != 0L) { - int bit = Long.numberOfTrailingZeros(lo); - consumer.accept(bit); // 0..63 - lo &= (lo - 1); // clear lowest set bit - } - - // high 7 bits (positions 64..70) - // hi &= 0x7FL; - while (hi != 0L) { - int bit = Long.numberOfTrailingZeros(hi); - consumer.accept(64 + bit); // 64..70 - hi &= (hi - 1L); - } + void forEachSlot(SlotVisitor visitor) { + for (var lo = bo[0]; lo != 0; lo &= lo - 1) processSlot(this, visitor, Long.numberOfTrailingZeros(lo)); + for (var hi = bo[1]; hi != 0; hi &= hi - 1) processSlot(this, visitor, 64 + Long.numberOfTrailingZeros(hi)); } } static Grid makeEmptyGrid() { return new Grid(new byte[SIZE], new long[2]); } @@ -351,73 +366,21 @@ public record SwedishGenerator() { return k; } - CandidateInfo candidateInfoForPattern(Context ctx, DictEntry entry, int len) { - var pattern = ctx.pattern; - var listBuffer = ctx.intListBuffer; - var listCount = 0; - for (var i = 0; i < len; i++) { - var ch = pattern[i]; - if (isLetter(ch)) { - listBuffer[listCount++] = entry.pos[i][ch - 'A']; - } - } - - if (listCount == 0) { - return new CandidateInfo(null, entry.words.size()); - } - - // Sort constraints by size to optimize intersection - for (int i = 0; i < listCount - 1; i++) { - for (int j = i + 1; j < listCount; j++) { - if (listBuffer[j].size() < listBuffer[i].size()) { - var tmp = listBuffer[i]; - listBuffer[i] = listBuffer[j]; - listBuffer[j] = tmp; - } - } - } - - var cur = listBuffer[0].data(); - var curLen = listBuffer[0].size(); - if (listCount == 1) return new CandidateInfo(cur, curLen); - - int[] b1 = ctx.inter1; - int[] b2 = ctx.inter2; - int[] in = cur; - int[] out = b1; - - for (var k = 1; k < listCount; k++) { - var nxt = listBuffer[k]; - curLen = intersectSorted(in, curLen, nxt.data(), nxt.size(), out); - in = out; - out = (out == b1) ? b2 : b1; - if (curLen == 0) break; - } - - return new CandidateInfo(in, curLen); - } static record Slot(int key, long packedPos) { - static Slot from(int key, long packedPos, int len) { - return new Slot(key, packedPos | ((long) len << 56)); - } - - public int len() { return (int) (packedPos >>> 56); } - public int clueR() { return Grid.r((key >>> 4)); } - public int clueIndex() { return key >>> 4; } - public int clueC() { return Grid.c((key >>> 4)); } - public int dir() { return key & 15; } - public boolean horiz() { return horiz(key); } - public int pos(int i) { return offset(packedPos, i); } - public static boolean horiz(int key) { return ((key & 15) & 1) == 0; } - public static int offset(long packedPos, int i) { return (int) ((packedPos >> (i * 7)) & 127); } + static Slot from(int key, long packedPos, int len) { return new Slot(key, packedPos | ((long) len << 56)); } + public int len() { return (int) (packedPos >>> 56); } + public int clueR() { return Grid.r((key >>> 4)); } + public int clueIndex() { return key >>> 4; } + public int clueC() { return Grid.c((key >>> 4)); } + public int dir() { return key & 15; } + public boolean horiz() { return horiz(key); } + public int pos(int i) { return offset(packedPos, i); } + public static boolean horiz(int key) { return ((key & 15) & 1) == 0; } + public static int offset(long packedPos, int i) { return (int) ((packedPos >> (i * 7)) & 127); } } static void undoPlace(Grid grid, Slot s, int mask) { - for (int i = 0, len = s.len(); i < len; i++) { - if ((mask & (1L << i)) != 0) { - grid.clear(s.pos(i)); - } - } + for (int i = 0, len = s.len(); i < len; i++) if ((mask & (1L << i)) != 0) grid.clear(s.pos(i)); } @FunctionalInterface interface SlotVisitor { @@ -425,22 +388,7 @@ public record SwedishGenerator() { void visit(int key, long packedPos, int len); } - void forEachSlot(Grid grid, SlotVisitor visitor) { - long lo = grid.bo[0], hi = grid.bo[1]; - int idx; - while (lo != 0) { - idx = Long.numberOfTrailingZeros(lo); - lo &= (lo - 1); - processSlot(grid, visitor, idx); - } - while (hi != 0) { - idx = 64 + Long.numberOfTrailingZeros(hi); - hi &= (hi - 1); - processSlot(grid, visitor, idx); - } - } - - private void processSlot(Grid grid, SlotVisitor visitor, int idx) { + private static void processSlot(Grid grid, SlotVisitor visitor, int idx) { var d = grid.digitAt(idx); var nbrs16 = OFFSETS[d]; int r = Grid.r(idx), c = Grid.c(idx), rr = r + nbrs16.r, cc = c + nbrs16.c; @@ -459,9 +407,9 @@ public record SwedishGenerator() { } } - ArrayList extractSlots(Grid grid) { + static ArrayList extractSlots(Grid grid) { var slots = new ArrayList(32); - forEachSlot(grid, (key, packedPos, len) -> slots.add(Slot.from(key, packedPos, len))); + grid.forEachSlot((key, packedPos, len) -> slots.add(Slot.from(key, packedPos, len))); return slots; } boolean hasRoomForClue(Grid grid, int idx, nbrs_16 nbrs16) { @@ -642,15 +590,13 @@ public record SwedishGenerator() { var nx = Math.cos(theta); var ny = Math.sin(theta); - for (var rci : IT) { - out.setAt(rci.i, ((rci.r - CROSS_X) * nx + (rci.c - CROSS_Y) * ny >= 0) ? a.byteAt(rci.i) : b.byteAt(rci.i)); - } + for (var rci : IT) out.setAt(rci.i, ((rci.r - CROSS_X) * nx + (rci.c - CROSS_Y) * ny >= 0) ? a.byteAt(rci.i) : b.byteAt(rci.i)); for (var rci : IT) if (out.isDigitAt(rci.i) && !hasRoomForClue(out, rci.i, OFFSETS[out.digitAt(rci.i)])) out.clearClue(rci.i); return out; } Grid hillclimb(Rng rng, Grid start, int[] lenCounts, int limit) { - var best = start;//.deepCopyGrid(); + var best = start; var bestF = maskFitness(best, lenCounts); var fails = 0; @@ -668,7 +614,7 @@ public record SwedishGenerator() { return best; } - public Grid generateMask(Rng rng, int[] lenCounts, int popSize, int gens, boolean verbose) { + public Grid generateMask(Rng rng, int[] lenCounts, int popSize, int gens) { class GridAndFit { Grid grid; @@ -679,7 +625,7 @@ public record SwedishGenerator() { return this.fite; } } - if (verbose) System.out.println("generateMask init pop: " + popSize); + if (Main.VERBOSE) System.out.println("generateMask init pop: " + popSize); var pop = new ArrayList(); for (var i = 0; i < popSize; i++) { pop.add(new GridAndFit(hillclimb(rng, randomMask(rng), lenCounts, 180))); @@ -714,7 +660,7 @@ public record SwedishGenerator() { } pop = next; - if (verbose && gen % 10 == 0) { + if (Main.VERBOSE && gen % 10 == 0) { var bestF = pop.get(0).fit(); System.out.println(" gen " + gen + "/" + gens + " bestFitness=" + bestF); } @@ -723,38 +669,6 @@ public record SwedishGenerator() { pop.sort(Comparator.comparingLong(GridAndFit::fit)); return pop.get(0).grid; } - - // ---------------- Fill (CSP) ---------------- - - public static final class FillStats { - - public long nodes; - public long backtracks; - public double seconds; - public int lastMRV; - } - - record Pick(Slot slot, CandidateInfo info, boolean done) { } - - static final Pick PICK_DONE = new Pick(null, null, true); - static final Pick PICK_NOT_DONE = new Pick(null, null, false); - - public static record FillResult(boolean ok, - Gridded grid, - HashMap clueMap, - FillStats stats, - double simplicity) { - - public FillResult(boolean ok, Gridded grid, HashMap assigned, FillStats stats) { - double totalSimplicity = 0; - if (ok) { - for (var w : assigned.values()) totalSimplicity += w.simpel; - totalSimplicity = assigned.isEmpty() ? 0 : totalSimplicity / assigned.size(); - } - this(ok, grid, assigned, stats, totalSimplicity); - } - } - static void patternForSlot(Grid grid, Slot s, byte[] pat) { for (int i = 0, len = s.len(); i < len; i++) { var ch = grid.byteAt(s.pos(i)); @@ -789,116 +703,199 @@ public record SwedishGenerator() { undoBuffer[offset] = mask; return true; } - - public FillResult fillMask(Rng rng, Grid mask, DictEntry[] dictIndex, - int logEveryMs, int timeLimitMs, boolean verbose) { - boolean multiThreaded = Thread.currentThread().getName().contains("pool"); - var grid = mask.deepCopyGrid(); - var slots = extractSlots(grid); + static CandidateInfo candidateInfoForPattern(Context ctx, DictEntry entry, int len) { + var pattern = ctx.pattern; + var listBuffer = ctx.intListBuffer; + var listCount = 0; + for (var i = 0; i < len; i++) { + var ch = pattern[i]; + if (isLetter(ch)) { + listBuffer[listCount++] = entry.pos[i][ch - 'A']; + } + } - var used = new Bit1029(); - var assigned = new HashMap(); + if (listCount == 0) { + return new CandidateInfo(null, entry.words.size()); + } - var ctx = CTX.get(); - var count = ctx.cellCount; - Arrays.fill(count, 0, SIZE, 0); - for (var s : slots) for (int i = 0, len = s.len(); i < len; i++) count[s.pos(i)]++; - - var t0 = System.currentTimeMillis(); - final var lastLog = new AtomicLong(t0); - - var stats = new FillStats(); - final var TOTAL = slots.size(); - - Runnable renderProgress = () -> { - if (!verbose || multiThreaded) return; - var now = System.currentTimeMillis(); - if ((now - lastLog.get()) < logEveryMs) return; - lastLog.set(now); - - var done = assigned.size(); - 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, stats.nodes, stats.backtracks, stats.lastMRV, elapsed - ); - System.out.print("\r" + padRight(msg, 120)); - System.out.flush(); - }; - - Supplier chooseMRV = () -> { - Slot best = null; - CandidateInfo bestInfo = null; - int bestSlot = -1; - for (var s : slots) { - if (assigned.containsKey(s.key())) continue; - var entry = dictIndex[s.len()]; - if (entry == null) return PICK_NOT_DONE; - patternForSlot(grid, s, ctx.pattern); - var info = candidateInfoForPattern(ctx, entry, s.len()); - - if (info.count == 0) return PICK_NOT_DONE; - var slotScore = -1; - if (best == null - || info.count < bestInfo.count - || (info.count == bestInfo.count && (slotScore = slotScore(count, s)) > bestSlot)) { - best = s; - bestSlot = (slotScore != -1) ? slotScore : slotScore(count, s); - if (info.indices != null && (info.indices == ctx.inter1 || info.indices == ctx.inter2)) { - bestInfo = new CandidateInfo(Arrays.copyOf(info.indices, info.count), info.count); - } else { - bestInfo = info; - } - if (info.count <= 1) break; + // Sort constraints by size to optimize intersection + for (int i = 0; i < listCount - 1; i++) { + for (int j = i + 1; j < listCount; j++) { + if (listBuffer[j].size() < listBuffer[i].size()) { + var tmp = listBuffer[i]; + listBuffer[i] = listBuffer[j]; + listBuffer[j] = tmp; } } - - if (best == null) { - return PICK_DONE; - } else { - return new Pick(best, bestInfo, false); - } - }; + } - class Solver { + var cur = listBuffer[0].data(); + var curLen = listBuffer[0].size(); + if (listCount == 1) return new CandidateInfo(cur, curLen); + + int[] b1 = ctx.inter1; + int[] b2 = ctx.inter2; + int[] in = cur; + int[] out = b1; + + for (var k = 1; k < listCount; k++) { + var nxt = listBuffer[k]; + curLen = intersectSorted(in, curLen, nxt.data(), nxt.size(), out); + in = out; + out = (out == b1) ? b2 : b1; + if (curLen == 0) break; + } + + return new CandidateInfo(in, curLen); + } + record CSP(Rng rng) { + + public FillResult fillMask(Grid mask, DictEntry[] dictIndex, + int logEveryMs, int timeLimitMs) { + boolean multiThreaded = Thread.currentThread().getName().contains("pool"); + var grid = mask.deepCopyGrid(); + var slots = extractSlots(grid); - boolean backtrack(int depth) { - if (Thread.currentThread().isInterrupted()) return false; - stats.nodes++; + var used = new Bit1029(); + var assigned = new HashMap(); + + var ctx = CTX.get(); + var count = ctx.cellCount; + Arrays.fill(count, 0, SIZE, 0); + for (var s : slots) for (int i = 0, len = s.len(); i < len; i++) count[s.pos(i)]++; + + var t0 = System.currentTimeMillis(); + final var lastLog = new AtomicLong(t0); + + var stats = new FillStats(); + final var TOTAL = slots.size(); + + Runnable renderProgress = () -> { + if (!Main.VERBOSE || multiThreaded) return; + var now = System.currentTimeMillis(); + if ((now - lastLog.get()) < logEveryMs) return; + lastLog.set(now); - if (timeLimitMs > 0 && (System.currentTimeMillis() - t0) > timeLimitMs) return false; + var done = assigned.size(); + 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 pick = chooseMRV.get(); - if (pick.done) return true; - if (pick.slot == null) { - stats.backtracks++; - return false; - } - val info = pick.info; - stats.lastMRV = info.count; - renderProgress.run(); - - var s = pick.slot; - var k = s.key(); - int patLen = s.len(); - var entry = dictIndex[patLen]; - var pat = new byte[patLen]; - patternForSlot(grid, s, pat); - if (info.indices != null && info.indices.length > 0) { - var idxs = info.indices; - var L = idxs.length; - var tries = Math.min(MAX_TRIES_PER_SLOT, L); + var msg = String.format( + Locale.ROOT, + "%s %d/%d slots | nodes=%d | backtracks=%d | mrv=%d | %s", + bar, done, TOTAL, stats.nodes, stats.backtracks, stats.lastMRV, elapsed + ); + System.out.print("\r" + Strings.padRight(msg, 120)); + System.out.flush(); + }; + + Supplier chooseMRV = () -> { + Slot best = null; + CandidateInfo bestInfo = null; + int bestSlot = -1; + for (var s : slots) { + if (assigned.containsKey(s.key())) continue; + var entry = dictIndex[s.len()]; + if (entry == null) return PICK_NOT_DONE; + patternForSlot(grid, s, ctx.pattern); + var info = SwedishGenerator.candidateInfoForPattern(ctx, entry, s.len()); + if (info.count == 0) return PICK_NOT_DONE; + var slotScore = -1; + if (best == null + || info.count < bestInfo.count + || (info.count == bestInfo.count && (slotScore = slotScore(count, s)) > bestSlot)) { + best = s; + bestSlot = (slotScore != -1) ? slotScore : slotScore(count, s); + if (info.indices != null && (info.indices == ctx.inter1 || info.indices == ctx.inter2)) { + bestInfo = new CandidateInfo(Arrays.copyOf(info.indices, info.count), info.count); + } else { + bestInfo = info; + } + if (info.count <= 1) break; + } + } + + if (best == null) { + return PICK_DONE; + } else { + return new Pick(best, bestInfo, false); + } + }; + + class Solver { + + boolean backtrack(int depth) { + if (Thread.currentThread().isInterrupted()) return false; + stats.nodes++; + + if (timeLimitMs > 0 && (System.currentTimeMillis() - t0) > timeLimitMs) return false; + + var pick = chooseMRV.get(); + if (pick.done) return true; + if (pick.slot == null) { + stats.backtracks++; + return false; + } + val info = pick.info; + stats.lastMRV = info.count; + renderProgress.run(); + + var s = pick.slot; + var k = s.key(); + int patLen = s.len(); + var entry = dictIndex[patLen]; + var pat = new byte[patLen]; + patternForSlot(grid, s, pat); + if (info.indices != null && info.indices.length > 0) { + var idxs = info.indices; + var L = idxs.length; + var tries = Math.min(MAX_TRIES_PER_SLOT, L); + + for (var t = 0; t < tries; t++) { + double r = rng.nextFloat(); + int idxInArray = (int) (r * r * r * L); + var idx = idxs[idxInArray]; + var w = entry.words.get(idx); + + if (used.get(w.index())) continue; + + boolean match = true; + for (var i = 0; i < patLen; i++) { + if (pat[i] != DASH && pat[i] != w.byteAt(i)) { + match = false; + break; + } + } + + if (!match || !placeWord(grid, s, w, ctx.undo, depth)) continue; + + used.set(w.index()); + assigned.put(k, w); + + if (backtrack(depth + 1)) return true; + + assigned.remove(k); + used.clear(w.index); + undoPlace(grid, s, ctx.undo[depth]); + } + stats.backtracks++; + return false; + } + + var N = entry.words.size(); + if (N == 0) { + stats.backtracks++; + return false; + } + + var tries = Math.min(MAX_TRIES_PER_SLOT, N); for (var t = 0; t < tries; t++) { double r = rng.nextFloat(); - int idxInArray = (int) (r * r * r * L); - var idx = idxs[idxInArray]; - var w = entry.words.get(idx); + int idxInArray = (int) (r * r * r * N); + var w = entry.words.get(idxInArray); if (used.get(w.index())) continue; @@ -909,7 +906,6 @@ public record SwedishGenerator() { break; } } - if (!match || !placeWord(grid, s, w, ctx.undo, depth)) continue; used.set(w.index()); @@ -921,76 +917,36 @@ public record SwedishGenerator() { used.clear(w.index); undoPlace(grid, s, ctx.undo[depth]); } + stats.backtracks++; return false; } - - var N = entry.words.size(); - if (N == 0) { - stats.backtracks++; - return false; - } - - var tries = Math.min(MAX_TRIES_PER_SLOT, N); - for (var t = 0; t < tries; t++) { - double r = rng.nextFloat(); - int idxInArray = (int) (r * r * r * N); - var w = entry.words.get(idxInArray); - - if (used.get(w.index())) continue; - - boolean match = true; - for (var i = 0; i < patLen; i++) { - if (pat[i] != DASH && pat[i] != w.byteAt(i)) { - match = false; - break; - } - } - if (!match || !placeWord(grid, s, w, ctx.undo, depth)) continue; - - used.set(w.index()); - assigned.put(k, w); - - if (backtrack(depth + 1)) return true; - - assigned.remove(k); - used.clear(w.index); - undoPlace(grid, s, ctx.undo[depth]); - } - - stats.backtracks++; - return false; } + + // initial render (same feel) + renderProgress.run(); + var ok = new Solver().backtrack(0); + // final progress line + if (!multiThreaded) { + System.out.print("\r" + Strings.padRight("", 120) + "\r"); + System.out.flush(); + } + + stats.seconds = (System.currentTimeMillis() - t0) / 1000.0; + var res = new FillResult(ok, new Gridded(grid), assigned, stats); + + // print a final progress line + if (Main.VERBOSE && !multiThreaded) { + System.out.println( + String.format(Locale.ROOT, + "[######################] %d/%d slots | nodes=%d | backtracks=%d | mrv=%d | %.1fs", + assigned.size(), TOTAL, stats.nodes, stats.backtracks, stats.lastMRV, stats.seconds + ) + ); + } + + return res; } - - // initial render (same feel) - renderProgress.run(); - var ok = new Solver().backtrack(0); - // final progress line - if (!multiThreaded) { - System.out.print("\r" + padRight("", 120) + "\r"); - System.out.flush(); - } - - stats.seconds = (System.currentTimeMillis() - t0) / 1000.0; - var res = new FillResult(ok, new Gridded(grid), assigned, stats); - - // print a final progress line - if (verbose && !multiThreaded) { - System.out.println( - String.format(Locale.ROOT, - "[######################] %d/%d slots | nodes=%d | backtracks=%d | mrv=%d | %.1fs", - assigned.size(), TOTAL, stats.nodes, stats.backtracks, stats.lastMRV, stats.seconds - ) - ); - } - - return res; - } - - static String padRight(String s, int n) { - if (s.length() >= n) return s; - return s + " ".repeat(n - s.length()); } } diff --git a/src/main/java/puzzle/TestGen.java b/src/main/java/puzzle/TestGen.java new file mode 100644 index 0000000..0e533f0 --- /dev/null +++ b/src/main/java/puzzle/TestGen.java @@ -0,0 +1,15 @@ +package puzzle; + +import precomp.Neighbors9x8; +public class TestGen { + + static void main() { + var cell = Neighbors9x8.IT[0]; + long n1 = cell.n1(); + long n2 = cell.n2(); + int count = cell.nbrCount(); + + // of direct je offsets: + var up = Neighbors9x8.OFFSETS[1]; + } +} diff --git a/src/main/java/puzzle/Trigger.java b/src/main/java/puzzle/Trigger.java new file mode 100644 index 0000000..4b04aed --- /dev/null +++ b/src/main/java/puzzle/Trigger.java @@ -0,0 +1,6 @@ +// file: app/Trigger.java +package puzzle; + +import gen.GenerateNeighbors; +@GenerateNeighbors(C = 9, R = 8, packageName = "precomp", className = "Neighbors9x8") +public final class Trigger { } diff --git a/src/test/java/puzzle/ExportFormatTest.java b/src/test/java/puzzle/ExportFormatTest.java index 8a11083..465ff7e 100644 --- a/src/test/java/puzzle/ExportFormatTest.java +++ b/src/test/java/puzzle/ExportFormatTest.java @@ -1,10 +1,9 @@ package puzzle; import org.junit.jupiter.api.Test; -import puzzle.ExportFormat.ExportedPuzzle; -import puzzle.ExportFormat.Gridded; -import puzzle.ExportFormat.Rewards; -import puzzle.Main.PuzzleResult; +import puzzle.Export.Gridded; +import puzzle.Export.Rewards; +import puzzle.Export.PuzzleResult; import puzzle.SwedishGenerator.FillResult; import puzzle.SwedishGenerator.Grid; import puzzle.SwedishGenerator.Lemma; @@ -26,8 +25,8 @@ public class ExportFormatTest { var clueMap = new HashMap(); // key = (cellIndex << 4) | direction - int key = (0 << 4) | 2; - Lemma lemma = new Lemma("TEST", 1, "A test word"); + var key = (0 << 4) | 2; + var lemma = new Lemma("TEST", 1, "A test word"); clueMap.put(key, lemma); // Manually fill the grid letters for "TEST" at (0,1), (0,2), (0,3), (0,4) @@ -41,8 +40,8 @@ public class ExportFormatTest { var fillResult = new FillResult(true, new Gridded(grid), clueMap, null); var puzzleResult = new PuzzleResult(swe, null, null, fillResult); - Rewards rewards = new Rewards(10, 5, 1); - ExportedPuzzle exported = ExportFormat.exportFormatFromFilled(puzzleResult, 2, rewards); + var rewards = new Rewards(10, 5, 1); + var exported = puzzleResult.exportFormatFromFilled(2, rewards); assertNotNull(exported); assertEquals(2, exported.difficulty()); @@ -81,13 +80,13 @@ public class ExportFormatTest { var fillResult = new FillResult(true, new Gridded(grid), new HashMap<>(), null); var puzzleResult = new PuzzleResult(swe, null, null, fillResult); - ExportedPuzzle exported = ExportFormat.exportFormatFromFilled(puzzleResult, 1, new Rewards(0, 0, 0)); + var exported = puzzleResult.exportFormatFromFilled(1, new Rewards(0, 0, 0)); assertNotNull(exported); assertEquals(0, exported.words().length); // Should return full grid with '#' assertEquals(SwedishGenerator.R, exported.gridv2().size()); - for (String row : exported.gridv2()) { + for (var row : exported.gridv2()) { assertEquals(SwedishGenerator.C, row.length()); assertTrue(row.matches("#+")); } diff --git a/src/test/java/puzzle/MainTest.java b/src/test/java/puzzle/MainTest.java index 6423496..ae0cf47 100644 --- a/src/test/java/puzzle/MainTest.java +++ b/src/test/java/puzzle/MainTest.java @@ -2,7 +2,7 @@ package puzzle; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import puzzle.Main.PuzzleResult; +import puzzle.Export.PuzzleResult; import puzzle.SwedishGenerator.Rng; import puzzle.SwedishGenerator.Slot; import java.util.concurrent.atomic.AtomicInteger; @@ -64,7 +64,7 @@ public class MainTest { grid.setClue(0, (byte) '2'); // right var count = new AtomicInteger(0); - generator.forEachSlot(grid, (key, packedPos, len) -> { + grid.forEachSlot((key, packedPos, len) -> { count.incrementAndGet(); assertEquals(8, len); assertEquals(0, Grid.r(Slot.offset(packedPos, 0))); diff --git a/src/test/java/puzzle/SwedishGeneratorTest.java b/src/test/java/puzzle/SwedishGeneratorTest.java index 662229c..60ea97f 100644 --- a/src/test/java/puzzle/SwedishGeneratorTest.java +++ b/src/test/java/puzzle/SwedishGeneratorTest.java @@ -3,6 +3,7 @@ package puzzle; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import puzzle.SwedishGenerator.*; +import java.nio.charset.StandardCharsets; import java.util.Arrays; import static org.junit.jupiter.api.Assertions.*; @@ -103,7 +104,7 @@ public class SwedishGeneratorTest { @Test void testLemmaAndDict() { var l1 = new Lemma("APPLE", 5, "A fruit"); - Assertions.assertArrayEquals("APPLE".getBytes(java.nio.charset.StandardCharsets.US_ASCII), l1.word()); + Assertions.assertArrayEquals("APPLE".getBytes(StandardCharsets.US_ASCII), l1.word()); assertEquals(5, l1.word().length); assertEquals(5, l1.simpel()); assertEquals((byte) 'A', l1.byteAt(0)); @@ -116,7 +117,7 @@ public class SwedishGeneratorTest { var entry3 = dict.index()[3]; assertEquals(1, entry3.words().size()); - Assertions.assertArrayEquals("AXE".getBytes(java.nio.charset.StandardCharsets.US_ASCII), entry3.words().getFirst().word()); + Assertions.assertArrayEquals("AXE".getBytes(StandardCharsets.US_ASCII), entry3.words().getFirst().word()); // Check pos indexing // AXE: A at 0, X at 1, E at 2 @@ -181,7 +182,7 @@ public class SwedishGeneratorTest { // Pattern "APP--" for length 5 var context = new Context(); context.setPatter(new byte[]{ 'A', 'P', 'P', SwedishGenerator.DASH, SwedishGenerator.DASH }); - var info = gen.candidateInfoForPattern(context, dict.index()[5], 5); + var info = SwedishGenerator.candidateInfoForPattern(context, dict.index()[5], 5); assertEquals(2, info.count()); assertNotNull(info.indices());