Gather data

This commit is contained in:
mike
2026-01-09 05:51:23 +01:00
parent abef2e2f0b
commit 850fdb4f67
4 changed files with 60 additions and 137 deletions

View File

@@ -10,7 +10,6 @@ import java.util.List;
import java.util.Objects;
import static puzzle.SwedishGenerator.R;
import static puzzle.SwedishGenerator.Lemma;
import static puzzle.SwedishGenerator.SIZE;
import static puzzle.SwedishGenerator.Slot;
import static puzzle.SwedishGenerator.C;
@@ -31,7 +30,7 @@ public record ExportFormat() {
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(r, c));
for (var c = 0; c < C; c++) sb.append((char) grid.byteAt(Grid.offset(r, c)));
}
return sb.toString();
}
@@ -40,7 +39,7 @@ public record ExportFormat() {
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(r, c));
sb.append(grid.isDigitAt(r, c) ? ' ' : (char) grid.byteAt(Grid.offset(r, c)));
}
}
return sb.toString();
@@ -105,7 +104,7 @@ public record ExportFormat() {
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(r, c) : '#');
sb.append(g.isLetterAt(r, c) ? (char) g.byteAt(Grid.offset(r, c)) : '#');
}
gridv2.add(sb.toString());
}
@@ -136,7 +135,7 @@ public record ExportFormat() {
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(rr, cc));
letterAt.put(pack(rr, cc), (char) g.byteAt(Grid.offset(rr, cc)));
}
}
}
@@ -170,40 +169,26 @@ public record ExportFormat() {
* Convert a generator Slot + assigned word into a Placed object for export.
*/
private static Placed extractPlacedFromSlot(Slot s, Lemma lemma) {
int r = s.clueR();
int c = s.clueC();
int d = s.dir();
var d = s.dir();
int[] cells = new int[s.len()];
for (int i = 0; i < s.len(); i++) {
cells[i] = s.pos(i);
}
var cells = new int[s.len()];
for (var i = 0; i < s.len(); i++) cells[i] = s.pos(i);
// Canonicalize: always output right/down
int startRow, startCol, arrowRow, arrowCol;
String direction;
boolean isReversed = false;
startRow = Grid.r(cells[0]);
startCol = Grid.c(cells[0]);
String direction;
var isReversed = false;
var startRow = Grid.r(cells[0]);
var startCol = Grid.c(cells[0]);
if (d == 2) { // right -> horizontal
direction = HORIZONTAL;
arrowRow = r;
arrowCol = c;
} else if (d == 3 || d == 5) { // down or down-bent -> vertical
direction = VERTICAL;
arrowRow = r;
arrowCol = c;
} else if (d == 4) { // left -> horizontal (REVERSED)
direction = HORIZONTAL;
isReversed = true;
arrowRow = r;
arrowCol = c;
} else if (d == 1) { // up -> vertical (REVERSED)
direction = VERTICAL;
isReversed = true;
arrowRow = r;
arrowCol = c;
} else {
return null;
}
@@ -213,8 +198,8 @@ public record ExportFormat() {
startRow,
startCol,
direction,
arrowRow,
arrowCol,
s.clueR(),
s.clueC(),
cells,
isReversed
);

View File

@@ -50,6 +50,7 @@ public record SwedishGenerator(int[] buff) {
new rci(6, 0, 6), new rci(6, 1, 14), new rci(6, 2, 22), new rci(6, 3, 30), new rci(6, 4, 38), new rci(6, 5, 46), new rci(6, 6, 54), new rci(6, 7, 62), new rci(6, 8, 70),
new rci(7, 0, 7), new rci(7, 1, 15), new rci(7, 2, 23), new rci(7, 3, 31), new rci(7, 4, 39), new rci(7, 5, 47), new rci(7, 6, 55), new rci(7, 7, 63), new rci(7, 8, 71),
};
static final int BAR_LEN = 22;
static final int C = Config.PUZZLE_COLS;
static final double CROSS_Y = (C - 1) / 2.0;
static final int R = Config.PUZZLE_ROWS;
@@ -154,9 +155,7 @@ public record SwedishGenerator(int[] buff) {
public byte byteAt(int r, int c) { return g[offset(r, c)]; }
public byte byteAt(int pos) { return g[pos]; }
void setCharAt(int r, int c, char ch) { g[offset(r, c)] = (byte) ch; }
void setByteAt(int r, int c, byte ch) { g[offset(r, c)] = ch; }
void setByteAt(int idx, byte ch) { g[idx] = ch; }
void clear(int r, int c) { g[offset(r, c)] = DASH; }
void clear(int idx) { g[idx] = DASH; }
public 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; }
@@ -366,32 +365,30 @@ public record SwedishGenerator(int[] buff) {
}
void forEachSlot(Grid grid, SlotVisitor visitor) {
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.r, cc = c + nbrs16.c;
if (rr < 0 || rr >= R || cc < 0 || cc >= C || grid.isDigitAt(rr, cc)) continue;
long packedPos = 0;
var n = 0;
while (rr >= 0 && rr < R && cc >= 0 && cc < C && grid.isLettercell(rr, cc) && n < MAX_WORD_LENGTH) {
packedPos |= (long) Grid.offset(rr, cc) << (n * 7);
n++;
rr += nbrs16.dr;
cc += nbrs16.dc;
}
if (n > 0) {
visitor.visit((r << 8) | (c << 4) | d, packedPos, n);
}
for (var rci : IT) {
if (!grid.isDigitAt(rci.i)) continue;
var d = grid.digitAt(rci.i);
var nbrs16 = OFFSETS[d];
int rr = rci.r + nbrs16.r, cc = rci.c + nbrs16.c;
if (rr < 0 || rr >= R || cc < 0 || cc >= C || grid.isDigitAt(rr, cc)) continue;
long packedPos = 0;
var n = 0;
while (rr >= 0 && rr < R && cc >= 0 && cc < C && grid.isLettercell(rr, cc) && n < MAX_WORD_LENGTH) {
packedPos |= (long) Grid.offset(rr, cc) << (n * 7);
n++;
rr += nbrs16.dr;
cc += nbrs16.dc;
}
if (n > 0) {
visitor.visit((rci.r << 8) | (rci.c << 4) | d, packedPos, n);
}
}
}
ArrayList<Slot> extractSlots(Grid grid) {
var slots = new ArrayList<Slot>(64);
var slots = new ArrayList<Slot>(32);
forEachSlot(grid, (key, packedPos, len) -> slots.add(Slot.from(key, packedPos, len)));
return slots;
}
@@ -507,7 +504,7 @@ public record SwedishGenerator(int[] buff) {
}
if (walls >= 3) penalty += 400;
}
return penalty;
}
@@ -537,15 +534,16 @@ public record SwedishGenerator(int[] buff) {
var steps = 4;
for (var k = 0; k < steps; k++) {
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);
var ri = Grid.offset(
clamp(cx + (rng.randint(-2, 2) + rng.randint(-2, 2)), 0, R - 1),
clamp(cy + (rng.randint(-2, 2) + rng.randint(-2, 2)), 0, C - 1));
if (g.isDigitAt(rr, cc)) {
g.clear(rr, cc);
if (g.isDigitAt(ri)) {
g.clear(ri);
} else {
var d = OFFSETS[rng.randint(1, 4)];
g.setByteAt(rr, cc, d.dbyte);
if (!hasRoomForClue(g, Grid.offset(rr, cc), d)) g.clear(rr, cc);
g.setByteAt(ri, d.dbyte);
if (!hasRoomForClue(g, ri, d)) g.clear(ri);
}
}
return g;
@@ -556,20 +554,13 @@ public record SwedishGenerator(int[] buff) {
var nx = Math.cos(theta);
var ny = Math.sin(theta);
for (var r = 0; r < R; r++)
for (var c = 0; c < C; c++) {
out.setByteAt(r, c, ((r - CROSS_X) * nx + (c - CROSS_Y) * ny >= 0) ? a.byteAt(r, c) : b.byteAt(r, c));
}
for (var r = 0; r < R; r++)
for (var c = 0; c < C; c++) {
if (out.isDigitAt(r, c) && !hasRoomForClue(out, Grid.offset(r, c), OFFSETS[out.digitAt(r, c)])) out.clear(r, c);
}
for (var rci : IT) out.setByteAt(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.clear(rci.i);
return out;
}
Grid hillclimb(Rng rng, Grid start, int[] lenCounts, int limit) {
var best = start.deepCopyGrid();
var best = start;//.deepCopyGrid();
var bestF = maskFitness(best, lenCounts);
var fails = 0;
@@ -643,53 +634,6 @@ public record SwedishGenerator(int[] buff) {
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>();
for (var i = 0; i < popSize; i++) {
var g = randomMask(rng);
pop.add(hillclimb(rng, g, lenCounts, 180));
}
for (var gen = 0; gen < gens; gen++) {
if (Thread.currentThread().isInterrupted()) break;
var children = new ArrayList<Grid>();
var pairs = Math.max(popSize, (int) Math.floor(popSize * 1.5));
for (var k = 0; k < pairs; k++) {
var p1 = pop.get(rng.randint(0, pop.size() - 1));
var p2 = pop.get(rng.randint(0, pop.size() - 1));
var child = crossover(rng, p1, p2);
children.add(hillclimb(rng, child, lenCounts, 70));
}
pop.addAll(children);
pop.sort(Comparator.comparingLong(g -> maskFitness(g, lenCounts)));
var next = new ArrayList<Grid>();
for (var cand : pop) {
if (next.size() >= popSize) break;
var ok = true;
for (var kept : next) {
if (cand.similarity(kept) > 0.92) {
ok = false;
break;
}
}
if (ok) next.add(cand);
}
pop = next;
if (verbose && gen % 10 == 0) {
var bestF = maskFitness(pop.get(0), lenCounts);
System.out.println(" gen " + gen + "/" + gens + " bestFitness=" + bestF);
}
}
pop.sort(Comparator.comparingLong(g -> maskFitness(g, lenCounts)));
return pop.get(0);
}
// ---------------- Fill (CSP) ----------------
public static final class FillStats {
@@ -725,17 +669,18 @@ public record SwedishGenerator(int[] buff) {
}
}
static int slotScore(int[] cellCount, Slot s, Grid grid) {
static int slotScore(int[] cellCount, Slot s) {
var cross = 0;
for (var i = 0; i < s.len(); i++) cross += (cellCount[s.pos(i)] - 1);
return cross * 10 + s.len();
}
static int placeWord(Grid grid, Slot s, Lemma w, int[] undoBuffer, int offset) {
int mask = 0;
int mask = 0, idx;
byte cur, ch;
for (var i = 0; i < s.len(); i++) {
int idx = s.pos(i);
var cur = grid.byteAt(idx);
var ch = w.byteAt(i);
idx = s.pos(i);
cur = grid.byteAt(idx);
ch = w.byteAt(i);
if (cur == DASH) {
mask |= (1 << i);
grid.setByteAt(idx, ch);
@@ -769,9 +714,8 @@ public record SwedishGenerator(int[] buff) {
var t0 = System.currentTimeMillis();
final var lastLog = new AtomicLong(t0);
var stats = new FillStats();
final var TOTAL = slots.size();
final var BAR_LEN = 22;
var stats = new FillStats();
final var TOTAL = slots.size();
Runnable renderProgress = () -> {
if (!verbose || multiThreaded) return;
@@ -816,7 +760,7 @@ public record SwedishGenerator(int[] buff) {
if (best == null
|| info.count < bestInfo.count
|| (info.count == bestInfo.count && slotScore(cellCount, s, grid) > slotScore(cellCount, best, grid))) {
|| (info.count == bestInfo.count && slotScore(cellCount, s) > slotScore(cellCount, best))) {
best = s;
bestInfo = info;
if (info.count <= 1) break;