Gather data

This commit is contained in:
mike
2026-01-06 02:57:17 +01:00
parent f031e97a58
commit effb1145d4
4 changed files with 89 additions and 51 deletions

View File

@@ -32,7 +32,7 @@ public final class ExportFormat {
// 1) extract "placed" list from all clue digits in the filled grid
var placed = new ArrayList<Placed>();
var allSlots = extractSlots(g);
var allSlots = puz.swe().extractSlots(g);
var clueMap = puz.filled().clueMap();
for (var s : allSlots) {

View File

@@ -14,7 +14,6 @@ import java.util.concurrent.*;
import java.util.stream.Collectors;
import static puzzle.SwedishGenerator.*;
import static puzzle.SwedishGenerator.fillMask;
import static puzzle.SwedishGenerator.loadWords;
public class Main {
@@ -41,6 +40,8 @@ public class Main {
public boolean reindex = false;
public int fillTimeout = 20_000;
public boolean verbose = false;
public int W = 9;
public int H = 8;
}
public void main(String[] args) {
@@ -73,13 +74,13 @@ public class Main {
info(String.format(Locale.ROOT, "simplicity : %.2f", res.filled().simplicity()));
section("Mask");
System.out.print(indentLines(gridToString(res.mask()), " "));
System.out.print(indentLines(res.swe().gridToString(res.mask()), " "));
section("Grid (raw)");
System.out.print(indentLines(gridToString(res.filled().grid()), " "));
System.out.print(indentLines(res.swe().gridToString(res.filled().grid()), " "));
section("Grid (human)");
System.out.print(indentLines(renderHuman(res.filled().grid()), " "));
System.out.print(indentLines(res.swe().renderHuman(res.filled().grid()), " "));
var exported = ExportFormat.exportFormatFromFilled(res, 1, new ExportFormat.Rewards(50, 2, 1));
@@ -167,7 +168,7 @@ public class Main {
return s.substring(0, Math.max(0, max - 1)) + "";
}
private static String indentLines(String s, String indent) {
static String indentLines(String s, String indent) {
if (s == null || s.isEmpty()) return "";
var lines = s.split("\\R", -1);
var sb = new StringBuilder();
@@ -247,7 +248,6 @@ public class Main {
var deadline = System.currentTimeMillis() + 40_000;
if (opts.threads > 1) {
info("mode : multi-threaded (" + opts.threads + ")");
var executor = Executors.newFixedThreadPool(opts.threads);
@@ -258,7 +258,7 @@ public class Main {
// Keep at least some tasks in flight
for (int i = 0; i < opts.threads; i++) {
final int attempt = ++submitted;
completionService.submit(() -> attempt(new Rng(opts.seed + attempt), dict, opts ));
completionService.submit(() -> attempt(new Rng(opts.seed + attempt), dict, opts));
}
while (System.currentTimeMillis() < deadline) {
@@ -274,7 +274,7 @@ public class Main {
// Submit another task if we still have time
if (System.currentTimeMillis() < deadline) {
final int attempt = ++submitted;
completionService.submit(() -> attempt(new Rng(opts.seed + attempt), dict, opts ));
completionService.submit(() -> attempt(new Rng(opts.seed + attempt), dict, opts));
}
}
warn("status : UNSOLVED (timeout)");
@@ -310,12 +310,13 @@ public class Main {
}
}
static PuzzleResult attempt(Rng rng, Dict dict, Opts opts ) {
var mask = SwedishGenerator.generateMask(rng, dict.lenCounts(), opts.pop, opts.gens, opts.verbose);
var filled = fillMask(rng, mask, dict.index(), 200, opts.fillTimeout, opts.verbose);
static PuzzleResult attempt(Rng rng, Dict dict, Opts opts) {
var swe = new SwedishGenerator(opts.W, opts.H);
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);
if (filled.ok() && (opts.minSimplicity <= 0 || filled.simplicity() >= opts.minSimplicity)) {
return new PuzzleResult(dict, mask, filled);
return new PuzzleResult(swe, dict, mask, filled);
}
if (opts.verbose && filled.ok()) {

View File

@@ -15,13 +15,14 @@ import java.util.function.Predicate;
* java SwedishGenerator [--seed N] [--pop N] [--gens N] [--tries N] [--words word-list.txt]
*/
@SuppressWarnings("ALL")
public class SwedishGenerator {
public record SwedishGenerator(int W, int H, int SIZE, int MAX_LEN) {
static final int W = 9, H = 8,
SIZE = W * H,
CLUE_SIZE = 4,
public SwedishGenerator(int W,int H) { this(W, H, W * H, Math.min(W, H)); }
public SwedishGenerator() { this(9, 8); }
static final int CLUE_SIZE = 4,
SIMPLICITY_DEFAULT_SCORE = 2;
static final int MIN_LEN = 2, MAX_LEN = 8;
static final int MIN_LEN = 2;
// Directions for '1'..'6'
static final int[][] OFFSETS = new int[7][2];
static final int[][] STEPS = new int[7][2];
@@ -81,19 +82,19 @@ public class SwedishGenerator {
static int clamp(int x, int a, int b) { return Math.max(a, Math.min(b, x)); }
// ---------------- Grid helpers ----------------
static char[][] makeEmptyGrid() {
char[][] makeEmptyGrid() {
var g = new char[H][W];
for (var r = 0; r < H; r++) Arrays.fill(g[r], '#');
return g;
}
static char[][] deepCopyGrid(char[][] g) {
char[][] deepCopyGrid(char[][] g) {
var out = new char[H][W];
for (var r = 0; r < H; r++) out[r] = Arrays.copyOf(g[r], W);
return out;
}
static String gridToString(char[][] g) {
String gridToString(char[][] g) {
var sb = new StringBuilder();
for (var r = 0; r < H; r++) {
if (r > 0) sb.append('\n');
@@ -102,7 +103,7 @@ public class SwedishGenerator {
return sb.toString();
}
static String renderHuman(char[][] g) {
public String renderHuman(char[][] g) {
var sb = new StringBuilder();
for (var r = 0; r < H; r++) {
if (r > 0) sb.append('\n');
@@ -295,7 +296,7 @@ public class SwedishGenerator {
public Slot(int clueR, int clueC, char dir, int[] rs, int[] cs) { this(clueR + "," + clueC + ":" + dir, clueR, clueC, dir, rs, cs, rs.length); }
}
static ArrayList<Slot> extractSlots(char[][] grid) {
ArrayList<Slot> extractSlots(char[][] grid) {
var slots = new ArrayList<Slot>();
for (var r = 0; r < H; r++) {
for (var c = 0; c < W; c++) {
@@ -332,7 +333,7 @@ public class SwedishGenerator {
}
return slots;
}
static boolean hasRoomForClue(char[][] grid, int r, int c, char d) {
boolean hasRoomForClue(char[][] grid, int r, int c, char d) {
var di = d - '0';
int or = OFFSETS[di][0], oc = OFFSETS[di][1];
int dr = STEPS[di][0], dc = STEPS[di][1];
@@ -353,7 +354,7 @@ public class SwedishGenerator {
{ 1, -1 }, { 1, 0 }, { 1, 1 }
};
static final int[][] nbrs4 = new int[][]{ { -1, 0 }, { 1, 0 }, { 0, -1 }, { 0, 1 } };
static long maskFitness(char[][] grid, int[] lenCounts) {
long maskFitness(char[][] grid, int[] lenCounts) {
long penalty = 0;
var clueCount = 0;
@@ -447,7 +448,7 @@ public class SwedishGenerator {
// ---------------- Mask generation ----------------
static char[][] randomMask(Rng rng) {
char[][] randomMask(Rng rng) {
var g = makeEmptyGrid();
var targetClues = (int) Math.round(SIZE * 0.25);
int placed = 0, guard = 0;
@@ -468,7 +469,7 @@ public class SwedishGenerator {
return g;
}
static char[][] mutate(Rng rng, char[][] grid) {
char[][] mutate(Rng rng, char[][] grid) {
var g = deepCopyGrid(grid);
var cx = rng.randint(0, H - 1);
var cy = rng.randint(0, W - 1);
@@ -490,7 +491,7 @@ public class SwedishGenerator {
return g;
}
static char[][] crossover(Rng rng, char[][] a, char[][] b) {
char[][] crossover(Rng rng, char[][] a, char[][] b) {
var out = makeEmptyGrid();
var cx = (H - 1) / 2.0;
var cy = (W - 1) / 2.0;
@@ -513,7 +514,7 @@ public class SwedishGenerator {
return out;
}
static char[][] hillclimb(Rng rng, char[][] start, int[] lenCounts, int limit) {
char[][] hillclimb(Rng rng, char[][] start, int[] lenCounts, int limit) {
var best = deepCopyGrid(start);
var bestF = maskFitness(best, lenCounts);
var fails = 0;
@@ -532,13 +533,13 @@ public class SwedishGenerator {
return best;
}
static double similarity(char[][] a, char[][] b) {
double similarity(char[][] a, char[][] b) {
var same = 0;
for (var r = 0; r < H; r++) for (var c = 0; c < W; c++) if (a[r][c] == b[r][c]) same++;
return same / (double) (W * H);
}
static char[][] generateMask(Rng rng, int[] lenCounts, int popSize, int gens, boolean verbose) {
public char[][] generateMask(Rng rng, int[] lenCounts, int popSize, int gens, boolean verbose) {
if (verbose) System.out.println("generateMask init pop: " + popSize);
var pop = new ArrayList<char[][]>();
@@ -660,7 +661,7 @@ public class SwedishGenerator {
record Pick(Slot slot,
CandidateInfo info,
boolean done) { }
static FillResult fillMask(Rng rng, char[][] mask, DictEntry[] dictIndex,
public FillResult fillMask(Rng rng, char[][] mask, DictEntry[] dictIndex,
int logEveryMs, int timeLimitMs, boolean verbose) {
var grid = deepCopyGrid(mask);
@@ -854,6 +855,6 @@ public class SwedishGenerator {
}
// ---------------- Top-level generatePuzzle ----------------
public record PuzzleResult(Dict dict, char[][] mask, FillResult filled) { }
public record PuzzleResult(SwedishGenerator swe, Dict dict, char[][] mask, FillResult filled) { }
}

View File

@@ -1,32 +1,68 @@
package puzzle;
//import org.junit.jupiter.api.Test;
//import static org.junit.jupiter.api.Assertions.*;
import puzzle.SwedishGenerator.Dict;
import puzzle.SwedishGenerator.Lemma;
import puzzle.SwedishGenerator.PuzzleResult;
import puzzle.SwedishGenerator.Rng;
import static puzzle.Main.indentLines;
public class MainTest {
static void main() {
new MainTest().testGeneratePuzzle();
public static void main(String[] args) {
new MainTest().testAttempt();
}
// @Test
public void testGeneratePuzzle() {
public void testAttempt() {
// Arrange
var opts = new Main.Opts();
opts.seed = 1234;
opts.pop = 18;
opts.gens = 300;
opts.wordsPath = "src/test/resources/puzzle/pool.txt";
//seed=1811328180
opts.seed = -1645461655;// (int) (System.nanoTime() ^ System.currentTimeMillis());
opts.pop = 18; // Small for micro-scale
opts.gens = 200;
opts.minSimplicity = 0;
opts.fillTimeout = 20_000;
opts.threads = 1;
opts.tries = 1;
opts.verbose = true;
opts.W = 3;
opts.H = 3;
// 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, 100, "NU"),
new Lemma(1, "ET", 2, 100, "ET"),
new Lemma(2, "NUT", 3, 1001, "NUT"),
new Lemma(3, "ETE", 4, 100, "ETE"),
new Lemma(4, "IK", 5, 100, "IK"),
new Lemma(5, "IN", 6, 100, "IN"),
new Lemma(6, "AU", 7, 100, "AU"),
new Lemma(7, "JE", 8, 100, "JE"),
new Lemma(8, "AI", 9, 100, "AI"),
new Lemma(9, "NA", 10, 100, "NA"),
new Lemma(10, "AF", 11, 100, "AF"),
new Lemma(11, "AL", 14, 1001, "AL"),
new Lemma(12, "EA", 15, 100, "EA"),
new Lemma(13, "AV", 18, 100, "AV"),
new Lemma(14, "IL", 19, 100, "IL"),
new Lemma(15, "EN", 22, 100, "EN")
});
// Act
var result = new Main().generatePuzzle(opts);
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
/* assertNotNull(result);
assertNotNull(result.mask());
assertNotNull(result.filled());
assertTrue(result.filled().ok);*/
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;
}
}
System.out.println("Test Note: Puzzle not generated in 1 attempt (this is possible depending on RNG)");
}
}