Gather data

This commit is contained in:
mike
2026-01-09 02:32:27 +01:00
parent f328c171f6
commit 5abbee5396
2 changed files with 102 additions and 52 deletions

View File

@@ -30,7 +30,12 @@ public record SwedishGenerator(int[] buff) {
record nbrs_8(int r, int c) { } record nbrs_8(int r, int c) { }
record nbrs_16(int r, int c, int dr, int dc) { } 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));
}
}
static final int C = Config.PUZZLE_COLS; static final int C = Config.PUZZLE_COLS;
static final double CROSS_Y = (C - 1) / 2.0; static final double CROSS_Y = (C - 1) / 2.0;
@@ -54,12 +59,12 @@ public record SwedishGenerator(int[] buff) {
// Directions for '1'..'6' // Directions for '1'..'6'
static final nbrs_16[] OFFSETS = new nbrs_16[]{ static final nbrs_16[] OFFSETS = new nbrs_16[]{
null, null,
new nbrs_16(-1, 0, -1, 0), // 1: up new nbrs_16(-1, 0, -1, 0, 1), // 1: up
new nbrs_16(0, 1, 0, 1), // 2: right new nbrs_16(0, 1, 0, 1, 2), // 2: right
new nbrs_16(1, 0, 1, 0),// 3: down new nbrs_16(1, 0, 1, 0, 3),// 3: down
new nbrs_16(0, -1, 0, -1),// 4: left new nbrs_16(0, -1, 0, -1, 4),// 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, 5),// 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, 6)// 6: vertical down, clue is on the left of the first letter
}; };
final static nbrs_8[] nbrs8 = new nbrs_8[]{ final static nbrs_8[] nbrs8 = new nbrs_8[]{
new nbrs_8(-1, -1), new nbrs_8(-1, -1),
@@ -110,6 +115,11 @@ public record SwedishGenerator(int[] buff) {
x = y; x = y;
return y; return y;
} }
byte randbyte(int min, int max) {
var u = (nextU32() & 0xFFFFFFFFL);
var range = (long) max - (long) min + 1L;
return (byte) (min + (u % range));
}
int randint(int min, int max) { int randint(int min, int max) {
var u = (nextU32() & 0xFFFFFFFFL); var u = (nextU32() & 0xFFFFFFFFL);
var range = (long) max - (long) min + 1L; var range = (long) max - (long) min + 1L;
@@ -118,18 +128,22 @@ public record SwedishGenerator(int[] buff) {
double nextFloat() { return (nextU32() & 0xFFFFFFFFL) / 4294967295.0; } double nextFloat() { return (nextU32() & 0xFFFFFFFFL) / 4294967295.0; }
} }
static final byte _48 = 48;
record Grid(byte[] g) { record Grid(byte[] g) {
int digitAt(int r, int c) { return g[offset(r, c)] - 48; }
public static int r(int offset) { return offset & 7; } public static int r(int offset) { return offset & 7; }
public static int c(int offset) { return offset >>> 3; } public static int c(int offset) { return offset >>> 3; }
static int offset(int r, int c) { return r | (c << 3); } static int offset(int r, int c) { return r | (c << 3); }
Grid deepCopyGrid() { return new Grid(g.clone()); } Grid deepCopyGrid() { return new Grid(g.clone()); }
char getCharAt(int r, int c) { return (char) (g[offset(r, c)]); } char getCharAt(int r, int c) { return (char) (g[offset(r, c)]); }
char getCharAt(int pos) { return (char) (g[pos]); } char getCharAt(int pos) { return (char) (g[pos]); }
int digitAt(int r, int c) { return g[offset(r, c)] - 48; }
byte byteAt(int r, int c) { return g[offset(r, c)]; } byte byteAt(int r, int c) { return g[offset(r, c)]; }
byte byteAt(int pos) { return g[pos]; }
byte byteAtStatic(int r, int c) { return g[offset(r, c)]; }
void setCharAt(int r, int c, char ch) { g[offset(r, c)] = (byte) ch; } 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 r, int c, byte ch) { g[offset(r, c)] = ch; }
void setDigitAt(int r, int c, int ch) { g[offset(r, c)] = (byte) (_48 + ch); }
void setCharAt(int idx, char ch) { g[idx] = (byte) ch; } void setCharAt(int idx, char ch) { g[idx] = (byte) ch; }
void setByteAt(int idx, byte ch) { g[idx] = ch; } void setByteAt(int idx, byte ch) { g[idx] = ch; }
void clear(int r, int c) { g[offset(r, c)] = 0; } void clear(int r, int c) { g[offset(r, c)] = 0; }
@@ -391,7 +405,17 @@ public record SwedishGenerator(int[] buff) {
forEachSlot(grid, (key, packedPos, len) -> slots.add(Slot.from(key, packedPos, len))); forEachSlot(grid, (key, packedPos, len) -> slots.add(Slot.from(key, packedPos, len)));
return slots; return slots;
} }
boolean hasRoomForClue(Grid grid, int idx, nbrs_16 nbrs16) {
int rr = Grid.r(idx) + nbrs16.r, cc = Grid.c(idx) + nbrs16.c;
var run = 0;
while (rr >= 0 && rr < R && cc >= 0 && cc < C && (grid.isLettercell(rr, cc)) && run < MAX_WORD_LENGTH) {
run++;
rr += nbrs16.dr;
cc += nbrs16.dc;
if (run >= MIN_LEN) return true;
}
return false;
}
boolean hasRoomForClue(Grid grid, int r, int c, char d) { boolean hasRoomForClue(Grid grid, int r, int c, char d) {
var nbrs16 = OFFSETS[d - '0']; var nbrs16 = OFFSETS[d - '0'];
int rr = r + nbrs16.r, cc = c + nbrs16.c; int rr = r + nbrs16.r, cc = c + nbrs16.c;
@@ -516,7 +540,25 @@ public record SwedishGenerator(int[] buff) {
} }
// ---------------- Mask generation ---------------- // ---------------- Mask generation ----------------
Grid randomMask2(Rng rng) {
var g = makeEmptyGrid();
int placed = 0, guard = 0;
while (placed < TARGET_CLUES && guard++ < 4000) {
var idx = rng.randint(0, SIZE - 1);
if (g.isDigitAt(idx)) continue;
var d = OFFSETS[rng.randbyte(1, 4)];
g.setByteAt(idx, d.dbyte);
if (!hasRoomForClue(g, idx, d)) {
g.clear(idx);
continue;
}
placed++;
}
return g;
}
Grid randomMask(Rng rng) { Grid randomMask(Rng rng) {
var g = makeEmptyGrid(); var g = makeEmptyGrid();
var targetClues = (int) Math.round(SIZE * 0.25); var targetClues = (int) Math.round(SIZE * 0.25);
@@ -537,7 +579,26 @@ public record SwedishGenerator(int[] buff) {
} }
return g; return g;
} }
Grid mutate2(Rng rng, Grid grid) {
var g = grid.deepCopyGrid();
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, R - 1);
var cc = clamp(cy + (rng.randint(-2, 2) + rng.randint(-2, 2)), 0, C - 1);
if (g.isDigitAt(rr, cc)) {
g.clear(rr, cc);
} 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);
}
}
return g;
}
Grid mutate(Rng rng, Grid grid) { Grid mutate(Rng rng, Grid grid) {
var g = grid.deepCopyGrid(); var g = grid.deepCopyGrid();
var cx = rng.randint(0, R - 1); var cx = rng.randint(0, R - 1);
@@ -558,7 +619,6 @@ public record SwedishGenerator(int[] buff) {
} }
return g; return g;
} }
Grid crossover(Rng rng, Grid a, Grid b) { Grid crossover(Rng rng, Grid a, Grid b) {
var out = makeEmptyGrid(); var out = makeEmptyGrid();
var theta = rng.nextFloat() * Math.PI; var theta = rng.nextFloat() * Math.PI;

View File

@@ -145,57 +145,47 @@ public class MainTest {
Assertions.assertTrue(grid.isDigitAt(1, 1)); Assertions.assertTrue(grid.isDigitAt(1, 1));
} }
@Test @Test
@Disabled
public void testAttempt() { public void testAttempt() {
// Arrange // Arrange
var opts = new Main.Opts(); var opts = new Main.Opts();
//seed=1811328180 opts.seed = 12347;
opts.seed = -1645461655;// (int) (System.nanoTime() ^ System.currentTimeMillis()); opts.pop = 4; // Tiny population
opts.pop = 18; // Small for micro-scale opts.gens = 20; // Very few generations
opts.gens = 200;
opts.minSimplicity = 0; opts.minSimplicity = 0;
opts.fillTimeout = 20_000; opts.fillTimeout = 10_000;
opts.threads = 1; opts.threads = 1;
opts.tries = 1; opts.tries = 1;
opts.verbose = true; opts.verbose = false;
// We need a small dictionary for testing var dict = SwedishGenerator.loadWords(opts.wordsPath);
// Instead of loading from file, we might want a way to create a mock Dict
// But SwedishGenerator.loadWords(path) is what we have.
// Let's try to load a real one or a small subset if possible.
var dict = new Dict(new Lemma[]{
new Lemma(0, "NU", 1, "NU"),
new Lemma(1, "ET", 2, "ET"),
new Lemma(2, "NUT", 3, "NUT"),
new Lemma(3, "ETE", 4, "ETE"),
new Lemma(4, "IK", 5, "IK"),
new Lemma(5, "IN", 6, "IN"),
new Lemma(6, "AU", 7, "AU"),
new Lemma(7, "JE", 8, "JE"),
new Lemma(8, "AI", 9, "AI"),
new Lemma(9, "NA", 10, "NA"),
new Lemma(10, "AF", 11, "AF"),
new Lemma(11, "AL", 14, "AL"),
new Lemma(12, "EA", 15, "EA"),
new Lemma(13, "AV", 18, "AV"),
new Lemma(14, "IL", 19, "IL"),
new Lemma(15, "EN", 22, "EN")
});
// Act // Act
for (int i = 0; i < 200; i++) { PuzzleResult res = null;
int seed = opts.seed + i; int foundSeed = -1;
var rng = new Rng(seed); for (int i = 0; i < 50; i++) {
PuzzleResult res = Main.attempt(rng, dict, opts); int seed = opts.seed + i;
// Assert var rng = new Rng(seed);
res = Main.attempt(rng, dict, opts);
if (res != null && res.filled().ok()) { if (res != null && res.filled().ok()) {
System.out.println("Test Passed: Puzzle generated"); foundSeed = seed;
System.out.println("Seed: " + seed); System.out.println("[DEBUG_LOG] Seed found: " + seed);
System.out.print(indentLines(res.swe().renderHuman(res.filled().grid()), " ")); System.out.println("[DEBUG_LOG] Simplicity: " + res.filled().simplicity());
return; System.out.println("[DEBUG_LOG] ClueMap Size: " + res.filled().clueMap().size());
System.out.println("[DEBUG_LOG] Grid:");
System.out.println(res.swe().renderHuman(res.filled().grid()));
break;
} }
} }
System.out.println("Test Note: Puzzle not generated in 1 attempt (this is possible depending on RNG)");
// Assert
Assertions.assertNotNull(res, "Puzzle generation failed (null result)");
Assertions.assertTrue(res.filled().ok(), "Puzzle generation failed (not ok)");
// Regression baseline for seed search starting at 12347, pop 4, gens 20
Assertions.assertEquals(12347, foundSeed, "Found seed changed");
Assertions.assertEquals(20, res.filled().clueMap().size(), "Number of assigned words changed");
Assertions.assertEquals(763.8, res.filled().simplicity(), 1e-9, "Simplicity value changed");
Assertions.assertArrayEquals(new char[]{ 'M', 'A', 'N', 'T', 'A' }, res.filled().clueMap().get(1377).word());
} }
@Test @Test
public void testIsLetterA() { public void testIsLetterA() {