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 // 1) extract "placed" list from all clue digits in the filled grid
var placed = new ArrayList<Placed>(); var placed = new ArrayList<Placed>();
var allSlots = extractSlots(g); var allSlots = puz.swe().extractSlots(g);
var clueMap = puz.filled().clueMap(); var clueMap = puz.filled().clueMap();
for (var s : allSlots) { for (var s : allSlots) {

View File

@@ -14,7 +14,6 @@ import java.util.concurrent.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static puzzle.SwedishGenerator.*; import static puzzle.SwedishGenerator.*;
import static puzzle.SwedishGenerator.fillMask;
import static puzzle.SwedishGenerator.loadWords; import static puzzle.SwedishGenerator.loadWords;
public class Main { public class Main {
@@ -41,6 +40,8 @@ public class Main {
public boolean reindex = false; public boolean reindex = false;
public int fillTimeout = 20_000; public int fillTimeout = 20_000;
public boolean verbose = false; public boolean verbose = false;
public int W = 9;
public int H = 8;
} }
public void main(String[] args) { public void main(String[] args) {
@@ -73,13 +74,13 @@ public class Main {
info(String.format(Locale.ROOT, "simplicity : %.2f", res.filled().simplicity())); info(String.format(Locale.ROOT, "simplicity : %.2f", res.filled().simplicity()));
section("Mask"); section("Mask");
System.out.print(indentLines(gridToString(res.mask()), " ")); System.out.print(indentLines(res.swe().gridToString(res.mask()), " "));
section("Grid (raw)"); section("Grid (raw)");
System.out.print(indentLines(gridToString(res.filled().grid()), " ")); System.out.print(indentLines(res.swe().gridToString(res.filled().grid()), " "));
section("Grid (human)"); 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)); 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)) + ""; 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 ""; if (s == null || s.isEmpty()) return "";
var lines = s.split("\\R", -1); var lines = s.split("\\R", -1);
var sb = new StringBuilder(); var sb = new StringBuilder();
@@ -247,7 +248,6 @@ public class Main {
var deadline = System.currentTimeMillis() + 40_000; var deadline = System.currentTimeMillis() + 40_000;
if (opts.threads > 1) { if (opts.threads > 1) {
info("mode : multi-threaded (" + opts.threads + ")"); info("mode : multi-threaded (" + opts.threads + ")");
var executor = Executors.newFixedThreadPool(opts.threads); var executor = Executors.newFixedThreadPool(opts.threads);
@@ -311,11 +311,12 @@ public class Main {
} }
static PuzzleResult attempt(Rng rng, Dict dict, Opts opts) { static PuzzleResult attempt(Rng rng, Dict dict, Opts opts) {
var mask = SwedishGenerator.generateMask(rng, dict.lenCounts(), opts.pop, opts.gens, opts.verbose); var swe = new SwedishGenerator(opts.W, opts.H);
var filled = fillMask(rng, mask, dict.index(), 200, opts.fillTimeout, opts.verbose); 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)) { 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()) { 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] * java SwedishGenerator [--seed N] [--pop N] [--gens N] [--tries N] [--words word-list.txt]
*/ */
@SuppressWarnings("ALL") @SuppressWarnings("ALL")
public class SwedishGenerator { public record SwedishGenerator(int W, int H, int SIZE, int MAX_LEN) {
static final int W = 9, H = 8, public SwedishGenerator(int W,int H) { this(W, H, W * H, Math.min(W, H)); }
SIZE = W * H, public SwedishGenerator() { this(9, 8); }
CLUE_SIZE = 4,
static final int CLUE_SIZE = 4,
SIMPLICITY_DEFAULT_SCORE = 2; SIMPLICITY_DEFAULT_SCORE = 2;
static final int MIN_LEN = 2, MAX_LEN = 8; static final int MIN_LEN = 2;
// Directions for '1'..'6' // Directions for '1'..'6'
static final int[][] OFFSETS = new int[7][2]; static final int[][] OFFSETS = new int[7][2];
static final int[][] STEPS = 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)); } static int clamp(int x, int a, int b) { return Math.max(a, Math.min(b, x)); }
// ---------------- Grid helpers ---------------- // ---------------- Grid helpers ----------------
static char[][] makeEmptyGrid() { char[][] makeEmptyGrid() {
var g = new char[H][W]; var g = new char[H][W];
for (var r = 0; r < H; r++) Arrays.fill(g[r], '#'); for (var r = 0; r < H; r++) Arrays.fill(g[r], '#');
return g; return g;
} }
static char[][] deepCopyGrid(char[][] g) { char[][] deepCopyGrid(char[][] g) {
var out = new char[H][W]; var out = new char[H][W];
for (var r = 0; r < H; r++) out[r] = Arrays.copyOf(g[r], W); for (var r = 0; r < H; r++) out[r] = Arrays.copyOf(g[r], W);
return out; return out;
} }
static String gridToString(char[][] g) { String gridToString(char[][] g) {
var sb = new StringBuilder(); var sb = new StringBuilder();
for (var r = 0; r < H; r++) { for (var r = 0; r < H; r++) {
if (r > 0) sb.append('\n'); if (r > 0) sb.append('\n');
@@ -102,7 +103,7 @@ public class SwedishGenerator {
return sb.toString(); return sb.toString();
} }
static String renderHuman(char[][] g) { public String renderHuman(char[][] g) {
var sb = new StringBuilder(); var sb = new StringBuilder();
for (var r = 0; r < H; r++) { for (var r = 0; r < H; r++) {
if (r > 0) sb.append('\n'); 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); } 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>(); var slots = new ArrayList<Slot>();
for (var r = 0; r < H; r++) { for (var r = 0; r < H; r++) {
for (var c = 0; c < W; c++) { for (var c = 0; c < W; c++) {
@@ -332,7 +333,7 @@ public class SwedishGenerator {
} }
return slots; 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'; var di = d - '0';
int or = OFFSETS[di][0], oc = OFFSETS[di][1]; int or = OFFSETS[di][0], oc = OFFSETS[di][1];
int dr = STEPS[di][0], dc = STEPS[di][1]; int dr = STEPS[di][0], dc = STEPS[di][1];
@@ -353,7 +354,7 @@ public class SwedishGenerator {
{ 1, -1 }, { 1, 0 }, { 1, 1 } { 1, -1 }, { 1, 0 }, { 1, 1 }
}; };
static final int[][] nbrs4 = new int[][]{ { -1, 0 }, { 1, 0 }, { 0, -1 }, { 0, 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; long penalty = 0;
var clueCount = 0; var clueCount = 0;
@@ -447,7 +448,7 @@ public class SwedishGenerator {
// ---------------- Mask generation ---------------- // ---------------- Mask generation ----------------
static char[][] randomMask(Rng rng) { char[][] 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);
int placed = 0, guard = 0; int placed = 0, guard = 0;
@@ -468,7 +469,7 @@ public class SwedishGenerator {
return g; return g;
} }
static char[][] mutate(Rng rng, char[][] grid) { char[][] mutate(Rng rng, char[][] grid) {
var g = deepCopyGrid(grid); var g = deepCopyGrid(grid);
var cx = rng.randint(0, H - 1); var cx = rng.randint(0, H - 1);
var cy = rng.randint(0, W - 1); var cy = rng.randint(0, W - 1);
@@ -490,7 +491,7 @@ public class SwedishGenerator {
return g; return g;
} }
static char[][] crossover(Rng rng, char[][] a, char[][] b) { char[][] crossover(Rng rng, char[][] a, char[][] b) {
var out = makeEmptyGrid(); var out = makeEmptyGrid();
var cx = (H - 1) / 2.0; var cx = (H - 1) / 2.0;
var cy = (W - 1) / 2.0; var cy = (W - 1) / 2.0;
@@ -513,7 +514,7 @@ public class SwedishGenerator {
return out; 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 best = deepCopyGrid(start);
var bestF = maskFitness(best, lenCounts); var bestF = maskFitness(best, lenCounts);
var fails = 0; var fails = 0;
@@ -532,13 +533,13 @@ public class SwedishGenerator {
return best; return best;
} }
static double similarity(char[][] a, char[][] b) { double similarity(char[][] a, char[][] b) {
var same = 0; 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++; 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); 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); if (verbose) System.out.println("generateMask init pop: " + popSize);
var pop = new ArrayList<char[][]>(); var pop = new ArrayList<char[][]>();
@@ -660,7 +661,7 @@ public class SwedishGenerator {
record Pick(Slot slot, record Pick(Slot slot,
CandidateInfo info, CandidateInfo info,
boolean done) { } 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) { int logEveryMs, int timeLimitMs, boolean verbose) {
var grid = deepCopyGrid(mask); var grid = deepCopyGrid(mask);
@@ -854,6 +855,6 @@ public class SwedishGenerator {
} }
// ---------------- Top-level generatePuzzle ---------------- // ---------------- 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; package puzzle;
//import org.junit.jupiter.api.Test; import puzzle.SwedishGenerator.Dict;
//import static org.junit.jupiter.api.Assertions.*; import puzzle.SwedishGenerator.Lemma;
import puzzle.SwedishGenerator.PuzzleResult;
import puzzle.SwedishGenerator.Rng;
import static puzzle.Main.indentLines;
public class MainTest { public class MainTest {
static void main() { public static void main(String[] args) {
new MainTest().testGeneratePuzzle(); new MainTest().testAttempt();
} }
// @Test
public void testGeneratePuzzle() { public void testAttempt() {
// Arrange // Arrange
var opts = new Main.Opts(); var opts = new Main.Opts();
opts.seed = 1234; //seed=1811328180
opts.pop = 18; opts.seed = -1645461655;// (int) (System.nanoTime() ^ System.currentTimeMillis());
opts.gens = 300; opts.pop = 18; // Small for micro-scale
opts.wordsPath = "src/test/resources/puzzle/pool.txt"; opts.gens = 200;
opts.minSimplicity = 0; opts.minSimplicity = 0;
opts.fillTimeout = 20_000;
opts.threads = 1; opts.threads = 1;
opts.tries = 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 // 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 // Assert
/* assertNotNull(result); if (res != null && res.filled().ok()) {
assertNotNull(result.mask()); System.out.println("Test Passed: Puzzle generated");
assertNotNull(result.filled()); System.out.println("Seed: " + seed);
assertTrue(result.filled().ok);*/ 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)");
} }
} }