package puzzle; import anno.ConstGen; import anno.GenerateNeighbor; import anno.GenerateNeighbors; import anno.Shaped; import lombok.AllArgsConstructor; import lombok.NoArgsConstructor; import lombok.val; import precomp.Neighbors9x8; import static java.lang.Long.bitCount; import static java.lang.Long.numberOfLeadingZeros; 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] */ @ConstGen(C = 9, R = 8, packageName = "precomp", className = "Const9x8") @GenerateNeighbors({ @GenerateNeighbor(C = 9, R = 8, packageName = "precomp", className = "Neighbors9x8", MIN_LEN = 2), @GenerateNeighbor(C = 4, R = 3, packageName = "precomp", className = "Neighbors4x3", MIN_LEN = 2) }) public record SwedishGenerator() { public static final int MAX_TRIES_PER_SLOT = 500;// MAX_TRIES_PER_SLOT; public static final long X = 0L; @Shaped static final int SIZE = Neighbors9x8.SIZE; @Shaped private static final long RANGE_0_SIZE = Neighbors9x8.RANGE_0_SIZE; @Shaped private static final long RANGE_0_624 = Neighbors9x8.RANGE_0_624; interface Bit1029 { private static int wordIndex(int bitIndex) { return bitIndex >> 6; } static boolean get(long[] bits, int bitIndex) { return (bits[wordIndex(bitIndex)] & 1L << bitIndex) != X; } static void set(long[] bits, int bitIndex) { bits[wordIndex(bitIndex)] |= 1L << bitIndex; } static void clear(long[] bits, int bitIndex) { bits[wordIndex(bitIndex)] &= ~(1L << bitIndex); } } //@formatter:off public record Dict(DictEntry[] index,DictEntry[] reversed, int length) { public Dict(DictEntry[] index,int length){this(index,index,length);} } public record DictEntry(long[] words, long[][] posBitsets, int length, int numlong) { } @AllArgsConstructor @NoArgsConstructor public static final class Assign { long w; } @AllArgsConstructor public static final class Grid { public final byte[] g; public long lo, hi; } public record FillResult(boolean ok, long nodes, long backtracks, int lastMRV, long elapsed ) { } //@formatter:on public static final class Rng { 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; } static final byte[] BYTE = new byte[]{ 0, 1, 2, 3, 4, 5 }; public byte randomClueDir() { return rand(BYTE); } public T rand(T[] p) { return p[(int) (((nextU32() & 0xFFFFFFFFL) % ((long) p.length)))]; } public byte rand(byte[] p) { return p[(int) (((nextU32() & 0xFFFFFFFFL) % ((long) p.length)))]; } 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); } } public interface Lemma { long LETTER_MASK = (1L << 40) - 1; // low 40 bits long LETTER_AND_LENGTH_MASK = (1L << 43) - 1; static long from(byte[] word) { return packShiftIn(word) | ((long) (word.length - 1) << 40); } static long pack(long w, int shardIndex) { return w | (((long) shardIndex) << 43) | ((long) length0(w)) << 40; } static long packShiftIn(byte[] b) { long w = 0; for (var i = b.length - 1; i >= 0; i--) w = (w << 5) | ((long) b[i] & 31); return w; } static long from(String word) { return packShiftIn(word.getBytes(US_ASCII)) | ((long) (word.length() - 1) << 40); } static byte byteAt(long word, int idx) { return (byte) ((word >>> (idx * 5)) & 0b11111L); } static int length0(long word) { return ((63 - numberOfLeadingZeros(word & LETTER_MASK)) / 5); } static String asWord(long word, byte[] bytes) { var bi = 0; for (var w = word & LETTER_MASK; w != 0; w >>>= 5) bytes[bi++] = (byte) ((w & 31) | 64); return new String(bytes, 0, bi, US_ASCII); } static int unpackIndex(long w) { return (int) (w >>> 40); } static int unpackSize(long w) { return (int) (w >>> 40) & 7; } static int unpackLetters(long w) { return (int) (w & LETTER_MASK); } static long packLetterAndLengthBits(long w) { return w & LETTER_AND_LENGTH_MASK; } } public record Slotinfo(int key, long lo, long hi, int score, Assign assign, DictEntry entry, int minL) { 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 long patternForSlot(final long glo, final long ghi, final byte[] g, final long lo, final long hi) { if (((lo & glo) == X) && (hi & ghi) == X) return X; long p = 0; int n = 0, offset, idx; for (var b = lo & glo; b != X; b &= b - 1) { idx = numberOfTrailingZeros(b); p |= ((long) (bitCount(lo & ((1L << idx) - 1)) * 26 + g[idx])) << (n++ << 3); } offset = bitCount(lo); for (var b = hi & ghi; b != X; b &= b - 1) { idx = numberOfTrailingZeros(b); p |= ((long) ((offset + bitCount(hi & ((1L << idx) - 1))) * 26 + g[64 | idx])) << (n++ << 3); } return p; } /// pattern cannot be X public static int[] candidateInfoForPattern(long[] res, long pattern, long[][] posBitsets, int numLongs) { var indices = new int[candidateCountForPattern(res, pattern, posBitsets, numLongs)]; for (int k = 0, ki = 0; k < numLongs; k++) for (var 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[][] pos, final int num) { System.arraycopy(pos[(int) (pattern & 0xFF) - 1], 0, res, 0, num); for (var p = pattern >>> 8; p != X; p >>>= 8) { var bs = pos[(int) (p & 0xFF) - 1]; for (var k = 0; k < num; k++) res[k] &= bs[k]; } var count = 0; for (var k = 0; k < num; k++) count += bitCount(res[k]); return count; } public static FillResult fillMask(final Rng rng, final Slotinfo[] slots, final long lo, final long hi, final byte[] g) { val used = new long[2048]; val bitset = new long[2500]; val TOTAL = slots.length; val t0 = System.currentTimeMillis(); class Solver { static final int PICK_NOT_DONE = -1; static final int PICK_DONE = 0; long nodes; long tracks; long glo = lo, ghi = hi; Slotinfo slot; int[] indices; boolean place(final long lo, final long hi, final long w) { int idx; for (var b = lo & glo; b != X; b &= b - 1) if (g[idx = numberOfTrailingZeros(b)] != Lemma.byteAt(w, bitCount(lo & ((1L << idx) - 1)))) return true; var bcLo = bitCount(lo); for (var b = hi & ghi; b != X; b &= b - 1) if (g[64 | (idx = numberOfTrailingZeros(b))] != Lemma.byteAt(w, bcLo + bitCount(hi & ((1L << idx) - 1)))) return true; long maskLo = lo & ~glo, maskHi = hi & ~ghi; if ((maskLo | maskHi) != X) { for (var b = maskLo; b != X; b &= b - 1) g[idx = numberOfTrailingZeros(b)] = Lemma.byteAt(w, bitCount(lo & ((1L << idx) - 1))); for (var 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; } return false; } int chooseMRV() { Slotinfo best = null; int max = -1, bestScore = -1; for (int i = 0, n = TOTAL; i < n; i++) { var s = slots[i]; if (s.assign.w != X) continue; var pattern = patternForSlot(glo, ghi, g, s.lo, s.hi); var index = s.entry; var count = pattern == X ? index.length : candidateCountForPattern(bitset, pattern, index.posBitsets, index.numlong); if (count == 0) return PICK_NOT_DONE; if (best == null || count < max || (count == max && s.score > bestScore)) { best = s; bestScore = s.score; max = count; if (count <= 1) break; } } if (best == null) return PICK_DONE; var pattern = patternForSlot(glo, ghi, g, best.lo, best.hi); slot = best; var index = best.entry; indices = pattern == X ? null : candidateInfoForPattern(bitset, pattern, index.posBitsets, index.numlong); return 1; } boolean backtrack(int depth) { if (Thread.currentThread().isInterrupted() || (System.currentTimeMillis() - t0) > 20_000) return false; nodes++; var status = chooseMRV(); if (status == PICK_DONE) return true; if (status == PICK_NOT_DONE) { tracks++; return false; } val info = indices; val s = slot; val slo = s.lo; val shi = s.hi; val assign = s.assign; val words = s.entry.words; long low, top; if (info != null && info.length > 0) { var L = info.length; var tries = Math.min(MAX_TRIES_PER_SLOT, L); for (var t = 0; t < tries; t++) { var w = words[info[rng.biasedIndexPow3(L - 1)]]; var lemIdx = Lemma.unpackIndex(w); if (Bit1029.get(used, lemIdx)) continue; low = glo; top = ghi; if (place(slo, shi, w)) continue; Bit1029.set(used, lemIdx); assign.w = w; if (backtrack(depth + 1)) return true; assign.w = X; Bit1029.clear(used, lemIdx); glo = low; ghi = top; } tracks++; return false; } var N = words.length; for (var t = 0; t < s.minL; t++) { var w = words[rng.biasedIndexPow3(N - 1)]; var lem = Lemma.unpackIndex(w); if (Bit1029.get(used, lem)) continue; low = glo; top = ghi; if (place(slo, shi, w)) continue; Bit1029.set(used, lem); assign.w = w; if (backtrack(depth + 1)) return true; assign.w = X; Bit1029.clear(used, lem); glo = low; ghi = top; } tracks++; return false; } } var solver = new Solver(); var ok = solver.backtrack(0); return new FillResult(ok, solver.nodes, solver.tracks, solver.slot == null ? 0 : solver.slot.entry.length, System.currentTimeMillis() - t0); } }