Gather data

This commit is contained in:
mike
2026-01-08 20:47:42 +01:00
parent dbdd8ecfdb
commit e9978e0f4c
2 changed files with 110 additions and 93 deletions

View File

@@ -2,13 +2,15 @@ package puzzle;
import puzzle.Main.PuzzleResult;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Objects;
import static puzzle.SwedishGenerator.H;
import static puzzle.SwedishGenerator.R;
import static puzzle.SwedishGenerator.Lemma;
import static puzzle.SwedishGenerator.SIZE;
import static puzzle.SwedishGenerator.Slot;
import static puzzle.SwedishGenerator.W;
import static puzzle.SwedishGenerator.C;
/**
* ExportFormat.java
@@ -19,19 +21,34 @@ import static puzzle.SwedishGenerator.W;
* - crops to bounding box (words + arrow cells) with 1-cell margin
* - outputs gridv2 + words[] (+ difficulty, rewards)
*/
public final class ExportFormat {
public record ExportFormat() {
record Bit(long[] bits) {
public Bit() { this(new long[(SIZE >> 6) + 1]); }
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); }
}
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 ExportFormat() { }
static final String HORIZONTAL = "h", VERTICAL = "v";
private static boolean inBounds(int H, int W, int r, int c) { return r >= 0 && r < H && c >= 0 && c < W; }
// ---------- Public API ----------
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();
// 1) extract "placed" list from all clue digits in the filled grid
var g = puz.filled().grid();
var placed = new ArrayList<Placed>();
var clueMap = puz.filled().clueMap();
puz.swe().forEachSlot(g, (int key, long rs, long cs, int len) -> {
@@ -44,10 +61,10 @@ public final class ExportFormat {
// If nothing placed: return full grid mapped to letters/# only
if (placed.isEmpty()) {
var gridv2 = new ArrayList<String>(H);
for (var r = 0; r < H; r++) {
var sb = new StringBuilder(W);
for (var c = 0; c < W; c++) {
var gridv2 = new ArrayList<String>(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) ? g.getCharAt(r, c) : '#');
}
gridv2.add(sb.toString());
@@ -78,7 +95,7 @@ public final class ExportFormat {
for (var p : placed) {
for (var rc : p.cells) {
int rr = rc[0], cc = rc[1];
if (inBounds(H, W, rr, cc) && g.isLetterAt(rr, cc)) {
if (inBounds(rr, cc) && g.isLetterAt(rr, cc)) {
letterAt.put(pack(rr, cc), g.getCharAt(rr, cc));
}
}
@@ -181,4 +198,5 @@ public final class ExportFormat {
}
public record ExportedPuzzle(List<String> gridv2, WordOut[] words, int difficulty, Rewards rewards) { }
}

View File

@@ -2,13 +2,14 @@ package puzzle;
import lombok.Data;
import lombok.Getter;
import puzzle.ExportFormat.Bit;
import puzzle.ExportFormat.Bit1029;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Locale;
@@ -27,16 +28,17 @@ public record SwedishGenerator(int[] buff) {
record CandidateInfo(int[] indices, int count) { }
record nbrs_8(int x, int y) { }
record nbrs_8(int r, int c) { }
record nbrs_16(int x, int y, int dx, int dy) { }
record nbrs_16(int r, int c, int dr, int dc) { }
static final int W = Config.PUZZLE_COLS;
static final double CROSS_Y = (W - 1) / 2.0;
static final int H = Config.PUZZLE_ROWS;
static final double CROSS_X = (H - 1) / 2.0;
static final int SIZE = W * H;
static final int MAX_WORD_LENGTH = Math.min(W, H);
static final int C = Config.PUZZLE_COLS;
static final double CROSS_Y = (C - 1) / 2.0;
static final int R = Config.PUZZLE_ROWS;
static final double CROSS_X = (R - 1) / 2.0;
static final int SIZE = C * R;// ~18
static final int TARGET_CLUES = SIZE >> 2;
static final int MAX_WORD_LENGTH = Math.min(C, R);
static final int MIN_LEN = Config.MIN_LEN;
static final int CLUE_SIZE = Config.CLUE_SIZE;
static final int SIMPLICITY_DEFAULT_SCORE = 2;
@@ -52,18 +54,12 @@ public record SwedishGenerator(int[] buff) {
// Directions for '1'..'6'
static final nbrs_16[] OFFSETS = new nbrs_16[]{
null,
// 1: up
new nbrs_16(-1, 0, -1, 0),
// 2: right
new nbrs_16(0, 1, 0, 1),
// 3: down
new nbrs_16(1, 0, 1, 0),
// 4: left
new nbrs_16(0, -1, 0, -1),
// 5: vertical down, clue is on the right of the first letter
new nbrs_16(0, -1, 1, 0),
// 6: vertical down, clue is on the left of the first letter
new nbrs_16(0, 1, 1, 0)
new nbrs_16(-1, 0, -1, 0), // 1: up
new nbrs_16(0, 1, 0, 1), // 2: right
new nbrs_16(1, 0, 1, 0),// 3: down
new nbrs_16(0, -1, 0, -1),// 4: left
new nbrs_16(0, -1, 1, 0),// 5: vertical down, clue is on the right of the first letter
new nbrs_16(0, 1, 1, 0)// 6: vertical down, clue is on the left of the first letter
};
final static nbrs_8[] nbrs8 = new nbrs_8[]{
new nbrs_8(-1, -1),
@@ -86,13 +82,13 @@ public record SwedishGenerator(int[] buff) {
int[] covV,
int[] cellCount,
int[] stack,
BitSet seen,
Bit seen,
char[] pattern,
IntList[] intListBuffer,
long[] undoBuffer) {
public Context() {
this(new int[SIZE], new int[SIZE], new int[SIZE], new int[SIZE], new BitSet(128), new char[MAX_WORD_LENGTH], new IntList[MAX_WORD_LENGTH],
this(new int[SIZE], new int[SIZE], new int[SIZE], new int[SIZE], new Bit(), new char[MAX_WORD_LENGTH], new IntList[MAX_WORD_LENGTH],
new long[2048]);
}
void setPatter(char[] chars) { System.arraycopy(chars, 0, this.pattern, 0, chars.length); }
@@ -114,7 +110,7 @@ public record SwedishGenerator(int[] buff) {
x = y;
return y;
}
int randint(int min, int max) { // inclusive
int randint(int min, int max) {
var u = (nextU32() & 0xFFFFFFFFL);
var range = (long) max - (long) min + 1L;
return (int) (min + (u % range));
@@ -124,8 +120,10 @@ public record SwedishGenerator(int[] buff) {
record Grid(byte[] g) {
public static int r(int offset) { return offset & 7; }
public static int c(int offset) { return offset >> 3; }
Grid deepCopyGrid() { return new Grid(g.clone()); }
private int offset(int r, int c) { return r | (c << 3); }
static int offset(int r, int c) { return r | (c << 3); }
boolean isLettercell(int r, int c) { return (g[offset(r, c)] & 48) != 48; }
char getCharAt(int r, int c) { return (char) (g[offset(r, c)]); }
int digitAt(int r, int c) { return g[offset(r, c)] - 48; }
@@ -133,6 +131,7 @@ public record SwedishGenerator(int[] buff) {
void setCharAt(int r, int c, char ch) { g[offset(r, c)] = (byte) ch; }
boolean isLetterAt(int r, int c) { return ((g[offset(r, c)] & 64) != 0); }
boolean isDigitAt(int r, int c) { return (g[offset(r, c)] & 48) == 48; }
boolean isDigitAt(int index) { return (g[index] & 48) == 48; }
public double similarity(Grid b) {
var same = 0;
for (int i = 0; i < SIZE; i++) if (g[i] == b.g[i]) same++;
@@ -143,18 +142,18 @@ public record SwedishGenerator(int[] buff) {
String gridToString(Grid g) {
var sb = new StringBuilder();
for (var r = 0; r < H; r++) {
for (var r = 0; r < R; r++) {
if (r > 0) sb.append('\n');
for (var c = 0; c < W; c++) sb.append(g.getCharAt(r, c));
for (var c = 0; c < C; c++) sb.append(g.getCharAt(r, c));
}
return sb.toString();
}
public String renderHuman(Grid g) {
var sb = new StringBuilder();
for (var r = 0; r < H; r++) {
for (var r = 0; r < R; r++) {
if (r > 0) sb.append('\n');
for (var c = 0; c < W; c++) {
for (var c = 0; c < C; c++) {
sb.append(g.isDigitAt(r, c) ? ' ' : g.getCharAt(r, c));
}
}
@@ -352,24 +351,24 @@ public record SwedishGenerator(int[] buff) {
}
void forEachSlot(Grid grid, SlotVisitor visitor) {
for (var r = 0; r < H; r++) {
for (var c = 0; c < W; c++) {
for (var r = 0; r < R; r++) {
for (var c = 0; c < C; c++) {
if (!grid.isDigitAt(r, c)) continue;
var d = grid.digitAt(r, c);
var nbrs16 = OFFSETS[d];
int rr = r + nbrs16.x, cc = c + nbrs16.y;
int rr = r + nbrs16.r, cc = c + nbrs16.c;
if (rr < 0 || rr >= H || cc < 0 || cc >= W || grid.isDigitAt(rr, cc)) continue;
if (rr < 0 || rr >= R || cc < 0 || cc >= C || grid.isDigitAt(rr, cc)) continue;
long packedRs = 0;
long packedCs = 0;
var n = 0;
while (rr >= 0 && rr < H && cc >= 0 && cc < W && grid.isLettercell(rr, cc) && n < MAX_WORD_LENGTH) {
while (rr >= 0 && rr < R && cc >= 0 && cc < C && grid.isLettercell(rr, cc) && n < MAX_WORD_LENGTH) {
packedRs |= (long) rr << (n << 2);
packedCs |= (long) cc << (n << 2);
n++;
rr += nbrs16.dx;
cc += nbrs16.dy;
rr += nbrs16.dr;
cc += nbrs16.dc;
}
if (n > 0) {
visitor.visit((r << 8) | (c << 4) | d, packedRs, packedCs, n);
@@ -386,12 +385,12 @@ public record SwedishGenerator(int[] buff) {
boolean hasRoomForClue(Grid grid, int r, int c, char d) {
var nbrs16 = OFFSETS[d - '0'];
int rr = r + nbrs16.x, cc = c + nbrs16.y;
int rr = r + nbrs16.r, cc = c + nbrs16.c;
var run = 0;
while (rr >= 0 && rr < H && cc >= 0 && cc < W && (grid.isLettercell(rr, cc)) && run < MAX_WORD_LENGTH) {
while (rr >= 0 && rr < R && cc >= 0 && cc < C && (grid.isLettercell(rr, cc)) && run < MAX_WORD_LENGTH) {
run++;
rr += nbrs16.dx;
cc += nbrs16.dy;
rr += nbrs16.dr;
cc += nbrs16.dc;
if (run >= MIN_LEN) return true;
}
return false;
@@ -401,10 +400,9 @@ public record SwedishGenerator(int[] buff) {
long penalty = 0;
var clueCount = 0;
for (var r = 0; r < H; r++) for (var c = 0; c < W; c++) if (grid.isDigitAt(r, c)) clueCount++;
for (var r = 0; r < R; r++) for (var c = 0; c < C; c++) if (grid.isDigitAt(r, c)) clueCount++;
var targetClues = (int) Math.round(SIZE * 0.25); // ~18
penalty += 8L * Math.abs(clueCount - targetClues);
penalty += 8L * Math.abs(clueCount - TARGET_CLUES);
var ctx = CTX.get();
var covH = ctx.covH;
@@ -413,25 +411,25 @@ public record SwedishGenerator(int[] buff) {
Arrays.fill(covV, 0, SIZE, 0);
boolean hasSlots = false;
for (var r = 0; r < H; r++) {
for (var c = 0; c < W; c++) {
for (var r = 0; r < R; r++) {
for (var c = 0; c < C; c++) {
if (!grid.isDigitAt(r, c)) continue;
var d = grid.digitAt(r, c);
var nbrs16 = OFFSETS[d];
int rr = r + nbrs16.x, cc = c + nbrs16.y;
if (rr < 0 || rr >= H || cc < 0 || cc >= W || grid.isDigitAt(rr, cc)) continue;
int rr = r + nbrs16.r, cc = c + nbrs16.c;
if (rr < 0 || rr >= R || cc < 0 || cc >= C || grid.isDigitAt(rr, cc)) continue;
long packedRs = 0;
long packedCs = 0;
var n = 0;
while (rr >= 0 && rr < H && cc >= 0 && cc < W && n < MAX_WORD_LENGTH) {
while (rr >= 0 && rr < R && cc >= 0 && cc < C && n < MAX_WORD_LENGTH) {
if (grid.isDigitAt(rr, cc)) break;
packedRs |= (long) rr << (n << 2);
packedCs |= (long) cc << (n << 2);
n++;
rr += nbrs16.dx;
cc += nbrs16.dy;
rr += nbrs16.dr;
cc += nbrs16.dc;
}
if (n == 0) continue;
hasSlots = true;
@@ -443,16 +441,16 @@ public record SwedishGenerator(int[] buff) {
}
var horiz = Slot.horiz(d) ? covH : covV;
for (var i = 0; i < n; i++) horiz[grid.offset(Slot.r(packedRs, i), Slot.c(packedCs, i))] += 1;
for (var i = 0; i < n; i++) horiz[Grid.offset(Slot.r(packedRs, i), Slot.c(packedCs, i))] += 1;
}
}
if (!hasSlots) return 1_000_000_000L;
for (var r = 0; r < H; r++)
for (var c = 0; c < W; c++) {
for (var r = 0; r < R; r++)
for (var c = 0; c < C; c++) {
if (grid.isDigitAt(r, c)) continue;
int idx = grid.offset(r, c);
int idx = Grid.offset(r, c);
int h = covH[idx], v = covV[idx];
if (h == 0 && v == 0) penalty += 1500;
else if (h > 0 && v > 0) { /* ok */ } else if (h + v == 1) penalty += 200;
@@ -465,8 +463,8 @@ public record SwedishGenerator(int[] buff) {
var stack = ctx.stack;
int sp, idx;
for (var r = 0; r < H; r++)
for (var c = 0; c < W; c++) {
for (var r = 0; r < R; r++)
for (var c = 0; c < C; c++) {
idx = grid.offset(r, c);
if (!grid.isDigitAt(r, c) || seen.get(idx)) continue;
sp = 0;
@@ -476,12 +474,12 @@ public record SwedishGenerator(int[] buff) {
while (sp > 0) {
var p = stack[--sp];
int x = p / W, y = p % W;
int x = p / C, y = p % C;
size++;
for (var d : nbrs8) {
int nx = x + d.x, ny = y + d.y;
if (nx < 0 || nx >= H || ny < 0 || ny >= W) continue;
int nx = x + d.r, ny = y + d.c;
if (nx < 0 || nx >= R || ny < 0 || ny >= C) continue;
int nidx = grid.offset(nx, ny);
if (seen.get(nidx)) continue;
if (!grid.isDigitAt(nx, ny)) continue;
@@ -494,13 +492,13 @@ public record SwedishGenerator(int[] buff) {
}
// dead-end-ish letter cell (3+ walls)
for (var r = 0; r < H; r++)
for (var c = 0; c < W; c++) {
for (var r = 0; r < R; r++)
for (var c = 0; c < C; c++) {
if (grid.isDigitAt(r, c)) continue;
var walls = 0;
for (var d : nbrs4) {
int rr = r + d.x, cc = c + d.y;
if (rr < 0 || rr >= H || cc < 0 || cc >= W) {
int rr = r + d.r, cc = c + d.c;
if (rr < 0 || rr >= R || cc < 0 || cc >= C) {
walls++;
continue;
}
@@ -520,8 +518,8 @@ public record SwedishGenerator(int[] buff) {
int placed = 0, guard = 0;
while (placed < targetClues && guard++ < 4000) {
var r = rng.randint(0, H - 1);
var c = rng.randint(0, W - 1);
var r = rng.randint(0, R - 1);
var c = rng.randint(0, C - 1);
if (g.isDigitAt(r, c)) continue;
var d = (char) ('0' + rng.randint(1, c == 0 ? CLUE_SIZE : 4));
@@ -537,13 +535,13 @@ public record SwedishGenerator(int[] buff) {
Grid mutate(Rng rng, Grid grid) {
var g = grid.deepCopyGrid();
var cx = rng.randint(0, H - 1);
var cy = rng.randint(0, W - 1);
var cx = rng.randint(0, R - 1);
var cy = rng.randint(0, C - 1);
var steps = 4;
for (var k = 0; k < steps; k++) {
var rr = clamp(cx + (rng.randint(-2, 2) + rng.randint(-2, 2)), 0, H - 1);
var cc = clamp(cy + (rng.randint(-2, 2) + rng.randint(-2, 2)), 0, W - 1);
var rr = clamp(cx + (rng.randint(-2, 2) + rng.randint(-2, 2)), 0, R - 1);
var cc = clamp(cy + (rng.randint(-2, 2) + rng.randint(-2, 2)), 0, C - 1);
if (g.isDigitAt(rr, cc)) {
g.setCharAt(rr, cc, C_DASH);
@@ -562,13 +560,13 @@ public record SwedishGenerator(int[] buff) {
var nx = Math.cos(theta);
var ny = Math.sin(theta);
for (var r = 0; r < H; r++)
for (var c = 0; c < W; c++) {
for (var r = 0; r < R; r++)
for (var c = 0; c < C; c++) {
out.setCharAt(r, c, ((r - CROSS_X) * nx + (c - CROSS_Y) * ny >= 0) ? a.getCharAt(r, c) : b.getCharAt(r, c));
}
for (var r = 0; r < H; r++)
for (var c = 0; c < W; c++) {
for (var r = 0; r < R; r++)
for (var c = 0; c < C; c++) {
if (out.isDigitAt(r, c) && !hasRoomForClue(out, r, c, out.getCharAt(r, c))) out.setCharAt(r, c, C_DASH);
}
return out;
@@ -648,6 +646,7 @@ public record SwedishGenerator(int[] buff) {
pop.sort(Comparator.comparingLong(GridAndFit::fit));
return pop.get(0).grid;
}
public Grid generateMask2(Rng rng, int[] lenCounts, int popSize, int gens, boolean verbose) {
if (verbose) System.out.println("generateMask init pop: " + popSize);
var pop = new ArrayList<Grid>();
@@ -734,7 +733,7 @@ public record SwedishGenerator(int[] buff) {
static int slotScore(int[] cellCount, Slot s, Grid grid) {
var cross = 0;
for (var i = 0; i < s.len(); i++) cross += (cellCount[grid.offset(s.r(i), s.c(i))] - 1);
for (var i = 0; i < s.len(); i++) cross += (cellCount[Grid.offset(s.r(i), s.c(i))] - 1);
return cross * 10 + s.len();
}
@@ -767,13 +766,13 @@ public record SwedishGenerator(int[] buff) {
var grid = mask.deepCopyGrid();
var slots = extractSlots(grid);
var used = new BitSet(SIZE << 1);
var used = new Bit1029();
var assigned = new HashMap<Integer, Lemma>();
var ctx = CTX.get();
var cellCount = ctx.cellCount;
Arrays.fill(cellCount, 0, SIZE, 0);
for (var s : slots) for (var i = 0; i < s.len(); i++) cellCount[grid.offset(s.r(i), s.c(i))]++;
for (var s : slots) for (var i = 0; i < s.len(); i++) cellCount[Grid.offset(s.r(i), s.c(i))]++;
var t0 = System.currentTimeMillis();
final var lastLog = new AtomicLong(t0);
@@ -894,7 +893,7 @@ public record SwedishGenerator(int[] buff) {
if (backtrack(depth + 1)) return true;
assigned.remove(k);
used.set(w.index, false);
used.clear(w.index);
undoPlace(grid, ctx.undoBuffer, undoOffset, nPlaced);
}
stats.backtracks++;
@@ -933,7 +932,7 @@ public record SwedishGenerator(int[] buff) {
if (backtrack(depth + 1)) return true;
assigned.remove(k);
used.set(w.index, false);
used.clear(w.index);
undoPlace(grid, ctx.undoBuffer, undoOffset, nPlaced);
}