Gather data
This commit is contained in:
@@ -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) {
|
||||||
|
|||||||
@@ -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();
|
||||||
@@ -245,8 +246,7 @@ public class Main {
|
|||||||
|
|
||||||
section("Search");
|
section("Search");
|
||||||
|
|
||||||
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 + ")");
|
||||||
@@ -258,7 +258,7 @@ public class Main {
|
|||||||
// Keep at least some tasks in flight
|
// Keep at least some tasks in flight
|
||||||
for (int i = 0; i < opts.threads; i++) {
|
for (int i = 0; i < opts.threads; i++) {
|
||||||
final int attempt = ++submitted;
|
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) {
|
while (System.currentTimeMillis() < deadline) {
|
||||||
@@ -274,7 +274,7 @@ public class Main {
|
|||||||
// Submit another task if we still have time
|
// Submit another task if we still have time
|
||||||
if (System.currentTimeMillis() < deadline) {
|
if (System.currentTimeMillis() < deadline) {
|
||||||
final int attempt = ++submitted;
|
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)");
|
warn("status : UNSOLVED (timeout)");
|
||||||
@@ -310,12 +310,13 @@ 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()) {
|
||||||
|
|||||||
@@ -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) { }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
// Assert
|
var rng = new Rng(seed);
|
||||||
/* assertNotNull(result);
|
PuzzleResult res = Main.attempt(rng, dict, opts);
|
||||||
assertNotNull(result.mask());
|
// Assert
|
||||||
assertNotNull(result.filled());
|
if (res != null && res.filled().ok()) {
|
||||||
assertTrue(result.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)");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user