From 20994d46d8796cecb1844c8daa9908469b6a342b Mon Sep 17 00:00:00 2001 From: mike Date: Sat, 24 Jan 2026 04:27:42 +0100 Subject: [PATCH] redo --- src/main/java/puzzle/Main.java | 129 ++++---- src/main/java/puzzle/Masker.java | 540 ++++++++++++++++--------------- src/main/java/puzzle/Riddle.java | 4 +- 3 files changed, 352 insertions(+), 321 deletions(-) diff --git a/src/main/java/puzzle/Main.java b/src/main/java/puzzle/Main.java index a9a0e6f..ee9f619 100644 --- a/src/main/java/puzzle/Main.java +++ b/src/main/java/puzzle/Main.java @@ -38,12 +38,6 @@ public class Main { final static String OUT_DIR = envOrDefault("OUT_DIR", "/data/puzzle"); final static Path PUZZLE_DIR = Paths.get(OUT_DIR, "puzzles"); static final Path INDEX_FILE = PUZZLE_DIR.resolve("index.json"); - static final OffsetDateTime now = OffsetDateTime.now(ZoneOffset.UTC); - static final String CREATED_AT = now.format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'")); - static final String FILE_ID = CREATED_AT.replace(":", "-") + "_" + System.currentTimeMillis() / 1000; - static final String FILE_NAME = FILE_ID + ".json"; - static final Path OUTPUT_PATH = PUZZLE_DIR.resolve(FILE_NAME); - static final String DATE_STRING = now.toLocalDate().toString(); static final boolean VERBOSE = false; static final AtomicLong TOTAL_NODES = new AtomicLong(0); static final AtomicLong TOTAL_BACKTRACKS = new AtomicLong(0); @@ -64,6 +58,7 @@ public class Main { public double minSimplicity = 0; // 0 means no limit public int threads = Math.max(1, Runtime.getRuntime().availableProcessors()); public int tries = threads; + public int count = 2; public boolean reindex = false; public boolean verbose = true; @@ -89,56 +84,71 @@ public class Main { section("Settings"); printSettings(opts); - var res = generatePuzzle(opts); - if (res == null) { - err("Search status : UNSOLVED"); - err("Reason : No solution found within tries."); - System.exit(1); - return; - } - - section("Mask"); - System.out.print(indentLines(res.cluesGridToString())); - - section("Grid (raw)"); - System.out.print(indentLines(res.gridGridToString())); - - section("Grid (human)"); - System.out.print(indentLines(res.gridRenderHuman())); - - var exported = res.exportFormatFromFilled(new Rewards(50, 2, 1)); - - section("Clues"); - info("status : generating..."); - info("generatedFor : " + exported.words().length); - info("status : done"); - info("simpel : " + exported.difficulty()); - - section("Words"); - printWordsTable(exported.words()); - - section("Grid"); - for (var row : exported.grid()) System.out.println(" " + row); - - var theme = "algemeen"; - section("Export"); - info("file : " + OUTPUT_PATH); - - try { - Files.createDirectories(PUZZLE_DIR); - var json = toJson(exported, DATE_STRING, theme); - Files.writeString(OUTPUT_PATH, json, StandardCharsets.UTF_8); + for (int count = 0; count < opts.count; count++) { + if (opts.count > 1) { + section("Generation " + (count + 1) + " / " + opts.count); + } + var res = generatePuzzle(opts); + opts.seed++; // Ensure different seed for next puzzle - // Update index.json - var pathInIndex = "/puzzles/" + FILE_NAME; - var indexRecord = toIndexRecordJson(FILE_ID, pathInIndex, DATE_STRING, theme, exported.difficulty(), CREATED_AT); - if (1 != 1) updateIndex(PUZZLE_DIR.toString(), indexRecord); - else rebuildIndex(); - info("indexUpdated : " + INDEX_FILE); - } catch (IOException e) { - err("Failed to write: " + FILE_NAME); - err("Reason : " + e.getMessage()); - System.exit(2); + if (res == null) { + err("Search status : UNSOLVED"); + err("Reason : No solution found within tries."); + if (opts.count == 1) System.exit(1); + continue; + } + + section("Mask"); + System.out.print(indentLines(res.cluesGridToString())); + + section("Grid (raw)"); + System.out.print(indentLines(res.gridGridToString())); + + section("Grid (human)"); + System.out.print(indentLines(res.gridRenderHuman())); + + var exported = res.exportFormatFromFilled(new Rewards(50, 2, 1)); + + section("Clues"); + info("status : generating..."); + info("generatedFor : " + exported.words().length); + info("status : done"); + info("simpel : " + exported.difficulty()); + + section("Words"); + printWordsTable(exported.words()); + + section("Grid"); + for (var row : exported.grid()) System.out.println(" " + row); + + var theme = "algemeen"; + + var now = OffsetDateTime.now(ZoneOffset.UTC); + var createdAt = now.format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'")); + var dateString = now.toLocalDate().toString(); + var fileId = createdAt.replace(":", "-") + "_" + System.currentTimeMillis() / 1000 + "_" + count; + var fileName = fileId + ".json"; + var outputPath = PUZZLE_DIR.resolve(fileName); + + section("Export"); + info("file : " + outputPath); + + try { + Files.createDirectories(PUZZLE_DIR); + var json = toJson(exported, dateString, theme); + Files.writeString(outputPath, json, StandardCharsets.UTF_8); + + // Update index.json + var pathInIndex = "/puzzles/" + fileName; + var indexRecord = toIndexRecordJson(fileId, pathInIndex, dateString, theme, exported.difficulty(), createdAt); + if (1 != 1) updateIndex(PUZZLE_DIR.toString(), indexRecord); + else rebuildIndex(); + info("indexUpdated : " + INDEX_FILE); + } catch (IOException e) { + err("Failed to write: " + fileName); + err("Reason : " + e.getMessage()); + if (opts.count == 1) System.exit(2); + } } } @@ -166,6 +176,7 @@ public class Main { System.out.printf(Locale.ROOT, " %-14s: %d%n", "generations", o.gens); System.out.printf(Locale.ROOT, " %-14s: %.2f%n", "minSimplicity", o.minSimplicity); System.out.printf(Locale.ROOT, " %-14s: %d%n", "threads", o.threads); + System.out.printf(Locale.ROOT, " %-14s: %d%n", "count", o.count); } private static String fmtPoint(int r, int c) { return String.format(Locale.ROOT, "(%d,%d)", r, c); } @@ -204,7 +215,7 @@ public class Main { static void usage() { System.out.printf(""" Usage: - java puzzle.Main [--seed N] [--clues N] [--pop N] [--offspring N] [--gens N] [--tries N] [--words FILE] [--min-simplicity N.N] [--threads N] [--reindex] + java puzzle.Main [--seed N] [--clues N] [--pop N] [--offspring N] [--gens N] [--tries N] [--words FILE] [--min-simplicity N.N] [--threads N] [--count N] [--reindex] Defaults: --clues 18 @@ -214,7 +225,8 @@ public class Main { --words nl_score_hints.csv --min-simplicity 0 (no limit) --threads %d - %n""", Math.max(1, Runtime.getRuntime().availableProcessors())); + --count 1 + %n""", Math.max(1, Runtime.getRuntime().availableProcessors())); } static Opts parseArgs(String[] argv) { @@ -252,6 +264,9 @@ public class Main { } else if (a.equals("--threads")) { out.threads = Integer.parseInt(v); i++; + } else if (a.equals("--count")) { + out.count = Integer.parseInt(v); + i++; } else if (a.equals("--reindex")) { out.reindex = true; } else { diff --git a/src/main/java/puzzle/Masker.java b/src/main/java/puzzle/Masker.java index 3de0b42..ec21d87 100644 --- a/src/main/java/puzzle/Masker.java +++ b/src/main/java/puzzle/Masker.java @@ -55,77 +55,8 @@ public final class Masker { this.stack = stack; this.cache = cache; } - public Clues cache(Clues clues) { - return cache.from(clues); - } + public Clues cache(Clues clues) { return cache.from(clues); } public static boolean isLo(int n) { return (n & 64) == 0; } - public static double similarity(Clues a, Clues b) { - var matchLo = (~(a.lo ^ b.lo)) & (~a.lo | (~(a.vlo ^ b.vlo) & ~(a.rlo ^ b.rlo) & ~(a.xlo ^ b.xlo))); - var matchHi = (~(a.hi ^ b.hi)) & (~a.hi | (~(a.vhi ^ b.vhi) & ~(a.rhi ^ b.rhi) & ~(a.xhi ^ b.xhi))); - - return (bitCount(matchLo & MASK_LO) + bitCount(matchHi & MASK_HI)) / SIZED; - } - public static Grid grid(Slotinfo[] slots) { - long lo = X, hi = X; - for (var slot : slots) { - lo |= slot.lo(); - hi |= slot.hi(); - } - return new Grid(new byte[SIZE], ~lo & MASK_LO, ~hi & MASK_HI); - } - - public boolean isValid(Clues c) { - return findOffendingClue(c) == -1; - } - - public int findOffendingClue(Clues c) { - //if (((c.xlo & c.rlo) & c.lo) != X) return numberOfTrailingZeros((c.xlo & c.rlo) & c.lo); - // if (((c.xhi & c.rhi) & c.hi) != X) return 64 | numberOfTrailingZeros((c.xhi & c.rhi) & c.hi); - - var num = 0; - for (var bits = c.lo; bits != X; bits &= bits - 1) activeCIdx[num++] = numberOfTrailingZeros(bits); - for (var bits = c.hi; bits != X; bits &= bits - 1) activeCIdx[num++] = 64 | numberOfTrailingZeros(bits); - - if (num == 0) return -1; - - var start = rng.randint0_SIZE(RANGE_0_SIZE) % num; - var n = 0; - for (var i = 0; i < num; i++) { - var idx = activeCIdx[(start + i) % num]; - int dir = c.getDir(idx); - var key = Slot.packSlotKey(idx, dir); - long sLo = PATH_LO[key], sHi = PATH_HI[key]; - long hLo = sLo & c.lo, hHi = sHi & c.hi; - if (Slotinfo.increasing(key)) { - if (hLo != X) { - sLo &= (1L << numberOfTrailingZeros(hLo)) - 1; - sHi = 0; - } else if (hHi != X) { sHi &= (1L << numberOfTrailingZeros(hHi)) - 1; } - } else { - if (hHi != X) { - sHi &= -(1L << (63 - numberOfLeadingZeros(hHi)) << 1); - sLo = 0; - } else if (hLo != X) { sLo &= -(1L << (63 - numberOfLeadingZeros(hLo)) << 1); } - } - if (bitCount(sLo) + bitCount(sHi) < MIN_LEN) return idx; - for (var j = 0; j < n; j++) if (bitCount(sLo & activeSLo[j]) + bitCount(sHi & activeSHi[j]) > 1) return idx; - activeSLo[n] = sLo; - activeSHi[n] = sHi; - n++; - } - return -1; - } - public static Grid toGrid(Clues c) { return new Grid(new byte[SIZE], c.lo, c.hi); } - public void cleanup(Clues c) { - var guard = 0; - while (guard++ < 50) { - var offending = findOffendingClue(c); - if (offending == -1) break; - if ((offending & 64) == 0) c.clearClueLo(~(1L << offending)); - else c.clearClueHi(~(1L << (offending & 63))); - } - } - static { for (var i = 0; i < SIZE; i++) { var k = 0; @@ -139,6 +70,148 @@ public final class Masker { } } } + public static double similarity(Clues a, Clues b) { + long diffLo = a.lo ^ b.lo; + long diffHi = a.hi ^ b.hi; + int diffPos = bitCount(diffLo) + bitCount(diffHi); + + long commonLo = a.lo & b.lo; + long commonHi = a.hi & b.hi; + int diffDir = bitCount(commonLo & (a.vlo ^ b.vlo | a.rlo ^ b.rlo | a.xlo ^ b.xlo)) + + bitCount(commonHi & (a.vhi ^ b.vhi | a.rhi ^ b.rhi | a.xhi ^ b.xhi)); + + return (SIZED - (diffPos + diffDir)) / SIZED; + } + public static Grid grid(Slotinfo[] slots) { + long lo = X, hi = X; + for (var slot : slots) { + lo |= slot.lo(); + hi |= slot.hi(); + } + return new Grid(new byte[SIZE], ~lo & MASK_LO, ~hi & MASK_HI); + } + + private long getWordPathLo(Clues c, int idx, int dir) { + int key = (idx << 3) | dir; + long sLo = PATH_LO[key], sHi = PATH_HI[key]; + long hLo = sLo & c.lo, hHi = sHi & c.hi; + if ((dir & 2) == 0) { // Increasing + if (hLo != X) return sLo & ((hLo & -hLo) - 1); + if (hHi != X) return sLo; + return sLo; + } else { + if (hHi != X) return 0; + if (hLo != X) return sLo & -(Long.highestOneBit(hLo) << 1); + return sLo; + } + } + + private long getWordPathHi(Clues c, int idx, int dir) { + int key = (idx << 3) | dir; + long sLo = PATH_LO[key], sHi = PATH_HI[key]; + long hLo = sLo & c.lo, hHi = sHi & c.hi; + if ((dir & 2) == 0) { // Increasing + if (hLo != X) return 0; + if (hHi != X) return sHi & ((hHi & -hHi) - 1); + return sHi; + } else { + if (hHi != X) return sHi & -(Long.highestOneBit(hHi) << 1); + if (hLo != X) return sHi; + return sHi; + } + } + + public boolean isValid(Clues c) { + return findOffendingClue(c, -1) == -1; + } + + public boolean isValid(Clues c, int ri) { + return findOffendingClue(c, ri) == -1; + } + + private boolean isDeadCell(Clues c, int idx) { + var rci = IT[idx]; + return (4 - rci.nbrCount()) + bitCount(rci.n1() & c.lo) + bitCount(rci.n2() & c.hi) >= 3; + } + + private int findAdjacentClue(Clues c, int ri) { + var rci = IT[ri]; + long nLo = rci.n1() & c.lo; + if (nLo != 0) return numberOfTrailingZeros(nLo); + long nHi = rci.n2() & c.hi; + if (nHi != 0) return 64 | numberOfTrailingZeros(nHi); + return -1; + } + + public int findOffendingClue(Clues c) { + return findOffendingClue(c, -1); + } + + public int findOffendingClue(Clues c, int ri) { + int num = 0; + for (long bits = c.lo; bits != X; bits &= bits - 1) activeCIdx[num++] = numberOfTrailingZeros(bits); + for (long bits = c.hi; bits != X; bits &= bits - 1) activeCIdx[num++] = 64 | numberOfTrailingZeros(bits); + if (num == 0) return -1; + + for (int i = 0; i < num; i++) { + int idx = activeCIdx[i]; + int dir = c.getDir(idx); + activeSLo[i] = getWordPathLo(c, idx, dir); + activeSHi[i] = getWordPathHi(c, idx, dir); + } + + long ri_lo = (ri != -1 && ri < 64) ? (1L << ri) : 0; + long ri_hi = (ri != -1 && ri >= 64) ? (1L << (ri - 64)) : 0; + + for (int i = 0; i < num; i++) { + int idx = activeCIdx[i]; + int dir = c.getDir(idx); + boolean affected = (ri == -1) || (idx == ri) || ((PATH_LO[(idx << 3) | dir] & ri_lo) != 0) || ((PATH_HI[(idx << 3) | dir] & ri_hi) != 0); + + if (affected) { + long sLo = activeSLo[i], sHi = activeSHi[i]; + if (bitCount(sLo) + bitCount(sHi) < MIN_LEN) return idx; + for (int j = 0; j < num; j++) { + if (i == j) continue; + long combined = (sLo & activeSLo[j]) | (sHi & activeSHi[j]); + if (combined != 0 && (combined & (combined - 1)) != 0) return idx; + } + } + } + + if (ri == -1) { + for (long bits = ~c.lo & MASK_LO; bits != X; bits &= bits - 1) { + int clueIdx = numberOfTrailingZeros(bits); + if (isDeadCell(c, clueIdx)) return findAdjacentClue(c, clueIdx); + } + for (long bits = ~c.hi & MASK_HI; bits != X; bits &= bits - 1) { + int clueIdx = numberOfTrailingZeros(bits); + if (isDeadCell(c, 64 | clueIdx)) return findAdjacentClue(c, 64 | clueIdx); + } + } else { + if (c.notClue(ri) && isDeadCell(c, ri)) return findAdjacentClue(c, ri); + var rci = IT[ri]; + for (long bits = rci.n1(); bits != X; bits &= bits - 1) { + int nIdx = numberOfTrailingZeros(bits); + if (c.notClue(nIdx) && isDeadCell(c, nIdx)) return findAdjacentClue(c, nIdx); + } + for (long bits = rci.n2(); bits != X; bits &= bits - 1) { + int nIdx = numberOfTrailingZeros(bits); + if (c.notClue(64 | nIdx) && isDeadCell(c, 64 | nIdx)) return findAdjacentClue(c, 64 | nIdx); + } + } + return -1; + } + public void cleanup(Clues c) { + var guard = 0; + while (guard++ < 50) { + var offending = findOffendingClue(c); + if (offending == -1) break; + if ((offending & 64) == 0) c.clearClueLo(~(1L << offending)); + else c.clearClueHi(~(1L << (offending & 63))); + } + } + // slice ray to stop before first clue, depending on direction monotonicity // right/down => increasing indices; up/left => decreasing indices // first clue is highest index among hits (hi first, then lo) @@ -250,171 +323,119 @@ public final class Masker { public static int offset(int r, int c) { return r | (c << 3); } public long maskFitness(final Clues grid, int clueSize) { + long cHLo = 0L, cHHi = 0L, cVLo = 0L, cVHi = 0L; + long cHLo2 = 0L, cHHi2 = 0L, cVLo2 = 0L, cVHi2 = 0L; + final long lo_cl = grid.lo, hi_cl = grid.hi; + final int numCluesTotal = bitCount(lo_cl) + bitCount(hi_cl); + if (numCluesTotal == 0) return 1_000_000_000L; - long cHLo = 0L, cHHi = 0L, cVLo = 0L, cVHi = 0L; - long cHLo2 = 0L, cHHi2 = 0L, cVLo2 = 0L, cVHi2 = 0L; - long lo_cl = grid.lo, hi_cl = grid.hi; - var penalty = (((long) Math.abs(grid.clueCount() - clueSize)) * 16000L); - var hasSlots = false; + long penalty = Math.abs(numCluesTotal - clueSize) * 16000L; + boolean hasSlots = false; + int numClues = 0; - var numClues = 0; - for (var bits = lo_cl; bits != X; bits &= bits - 1) { - var lsb = bits & -bits; - var clueIdx = numberOfTrailingZeros(lsb); - int dir = grid.getDir(clueIdx); - var key = Slot.packSlotKey(clueIdx, dir); - long rLo = PATH_LO[key], rHi = PATH_HI[key]; - long hLo = rLo & lo_cl, hHi = rHi & hi_cl; - if (Slotinfo.increasing(key)) { - if (hLo != X) { - rLo &= ((1L << numberOfTrailingZeros(hLo)) - 1); - rHi = 0; - } else if (hHi != X) rHi &= ((1L << numberOfTrailingZeros(hHi)) - 1); - } else if (hHi != X) { - var msb = 63 - numberOfLeadingZeros(hHi); - rHi &= -(1L << msb << 1); - rLo = 0; - } else if (hLo != X) { - var msb = 63 - numberOfLeadingZeros(hLo); - rLo &= -(1L << msb << 1); + for (int part = 0; part < 2; part++) { + long bits = (part == 0) ? lo_cl : hi_cl; + long vlo = (part == 0) ? grid.vlo : grid.vhi; + long rlo = (part == 0) ? grid.rlo : grid.rhi; + long xlo = (part == 0) ? grid.xlo : grid.xhi; + int base = (part == 0) ? 0 : 64; + while (bits != X) { + long lsb = bits & -bits; + int clueIdx = base | numberOfTrailingZeros(lsb); + int dir = (vlo & lsb) != 0 ? 1 : 0; + if ((rlo & lsb) != 0) dir |= 2; + if ((xlo & lsb) != 0) dir |= 4; + int key = (clueIdx << 3) | dir; + long rLo = PATH_LO[key], rHi = PATH_HI[key]; + long hLo = rLo & lo_cl, hHi = rHi & hi_cl; + if ((dir & 2) == 0) { // Increasing + if (hLo != X) { + rLo &= (hLo & -hLo) - 1; + rHi = 0; + } else if (hHi != X) rHi &= (hHi & -hHi) - 1; + } else if (hHi != X) { + rHi &= -(Long.highestOneBit(hHi) << 1); + rLo = 0; + } else if (hLo != X) { rLo &= -(Long.highestOneBit(hLo) << 1); } + + activeCIdx[numClues] = clueIdx; + activeSLo[numClues] = rLo; + activeSHi[numClues] = rHi; + numClues++; + + long combined = rLo | rHi; + if (combined != X) { + hasSlots = true; + if (Slot.horiz(dir)) { + cHLo2 |= (cHLo & rLo); + cHHi2 |= (cHHi & rHi); + cHLo |= rLo; + cHHi |= rHi; + } else { + cVLo2 |= (cVLo & rLo); + cVHi2 |= (cVHi & rHi); + cVLo |= rLo; + cVHi |= rHi; + } + int wordLen = bitCount(combined); + if (wordLen < MIN_LEN) penalty += 8000; + if (wordLen > 6) penalty += (wordLen - 6) * 1000L; + } else penalty += 25000; + bits &= ~lsb; } - - activeCIdx[numClues] = clueIdx; - activeSLo[numClues] = rLo; - activeSHi[numClues] = rHi; - numClues++; - - if ((rLo | rHi) != X) { - hasSlots = true; - if (Slot.horiz(key)) { - cHLo2 |= (cHLo & rLo); - cHHi2 |= (cHHi & rHi); - cHLo |= rLo; - cHHi |= rHi; - } else { - cVLo2 |= (cVLo & rLo); - cVHi2 |= (cVHi & rHi); - cVLo |= rLo; - cVHi |= rHi; - } - if ((bitCount(rLo) + bitCount(rHi)) < MIN_LEN) penalty += 8000; - var wordLen = bitCount(rLo) + bitCount(rHi); - if (wordLen > 6) penalty += (wordLen - 6) * 1000L; - } else penalty += 25000; - } - for (var bits = hi_cl; bits != X; bits &= bits - 1) { - var lsb = bits & -bits; - var clueIdx = numberOfTrailingZeros(lsb); - int dir = grid.getDir(64 | clueIdx); - var key = Slot.packSlotKey(64 | clueIdx, dir); - long rLo = PATH_LO[key], rHi = PATH_HI[key]; - long hLo = rLo & lo_cl, hHi = rHi & hi_cl; - if (Slotinfo.increasing(key)) { - if (hLo != X) { - rLo &= ((1L << numberOfTrailingZeros(hLo)) - 1); - rHi = 0; - } else if (hHi != X) rHi &= ((1L << numberOfTrailingZeros(hHi)) - 1); - } else if (hHi != X) { - var msb = 63 - numberOfLeadingZeros(hHi); - rHi &= -(1L << msb << 1); - rLo = 0; - } else if (hLo != X) { - var msb = 63 - numberOfLeadingZeros(hLo); - rLo &= -(1L << msb << 1); - } - - activeCIdx[numClues] = 64 | clueIdx; - activeSLo[numClues] = rLo; - activeSHi[numClues] = rHi; - numClues++; - - if ((rLo | rHi) != X) { - hasSlots = true; - if (Slot.horiz(key)) { - cHLo2 |= (cHLo & rLo); - cHHi2 |= (cHHi & rHi); - cHLo |= rLo; - cHHi |= rHi; - } else { - cVLo2 |= (cVLo & rLo); - cVHi2 |= (cVHi & rHi); - cVLo |= rLo; - cVHi |= rHi; - } - if ((bitCount(rLo) + bitCount(rHi)) < MIN_LEN) penalty += 8000; - var wordLen = bitCount(rLo) + bitCount(rHi); - if (wordLen > 6) penalty += (wordLen - 6) * 1000L; - } else penalty += 25000; } if (!hasSlots) return 1_000_000_000L; + penalty += (bitCount(cHLo2) + bitCount(cHHi2) + bitCount(cVLo2) + bitCount(cVHi2)) * 10000L; Arrays.fill(rCount, 0); Arrays.fill(cCount, 0); - for (var i = 0; i < numClues; i++) { - var idx = activeCIdx[i]; + for (int i = 0; i < numClues; i++) { + int idx = activeCIdx[i]; rCount[idx & 7]++; cCount[idx >> 3]++; } - for (var rc : rCount) if (rc < 2) penalty += (2 - rc) * 4000L; - for (var cc : cCount) if (cc < 2) penalty += (2 - cc) * 4000L; - - // Connectiviteitscheck - for (var i = 0; i < numClues; i++) { - adjLo[i] = 0; - adjHi[i] = 0; + for (int rc : rCount) { + if (rc < 2) penalty += (2 - rc) * 4000L; + if (rc > 4) penalty += (rc - 4) * 4000L; } - for (var i = 0; i < numClues; i++) { - for (var j = i + 1; j < numClues; j++) { - var connected = false; - // 1. Intersectie + for (int cc : cCount) { + if (cc < 2) penalty += (2 - cc) * 4000L; + if (cc > 4) penalty += (cc - 4) * 4000L; + } + + // Connectivity check + for (int i = 0; i < numClues; i++) adjLo[i] = 0; + for (int i = 0; i < numClues; i++) { + for (int j = i + 1; j < numClues; j++) { if (((activeSLo[i] & activeSLo[j]) | (activeSHi[i] & activeSHi[j])) != 0) { - connected = true; - } - - if (connected) { - if (j < 64) adjLo[i] |= (1L << j); - else adjHi[i] |= (1L << (j - 64)); - if (i < 64) adjLo[j] |= (1L << i); - else adjHi[j] |= (1L << (i - 64)); + adjLo[i] |= (1L << j); + adjLo[j] |= (1L << i); } } } if (numClues > 0) { - var maxReached = 0; - long totalReachedLo = 0, totalReachedHi = 0; - for (var i = 0; i < numClues; i++) { - if (i < 64) { if ((totalReachedLo & (1L << i)) != 0) continue; } else { if ((totalReachedHi & (1L << (i - 64))) != 0) continue; } - - var currentReachedLo = (i < 64) ? (1L << i) : 0; - var currentReachedHi = (i >= 64) ? (1L << (i - 64)) : 0; + int maxReached = 0; + long totalReached = 0; + for (int i = 0; i < numClues; i++) { + if ((totalReached & (1L << i)) != 0) continue; + long currentReached = (1L << i); stack[0] = i; - var sp = 1; - var count = 0; + int sp = 1, count = 0; while (sp > 0) { - var cur = stack[--sp]; + int cur = stack[--sp]; count++; - var nLo = adjLo[cur] & ~currentReachedLo; - var nHi = adjHi[cur] & ~currentReachedHi; - while (nLo != 0) { - var lsb = nLo & -nLo; - var idx = numberOfTrailingZeros(lsb); - currentReachedLo |= lsb; - stack[sp++] = idx; - nLo &= ~lsb; - } - while (nHi != 0) { - var lsb = nHi & -nHi; - var idx = 64 | numberOfTrailingZeros(lsb); - currentReachedHi |= lsb; - stack[sp++] = idx; - nHi &= ~lsb; + long neighbors = adjLo[cur] & ~currentReached; + while (neighbors != 0) { + long lsb = neighbors & -neighbors; + currentReached |= lsb; + stack[sp++] = numberOfTrailingZeros(lsb); + neighbors &= ~lsb; } } if (count > maxReached) maxReached = count; - totalReachedLo |= currentReachedLo; - totalReachedHi |= currentReachedHi; + totalReached |= currentReached; } if (maxReached < numClues) { penalty += (numClues - maxReached) * 4000L; @@ -422,57 +443,52 @@ public final class Masker { } } - for (var bits = ~lo_cl & MASK_LO; bits != X; bits &= bits - 1) { - var clueIdx = numberOfTrailingZeros(bits); + for (long bits = ~lo_cl & MASK_LO; bits != X; bits &= bits - 1) { + int clueIdx = numberOfTrailingZeros(bits); var rci = IT[clueIdx]; - if ((4 - rci.nbrCount()) + bitCount(rci.n1() & lo_cl) + bitCount(rci.n2() & hi_cl) >= 3) penalty += 400; - var h = (cHLo & (1L << clueIdx)) != X; - var v = (cVLo & (1L << clueIdx)) != X; - if (!h && !v) penalty += 2000; - else if (h && v) { /* ok */ } else if (((h ? cHLo2 : cVLo2) & (1L << clueIdx)) != X) penalty += 1000; - else penalty += 1000; + if ((4 - rci.nbrCount()) + bitCount(rci.n1() & lo_cl) + bitCount(rci.n2() & hi_cl) >= 3) penalty += 3000; + boolean h = (cHLo & (1L << clueIdx)) != X, v = (cVLo & (1L << clueIdx)) != X; + if (!h && !v) penalty += 4000; + else if (h && v) { /* ok */ } else penalty += 1500; } - for (var bits = ~hi_cl & MASK_HI; bits != X; bits &= bits - 1) { - var clueIdx = numberOfTrailingZeros(bits); + for (long bits = ~hi_cl & MASK_HI; bits != X; bits &= bits - 1) { + int clueIdx = numberOfTrailingZeros(bits); var rci = IT[64 | clueIdx]; - if ((4 - rci.nbrCount()) + bitCount(rci.n1() & lo_cl) + bitCount(rci.n2() & hi_cl) >= 3) penalty += 400; - var h = (cHHi & (1L << clueIdx)) != X; - var v = (cVHi & (1L << clueIdx)) != X; - if (!h && !v) - penalty += 2000; - else if (h && v) { /* ok */ } else if (((h ? cHHi2 : cVHi2) & (1L << clueIdx)) != X) penalty += 1000; - else penalty += 1000; + if ((4 - rci.nbrCount()) + bitCount(rci.n1() & lo_cl) + bitCount(rci.n2() & hi_cl) >= 3) penalty += 3000; + boolean h = (cHHi & (1L << clueIdx)) != X, v = (cVHi & (1L << clueIdx)) != X; + if (!h && !v) penalty += 4000; + else if (h && v) { /* ok */ } else penalty += 1500; } long remLo = lo_cl, remHi = hi_cl; while ((remLo | remHi) != X) { - long compLo = 0, compHi = 0; - if (remLo != X) compLo = lowestOneBit(remLo); - else compHi = lowestOneBit(remHi); - long lastLo, lastHi; - do { - lastLo = compLo; - lastHi = compHi; - long expandedLo = 0, expandedHi = 0; - for (var bits = compLo; bits != X; bits &= bits - 1) { - var idx = numberOfTrailingZeros(bits); - expandedLo |= NBR_LO[idx]; - expandedHi |= NBR_HI[idx]; + int start = (remLo != X) ? numberOfTrailingZeros(remLo) : (64 | numberOfTrailingZeros(remHi)); + stack[0] = start; + long currentCompLo = (start < 64) ? (1L << start) : 0; + long currentCompHi = (start >= 64) ? (1L << (start - 64)) : 0; + int sp = 1, s = 0; + while (sp > 0) { + int cur = stack[--sp]; + s++; + long nLo = NBR_LO[cur] & lo_cl & ~currentCompLo; + long nHi = NBR_HI[cur] & hi_cl & ~currentCompHi; + while (nLo != 0) { + long lsb = nLo & -nLo; + currentCompLo |= lsb; + stack[sp++] = numberOfTrailingZeros(lsb); + nLo &= ~lsb; } - for (var bits = compHi; bits != X; bits &= bits - 1) { - var idx = 64 | numberOfTrailingZeros(bits); - expandedLo |= NBR_LO[idx]; - expandedHi |= NBR_HI[idx]; + while (nHi != 0) { + long lsb = nHi & -nHi; + currentCompHi |= lsb; + stack[sp++] = 64 | numberOfTrailingZeros(lsb); + nHi &= ~lsb; } - compLo |= expandedLo & lo_cl; - compHi |= expandedHi & hi_cl; - } while (compLo != lastLo || compHi != lastHi); - var s = bitCount(compLo) + bitCount(compHi); - if (s >= 2) penalty += (long) (s - 1) * 720; - remLo &= ~compLo; - remHi &= ~compHi; + } + if (s >= 2) penalty += (long) (s - 1) * 3000; + remLo &= ~currentCompLo; + remHi &= ~currentCompHi; } - return penalty; } public static boolean hasRoomForClue(Clues c, int key) { @@ -490,7 +506,7 @@ public final class Masker { var key = Slot.packSlotKey(ri, d_idx); if (hasRoomForClue(g, key)) { g.setClueLo(1L << ri, d_idx); - if (isValid(g)) placed++; + if (isValid(g, ri)) placed++; else g.clearClueLo(~(1L << ri)); } } else { @@ -499,7 +515,7 @@ public final class Masker { var key = Slot.packSlotKey(ri, d_idx); if (hasRoomForClue(g, key)) { g.setClueHi(1L << (ri & 63), d_idx); - if (isValid(g)) placed++; + if (isValid(g, ri)) placed++; else g.clearClueHi(~(1L << (ri & 63))); } } @@ -518,11 +534,11 @@ public final class Masker { if (hasRoomForClue(c, key)) { if (isLo(ri)) { c.setClueLo(1L << ri, d); - if (!isValid(c)) c.clearClueLo(~(1L << ri)); + if (!isValid(c, ri)) c.clearClueLo(~(1L << ri)); else continue; } else { c.setClueHi(1L << (ri & 63), d); - if (!isValid(c)) c.clearClueHi(~(1L << (ri & 63))); + if (!isValid(c, ri)) c.clearClueHi(~(1L << (ri & 63))); else continue; } } @@ -532,11 +548,11 @@ public final class Masker { var oldD = c.getDir(ri); if (isLo(ri)) { c.clearClueLo(~(1L << ri)); - if (!isValid(c)) c.setClueLo(1L << ri, oldD); + if (!isValid(c, ri)) c.setClueLo(1L << ri, oldD); else continue; } else { c.clearClueHi(~(1L << (ri & 63))); - if (!isValid(c)) c.setClueHi(1L << (ri & 63), oldD); + if (!isValid(c, ri)) c.setClueHi(1L << (ri & 63), oldD); else continue; } } @@ -547,11 +563,11 @@ public final class Masker { var oldD = c.getDir(ri); if (isLo(ri)) { c.setClueLo(1L << ri, d); - if (!isValid(c)) c.setClueLo(1L << ri, oldD); + if (!isValid(c, ri)) c.setClueLo(1L << ri, oldD); else continue; } else { c.setClueHi(1L << (ri & 63), d); - if (!isValid(c)) c.setClueHi(1L << (ri & 63), oldD); + if (!isValid(c, ri)) c.setClueHi(1L << (ri & 63), oldD); else continue; } } @@ -565,7 +581,7 @@ public final class Masker { else c.clearClueHi(~(1L << (ri & 63))); if (isLo(nri)) c.setClueLo(1L << nri, d); else c.setClueHi(1L << (nri & 63), d); - if (!isValid(c)) { + if (!isValid(c, -1)) { // For MOVE, it's easier to check full grid or we'd need to check both ri and nri if (isLo(nri)) c.clearClueLo(~(1L << nri)); else c.clearClueHi(~(1L << (nri & 63))); if (isLo(ri)) c.setClueLo(1L << ri, d); @@ -644,11 +660,11 @@ public final class Masker { } for (var gen = 0; gen < gens; gen++) { - if (Thread.currentThread().isInterrupted()) break; + if (Thread.currentThread().isInterrupted()) return null; var children = new GridAndFit[offspring]; var childCount = 0; for (var k = 0; k < offspring; k++) { - if (Thread.currentThread().isInterrupted()) break; + if (Thread.currentThread().isInterrupted()) return null; var p1 = rng.rand(pop); var p2 = rng.rand(pop); var child = crossover(p1.grid, p2.grid); diff --git a/src/main/java/puzzle/Riddle.java b/src/main/java/puzzle/Riddle.java index 866c50f..79b3f4c 100644 --- a/src/main/java/puzzle/Riddle.java +++ b/src/main/java/puzzle/Riddle.java @@ -104,9 +104,9 @@ public class Riddle { try { val rec = Meta.lookupSilent(w); - System.out.println("\nQuery: w=" + w + " -> i=" + rec.mmap()); + if (Main.VERBOSE) System.out.println("\nQuery: w=" + w + " -> i=" + rec.mmap()); var word1 = Lemma.asWord(w, bytes); - System.out.println(" word=" + word1 + "\n" + " simpel=" + rec.simpel() + "\n" + " clues=" + Arrays.toString(rec.clues())); + if (Main.VERBOSE) System.out.println(" word=" + word1 + "\n" + " simpel=" + rec.simpel() + "\n" + " clues=" + Arrays.toString(rec.clues())); return new ShaLemma(word1, rec); } catch (Exception e) { throw new RuntimeException(e);