package puzzle; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.RequiredArgsConstructor; import lombok.experimental.Accessors; import lombok.experimental.Delegate; import lombok.val; import precomp.Neighbors9x8; import precomp.Neighbors9x8.rci; import java.util.Locale; import static java.lang.Long.*; import static java.lang.Long.numberOfTrailingZeros; import static java.nio.charset.StandardCharsets.US_ASCII; /** * NOTE: * A) generate randoms for and idx and direction (clue together) * B) convert IDX_PATH_0_BASE into bit-pattern (i think) Length should not be checked, just left empty if not good. * C) store more information in the byte[] even consider the long[] or try sqeeze the 288 options (clue) in a byte. * F) pre-determine random clue arrangements, so they do not involve impossible clue's such as bottom at last or the line before that and such to all sides * G) Check 3wall, the current implementation may be faster, but the accuracy is lower, degrade performance on the filler */ /** * SwedishGenerator.java * * Usage: * javac SwedishGenerator.java * java SwedishGenerator [--seed N] [--pop N] [--gens N] [--tries N] [--words word-list.txt] */ @SuppressWarnings("ALL") public class SwedishGenerator { public static final long GT_1_OFFSET_53_BIT = 0x3E00000000000000L; public static final long X = 0L; public static final int LOG_EVERY_MS = 200; public static final int BAR_LEN = 22; public static final int C = Config.PUZZLE_COLS; public static final double CROSS_R = (C - 1) / 2.0; public static final int R = Config.PUZZLE_ROWS; public static final double CROSS_C = (R - 1) / 2.0; public static final int SIZE = C * R;// ~18 public static final int SIZE_MIN_1 = SIZE - 1;// ~18 public static final double SIZED = (double) SIZE;// ~18 public static final long MASK_LO = (SIZE >= 64) ? -1L : (1L << SIZE) - 1; public static final long MASK_HI = (SIZE <= 64) ? 0L : (SIZE >= 128 ? -1L : (1L << (SIZE - 64)) - 1); public static final int MAX_WORD_LENGTH = C <= R ? C : R; public static final int MAX_WORD_LENGTH_PLUS_ONE = MAX_WORD_LENGTH + 1; public static final int MIN_LEN = Config.MIN_LEN; public static final int MAX_TRIES_PER_SLOT = Config.MAX_TRIES_PER_SLOT; public static final int STACK_SIZE = 64; public static final char C_DASH = '\0'; public static final byte DASH = (byte) C_DASH; public static final long RANGE_0_SIZE = (long) SIZE_MIN_1 - 0L + 1L; public static final long RANGE_0_624 = 624L - 0L + 1L; public static final int CLUE_INDEX_MAX_SIZE = (288 | 3) + 1; public static int clamp(int x, int a, int b) { return Math.max(a, Math.min(b, x)); } interface Bit1029 { static long[] bit1029() { return new long[2048]; } private static int wordIndex(int bitIndex) { return bitIndex >> 6; } static boolean get(long[] bits, int bitIndex) { return (bits[wordIndex(bitIndex)] & 1L << bitIndex) != 0L; } static void set(long[] bits, int bitIndex) { bits[wordIndex(bitIndex)] |= 1L << bitIndex; } static void clear(long[] bits, int bitIndex) { bits[wordIndex(bitIndex)] &= ~(1L << bitIndex); } } static String padRight(String s, int n) { if (s.length() >= n) return s; return s + " ".repeat(n - s.length()); } @AllArgsConstructor public static class Pick { public Slotinfo slot; public int[] indices; public int count; } public static final long[] OFFSETS_D_IDX = Neighbors9x8.OFFSET_D_IDX_0_BASE; public static final rci[] IT = Neighbors9x8.IT; public static final long[] NBR8_PACKED_LO = Neighbors9x8.NBR8_PACKED_LO; public static final long[] NBR8_PACKED_HI = Neighbors9x8.NBR8_PACKED_HI; public static final long[] PATH_LO = Neighbors9x8.PATH_LO; public static final long[] PATH_HI = Neighbors9x8.PATH_HI; public static final Pick PICK_DONE = null;//new Pick(null, null, 0, true); public static final Pick PICK_NOT_DONE = new Pick(null, null, 0); @RequiredArgsConstructor public static final class FillStats { public double simplicity; } public static record FillResult(boolean ok, long nodes, long backtracks, int lastMRV, long elapsed, FillStats stats) { } public static final class Rng { @Getter private int x; public Rng(int seed) { var s = seed; if (s == 0) s = 1; this.x = s; } public int nextU32() { var y = x; y ^= (y << 13); y ^= (y >>> 17); y ^= (y << 5); x = y; return y; } public int randint2bit() { return nextU32() & 3; } public byte randint2bitByte() { var b = (byte) (nextU32() & 3); /*if (b == 3) { return 1; }*/ return b; } public int randint(int max) { return (int) (((nextU32() & 0xFFFFFFFFL) % ((long) max - 0L + 1L))); } public int randint0_SIZE() { return (int) (((nextU32() & 0xFFFFFFFFL) % RANGE_0_SIZE)); } public int randint0_624() { return (int) (((nextU32() & 0xFFFFFFFFL) % RANGE_0_624)); } public double nextFloat() { return (nextU32() & 0xFFFFFFFFL) / 4294967295.0; } public int biasedIndexPow3(int N) { return (int) (((Math.min(nextU32(), Math.min(nextU32(), nextU32())) & 0xFFFFFFFFL) * (long) N) >>> 32); } } @AllArgsConstructor public static class Grid { public final byte[] g; public long lo, hi; public static int offset(int r, int c) { return r | (c << 3); } } public static record DictEntry(long[] words, long[][] posBitsets, int length, int numlong) { } public static interface Lemma { static final long LETTER_MASK = (1L << 40) - 1; // low 40 bits static final long INDEX_MASK = (1L << 24) - 1; // 24 bits static long pack(String word) { return pack(word.getBytes(US_ASCII)); } static long packW(byte[] b) { return pack(b) /*| ((long) index << 40)*/; } static long pack(byte[] b) { long w = 0; for (var i = 0; i < b.length; i++) w |= ((long) b[i] & 31) << (i * 5); return w; } static public long from(String word) { return packW(word.getBytes(US_ASCII)); } static byte byteAt(long word, int idx) { return (byte) ((word >>> (idx * 5)) & 0b11111); } static int length(long word) { return ((63 - numberOfLeadingZeros(word & LETTER_MASK)) / 5) + 1; } static int length0(long word) { return ((63 - numberOfLeadingZeros(word & LETTER_MASK)) / 5); } static ThreadLocal BYTES = ThreadLocal.withInitial(() -> new byte[MAX_WORD_LENGTH]); public static String asWord(long word) { val len = Lemma.length(word); var b = BYTES.get();//new byte[Lemma.length(word)]; for (int i = 0, bi = 0; i < len * 5; bi++, i += 5) b[bi] = (byte) (((word >>> i) & 31) | 64); return new String(b, 0, 0, len); } static int unpackIndex(long w) { return (int) (w >>> 40); } static int unpackLetters(long w) { return (int) (w & LETTER_MASK); } } public static record Dict(DictEntry[] index, int length) { } @AllArgsConstructor @NoArgsConstructor static class Assign { long w; } public static record Slotinfo(int key, long lo, long hi, int score, Assign assign, DictEntry entry) { public static int wordCount(int k, Slotinfo[] arr) { for (var n = 1; n < arr.length; n++) if (arr[n].assign.w != X) k++; return k; } public static boolean increasing(int dir) { return (dir & 2) == 0; } } public static boolean isLo(int n) { return (n & 64) == 0; } public static long patternForSlot(final long glo, final long ghi, final byte[] g, final int key, final long lo, final long hi) { if (((lo & glo) | (hi & ghi)) == X) return 0; long p = 0; int n = 0; if (Slotinfo.increasing(key)) { for (long b = lo & glo; b != X; b &= b - 1) { int idx = numberOfTrailingZeros(b); int i = bitCount(lo & ((1L << idx) - 1)); p |= ((long) (i * 26 + g[idx])) << (n++ << 3); } int offset = bitCount(lo); for (long b = hi & ghi; b != X; b &= b - 1) { int idx = numberOfTrailingZeros(b); int i = offset + bitCount(hi & ((1L << idx) - 1)); p |= ((long) (i * 26 + g[64 | idx])) << (n++ << 3); } } else { int offset = bitCount(hi); for (long b = hi & ghi; b != X; b &= b - 1) { int idx = numberOfTrailingZeros(b); int i = bitCount(hi & ~((1L << idx) | ((1L << idx) - 1))); p |= ((long) (i * 26 + g[64 | idx])) << (n++ << 3); } for (long b = lo & glo; b != X; b &= b - 1) { int idx = numberOfTrailingZeros(b); int i = offset + bitCount(lo & ~((1L << idx) | ((1L << idx) - 1))); p |= ((long) (i * 26 + g[idx])) << (n++ << 3); } } return p; } /// pattern cannot be X public static int[] candidateInfoForPattern(long[] res, long pattern, long[][] posBitsets, int numLongs) { System.arraycopy(posBitsets[(int) (pattern & 0xFF) - 1], 0, res, 0, numLongs); for (long p = pattern >>> 8; p != X; p >>>= 8) { long[] bs = posBitsets[(int) (p & 0xFF) - 1]; for (int k = 0; k < numLongs; k++) res[k] &= bs[k]; } int count = 0; for (int k = 0; k < numLongs; k++) count += bitCount(res[k]); int[] indices = new int[count]; for (int k = 0, ki = 0; k < numLongs; k++) { for (long w = res[k]; w != X; w &= w - 1) indices[ki++] = (k << 6) | numberOfTrailingZeros(w); } return indices; } /// pattern cannot be X public static int candidateCountForPattern(final long[] res, final long pattern, final long[][] posBitsets, final int numLongs) { System.arraycopy(posBitsets[(int) (pattern & 0xFF) - 1], 0, res, 0, numLongs); for (long p = pattern >>> 8; p != X; p >>>= 8) { long[] bs = posBitsets[(int) (p & 0xFF) - 1]; for (int k = 0; k < numLongs; k++) res[k] &= bs[k]; } int count = 0; for (int k = 0; k < numLongs; k++) count += bitCount(res[k]); return count; } public static FillResult fillMask(final Rng rng, final Slotinfo[] slots, final Grid grid, final boolean multiThreaded) { val NO_LOG = (!Main.VERBOSE || multiThreaded); val used = new long[2048]; val bitset = new long[2500]; val g = grid.g; val TOTAL = slots.length; val t0 = System.currentTimeMillis(); val CARRIER = new Pick(null, null, 0); class Solver { long nodes; long backtracks; int lastMRV; long lastLog = t0, glo = grid.lo, ghi = grid.hi; Pick current = CARRIER; void renderProgress() { var now = System.currentTimeMillis(); if ((now - lastLog) < LOG_EVERY_MS) return; lastLog = (now); var done = 0; for (var lemma : slots) { if (lemma.assign.w != X) done++; } var pct = (TOTAL == 0) ? 100 : (int) Math.floor((done / (double) TOTAL) * 100); var filled = Math.min(BAR_LEN, (int) Math.floor((pct / 100.0) * BAR_LEN)); var bar = "[" + "#".repeat(filled) + "-".repeat(BAR_LEN - filled) + "]"; var elapsed = String.format(Locale.ROOT, "%.1fs", (now - t0) / 1000.0); var msg = String.format( Locale.ROOT, "%s %d/%d slots | nodes=%d | backtracks=%d | mrv=%d | %s", bar, done, TOTAL, nodes, backtracks, lastMRV, elapsed ); System.out.print("\r" + padRight(msg, 120)); System.out.flush(); } boolean placeWord(final int key, final long lo, final long hi, final long w) { int idx; if (Slotinfo.increasing(key)) { for (long b = lo & glo; b != X; b &= b - 1) if (g[idx = numberOfTrailingZeros(b)] != Lemma.byteAt(w, bitCount(lo & ((1L << idx) - 1)))) return false; int bcLo = bitCount(lo); for (long b = hi & ghi; b != X; b &= b - 1) if (g[64 | (idx = numberOfTrailingZeros(b))] != Lemma.byteAt(w, bcLo + bitCount(hi & ((1L << idx) - 1)))) return false; long maskLo = lo & ~glo, maskHi = hi & ~ghi; if ((maskLo | maskHi) != X) { for (long b = maskLo; b != X; b &= b - 1) g[idx = idx = numberOfTrailingZeros(b)] = Lemma.byteAt(w, bitCount(lo & ((1L << idx) - 1))); for (long b = maskHi; b != X; b &= b - 1) g[64 | (idx = numberOfTrailingZeros(b))] = Lemma.byteAt(w, bcLo + bitCount(hi & ((1L << idx) - 1))); glo |= maskLo; ghi |= maskHi; } } else { int bcHi = bitCount(hi); for (long b = hi & ghi; b != X; b &= b - 1) if (g[64 | (idx = numberOfTrailingZeros(b))] != Lemma.byteAt(w, bitCount(hi & ~((1L << idx) | ((1L << idx) - 1))))) return false; for (long b = lo & glo; b != X; b &= b - 1) if (g[idx = numberOfTrailingZeros(b)] != Lemma.byteAt(w, bcHi + bitCount(lo & ~((1L << idx) | ((1L << idx) - 1))))) return false; long maskLo = lo & ~glo, maskHi = hi & ~ghi; if ((maskLo | maskHi) != X) { for (long b = maskHi; b != X; b &= b - 1) g[64 | (idx = numberOfTrailingZeros(b))] = Lemma.byteAt(w, bitCount(hi & ~((1L << idx) | ((1L << idx) - 1)))); for (long b = maskLo; b != X; b &= b - 1) g[idx = numberOfTrailingZeros(b)] = Lemma.byteAt(w, bcHi + bitCount(lo & ~((1L << idx) | ((1L << idx) - 1)))); glo |= maskLo; ghi |= maskHi; } } return true; } void chooseMRV() { Slotinfo best = null; for (int i = 0, count, count2 = -1, bestScore = -1, n = TOTAL; i < n; i++) { var s = slots[i]; if (s.assign.w != X) continue; var pattern = patternForSlot(glo, ghi, g, s.key, s.lo, s.hi); var index = s.entry; count = pattern == X ? index.length : candidateCountForPattern(bitset, pattern, index.posBitsets, index.numlong); if (count == 0) { current = PICK_NOT_DONE; return; } if (best == null || count < count2 || (count == count2 && s.score > bestScore)) { best = s; bestScore = s.score; count2 = count; if (count <= 1) break; } } // Re-calculate for the best slot to get actual indices if (best == null) { current = PICK_DONE; return; } var pattern = patternForSlot(glo, ghi, g, best.key, best.lo, best.hi); var index = best.entry; current = CARRIER; current.slot = best; current.count = index.length; if (pattern == X) { current.indices = null; return; } current.indices = candidateInfoForPattern(bitset, pattern, index.posBitsets, index.numlong); } boolean backtrack(int depth) { if (Thread.currentThread().isInterrupted()) return false; nodes++; if (20_000 > 0 && (System.currentTimeMillis() - t0) > 20_000) return false; chooseMRV(); var pick = current; if (pick == PICK_DONE) return true; if (pick.slot == null) { backtracks++; return false; } val info = pick.indices; lastMRV = pick.count; if (!NO_LOG) renderProgress(); val s = pick.slot; val k = s.key; val slo = s.lo; val shi = s.hi; val entry = s.entry; long low, top; if (info != null && info.length > 0) { var idxs = info; var L = idxs.length; var tries = Math.min(MAX_TRIES_PER_SLOT, L); for (var t = 0; t < tries; t++) { var r = rng.nextFloat(); //int idxInArray = rng.biasedIndexPow3(L - 1); var arrIndex = (int) (r * r * r * (L - 1)); var shardIdx = idxs[arrIndex]; var w = entry.words[shardIdx]; var lemIdx = Lemma.unpackIndex(w); if (Bit1029.get(used, lemIdx)) continue; low = glo; top = ghi; if (!placeWord(k, slo, shi, w)) continue; Bit1029.set(used, lemIdx); s.assign.w = w; if (backtrack(depth + 1)) return true; s.assign.w = X; Bit1029.clear(used, lemIdx); glo = low; ghi = top; } backtracks++; return false; } var N = entry.words.length; var tries = Math.min(MAX_TRIES_PER_SLOT, N); for (var t = 0; t < tries; t++) { double r = rng.nextFloat(); var shardIdx = (int) (r * r * r * (N - 1)); var w = entry.words[shardIdx]; var lemIdx = Lemma.unpackIndex(w); if (Bit1029.get(used, lemIdx)) continue; low = glo; top = ghi; if (!placeWord(k, slo, shi, w)) continue; Bit1029.set(used, lemIdx); s.assign.w = w; if (backtrack(depth + 1)) return true; s.assign.w = X; Bit1029.clear(used, lemIdx); glo = low; ghi = top; } backtracks++; return false; } } // initial render (same feel) var solver = new Solver(); if (!NO_LOG) solver.renderProgress(); var ok = solver.backtrack(0); // final progress line grid.lo = solver.glo; grid.hi = solver.ghi; return new FillResult(ok, solver.nodes, solver.backtracks, solver.lastMRV, System.currentTimeMillis() - t0, new FillStats()); } }