diff --git a/src/main/java/puzzle/Main.java b/src/main/java/puzzle/Main.java index b9d75c7..18dbacf 100644 --- a/src/main/java/puzzle/Main.java +++ b/src/main/java/puzzle/Main.java @@ -341,7 +341,7 @@ public class Main { TOTAL_ATTEMPTS.incrementAndGet(); var swe = new SwedishGenerator(rng); var mask = swe.generateMask(opts.pop, opts.gens); - var filled = new CSP(rng).fillMask(mask, dict.index(), opts.fillTimeout); + var filled = swe.fillMask(mask, dict.index(), opts.fillTimeout); TOTAL_NODES.addAndGet(filled.stats().nodes); TOTAL_BACKTRACKS.addAndGet(filled.stats().backtracks); diff --git a/src/main/java/puzzle/SwedishGenerator.java b/src/main/java/puzzle/SwedishGenerator.java index f817fa4..4cb09fd 100644 --- a/src/main/java/puzzle/SwedishGenerator.java +++ b/src/main/java/puzzle/SwedishGenerator.java @@ -294,7 +294,7 @@ public record SwedishGenerator(Rng rng) { if (rawClue.startsWith("\"") && rawClue.endsWith("\"")) { rawClue = rawClue.substring(1, rawClue.length() - 1).replace("\"\"", "\""); } - map.add(new Lemma(id,s, simpel, GSON.fromJson(rawClue, String[].class))); + map.add(new Lemma(id, s, simpel, GSON.fromJson(rawClue, String[].class))); } return new Dict(map.toArray(Lemma[]::new)); @@ -379,8 +379,8 @@ public record SwedishGenerator(Rng rng) { Arrays.fill(covH, 0, SIZE, 0); Arrays.fill(covV, 0, SIZE, 0); - boolean[] hasSlots = { false }; - long lo_cl = grid.bo[0], hi_cl = grid.bo[1]; + boolean hasSlots = false; + long lo_cl = grid.bo[0], hi_cl = grid.bo[1]; while (lo_cl != 0) { int clueIdx = Long.numberOfTrailingZeros(lo_cl); lo_cl &= (lo_cl - 1); @@ -400,12 +400,10 @@ public record SwedishGenerator(Rng rng) { cc += nbrs16.dc(); } if (n == 0) continue; - hasSlots[0] = true; + hasSlots = true; if (n < MIN_LEN) { penalty[0] += 8000; - } /*else if (lenCounts[n] <= 0) { - penalty[0] += 12000; - }*/ + } var horiz = Slot.horiz(d) ? covH : covV; for (var i = 0; i < n; i++) horiz[Slot.offset(packedPos, i)] += 1; } @@ -428,17 +426,15 @@ public record SwedishGenerator(Rng rng) { cc += nbrs16.dc(); } if (n == 0) continue; - hasSlots[0] = true; + hasSlots = true; if (n < MIN_LEN) { penalty[0] += 8000; - } /*else if (lenCounts[n] <= 0) { - penalty[0] += 12000; - }*/ + } var horiz = Slot.horiz(d) ? covH : covV; for (var i = 0; i < n; i++) horiz[Slot.offset(packedPos, i)] += 1; } - if (!hasSlots[0]) return 1_000_000_000L; + if (!hasSlots) return 1_000_000_000L; int idx, h, v; for (idx = 0; idx < SIZE; idx++) { @@ -480,8 +476,19 @@ public record SwedishGenerator(Rng rng) { if (size >= 2) penalty[0] += (size - 1L) * 120L; }); - int walls; // dead-end-ish letter cell (3+ walls) + int walls, wc, wr; + /*for (var rci : IT) { + if (grid.isDigitAt(rci.i())) continue; + walls = 0; + for (var d : nbrs4) { + wr = rci.r() + d.r(); + wc = rci.c() + d.c(); + if (wr < 0 || wr >= R || wc < 0 || wc >= C || grid.isDigitAt(wr, wc)) walls++; + } + if (walls >= 3) penalty[0] += 400; + }*/ + for (var rci : IT) { if (grid.isDigitAt(rci.i())) continue; walls = (4 - rci.nbrCount()) + Long.bitCount(rci.n1() & grid.bo[0]) + Long.bitCount(rci.n2() & grid.bo[1]); @@ -691,33 +698,31 @@ public record SwedishGenerator(Rng rng) { return new CandidateInfo(in, curLen); } - record CSP(Rng rng) { + public FillResult fillMask(Grid mask, DictEntry[] dictIndex, + int timeLimitMs) { + val multiThreaded = Thread.currentThread().getName().contains("pool"); + val grid = mask.deepCopyGrid(); + val used = new Bit1029(); + val assigned = new HashMap(); + val ctx = CTX.get(); + val count = ctx.cellCount; + Arrays.fill(count, 0, SIZE, 0); - public FillResult fillMask(Grid mask, DictEntry[] dictIndex, - int timeLimitMs) { - boolean multiThreaded = Thread.currentThread().getName().contains("pool"); - var grid = mask.deepCopyGrid(); - var slots = extractSlots(grid); + val slots = extractSlots(grid); + for (var s : slots) for (int i = 0, len = s.len(); i < len; i++) count[s.pos(i)]++; + + val t0 = System.currentTimeMillis(); + val stats = new FillStats(); + val TOTAL = slots.size(); + + class Solver { - var used = new Bit1029(); - var assigned = new HashMap(); - - var ctx = CTX.get(); - var count = ctx.cellCount; - Arrays.fill(count, 0, SIZE, 0); - for (var s : slots) for (int i = 0, len = s.len(); i < len; i++) count[s.pos(i)]++; - - var t0 = System.currentTimeMillis(); - final var lastLog = new AtomicLong(t0); - - var stats = new FillStats(); - final var TOTAL = slots.size(); - - Runnable renderProgress = () -> { + long lastLog = t0; + void renderProgress() { if (!Main.VERBOSE || multiThreaded) return; var now = System.currentTimeMillis(); - if ((now - lastLog.get()) < LOG_EVERY_MS) return; - lastLog.set(now); + if ((now - lastLog) < LOG_EVERY_MS) return; + lastLog = (now); var done = assigned.size(); var pct = (TOTAL == 0) ? 100 : (int) Math.floor((done / (double) TOTAL) * 100); @@ -732,9 +737,8 @@ public record SwedishGenerator(Rng rng) { ); System.out.print("\r" + Strings.padRight(msg, 120)); System.out.flush(); - }; - - Supplier chooseMRV = () -> { + } + Pick chooseMRV() { Slot best = null; CandidateInfo bestInfo = null; int bestSlot = -1; @@ -766,79 +770,39 @@ public record SwedishGenerator(Rng rng) { } else { return new Pick(best, bestInfo, false); } - }; - - class Solver { + } + boolean backtrack(int depth) { + if (Thread.currentThread().isInterrupted()) return false; + stats.nodes++; - boolean backtrack(int depth) { - if (Thread.currentThread().isInterrupted()) return false; - stats.nodes++; + if (timeLimitMs > 0 && (System.currentTimeMillis() - t0) > timeLimitMs) return false; + + var pick = chooseMRV(); + if (pick.done) return true; + if (pick.slot == null) { + stats.backtracks++; + return false; + } + val info = pick.info; + stats.lastMRV = info.count; + renderProgress(); + + var s = pick.slot; + var k = s.key(); + int patLen = s.len(); + var entry = dictIndex[patLen]; + var pat = new byte[patLen]; + patternForSlot(grid, s, pat); + if (info.indices != null && info.indices.length > 0) { + var idxs = info.indices; + var L = idxs.length; + var tries = Math.min(MAX_TRIES_PER_SLOT, L); - if (timeLimitMs > 0 && (System.currentTimeMillis() - t0) > timeLimitMs) return false; - - var pick = chooseMRV.get(); - if (pick.done) return true; - if (pick.slot == null) { - stats.backtracks++; - return false; - } - val info = pick.info; - stats.lastMRV = info.count; - renderProgress.run(); - - var s = pick.slot; - var k = s.key(); - int patLen = s.len(); - var entry = dictIndex[patLen]; - var pat = new byte[patLen]; - patternForSlot(grid, s, pat); - if (info.indices != null && info.indices.length > 0) { - var idxs = info.indices; - var L = idxs.length; - var tries = Math.min(MAX_TRIES_PER_SLOT, L); - - for (var t = 0; t < tries; t++) { - double r = rng.nextFloat(); - int idxInArray = (int) (r * r * r * L); - var idx = idxs[idxInArray]; - var w = entry.words.get(idx); - - if (used.get(w.index())) continue; - - boolean match = true; - for (var i = 0; i < patLen; i++) { - if (pat[i] != DASH && pat[i] != w.byteAt(i)) { - match = false; - break; - } - } - - if (!match || !placeWord(grid, s, w, ctx.undo, depth)) continue; - - used.set(w.index()); - assigned.put(k, w); - - if (backtrack(depth + 1)) return true; - - assigned.remove(k); - used.clear(w.index); - s.undoPlace(grid, ctx.undo[depth]); - } - stats.backtracks++; - return false; - } - - var N = entry.words.size(); - if (N == 0) { - stats.backtracks++; - return false; - } - - var tries = Math.min(MAX_TRIES_PER_SLOT, N); for (var t = 0; t < tries; t++) { double r = rng.nextFloat(); - int idxInArray = (int) (r * r * r * N); - var w = entry.words.get(idxInArray); + int idxInArray = (int) (r * r * r * L); + var idx = idxs[idxInArray]; + var w = entry.words.get(idx); if (used.get(w.index())) continue; @@ -849,6 +813,7 @@ public record SwedishGenerator(Rng rng) { break; } } + if (!match || !placeWord(grid, s, w, ctx.undo, depth)) continue; used.set(w.index()); @@ -860,36 +825,71 @@ public record SwedishGenerator(Rng rng) { used.clear(w.index); s.undoPlace(grid, ctx.undo[depth]); } - stats.backtracks++; return false; } + + var N = entry.words.size(); + if (N == 0) { + stats.backtracks++; + return false; + } + + var tries = Math.min(MAX_TRIES_PER_SLOT, N); + for (var t = 0; t < tries; t++) { + double r = rng.nextFloat(); + int idxInArray = (int) (r * r * r * N); + var w = entry.words.get(idxInArray); + + if (used.get(w.index())) continue; + + boolean match = true; + for (var i = 0; i < patLen; i++) { + if (pat[i] != DASH && pat[i] != w.byteAt(i)) { + match = false; + break; + } + } + if (!match || !placeWord(grid, s, w, ctx.undo, depth)) continue; + + used.set(w.index()); + assigned.put(k, w); + + if (backtrack(depth + 1)) return true; + + assigned.remove(k); + used.clear(w.index); + s.undoPlace(grid, ctx.undo[depth]); + } + + stats.backtracks++; + return false; } - - // initial render (same feel) - renderProgress.run(); - var ok = new Solver().backtrack(0); - // final progress line - if (!multiThreaded) { - System.out.print("\r" + Strings.padRight("", 120) + "\r"); - System.out.flush(); - } - - stats.seconds = (System.currentTimeMillis() - t0) / 1000.0; - var res = new FillResult(ok, new Gridded(grid), assigned, stats); - - // print a final progress line - if (Main.VERBOSE && !multiThreaded) { - System.out.println( - String.format(Locale.ROOT, - "[######################] %d/%d slots | nodes=%d | backtracks=%d | mrv=%d | %.1fs", - assigned.size(), TOTAL, stats.nodes, stats.backtracks, stats.lastMRV, stats.seconds - ) - ); - } - - return res; } + + // initial render (same feel) + var solver = new Solver(); + solver.renderProgress(); + var ok = solver.backtrack(0); + // final progress line + if (!multiThreaded) { + System.out.print("\r" + Strings.padRight("", 120) + "\r"); + System.out.flush(); + } + + stats.seconds = (System.currentTimeMillis() - t0) / 1000.0; + var res = new FillResult(ok, new Gridded(grid), assigned, stats); + + // print a final progress line + if (Main.VERBOSE && !multiThreaded) { + System.out.println( + String.format(Locale.ROOT, + "[######################] %d/%d slots | nodes=%d | backtracks=%d | mrv=%d | %.1fs", + assigned.size(), TOTAL, stats.nodes, stats.backtracks, stats.lastMRV, stats.seconds + ) + ); + } + + return res; } - }