1096 lines
42 KiB
Java
1096 lines
42 KiB
Java
package puzzle;
|
|
|
|
import lombok.AllArgsConstructor;
|
|
import lombok.Getter;
|
|
import lombok.RequiredArgsConstructor;
|
|
import lombok.experimental.Accessors;
|
|
import lombok.experimental.Delegate;
|
|
import lombok.val;
|
|
import precomp.Neighbors9x8;
|
|
import precomp.Neighbors9x8.rci;
|
|
import puzzle.Export.Bit1029;
|
|
import puzzle.Export.DictEntryDTO;
|
|
import puzzle.Export.Gridded;
|
|
import puzzle.Export.Strings;
|
|
import java.io.IOException;
|
|
import java.nio.file.Files;
|
|
import java.nio.file.Path;
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.Comparator;
|
|
import java.util.Locale;
|
|
import java.util.stream.IntStream;
|
|
import static java.nio.charset.StandardCharsets.*;
|
|
|
|
/**
|
|
* 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(Rng rng, int[] stack, Clues cache) {
|
|
|
|
record CandidateInfo(int[] indices, int count) { }
|
|
|
|
//@formatter:off
|
|
@FunctionalInterface interface SlotVisitor { void visit(int key, long lo, long hi); }
|
|
//@formatter:on
|
|
static final long GT_1_OFFSET_53_BIT = 0x3E00000000000000L;
|
|
static final long X = 0L;
|
|
static final int LOG_EVERY_MS = 200;
|
|
static final int BAR_LEN = 22;
|
|
static final int C = Config.PUZZLE_COLS;
|
|
static final double CROSS_R = (C - 1) / 2.0;
|
|
static final int R = Config.PUZZLE_ROWS;
|
|
static final double CROSS_C = (R - 1) / 2.0;
|
|
static final int SIZE = C * R;// ~18
|
|
static final int SIZE_MIN_1 = SIZE - 1;// ~18
|
|
static final double SIZED = (double) SIZE;// ~18
|
|
static final long MASK_LO = (SIZE >= 64) ? -1L : (1L << SIZE) - 1;
|
|
static final long MASK_HI = (SIZE <= 64) ? 0L : (SIZE >= 128 ? -1L : (1L << (SIZE - 64)) - 1);
|
|
static final int TARGET_CLUES = SIZE >>> 2;
|
|
static final int MAX_WORD_LENGTH = C <= R ? C : R;
|
|
static final int MAX_WORD_LENGTH_PLUS_ONE = MAX_WORD_LENGTH + 1;
|
|
static final int MIN_LEN = Config.MIN_LEN;
|
|
static final int MAX_TRIES_PER_SLOT = Config.MAX_TRIES_PER_SLOT;
|
|
static final int STACK_SIZE = 64;
|
|
static final char C_DASH = '\0';
|
|
static final byte _1 = 49, _9 = 57, A = 65, Z = 90, DASH = (byte) C_DASH;
|
|
//72 << 3;
|
|
static final int CLUE_INDEX_MAX_SIZE = (288 | 3) + 1;
|
|
static int clamp(int x, int a, int b) { return Math.max(a, Math.min(b, x)); }
|
|
@AllArgsConstructor
|
|
static class Pick {
|
|
|
|
Slot slot;
|
|
int[] indices;
|
|
int count;
|
|
}
|
|
|
|
// 0b11
|
|
//0b00
|
|
// 0b01
|
|
// 0b10
|
|
|
|
static final byte B0 = (byte) 0;
|
|
static final byte B64 = (byte) 64;
|
|
static final long[] OFFSETS_D_IDX = Neighbors9x8.OFFSET_D_IDX_0_BASE;
|
|
static final rci[] IT = Neighbors9x8.IT;
|
|
static final int[][] MUTATE_RI = new int[SIZE][625];
|
|
static final long[] NBR8_PACKED_LO = Neighbors9x8.NBR8_PACKED_LO;
|
|
static final long[] NBR8_PACKED_HI = Neighbors9x8.NBR8_PACKED_HI;
|
|
static final long[] PATH_LO = Neighbors9x8.PATH_LO;
|
|
static final long[] PATH_HI = Neighbors9x8.PATH_HI;
|
|
|
|
static {
|
|
for (int i = 0; i < SIZE; i++) {
|
|
int k = 0;
|
|
for (int dr1 = -2; dr1 <= 2; dr1++)
|
|
for (int dr2 = -2; dr2 <= 2; dr2++)
|
|
for (int dc1 = -2; dc1 <= 2; dc1++)
|
|
for (int dc2 = -2; dc2 <= 2; dc2++) {
|
|
val ti = IT[i];
|
|
MUTATE_RI[i][k++] = Grid.offset(clamp(ti.r() + dr1 + dr2, 0, R - 1),
|
|
clamp(ti.c() + dc1 + dc2, 0, C - 1));
|
|
}
|
|
}
|
|
}
|
|
|
|
static final Pick PICK_DONE = null;//new Pick(null, null, 0, true);
|
|
static final Pick PICK_NOT_DONE = new Pick(null, null, 0);
|
|
|
|
@RequiredArgsConstructor
|
|
@Getter
|
|
@Accessors(fluent = true)
|
|
public static final class FillStats {
|
|
|
|
final public long nodes;
|
|
final public long backtracks;
|
|
final public double seconds;
|
|
final public int lastMRV;
|
|
public double simplicity;
|
|
}
|
|
|
|
public static record FillResult(boolean ok,
|
|
Gridded grid,
|
|
long[] clueMap,
|
|
@Delegate FillStats stats) {
|
|
|
|
public void calcSimpel() {
|
|
if (ok) {
|
|
int k = 0;
|
|
for (var n = 1; n < clueMap.length; n++) {
|
|
if (clueMap[n] != X) {
|
|
k++;
|
|
stats.simplicity += Lemma.simpel(clueMap[n]);
|
|
}
|
|
}
|
|
stats.simplicity = k == 0 ? 0 : stats.simplicity / k;
|
|
}
|
|
}
|
|
public int wordCount() {
|
|
int k = 0;
|
|
for (var n = 1; n < clueMap.length; n++) {
|
|
if (clueMap[n] != X) {
|
|
k++;
|
|
}
|
|
}
|
|
return k;
|
|
}
|
|
}
|
|
|
|
static final class Rng {
|
|
|
|
@Getter private int x;
|
|
Rng(int seed) {
|
|
var s = seed;
|
|
if (s == 0) s = 1;
|
|
this.x = s;
|
|
}
|
|
int nextU32() {
|
|
var y = x;
|
|
y ^= (y << 13);
|
|
y ^= (y >>> 17);
|
|
y ^= (y << 5);
|
|
x = y;
|
|
return y;
|
|
}
|
|
byte randbyte(int min, int max) {
|
|
var u = (nextU32() & 0xFFFFFFFFL);
|
|
var range = (long) max - (long) min + 1L;
|
|
return (byte) (min + (u % range));
|
|
}
|
|
int randint2bit() { return nextU32() & 3; }
|
|
byte randint2bitByte() { return (byte) (nextU32() & 3); }
|
|
int randint(int min, int max) {
|
|
var u = (nextU32() & 0xFFFFFFFFL);
|
|
var range = (long) max - (long) min + 1L;
|
|
return (int) (min + (u % range));
|
|
}
|
|
double nextFloat() { return (nextU32() & 0xFFFFFFFFL) / 4294967295.0; }
|
|
int biasedIndexPow3(int N) {
|
|
int m = Math.min(nextU32(), Math.min(nextU32(), nextU32()));
|
|
return (int) (((m & 0xFFFFFFFFL) * (long) N) >>> 32);
|
|
}
|
|
}
|
|
|
|
@AllArgsConstructor
|
|
static class Clues {
|
|
|
|
long lo, hi, vlo, vhi, rlo, rhi;
|
|
public static Clues createEmpty() {
|
|
return new Clues(0, 0, 0, 0, 0, 0);
|
|
}
|
|
public Clues deepCopyGrid() { return new Clues(lo, hi, vlo, vhi, rlo, rhi); }
|
|
boolean clueless(int idx) {
|
|
if (!isClue(idx)) return false;
|
|
clearClue(idx);
|
|
return true;
|
|
}
|
|
public boolean hasRoomForClue(long packed) { return (packed) != X && notClue(packed & 0x7FL) && notClue((packed >>> 7) & 0x7FL); }
|
|
|
|
public void setClue(int ri, byte idx) {
|
|
if ((ri & 64) == 0) {
|
|
long mask = 1L << ri;
|
|
lo |= mask;
|
|
if ((idx & 1) != 0) vlo |= mask;
|
|
else vlo &= ~mask;
|
|
if ((idx & 2) != 0) rlo |= mask;
|
|
else rlo &= ~mask;
|
|
} else {
|
|
long mask = 1L << (ri & 63);
|
|
hi |= mask;
|
|
if ((idx & 1) != 0) vhi |= mask;
|
|
else vhi &= ~mask;
|
|
if ((idx & 2) != 0) rhi |= mask;
|
|
else rhi &= ~mask;
|
|
}
|
|
}
|
|
public byte digitAt(int idx) {
|
|
if ((idx & 64) == 0) {
|
|
int v = (int) ((vlo >>> idx) & 1L);
|
|
int r = (int) ((rlo >>> idx) & 1L);
|
|
return (byte) ((r << 1) | v);
|
|
} else {
|
|
int v = (int) ((vhi >>> (idx & 63)) & 1L);
|
|
int r = (int) ((rhi >>> (idx & 63)) & 1L);
|
|
return (byte) ((r << 1) | v);
|
|
}
|
|
}
|
|
public void clearClue(int idx) {
|
|
if ((idx & 64) == 0) {
|
|
long mask = ~(1L << idx);
|
|
lo &= mask;
|
|
vlo &= mask;
|
|
rlo &= mask;
|
|
} else {
|
|
long mask = ~(1L << (idx & 63));
|
|
hi &= mask;
|
|
vhi &= mask;
|
|
rhi &= mask;
|
|
}
|
|
}
|
|
public boolean isClue(long index) { return ((index & 64) == 0) ? ((lo >>> index) & 1L) != X : ((hi >>> (index & 63)) & 1L) != X; }
|
|
public boolean isClue(int index) { return ((index & 64) == 0) ? ((lo >>> index) & 1L) != X : ((hi >>> (index & 63)) & 1L) != X; }
|
|
public boolean notClue(long index) { return ((index & 64) == 0) ? ((lo >>> index) & 1L) == X : ((hi >>> (index & 63)) & 1L) == X; }
|
|
public boolean notClue(int index) { return ((index & 64) == 0) ? ((lo >>> index) & 1L) == X : ((hi >>> (index & 63)) & 1L) == X; }
|
|
|
|
public int clueCount() { return Long.bitCount(lo) + Long.bitCount(hi); }
|
|
public double similarity(Clues b) {
|
|
long matchLo = (~(lo ^ b.lo)) & (~lo | (~(vlo ^ b.vlo) & ~(rlo ^ b.rlo)));
|
|
long matchHi = (~(hi ^ b.hi)) & (~hi | (~(vhi ^ b.vhi) & ~(rhi ^ b.rhi)));
|
|
|
|
return (Long.bitCount(matchLo & MASK_LO) + Long.bitCount(matchHi & MASK_HI)) / SIZED;
|
|
}
|
|
public Grid toGrid() { return new Grid(new byte[SIZE], lo, hi); }
|
|
public void forEachSlot(SlotVisitor visitor) {
|
|
for (var l = lo; l != X; l &= l - 1) processSlot(this, visitor, Long.numberOfTrailingZeros(l));
|
|
for (var h = hi; h != X; h &= h - 1) processSlot(this, visitor, 64 | Long.numberOfTrailingZeros(h));
|
|
}
|
|
public Clues from(Clues best) {
|
|
lo = best.lo;
|
|
hi = best.hi;
|
|
vlo = best.vlo;
|
|
vhi = best.vhi;
|
|
rlo = best.rlo;
|
|
rhi = best.rhi;
|
|
return this;
|
|
}
|
|
}
|
|
|
|
@AllArgsConstructor
|
|
static class Grid {
|
|
|
|
final byte[] g;
|
|
public long lo, hi;
|
|
static int offset(int r, int c) { return r | (c << 3); }
|
|
/// the pos will never target a clue
|
|
public byte letter32At(int pos) { return g[pos]; }
|
|
public boolean lisLetterAt(int pos) {
|
|
if ((pos & 64) == 0)
|
|
return lisLetterAtLo(pos);
|
|
return lisLetterAtHi(pos);
|
|
}
|
|
|
|
public boolean lisLetterAtLo(int pos) { return (lo & (1L << pos)) != X; }
|
|
public boolean lisLetterAtHi(int pos) { return (hi & (1L << (pos & 63))) != X; }
|
|
void setLetterLo(int idx, byte ch) {
|
|
lo |= (1L << idx);
|
|
|
|
g[idx] = ch;
|
|
}
|
|
void setLetterHi(int idx, byte ch) {
|
|
hi |= (1L << (idx & 63));
|
|
|
|
g[idx] = ch;
|
|
}
|
|
}
|
|
|
|
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 pack(int index, 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(int index, String word) { return pack(index, word.getBytes(US_ASCII)); }
|
|
static byte byteAt(long word, int idx) { return (byte) ((word >>> (idx * 5)) & 0b11111); }
|
|
static String[] clue(long w) { return CsvIndexService.clues(unpackIndex(w)); }
|
|
static int simpel(long w) { return CsvIndexService.simpel(unpackIndex(w)); }
|
|
static int length(long word) { return ((63 - Long.numberOfLeadingZeros(word & LETTER_MASK)) / 5) + 1; }
|
|
public static String asWord(long word) {
|
|
var b = new byte[Lemma.length(word)];
|
|
for (var i = 0; i < b.length; i++) b[i] = (byte) ((word >>> (i * 5)) & 0b11111 | B64);
|
|
return new String(b, US_ASCII);
|
|
}
|
|
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) {
|
|
|
|
public Dict(long[] wordz) {
|
|
var index = new DictEntryDTO[MAX_WORD_LENGTH_PLUS_ONE];
|
|
Arrays.setAll(index, i -> new DictEntryDTO(i));
|
|
for (var lemma : wordz) {
|
|
var L = Lemma.length(lemma);
|
|
|
|
var entry = index[L];
|
|
var idx = entry.words().size();
|
|
entry.words().add(lemma);
|
|
|
|
for (var i = 0; i < L; i++) {
|
|
var letter = Lemma.byteAt(lemma, i) - 1;
|
|
if (letter < 0 || letter >= 26) throw new RuntimeException("Illegal letter: " + letter + " in word " + lemma);
|
|
entry.pos()[i][letter].add(idx);
|
|
}
|
|
}
|
|
for (int i = MIN_LEN; i < index.length; i++) if (index[i].words().size() <= 0) throw new RuntimeException("No words for length " + i);
|
|
this(Arrays.stream(index).map(i -> {
|
|
var words = i.words().toArray();
|
|
int numWords = words.length;
|
|
int numLongs = (numWords + 63) >>> 6;
|
|
var bitsets = new long[i.pos().length * 26][numLongs];
|
|
for (int p = 0; p < i.pos().length; p++) {
|
|
for (int l = 0; l < 26; l++) {
|
|
var list = i.pos()[p][l];
|
|
var bs = bitsets[p * 26 + l];
|
|
for (int k = 0; k < list.size(); k++) {
|
|
int wordIdx = list.data()[k];
|
|
bs[wordIdx >>> 6] |= (1L << (wordIdx & 63));
|
|
}
|
|
}
|
|
}
|
|
return new DictEntry(words, bitsets, words.length, (words.length + 63) >>> 6);
|
|
}).toArray(DictEntry[]::new),
|
|
Arrays.stream(index).mapToInt(i -> i.words().size()).sum());
|
|
}
|
|
static Dict loadDict(String wordsPath) {
|
|
try {
|
|
var map = new LongArrayList(100_000);
|
|
Files.lines(Path.of(wordsPath), UTF_8).forEach(line -> CsvIndexService.lineToLemma(line, map::add));
|
|
return new Dict(map.toArray());
|
|
} catch (IOException e) {
|
|
e.printStackTrace();
|
|
throw new RuntimeException("Failed to load dictionary from " + wordsPath, e);
|
|
}
|
|
}
|
|
}
|
|
|
|
static record Slot(int key, long lo, long hi, DictEntry entry) {
|
|
|
|
static final int BIT_FOR_DIR = 2;
|
|
static Slot from(int key, long lo, long hi, DictEntry entry) { return new Slot(key, lo, hi, entry); }
|
|
public static int length(long lo, long hi) { return Long.bitCount(lo) + Long.bitCount(hi); }
|
|
public static int clueIndex(int key) { return key >>> BIT_FOR_DIR; }
|
|
public static int dir(int key) { return key & 3; }
|
|
public static boolean increasing(int dir) { return (dir & 2) == 0; }
|
|
public IntStream walk() { return Gridded.walk((byte) key, lo, hi); }
|
|
public static boolean horiz(int d) { return (d & 1) != 0; }
|
|
public static int packSlotDir(int idx, int d) { return (idx << BIT_FOR_DIR) | d; }
|
|
}
|
|
|
|
private static void processSlot(Clues grid, SlotVisitor visitor, int idx) {
|
|
int key = Slot.packSlotDir(idx, grid.digitAt(idx)); // 0..3
|
|
|
|
long rayLo = PATH_LO[key];
|
|
long rayHi = PATH_HI[key];
|
|
|
|
// only consider clue cells
|
|
long hitsLo = rayLo & grid.lo;
|
|
long hitsHi = rayHi & grid.hi;
|
|
|
|
// slice ray to stop before first clue, depending on direction monotonicity
|
|
// right/down => increasing indices; up/left => decreasing indices
|
|
|
|
if (Slot.increasing(key)) {
|
|
// first clue is lowest index among hits (lo first, then hi)
|
|
if (hitsLo != X) {
|
|
long stop = 1L << Long.numberOfTrailingZeros(hitsLo);
|
|
rayLo &= (stop - 1);
|
|
rayHi = 0; // any hi is beyond the stop
|
|
} else if (hitsHi != X) {
|
|
long stop = 1L << Long.numberOfTrailingZeros(hitsHi);
|
|
// keep all lo (lo indices are < any hi index), but cut hi below stop
|
|
rayHi &= (stop - 1);
|
|
}
|
|
} else {
|
|
// first clue is highest index among hits (hi first, then lo)
|
|
if (hitsHi != X) {
|
|
int msb = 63 - Long.numberOfLeadingZeros(hitsHi);
|
|
long stop = 1L << msb;
|
|
rayHi &= ~((stop << 1) - 1); // keep bits > stop
|
|
rayLo = 0; // lo indices are below stop
|
|
} else if (hitsLo != X) {
|
|
int msb = 63 - Long.numberOfLeadingZeros(hitsLo);
|
|
long stop = 1L << msb;
|
|
rayLo &= ~((stop << 1) - 1);
|
|
}
|
|
}
|
|
|
|
visitor.visit(key, rayLo, rayHi);
|
|
}
|
|
|
|
static Slot[] extractSlots(Clues grid, DictEntry[] index) {
|
|
var slots = new Slot[grid.clueCount()];
|
|
int[] N = new int[]{ 0 };
|
|
grid.forEachSlot((key, lo, hi) -> slots[N[0]++] = Slot.from(key, lo, hi, index[Slot.length(lo, hi)]));
|
|
return slots;
|
|
}
|
|
|
|
/// does not modify the grid
|
|
long maskFitness(final Clues grid) {
|
|
|
|
long cHLo = 0L, cHHi = 0L, cVLo = 0L, cVHi = 0L;
|
|
long lo_cl = grid.lo, hi_cl = grid.hi;
|
|
long penalty = (((long) Math.abs(grid.clueCount() - TARGET_CLUES)) * 16000L);
|
|
boolean hasSlots = false;
|
|
|
|
for (long bits = lo_cl; bits != X; bits &= bits - 1) {
|
|
long lsb = bits & -bits;
|
|
int clueIdx = Long.numberOfTrailingZeros(lsb);
|
|
int v = (grid.vlo & lsb) != 0 ? 1 : 0;
|
|
int r = (grid.rlo & lsb) != 0 ? 1 : 0;
|
|
int key = Slot.packSlotDir(clueIdx, (r << 1) | v);
|
|
long rLo = PATH_LO[key], rHi = PATH_HI[key];
|
|
long hLo = rLo & lo_cl, hHi = rHi & hi_cl;
|
|
if (Slot.increasing(key)) {
|
|
if (hLo != X) {
|
|
rLo &= ((1L << Long.numberOfTrailingZeros(hLo)) - 1);
|
|
rHi = 0;
|
|
} else if (hHi != X) { rHi &= ((1L << Long.numberOfTrailingZeros(hHi)) - 1); }
|
|
} else {
|
|
if (hHi != X) {
|
|
int msb = 63 - Long.numberOfLeadingZeros(hHi);
|
|
rHi &= ~((1L << msb << 1) - 1);
|
|
rLo = 0;
|
|
} else if (hLo != X) {
|
|
int msb = 63 - Long.numberOfLeadingZeros(hLo);
|
|
rLo &= ~((1L << msb << 1) - 1);
|
|
}
|
|
}
|
|
if ((rLo | rHi) != X) {
|
|
hasSlots = true;
|
|
if (Slot.horiz(key)) {
|
|
cHLo |= rLo;
|
|
cHHi |= rHi;
|
|
} else {
|
|
cVLo |= rLo;
|
|
cVHi |= rHi;
|
|
}
|
|
if ((Long.bitCount(rLo) + Long.bitCount(rHi)) < MIN_LEN) penalty += 8000;
|
|
} else {
|
|
penalty += 25000;
|
|
}
|
|
}
|
|
for (long bits = hi_cl; bits != X; bits &= bits - 1) {
|
|
long lsb = bits & -bits;
|
|
int clueIdx = Long.numberOfTrailingZeros(lsb);
|
|
int v = (grid.vhi & lsb) != 0 ? 1 : 0;
|
|
int r = (grid.rhi & lsb) != 0 ? 1 : 0;
|
|
int key = Slot.packSlotDir(64 | clueIdx, (r << 1) | v);
|
|
long rLo = PATH_LO[key], rHi = PATH_HI[key];
|
|
long hLo = rLo & lo_cl, hHi = rHi & hi_cl;
|
|
if (Slot.increasing(key)) {
|
|
if (hLo != X) {
|
|
rLo &= ((1L << Long.numberOfTrailingZeros(hLo)) - 1);
|
|
rHi = 0;
|
|
} else if (hHi != X) { rHi &= ((1L << Long.numberOfTrailingZeros(hHi)) - 1); }
|
|
} else {
|
|
if (hHi != X) {
|
|
int msb = 63 - Long.numberOfLeadingZeros(hHi);
|
|
rHi &= ~((1L << msb << 1) - 1);
|
|
rLo = 0;
|
|
} else if (hLo != X) {
|
|
int msb = 63 - Long.numberOfLeadingZeros(hLo);
|
|
rLo &= ~((1L << msb << 1) - 1);
|
|
}
|
|
}
|
|
if ((rLo | rHi) != X) {
|
|
hasSlots = true;
|
|
if (Slot.horiz(key)) {
|
|
cHLo |= rLo;
|
|
cHHi |= rHi;
|
|
} else {
|
|
cVLo |= rLo;
|
|
cVHi |= rHi;
|
|
}
|
|
if ((Long.bitCount(rLo) + Long.bitCount(rHi)) < MIN_LEN) penalty += 8000;
|
|
} else {
|
|
penalty += 25000;
|
|
}
|
|
}
|
|
|
|
if (!hasSlots) return 1_000_000_000L;
|
|
long seenLo = X, seenHi = X;
|
|
|
|
// loop over beide helften
|
|
for (int base = 0; base <= 64; base += 64) {
|
|
long clueMask = (base == 0) ? lo_cl : hi_cl;
|
|
long seenMask = (base == 0) ? seenLo : seenHi;
|
|
|
|
// "unseen clues" in deze helft
|
|
for (long bits = clueMask & ~seenMask; bits != X; bits &= bits - 1) {
|
|
int clueIdx = base | Long.numberOfTrailingZeros(bits);
|
|
|
|
// start nieuwe component
|
|
int size = 0;
|
|
int sp = 0;
|
|
stack[sp++] = clueIdx;
|
|
|
|
// mark seen
|
|
if ((clueIdx & 64) == 0) seenLo |= 1L << clueIdx;
|
|
else seenHi |= 1L << (clueIdx & 63);
|
|
|
|
// flood fill / bfs
|
|
while (sp > 0) {
|
|
int cur = stack[--sp];
|
|
size++;
|
|
|
|
// neighbors als 2x long masks
|
|
long nLo = NBR8_PACKED_LO[cur];
|
|
long nHi = NBR8_PACKED_HI[cur];
|
|
|
|
// filter: alleen clues, en nog niet seen
|
|
nLo &= lo_cl & ~seenLo;
|
|
nHi &= hi_cl & ~seenHi;
|
|
|
|
// push lo-neighbors
|
|
while (nLo != X) {
|
|
long lsb = nLo & -nLo;
|
|
int nidx = Long.numberOfTrailingZeros(nLo); // 0..63
|
|
seenLo |= lsb;
|
|
|
|
stack[sp++] = nidx;
|
|
|
|
nLo &= nLo - 1;
|
|
}
|
|
|
|
// push hi-neighbors
|
|
while (nHi != X) {
|
|
long lsb = nHi & -nHi;
|
|
int nidx = 64 | Long.numberOfTrailingZeros(nHi); // 64..127
|
|
seenHi |= lsb;
|
|
|
|
stack[sp++] = nidx;
|
|
|
|
nHi &= nHi - 1;
|
|
}
|
|
}
|
|
|
|
if (size >= 2) penalty += (size - 1L) * 120L;
|
|
}
|
|
}
|
|
|
|
for (long bits = ~lo_cl; bits != X; bits &= bits - 1) {
|
|
int clueIdx = Long.numberOfTrailingZeros(bits);
|
|
var rci = IT[clueIdx];
|
|
if ((4 - rci.nbrCount()) + Long.bitCount(rci.n1() & lo_cl) + Long.bitCount(rci.n2() & hi_cl) >= 3) penalty += 400;
|
|
boolean h = (cHLo & (1L << clueIdx)) != X;
|
|
boolean v = (cVLo & (1L << clueIdx)) != X;
|
|
if (!h && !v) penalty += 1500;
|
|
else if (h && v) { /* ok */ } else if (h | v) penalty += 200;
|
|
else penalty += 600;
|
|
}
|
|
for (long bits = ~hi_cl & 0xFFL; bits != X; bits &= bits - 1) {
|
|
int clueIdx = Long.numberOfTrailingZeros(bits);
|
|
var rci = IT[64 | clueIdx];
|
|
if ((4 - rci.nbrCount()) + Long.bitCount(rci.n1() & lo_cl) + Long.bitCount(rci.n2() & hi_cl) >= 3) penalty += 400;
|
|
boolean h = (cHHi & (1L << clueIdx)) != X;
|
|
boolean v = (cVHi & (1L << clueIdx)) != X;
|
|
if (!h && !v) penalty += 1500;
|
|
else if (h && v) { /* ok */ } else if (h | v) penalty += 200;
|
|
else penalty += 600;
|
|
}
|
|
|
|
return penalty;
|
|
}
|
|
|
|
Clues randomMask() {
|
|
var g = Clues.createEmpty();
|
|
for (int placed = 0, guard = 0, idx; placed < TARGET_CLUES && guard < 4000; guard++) {
|
|
idx = rng.randint(0, SIZE_MIN_1);
|
|
if (g.isClue(idx)) continue;
|
|
var d_idx = rng.randint2bitByte();
|
|
if (g.hasRoomForClue(OFFSETS_D_IDX[Slot.packSlotDir(idx, d_idx)])) {
|
|
g.setClue(idx, d_idx);
|
|
placed++;
|
|
}
|
|
}
|
|
return g;
|
|
}
|
|
Clues mutate(Clues c) {
|
|
int ri;
|
|
var bytes = MUTATE_RI[rng.randint(0, SIZE_MIN_1)];
|
|
for (var k = 0; k < 4; k++) {
|
|
ri = bytes[rng.randint(0, 624)];
|
|
if (!c.clueless(ri)) {
|
|
var d_idx = rng.randint2bitByte();
|
|
if (c.hasRoomForClue(OFFSETS_D_IDX[Slot.packSlotDir(ri, d_idx)])) c.setClue(ri, d_idx);
|
|
}
|
|
}
|
|
return c;
|
|
}
|
|
Clues crossover(Clues a, Clues other) {
|
|
var out = a.deepCopyGrid();
|
|
var theta = rng.nextFloat() * Math.PI;
|
|
var nc = Math.cos(theta);
|
|
var nr = Math.sin(theta);
|
|
|
|
long maskLo = 0, maskHi = 0;
|
|
for (var rci : IT) {
|
|
if ((rci.cross_r()) * nc + (rci.cross_c()) * nr < 0) {
|
|
int i = rci.i();
|
|
if ((i & 64) == 0) maskLo |= (1L << i);
|
|
else maskHi |= (1L << (i - 64));
|
|
}
|
|
}
|
|
|
|
out.lo = (out.lo & ~maskLo) | (other.lo & maskLo);
|
|
out.hi = (out.hi & ~maskHi) | (other.hi & maskHi);
|
|
out.vlo = (out.vlo & ~maskLo) | (other.vlo & maskLo);
|
|
out.vhi = (out.vhi & ~maskHi) | (other.vhi & maskHi);
|
|
out.rlo = (out.rlo & ~maskLo) | (other.rlo & maskLo);
|
|
out.rhi = (out.rhi & ~maskHi) | (other.rhi & maskHi);
|
|
|
|
for (var lo = out.lo; lo != X; lo &= lo - 1L) clearClues(out, Long.numberOfTrailingZeros(lo));
|
|
for (var hi = out.hi; hi != X; hi &= hi - 1L) clearClues(out, 64 | Long.numberOfTrailingZeros(hi));
|
|
return out;
|
|
}
|
|
public static void clearClues(Clues out, int idx) { if (!out.hasRoomForClue(OFFSETS_D_IDX[Slot.packSlotDir(idx, out.digitAt(idx))])) out.clearClue(idx); }
|
|
|
|
Clues hillclimb(Clues start, int limit) {
|
|
var best = start;
|
|
var bestF = maskFitness(best);
|
|
var fails = 0;
|
|
|
|
while (fails < limit) {
|
|
cache.from(best);
|
|
var cand = mutate(best);
|
|
var f = maskFitness(cand);
|
|
if (f < bestF) {
|
|
best = cand;
|
|
bestF = f;
|
|
fails = 0;
|
|
} else {
|
|
best.from(cache);
|
|
fails++;
|
|
}
|
|
}
|
|
return best;
|
|
}
|
|
|
|
public Clues generateMask(int popSize, int gens, int pairs) {
|
|
class GridAndFit {
|
|
|
|
Clues grid;
|
|
long fite = -1;
|
|
GridAndFit(Clues grid) { this.grid = grid; }
|
|
long fit() {
|
|
if (fite == -1) this.fite = maskFitness(grid);
|
|
return this.fite;
|
|
}
|
|
}
|
|
if (Main.VERBOSE) System.out.println("generateMask init pop: " + popSize);
|
|
var pop = new ArrayList<GridAndFit>();
|
|
for (var i = 0; i < popSize; i++) {
|
|
pop.add(new GridAndFit(hillclimb(randomMask(), 180)));
|
|
}
|
|
|
|
for (var gen = 0; gen < gens; gen++) {
|
|
if (Thread.currentThread().isInterrupted()) break;
|
|
var children = new ArrayList<GridAndFit>();
|
|
|
|
for (var k = 0; k < pairs; k++) {
|
|
var p1 = pop.get(rng.randint(0, pop.size() - 1));
|
|
var p2 = pop.get(rng.randint(0, pop.size() - 1));
|
|
var child = crossover(p1.grid, p2.grid);
|
|
children.add(new GridAndFit(hillclimb(child, 70)));
|
|
}
|
|
|
|
pop.addAll(children);
|
|
pop.sort(Comparator.comparingLong(GridAndFit::fit));
|
|
|
|
var next = new ArrayList<GridAndFit>();
|
|
for (var cand : pop) {
|
|
if (next.size() >= popSize) break;
|
|
var ok = true;
|
|
for (var kept : next) {
|
|
if (cand.grid.similarity(kept.grid) > 0.92) {
|
|
ok = false;
|
|
break;
|
|
}
|
|
}
|
|
if (ok) next.add(cand);
|
|
}
|
|
pop = next;
|
|
|
|
if (Main.VERBOSE && (gen & 15) == 15) System.out.println(" gen " + gen + "/" + gens + " bestFitness=" + pop.get(0).fit());
|
|
}
|
|
GridAndFit best = pop.get(0);
|
|
for (int i = 1; i < pop.size(); i++) {
|
|
var x = pop.get(i);
|
|
if (x.fit() < best.fit()) best = x;
|
|
}
|
|
return best.grid;
|
|
}
|
|
|
|
static long patternForSlot(Grid grid, final int key, final long lo, final long hi) {
|
|
final long glo = grid.lo, ghi = grid.hi;
|
|
if ((lo & glo) == X && (hi & ghi) == X) {
|
|
return 0;
|
|
}
|
|
long p = 0;
|
|
if (Slot.increasing(key)) {
|
|
for (long b = lo & glo; b != X; b &= b - 1) {
|
|
int idx = Long.numberOfTrailingZeros(b);
|
|
byte val = grid.g[idx];
|
|
int i = Long.bitCount(lo & ((1L << idx) - 1));
|
|
p |= ((long) (i * 26 + val)) << (i << 3);
|
|
}
|
|
int offset = Long.bitCount(lo);
|
|
for (long b = hi & ghi; b != X; b &= b - 1) {
|
|
int idx = Long.numberOfTrailingZeros(b);
|
|
byte val = grid.g[64 | idx];
|
|
int i = offset + Long.bitCount(hi & ((1L << idx) - 1));
|
|
p |= ((long) (i * 26 + val)) << (i << 3);
|
|
}
|
|
} else {
|
|
int offset = Long.bitCount(hi);
|
|
for (long b = hi & ghi; b != X; b &= b - 1) {
|
|
int idx = Long.numberOfTrailingZeros(b);
|
|
byte val = grid.g[64 | idx];
|
|
int i = Long.bitCount(hi & ~((1L << idx) | ((1L << idx) - 1)));
|
|
p |= ((long) (i * 26 + val)) << (i << 3);
|
|
}
|
|
for (long b = lo & glo; b != X; b &= b - 1) {
|
|
int idx = Long.numberOfTrailingZeros(b);
|
|
byte val = grid.g[idx];
|
|
int i = offset + Long.bitCount(lo & ~((1L << idx) | ((1L << idx) - 1)));
|
|
p |= ((long) (i * 26 + val)) << (i << 3);
|
|
}
|
|
}
|
|
return p;
|
|
}
|
|
static int slotScore(byte[] count, long lo, long hi) {
|
|
int cross = 0;
|
|
for (long b = lo; b != X; b &= b - 1) cross += (count[Long.numberOfTrailingZeros(b)] - 1);
|
|
for (long b = hi; b != X; b &= b - 1) cross += (count[64 | Long.numberOfTrailingZeros(b)] - 1);
|
|
return cross * 10 + Slot.length(lo, hi);
|
|
}
|
|
static boolean placeWord(Grid grid, final int key, final long lo, final long hi, final long w) {
|
|
final long glo = grid.lo, ghi = grid.hi;
|
|
if (Slot.increasing(key)) {
|
|
for (long b = lo & glo; b != X; b &= b - 1) {
|
|
int idx = Long.numberOfTrailingZeros(b);
|
|
if (grid.g[idx] != Lemma.byteAt(w, Long.bitCount(lo & ((1L << idx) - 1)))) return false;
|
|
}
|
|
int bcLo = Long.bitCount(lo);
|
|
for (long b = hi & ghi; b != X; b &= b - 1) {
|
|
int idx = Long.numberOfTrailingZeros(b);
|
|
if (grid.g[64 | idx] != Lemma.byteAt(w, bcLo + Long.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) {
|
|
int idx = Long.numberOfTrailingZeros(b);
|
|
grid.g[idx] = Lemma.byteAt(w, Long.bitCount(lo & ((1L << idx) - 1)));
|
|
}
|
|
for (long b = maskHi; b != X; b &= b - 1) {
|
|
int idx = Long.numberOfTrailingZeros(b);
|
|
grid.g[64 | idx] = Lemma.byteAt(w, bcLo + Long.bitCount(hi & ((1L << idx) - 1)));
|
|
}
|
|
grid.lo |= maskLo;
|
|
grid.hi |= maskHi;
|
|
}
|
|
} else {
|
|
int bcHi = Long.bitCount(hi);
|
|
for (long b = hi & ghi; b != X; b &= b - 1) {
|
|
int idx = Long.numberOfTrailingZeros(b);
|
|
if (grid.g[64 | idx] != Lemma.byteAt(w, Long.bitCount(hi & ~((1L << idx) | ((1L << idx) - 1))))) return false;
|
|
}
|
|
for (long b = lo & glo; b != X; b &= b - 1) {
|
|
int idx = Long.numberOfTrailingZeros(b);
|
|
if (grid.g[idx] != Lemma.byteAt(w, bcHi + Long.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) {
|
|
int idx = Long.numberOfTrailingZeros(b);
|
|
grid.g[64 | idx] = Lemma.byteAt(w, Long.bitCount(hi & ~((1L << idx) | ((1L << idx) - 1))));
|
|
}
|
|
for (long b = maskLo; b != X; b &= b - 1) {
|
|
int idx = Long.numberOfTrailingZeros(b);
|
|
grid.g[idx] = Lemma.byteAt(w, bcHi + Long.bitCount(lo & ~((1L << idx) | ((1L << idx) - 1))));
|
|
}
|
|
grid.lo |= maskLo;
|
|
grid.hi |= maskHi;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static int[] candidateInfoForPattern(long[] res, long pattern, long[][] posBitsets, int numLongs) {
|
|
boolean first = true;
|
|
|
|
for (long p = pattern; p != X; ) {
|
|
int combined = (int) (p & 0xFF);
|
|
if (combined != X) {
|
|
long[] bs = posBitsets[combined - 1];
|
|
if (first) {
|
|
System.arraycopy(bs, 0, res, 0, numLongs);
|
|
first = false;
|
|
} else {
|
|
for (int k = 0; k < numLongs; k++) res[k] &= bs[k];
|
|
}
|
|
p >>>= 8;
|
|
} else {
|
|
p >>>= (Long.numberOfTrailingZeros(p) & ~7);
|
|
}
|
|
}
|
|
|
|
int count = 0;
|
|
for (int k = 0; k < numLongs; k++) count += Long.bitCount(res[k]);
|
|
|
|
int[] indices = new int[count];
|
|
int ki = 0;
|
|
for (int k = 0; k < numLongs; k++) {
|
|
long w = res[k];
|
|
while (w != X) {
|
|
int t = Long.numberOfTrailingZeros(w);
|
|
indices[ki++] = (k << 6) | t;
|
|
w &= w - 1;
|
|
}
|
|
}
|
|
|
|
return indices;
|
|
}
|
|
|
|
static int candidateCountForPattern(final long[] res, final long pattern, final long[][] posBitsets, final int numLongs) {
|
|
boolean first = true;
|
|
|
|
for (long p = pattern; p != X; ) {
|
|
int combined = (int) (p & 0xFF);
|
|
if (combined != X) {
|
|
long[] bs = posBitsets[combined - 1];
|
|
if (first) {
|
|
System.arraycopy(bs, 0, res, 0, numLongs);
|
|
first = false;
|
|
} else {
|
|
for (int k = 0; k < numLongs; k++) res[k] &= bs[k];
|
|
}
|
|
p >>>= 8;
|
|
} else {
|
|
p >>>= (Long.numberOfTrailingZeros(p) & ~7);
|
|
}
|
|
}
|
|
|
|
int count = 0;
|
|
for (int k = 0; k < numLongs; k++) count += Long.bitCount(res[k]);
|
|
return count;
|
|
}
|
|
|
|
static void scoreSlots(int[] slotScores, Slot[] slots) {
|
|
val count = new byte[SIZE];
|
|
for (var s : slots) {
|
|
for (long b = s.lo; b != X; b &= b - 1) count[Long.numberOfTrailingZeros(b)]++;
|
|
for (long b = s.hi; b != X; b &= b - 1) count[64 | Long.numberOfTrailingZeros(b)]++;
|
|
}
|
|
for (int i = 0; i < slots.length; i++) {
|
|
var slot = slots[i];
|
|
slotScores[i] = slotScore(count, slot.lo, slot.hi);
|
|
}
|
|
|
|
}
|
|
public static FillResult fillMask(Rng rng, Slot[] slots, Grid mask) {
|
|
val multiThreaded = Thread.currentThread().getName().contains("pool");
|
|
val NO_LOG = (!Main.VERBOSE || multiThreaded);
|
|
val grid = mask;
|
|
val used = new Bit1029();
|
|
val assigned = new long[CLUE_INDEX_MAX_SIZE];
|
|
val bitset = new long[2500];
|
|
|
|
val TOTAL = slots.length;
|
|
val slotScores = new int[TOTAL];
|
|
|
|
scoreSlots(slotScores, slots);
|
|
|
|
val t0 = System.currentTimeMillis();
|
|
|
|
class Solver {
|
|
|
|
private final Pick CARRIER = new Pick(null, null, 0);
|
|
long nodes;
|
|
long backtracks;
|
|
int lastMRV;
|
|
long lastLog = t0;
|
|
Pick current = CARRIER;
|
|
void renderProgress() {
|
|
var now = System.currentTimeMillis();
|
|
if ((now - lastLog) < LOG_EVERY_MS) return;
|
|
lastLog = (now);
|
|
var done = 0;
|
|
for (var lemma : assigned) {
|
|
if (lemma != 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" + Strings.padRight(msg, 120));
|
|
System.out.flush();
|
|
}
|
|
void chooseMRV() {
|
|
Slot best = null;
|
|
for (int i = 0, count, count2 = -1, bestScore = -1, n = TOTAL; i < n; i++) {
|
|
var s = slots[i];
|
|
if (assigned[s.key] != X) continue;
|
|
var pattern = patternForSlot(grid, 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 && slotScores[i] > bestScore)) {
|
|
best = s;
|
|
bestScore = slotScores[i];
|
|
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(grid, 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);
|
|
int idxInArray = (int) (r * r * r * (L - 1));
|
|
var idx = idxs[idxInArray];
|
|
var w = entry.words[idx];
|
|
var lemIdx = Lemma.unpackIndex(w);
|
|
if (used.get(lemIdx)) continue;
|
|
low = grid.lo;
|
|
top = grid.hi;
|
|
if (!placeWord(grid, k, slo, shi, w)) continue;
|
|
|
|
used.set(lemIdx);
|
|
assigned[k] = w;
|
|
|
|
if (backtrack(depth + 1)) return true;
|
|
|
|
assigned[k] = X;
|
|
used.clear(lemIdx);
|
|
grid.lo = low;
|
|
grid.hi = 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();
|
|
int idxInArray = (int) (r * r * r * (N - 1));
|
|
var w = entry.words[idxInArray];
|
|
var lemIdx = Lemma.unpackIndex(w);
|
|
if (used.get(lemIdx)) continue;
|
|
low = grid.lo;
|
|
top = grid.hi;
|
|
if (!placeWord(grid, k, slo, shi, w)) continue;
|
|
|
|
used.set(lemIdx);
|
|
assigned[k] = w;
|
|
|
|
if (backtrack(depth + 1)) return true;
|
|
|
|
assigned[k] = X;
|
|
used.clear(lemIdx);
|
|
grid.lo = low;
|
|
grid.hi = 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
|
|
|
|
var res = new FillResult(ok, new Gridded(grid), assigned, new FillStats(solver.nodes, solver.backtracks, (System.currentTimeMillis() - t0) / 1000.0, solver.lastMRV));
|
|
if (!multiThreaded) {
|
|
System.out.print("\r" + Strings.padRight("", 120) + "\r");
|
|
System.out.flush();
|
|
}
|
|
// 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",
|
|
res.wordCount(), TOTAL, res.nodes(), res.backtracks(), res.lastMRV(), res.seconds()
|
|
)
|
|
);
|
|
}
|
|
|
|
return res;
|
|
}
|
|
}
|