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());