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_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 double CROSS_Y = (C - 1) / 2.0;
@@ -54,12 +59,12 @@ public record SwedishGenerator(int[] buff) {
// Directions for '1'..'6'
static final nbrs_16[] OFFSETS = new nbrs_16[]{
null,
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
new nbrs_16(-1, 0, -1, 0, 1), // 1: up
new nbrs_16(0, 1, 0, 1, 2), // 2: right
new nbrs_16(1, 0, 1, 0, 3),// 3: down
new nbrs_16(0, -1, 0, -1, 4),// 4: left
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)// 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),
@@ -110,6 +115,11 @@ public record SwedishGenerator(int[] buff) {
x = 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) {
var u = (nextU32() & 0xFFFFFFFFL);
var range = (long) max - (long) min + 1L;
@@ -118,18 +128,22 @@ public record SwedishGenerator(int[] buff) {
double nextFloat() { return (nextU32() & 0xFFFFFFFFL) / 4294967295.0; }
}
record Grid(byte[] g) {
static final byte _48 = 48;
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 c(int offset) { return offset >>> 3; }
static int offset(int r, int c) { return r | (c << 3); }
Grid deepCopyGrid() { return new Grid(g.clone()); }
char getCharAt(int r, int c) { return (char) (g[offset(r, c)]); }
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 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 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 setByteAt(int idx, byte ch) { g[idx] = ch; }
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)));
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) {
var nbrs16 = OFFSETS[d - '0'];
int rr = r + nbrs16.r, cc = c + nbrs16.c;
@@ -516,7 +540,25 @@ public record SwedishGenerator(int[] buff) {
}
// ---------------- 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) {
var g = makeEmptyGrid();
var targetClues = (int) Math.round(SIZE * 0.25);
@@ -537,7 +579,26 @@ public record SwedishGenerator(int[] buff) {
}
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) {
var g = grid.deepCopyGrid();
var cx = rng.randint(0, R - 1);
@@ -558,7 +619,6 @@ public record SwedishGenerator(int[] buff) {
}
return g;
}
Grid crossover(Rng rng, Grid a, Grid b) {
var out = makeEmptyGrid();
var theta = rng.nextFloat() * Math.PI;

View File

@@ -145,57 +145,47 @@ public class MainTest {
Assertions.assertTrue(grid.isDigitAt(1, 1));
}
@Test
@Disabled
public void testAttempt() {
// Arrange
var opts = new Main.Opts();
//seed=1811328180
opts.seed = -1645461655;// (int) (System.nanoTime() ^ System.currentTimeMillis());
opts.pop = 18; // Small for micro-scale
opts.gens = 200;
opts.seed = 12347;
opts.pop = 4; // Tiny population
opts.gens = 20; // Very few generations
opts.minSimplicity = 0;
opts.fillTimeout = 20_000;
opts.fillTimeout = 10_000;
opts.threads = 1;
opts.tries = 1;
opts.verbose = true;
opts.verbose = false;
// We need a small dictionary for testing
// 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")
});
var dict = SwedishGenerator.loadWords(opts.wordsPath);
// Act
for (int i = 0; i < 200; i++) {
int seed = opts.seed + i;
var rng = new Rng(seed);
PuzzleResult res = Main.attempt(rng, dict, opts);
// Assert
PuzzleResult res = null;
int foundSeed = -1;
for (int i = 0; i < 50; i++) {
int seed = opts.seed + i;
var rng = new Rng(seed);
res = Main.attempt(rng, dict, opts);
if (res != null && res.filled().ok()) {
System.out.println("Test Passed: Puzzle generated");
System.out.println("Seed: " + seed);
System.out.print(indentLines(res.swe().renderHuman(res.filled().grid()), " "));
return;
foundSeed = seed;
System.out.println("[DEBUG_LOG] Seed found: " + seed);
System.out.println("[DEBUG_LOG] Simplicity: " + res.filled().simplicity());
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
public void testIsLetterA() {