diff --git a/src/main/java/puzzle/ExportFormat.java b/src/main/java/puzzle/ExportFormat.java index bd6c202..3335437 100644 --- a/src/main/java/puzzle/ExportFormat.java +++ b/src/main/java/puzzle/ExportFormat.java @@ -2,13 +2,15 @@ package puzzle; import puzzle.Main.PuzzleResult; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Objects; -import static puzzle.SwedishGenerator.H; +import static puzzle.SwedishGenerator.R; import static puzzle.SwedishGenerator.Lemma; +import static puzzle.SwedishGenerator.SIZE; import static puzzle.SwedishGenerator.Slot; -import static puzzle.SwedishGenerator.W; +import static puzzle.SwedishGenerator.C; /** * ExportFormat.java @@ -19,19 +21,34 @@ import static puzzle.SwedishGenerator.W; * - crops to bounding box (words + arrow cells) with 1-cell margin * - outputs gridv2 + words[] (+ difficulty, rewards) */ -public final class ExportFormat { +public record ExportFormat() { + + record Bit(long[] bits) { + + public Bit() { this(new long[(SIZE >> 6) + 1]); } + static int wordIndex(int bitIndex) { return bitIndex >> 6; } + public boolean get(int bitIndex) { return (this.bits[wordIndex(bitIndex)] & 1L << bitIndex) != 0L; } + public void set(int bitIndex) { bits[wordIndex(bitIndex)] |= 1L << bitIndex; } + public void clear(int bitIndex) { this.bits[wordIndex(bitIndex)] &= ~(1L << bitIndex); } + public void clear() { Arrays.fill(bits, 0L); } + } + + record Bit1029(long[] bits) { + + public Bit1029() { this(new long[1029]); } + static int wordIndex(int bitIndex) { return bitIndex >> 6; } + public boolean get(int bitIndex) { return (this.bits[wordIndex(bitIndex)] & 1L << bitIndex) != 0L; } + public void set(int bitIndex) { bits[wordIndex(bitIndex)] |= 1L << bitIndex; } + public void clear(int bitIndex) { this.bits[wordIndex(bitIndex)] &= ~(1L << bitIndex); } + public void clear() { Arrays.fill(bits, 0L); } + } - private ExportFormat() { } static final String HORIZONTAL = "h", VERTICAL = "v"; - private static boolean inBounds(int H, int W, int r, int c) { return r >= 0 && r < H && c >= 0 && c < W; } - - // ---------- Public API ---------- + private static boolean inBounds(int r, int c) { return r >= 0 && r < SwedishGenerator.R && c >= 0 && c < SwedishGenerator.C; } public static ExportedPuzzle exportFormatFromFilled(PuzzleResult puz, int difficulty, Rewards rewards) { Objects.requireNonNull(puz, "puz"); - var g = puz.filled().grid(); - - // 1) extract "placed" list from all clue digits in the filled grid + var g = puz.filled().grid(); var placed = new ArrayList(); var clueMap = puz.filled().clueMap(); puz.swe().forEachSlot(g, (int key, long rs, long cs, int len) -> { @@ -44,10 +61,10 @@ public final class ExportFormat { // If nothing placed: return full grid mapped to letters/# only if (placed.isEmpty()) { - var gridv2 = new ArrayList(H); - for (var r = 0; r < H; r++) { - var sb = new StringBuilder(W); - for (var c = 0; c < W; c++) { + var gridv2 = new ArrayList(R); + for (var r = 0; r < R; r++) { + var sb = new StringBuilder(C); + for (var c = 0; c < C; c++) { sb.append(g.isLetterAt(r, c) ? g.getCharAt(r, c) : '#'); } gridv2.add(sb.toString()); @@ -78,7 +95,7 @@ public final class ExportFormat { for (var p : placed) { for (var rc : p.cells) { int rr = rc[0], cc = rc[1]; - if (inBounds(H, W, rr, cc) && g.isLetterAt(rr, cc)) { + if (inBounds(rr, cc) && g.isLetterAt(rr, cc)) { letterAt.put(pack(rr, cc), g.getCharAt(rr, cc)); } } @@ -181,4 +198,5 @@ public final class ExportFormat { } public record ExportedPuzzle(List gridv2, WordOut[] words, int difficulty, Rewards rewards) { } + } diff --git a/src/main/java/puzzle/SwedishGenerator.java b/src/main/java/puzzle/SwedishGenerator.java index c876077..5531710 100644 --- a/src/main/java/puzzle/SwedishGenerator.java +++ b/src/main/java/puzzle/SwedishGenerator.java @@ -2,13 +2,14 @@ package puzzle; import lombok.Data; import lombok.Getter; +import puzzle.ExportFormat.Bit; +import puzzle.ExportFormat.Bit1029; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; -import java.util.BitSet; import java.util.Comparator; import java.util.HashMap; import java.util.Locale; @@ -27,16 +28,17 @@ public record SwedishGenerator(int[] buff) { record CandidateInfo(int[] indices, int count) { } - record nbrs_8(int x, int y) { } + record nbrs_8(int r, int c) { } - record nbrs_16(int x, int y, int dx, int dy) { } + record nbrs_16(int r, int c, int dr, int dc) { } - static final int W = Config.PUZZLE_COLS; - static final double CROSS_Y = (W - 1) / 2.0; - static final int H = Config.PUZZLE_ROWS; - static final double CROSS_X = (H - 1) / 2.0; - static final int SIZE = W * H; - static final int MAX_WORD_LENGTH = Math.min(W, H); + static final int C = Config.PUZZLE_COLS; + static final double CROSS_Y = (C - 1) / 2.0; + static final int R = Config.PUZZLE_ROWS; + static final double CROSS_X = (R - 1) / 2.0; + static final int SIZE = C * R;// ~18 + static final int TARGET_CLUES = SIZE >> 2; + static final int MAX_WORD_LENGTH = Math.min(C, R); static final int MIN_LEN = Config.MIN_LEN; static final int CLUE_SIZE = Config.CLUE_SIZE; static final int SIMPLICITY_DEFAULT_SCORE = 2; @@ -52,18 +54,12 @@ public record SwedishGenerator(int[] buff) { // Directions for '1'..'6' static final nbrs_16[] OFFSETS = new nbrs_16[]{ null, - // 1: up - new nbrs_16(-1, 0, -1, 0), - // 2: right - new nbrs_16(0, 1, 0, 1), - // 3: down - new nbrs_16(1, 0, 1, 0), - // 4: left - new nbrs_16(0, -1, 0, -1), - // 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) + 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 }; final static nbrs_8[] nbrs8 = new nbrs_8[]{ new nbrs_8(-1, -1), @@ -86,13 +82,13 @@ public record SwedishGenerator(int[] buff) { int[] covV, int[] cellCount, int[] stack, - BitSet seen, + Bit seen, char[] pattern, IntList[] intListBuffer, long[] undoBuffer) { public Context() { - this(new int[SIZE], new int[SIZE], new int[SIZE], new int[SIZE], new BitSet(128), new char[MAX_WORD_LENGTH], new IntList[MAX_WORD_LENGTH], + this(new int[SIZE], new int[SIZE], new int[SIZE], new int[SIZE], new Bit(), new char[MAX_WORD_LENGTH], new IntList[MAX_WORD_LENGTH], new long[2048]); } void setPatter(char[] chars) { System.arraycopy(chars, 0, this.pattern, 0, chars.length); } @@ -114,7 +110,7 @@ public record SwedishGenerator(int[] buff) { x = y; return y; } - int randint(int min, int max) { // inclusive + int randint(int min, int max) { var u = (nextU32() & 0xFFFFFFFFL); var range = (long) max - (long) min + 1L; return (int) (min + (u % range)); @@ -124,8 +120,10 @@ public record SwedishGenerator(int[] buff) { record Grid(byte[] g) { + public static int r(int offset) { return offset & 7; } + public static int c(int offset) { return offset >> 3; } Grid deepCopyGrid() { return new Grid(g.clone()); } - private int offset(int r, int c) { return r | (c << 3); } + static int offset(int r, int c) { return r | (c << 3); } boolean isLettercell(int r, int c) { return (g[offset(r, c)] & 48) != 48; } char getCharAt(int r, int c) { return (char) (g[offset(r, c)]); } int digitAt(int r, int c) { return g[offset(r, c)] - 48; } @@ -133,6 +131,7 @@ public record SwedishGenerator(int[] buff) { void setCharAt(int r, int c, char ch) { g[offset(r, c)] = (byte) ch; } boolean isLetterAt(int r, int c) { return ((g[offset(r, c)] & 64) != 0); } boolean isDigitAt(int r, int c) { return (g[offset(r, c)] & 48) == 48; } + boolean isDigitAt(int index) { return (g[index] & 48) == 48; } public double similarity(Grid b) { var same = 0; for (int i = 0; i < SIZE; i++) if (g[i] == b.g[i]) same++; @@ -143,18 +142,18 @@ public record SwedishGenerator(int[] buff) { String gridToString(Grid g) { var sb = new StringBuilder(); - for (var r = 0; r < H; r++) { + for (var r = 0; r < R; r++) { if (r > 0) sb.append('\n'); - for (var c = 0; c < W; c++) sb.append(g.getCharAt(r, c)); + for (var c = 0; c < C; c++) sb.append(g.getCharAt(r, c)); } return sb.toString(); } public String renderHuman(Grid g) { var sb = new StringBuilder(); - for (var r = 0; r < H; r++) { + for (var r = 0; r < R; r++) { if (r > 0) sb.append('\n'); - for (var c = 0; c < W; c++) { + for (var c = 0; c < C; c++) { sb.append(g.isDigitAt(r, c) ? ' ' : g.getCharAt(r, c)); } } @@ -352,24 +351,24 @@ public record SwedishGenerator(int[] buff) { } void forEachSlot(Grid grid, SlotVisitor visitor) { - for (var r = 0; r < H; r++) { - for (var c = 0; c < W; c++) { + for (var r = 0; r < R; r++) { + for (var c = 0; c < C; c++) { if (!grid.isDigitAt(r, c)) continue; var d = grid.digitAt(r, c); var nbrs16 = OFFSETS[d]; - int rr = r + nbrs16.x, cc = c + nbrs16.y; + int rr = r + nbrs16.r, cc = c + nbrs16.c; - if (rr < 0 || rr >= H || cc < 0 || cc >= W || grid.isDigitAt(rr, cc)) continue; + if (rr < 0 || rr >= R || cc < 0 || cc >= C || grid.isDigitAt(rr, cc)) continue; long packedRs = 0; long packedCs = 0; var n = 0; - while (rr >= 0 && rr < H && cc >= 0 && cc < W && grid.isLettercell(rr, cc) && n < MAX_WORD_LENGTH) { + while (rr >= 0 && rr < R && cc >= 0 && cc < C && grid.isLettercell(rr, cc) && n < MAX_WORD_LENGTH) { packedRs |= (long) rr << (n << 2); packedCs |= (long) cc << (n << 2); n++; - rr += nbrs16.dx; - cc += nbrs16.dy; + rr += nbrs16.dr; + cc += nbrs16.dc; } if (n > 0) { visitor.visit((r << 8) | (c << 4) | d, packedRs, packedCs, n); @@ -386,12 +385,12 @@ public record SwedishGenerator(int[] buff) { boolean hasRoomForClue(Grid grid, int r, int c, char d) { var nbrs16 = OFFSETS[d - '0']; - int rr = r + nbrs16.x, cc = c + nbrs16.y; + int rr = r + nbrs16.r, cc = c + nbrs16.c; var run = 0; - while (rr >= 0 && rr < H && cc >= 0 && cc < W && (grid.isLettercell(rr, cc)) && run < MAX_WORD_LENGTH) { + while (rr >= 0 && rr < R && cc >= 0 && cc < C && (grid.isLettercell(rr, cc)) && run < MAX_WORD_LENGTH) { run++; - rr += nbrs16.dx; - cc += nbrs16.dy; + rr += nbrs16.dr; + cc += nbrs16.dc; if (run >= MIN_LEN) return true; } return false; @@ -401,10 +400,9 @@ public record SwedishGenerator(int[] buff) { long penalty = 0; var clueCount = 0; - for (var r = 0; r < H; r++) for (var c = 0; c < W; c++) if (grid.isDigitAt(r, c)) clueCount++; + for (var r = 0; r < R; r++) for (var c = 0; c < C; c++) if (grid.isDigitAt(r, c)) clueCount++; - var targetClues = (int) Math.round(SIZE * 0.25); // ~18 - penalty += 8L * Math.abs(clueCount - targetClues); + penalty += 8L * Math.abs(clueCount - TARGET_CLUES); var ctx = CTX.get(); var covH = ctx.covH; @@ -413,25 +411,25 @@ public record SwedishGenerator(int[] buff) { Arrays.fill(covV, 0, SIZE, 0); boolean hasSlots = false; - for (var r = 0; r < H; r++) { - for (var c = 0; c < W; c++) { + for (var r = 0; r < R; r++) { + for (var c = 0; c < C; c++) { if (!grid.isDigitAt(r, c)) continue; var d = grid.digitAt(r, c); var nbrs16 = OFFSETS[d]; - int rr = r + nbrs16.x, cc = c + nbrs16.y; - if (rr < 0 || rr >= H || cc < 0 || cc >= W || grid.isDigitAt(rr, cc)) continue; + int rr = r + nbrs16.r, cc = c + nbrs16.c; + if (rr < 0 || rr >= R || cc < 0 || cc >= C || grid.isDigitAt(rr, cc)) continue; long packedRs = 0; long packedCs = 0; var n = 0; - while (rr >= 0 && rr < H && cc >= 0 && cc < W && n < MAX_WORD_LENGTH) { + while (rr >= 0 && rr < R && cc >= 0 && cc < C && n < MAX_WORD_LENGTH) { if (grid.isDigitAt(rr, cc)) break; packedRs |= (long) rr << (n << 2); packedCs |= (long) cc << (n << 2); n++; - rr += nbrs16.dx; - cc += nbrs16.dy; + rr += nbrs16.dr; + cc += nbrs16.dc; } if (n == 0) continue; hasSlots = true; @@ -443,16 +441,16 @@ public record SwedishGenerator(int[] buff) { } var horiz = Slot.horiz(d) ? covH : covV; - for (var i = 0; i < n; i++) horiz[grid.offset(Slot.r(packedRs, i), Slot.c(packedCs, i))] += 1; + for (var i = 0; i < n; i++) horiz[Grid.offset(Slot.r(packedRs, i), Slot.c(packedCs, i))] += 1; } } if (!hasSlots) return 1_000_000_000L; - for (var r = 0; r < H; r++) - for (var c = 0; c < W; c++) { + for (var r = 0; r < R; r++) + for (var c = 0; c < C; c++) { if (grid.isDigitAt(r, c)) continue; - int idx = grid.offset(r, c); + int idx = Grid.offset(r, c); int h = covH[idx], v = covV[idx]; if (h == 0 && v == 0) penalty += 1500; else if (h > 0 && v > 0) { /* ok */ } else if (h + v == 1) penalty += 200; @@ -465,8 +463,8 @@ public record SwedishGenerator(int[] buff) { var stack = ctx.stack; int sp, idx; - for (var r = 0; r < H; r++) - for (var c = 0; c < W; c++) { + for (var r = 0; r < R; r++) + for (var c = 0; c < C; c++) { idx = grid.offset(r, c); if (!grid.isDigitAt(r, c) || seen.get(idx)) continue; sp = 0; @@ -476,12 +474,12 @@ public record SwedishGenerator(int[] buff) { while (sp > 0) { var p = stack[--sp]; - int x = p / W, y = p % W; + int x = p / C, y = p % C; size++; for (var d : nbrs8) { - int nx = x + d.x, ny = y + d.y; - if (nx < 0 || nx >= H || ny < 0 || ny >= W) continue; + int nx = x + d.r, ny = y + d.c; + if (nx < 0 || nx >= R || ny < 0 || ny >= C) continue; int nidx = grid.offset(nx, ny); if (seen.get(nidx)) continue; if (!grid.isDigitAt(nx, ny)) continue; @@ -494,13 +492,13 @@ public record SwedishGenerator(int[] buff) { } // dead-end-ish letter cell (3+ walls) - for (var r = 0; r < H; r++) - for (var c = 0; c < W; c++) { + for (var r = 0; r < R; r++) + for (var c = 0; c < C; c++) { if (grid.isDigitAt(r, c)) continue; var walls = 0; for (var d : nbrs4) { - int rr = r + d.x, cc = c + d.y; - if (rr < 0 || rr >= H || cc < 0 || cc >= W) { + int rr = r + d.r, cc = c + d.c; + if (rr < 0 || rr >= R || cc < 0 || cc >= C) { walls++; continue; } @@ -520,8 +518,8 @@ public record SwedishGenerator(int[] buff) { int placed = 0, guard = 0; while (placed < targetClues && guard++ < 4000) { - var r = rng.randint(0, H - 1); - var c = rng.randint(0, W - 1); + var r = rng.randint(0, R - 1); + var c = rng.randint(0, C - 1); if (g.isDigitAt(r, c)) continue; var d = (char) ('0' + rng.randint(1, c == 0 ? CLUE_SIZE : 4)); @@ -537,13 +535,13 @@ public record SwedishGenerator(int[] buff) { Grid mutate(Rng rng, Grid grid) { var g = grid.deepCopyGrid(); - var cx = rng.randint(0, H - 1); - var cy = rng.randint(0, W - 1); + 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, H - 1); - var cc = clamp(cy + (rng.randint(-2, 2) + rng.randint(-2, 2)), 0, W - 1); + 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.setCharAt(rr, cc, C_DASH); @@ -562,13 +560,13 @@ public record SwedishGenerator(int[] buff) { var nx = Math.cos(theta); var ny = Math.sin(theta); - for (var r = 0; r < H; r++) - for (var c = 0; c < W; c++) { + for (var r = 0; r < R; r++) + for (var c = 0; c < C; c++) { out.setCharAt(r, c, ((r - CROSS_X) * nx + (c - CROSS_Y) * ny >= 0) ? a.getCharAt(r, c) : b.getCharAt(r, c)); } - for (var r = 0; r < H; r++) - for (var c = 0; c < W; c++) { + for (var r = 0; r < R; r++) + for (var c = 0; c < C; c++) { if (out.isDigitAt(r, c) && !hasRoomForClue(out, r, c, out.getCharAt(r, c))) out.setCharAt(r, c, C_DASH); } return out; @@ -648,6 +646,7 @@ public record SwedishGenerator(int[] buff) { pop.sort(Comparator.comparingLong(GridAndFit::fit)); return pop.get(0).grid; } + public Grid generateMask2(Rng rng, int[] lenCounts, int popSize, int gens, boolean verbose) { if (verbose) System.out.println("generateMask init pop: " + popSize); var pop = new ArrayList(); @@ -734,7 +733,7 @@ public record SwedishGenerator(int[] buff) { static int slotScore(int[] cellCount, Slot s, Grid grid) { var cross = 0; - for (var i = 0; i < s.len(); i++) cross += (cellCount[grid.offset(s.r(i), s.c(i))] - 1); + for (var i = 0; i < s.len(); i++) cross += (cellCount[Grid.offset(s.r(i), s.c(i))] - 1); return cross * 10 + s.len(); } @@ -767,13 +766,13 @@ public record SwedishGenerator(int[] buff) { var grid = mask.deepCopyGrid(); var slots = extractSlots(grid); - var used = new BitSet(SIZE << 1); + var used = new Bit1029(); var assigned = new HashMap(); var ctx = CTX.get(); var cellCount = ctx.cellCount; Arrays.fill(cellCount, 0, SIZE, 0); - for (var s : slots) for (var i = 0; i < s.len(); i++) cellCount[grid.offset(s.r(i), s.c(i))]++; + for (var s : slots) for (var i = 0; i < s.len(); i++) cellCount[Grid.offset(s.r(i), s.c(i))]++; var t0 = System.currentTimeMillis(); final var lastLog = new AtomicLong(t0); @@ -894,7 +893,7 @@ public record SwedishGenerator(int[] buff) { if (backtrack(depth + 1)) return true; assigned.remove(k); - used.set(w.index, false); + used.clear(w.index); undoPlace(grid, ctx.undoBuffer, undoOffset, nPlaced); } stats.backtracks++; @@ -933,7 +932,7 @@ public record SwedishGenerator(int[] buff) { if (backtrack(depth + 1)) return true; assigned.remove(k); - used.set(w.index, false); + used.clear(w.index); undoPlace(grid, ctx.undoBuffer, undoOffset, nPlaced); }