Files
puzzle-generator/src/main/java/puzzle/SwedishGenerator.java
2026-01-20 07:08:31 +01:00

357 lines
16 KiB
Java

package puzzle;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import lombok.val;
import precomp.Neighbors9x8;
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 record SwedishGenerator() {
public static final int THRESS = 800;
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 int R = Config.PUZZLE_ROWS;
public static final int SIZE = Neighbors9x8.SIZE;// ~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 = -1L;
public static final long MASK_HI = Neighbors9x8.MASK_HI;//(1L << (SIZE - 64)) - 1;
public static final int MAX_WORD_LENGTH = Config.PUZZLE_ROWS;
public static final int MAX_WORD_LENGTH_PLUS_ONE = MAX_WORD_LENGTH + 1;
public static final int MIN_LEN = 2;//Neighbors9x8.MIN_LEN;//Config.MIN_LEN;
public static final int MAX_TRIES_PER_SLOT = 500;//Config.MAX_TRIES_PER_SLOT;
public static final int STACK_SIZE = 128;
public static final long RANGE_0_SIZE = Neighbors9x8.RANGE_0_SIZE;// (long) SIZE_MIN_1 - 0L + 1L
public static final long RANGE_0_624 = Neighbors9x8.RANGE_0_624;//624L - 0L + 1L;
static final int PICK_NOT_DONE = -1;
static final int PICK_DONE = 0;
public static boolean isLo(int n) { return (n & 64) == 0; }
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) != 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
@AllArgsConstructor @NoArgsConstructor static final class Assign { long w; }
public static final class FillStats { public double simplicity; }
@AllArgsConstructor public static final class Grid { public final byte[] g; public long lo, hi; }
public static record Dict(DictEntry[] index, int length) { }
public static record FillResult(boolean ok, long nodes, long backtracks, int lastMRV, long elapsed, FillStats stats) { }
public static record DictEntry(long[] words, long[][] posBitsets, int length, int numlong) { }
//@formatter:on
public static final long[] PATH_LO = Neighbors9x8.PATH_LO;
public static final long[] PATH_HI = Neighbors9x8.PATH_HI;
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> 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 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 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 (int i = b.length - 1; i >= 0; i--) w = (w << 5) | ((long) b[i] & 31);
return w;
}
static public 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); }
public static String asWord(long word, byte[] bytes) {
int bi = 0;
for (long 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 unpackShardIndex(long w) { return (int) (w >>> 43); }
static int unpackSize(long w) { return (int) (w >>> 40) & 7; }
static int unpackLetters(long w) { return (int) (w & LETTER_MASK); }
}
public static 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 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, ~hi);
}
}
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 X;
long p = 0;
int n = 0, offset, idx;
if (Slotinfo.increasing(key)) {
for (long 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 (long 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);
}
} else {
offset = bitCount(hi);
for (long b = hi & ghi; b != X; b &= b - 1) {
idx = numberOfTrailingZeros(b);
p |= ((long) (bitCount(hi & ~((1L << idx) | ((1L << idx) - 1))) * 26 + g[64 | idx])) << (n++ << 3);
}
for (long b = lo & glo; b != X; b &= b - 1) {
idx = numberOfTrailingZeros(b);
p |= ((long) ((offset + bitCount(lo & ~((1L << idx) | ((1L << idx) - 1)))) * 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) {
val used = new long[2048];
val bitset = new long[2500];
val g = grid.g;
val TOTAL = slots.length;
val t0 = System.currentTimeMillis();
class Solver {
long nodes;
long backtracks;
long glo = grid.lo, ghi = grid.hi;
Slotinfo currentSlot;
int[] currentIndices;
boolean placeWordDec(final long lo, final long hi, final long w) {
int idx;
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;
}
boolean placeWordInc(final long lo, final long hi, final long w) {
int idx;
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;
}
return true;
}
int chooseMRV() {
Slotinfo best = null;
int count2 = -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.key, s.lo, s.hi);
var index = s.entry;
int count = pattern == X ? index.length : candidateCountForPattern(bitset, pattern, index.posBitsets, index.numlong);
if (count == 0) return PICK_NOT_DONE;
if (best == null
|| count < count2
|| (count == count2 && s.score > bestScore)) {
best = s;
bestScore = s.score;
count2 = count;
if (count <= 1) break;
}
}
if (best == null) return PICK_DONE;
var pattern = patternForSlot(glo, ghi, g, best.key, best.lo, best.hi);
currentSlot = best;
var index = best.entry;
currentIndices = 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++;
int status = chooseMRV();
if (status == PICK_DONE) return true;
if (status == PICK_NOT_DONE) {
backtracks++;
return false;
}
val info = currentIndices;
val s = currentSlot;
val inc = Slotinfo.increasing(s.key);
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 idxs = info;
var L = idxs.length;
var tries = Math.min(MAX_TRIES_PER_SLOT, L);
for (var t = 0; t < tries; t++) {
var w = words[idxs[rng.biasedIndexPow3(L - 1)]];
var lemIdx = Lemma.unpackIndex(w);
if (Bit1029.get(used, lemIdx)) continue;
low = glo;
top = ghi;
if (inc) {
if (!placeWordInc(slo, shi, w)) continue;
} else {
if (!placeWordDec(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;
}
backtracks++;
return false;
}
var N = words.length;
for (var t = 0; t < s.minL; t++) {
var w = words[rng.biasedIndexPow3(N - 1)];
var lemIdx = Lemma.unpackIndex(w);
if (Bit1029.get(used, lemIdx)) continue;
low = glo;
top = ghi;
if (inc) {
if (!placeWordInc(slo, shi, w)) continue;
} else {
if (!placeWordDec(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;
}
backtracks++;
return false;
}
}
var solver = new Solver();
var ok = solver.backtrack(0);
grid.lo = solver.glo;
grid.hi = solver.ghi;
return new FillResult(ok, solver.nodes, solver.backtracks, solver.currentSlot == null ? 0 : solver.currentSlot.entry.length, System.currentTimeMillis() - t0,
new FillStats());
}
}