introduce bitloops
This commit is contained in:
@@ -6,7 +6,7 @@ import lombok.experimental.Delegate;
|
|||||||
import lombok.val;
|
import lombok.val;
|
||||||
import puzzle.Export.Gridded.Replacar.Cell;
|
import puzzle.Export.Gridded.Replacar.Cell;
|
||||||
import puzzle.Export.LetterVisit.LetterAt;
|
import puzzle.Export.LetterVisit.LetterAt;
|
||||||
import puzzle.SwedishGenerator.Clues;
|
import puzzle.Masker.Clues;
|
||||||
import puzzle.SwedishGenerator.FillResult;
|
import puzzle.SwedishGenerator.FillResult;
|
||||||
import puzzle.SwedishGenerator.Grid;
|
import puzzle.SwedishGenerator.Grid;
|
||||||
import puzzle.SwedishGenerator.Slotinfo;
|
import puzzle.SwedishGenerator.Slotinfo;
|
||||||
@@ -19,7 +19,7 @@ import java.util.stream.IntStream;
|
|||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
import static puzzle.SwedishGenerator.R;
|
import static puzzle.SwedishGenerator.R;
|
||||||
import static puzzle.SwedishGenerator.Lemma;
|
import static puzzle.SwedishGenerator.Lemma;
|
||||||
import static puzzle.SwedishGenerator.Slot;
|
import static puzzle.Masker.Slot;
|
||||||
import static puzzle.SwedishGenerator.C;
|
import static puzzle.SwedishGenerator.C;
|
||||||
import static puzzle.SwedishGenerator.X;
|
import static puzzle.SwedishGenerator.X;
|
||||||
|
|
||||||
@@ -100,7 +100,7 @@ public record Export() {
|
|||||||
for (var h = grid.hi & ~clues.hi; h != X; h &= h - 1) visitor.visit(64 | Long.numberOfTrailingZeros(h), grid.g);
|
for (var h = grid.hi & ~clues.hi; h != X; h &= h - 1) visitor.visit(64 | Long.numberOfTrailingZeros(h), grid.g);
|
||||||
}
|
}
|
||||||
public static IntStream walk(byte base, long lo, long hi) {
|
public static IntStream walk(byte base, long lo, long hi) {
|
||||||
if (Slot.increasing(base)) {
|
if (Slotinfo.increasing(base)) {
|
||||||
return IntStream.concat(
|
return IntStream.concat(
|
||||||
IntStream.generate(new IntSupplier() {
|
IntStream.generate(new IntSupplier() {
|
||||||
|
|
||||||
@@ -209,7 +209,7 @@ public record Export() {
|
|||||||
public int arrowRow() { return SwedishGenerator.IT[Slot.clueIndex(slotKey)].r(); }
|
public int arrowRow() { return SwedishGenerator.IT[Slot.clueIndex(slotKey)].r(); }
|
||||||
public int startRow() { return SwedishGenerator.IT[cells[0]].r(); }
|
public int startRow() { return SwedishGenerator.IT[cells[0]].r(); }
|
||||||
public int startCol() { return SwedishGenerator.IT[cells[0]].c(); }
|
public int startCol() { return SwedishGenerator.IT[cells[0]].c(); }
|
||||||
public boolean isReversed() { return !Slot.increasing(slotKey); }
|
public boolean isReversed() { return !Slotinfo.increasing(slotKey); }
|
||||||
public char direction() { return DIRECTION[Slot.dir(slotKey)]; }
|
public char direction() { return DIRECTION[Slot.dir(slotKey)]; }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -224,10 +224,9 @@ public record Export() {
|
|||||||
|
|
||||||
public record ExportedPuzzle(String[] grid, WordOut[] words, int difficulty, Rewards rewards) { }
|
public record ExportedPuzzle(String[] grid, WordOut[] words, int difficulty, Rewards rewards) { }
|
||||||
|
|
||||||
public record PuzzleResult(Clued clues, Slotinfo[] slots, FillResult filled) {
|
public record PuzzleResult(Clued clues, Gridded grid, Slotinfo[] slots, FillResult filled) {
|
||||||
|
|
||||||
public ExportedPuzzle exportFormatFromFilled(int difficulty, Rewards rewards) {
|
public ExportedPuzzle exportFormatFromFilled(int difficulty, Rewards rewards) {
|
||||||
var g = filled().grid();
|
|
||||||
var placed = new ArrayList<Placed>();
|
var placed = new ArrayList<Placed>();
|
||||||
for (var slot : slots) {
|
for (var slot : slots) {
|
||||||
placed.add(new Placed(slot.assign().w, slot.key(), Gridded.walk((byte) slot.key(), slot.lo(), slot.hi()).toArray()));
|
placed.add(new Placed(slot.assign().w, slot.key(), Gridded.walk((byte) slot.key(), slot.lo(), slot.hi()).toArray()));
|
||||||
@@ -235,7 +234,7 @@ public record Export() {
|
|||||||
|
|
||||||
// If nothing placed: return full grid mapped to letters/# only
|
// If nothing placed: return full grid mapped to letters/# only
|
||||||
if (placed.isEmpty()) {
|
if (placed.isEmpty()) {
|
||||||
return new ExportedPuzzle(g.exportGrid(clues.c, _ -> '#', '#'), new WordOut[0], difficulty, rewards);
|
return new ExportedPuzzle(grid.exportGrid(clues.c, _ -> '#', '#'), new WordOut[0], difficulty, rewards);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2) bounding box around all word cells + arrow cells, with 1-cell margin
|
// 2) bounding box around all word cells + arrow cells, with 1-cell margin
|
||||||
@@ -259,7 +258,7 @@ public record Export() {
|
|||||||
|
|
||||||
// 3) map of only used letter cells (everything else becomes '#')
|
// 3) map of only used letter cells (everything else becomes '#')
|
||||||
var letterAt = new HashMap<Integer, Character>();
|
var letterAt = new HashMap<Integer, Character>();
|
||||||
g.forEachLetter(clues.c(), (idx, letter) -> {
|
grid.forEachLetter(clues.c(), (idx, letter) -> {
|
||||||
if (letter == 0) return;
|
if (letter == 0) return;
|
||||||
letterAt.put(idx, (char) (64 | letter));
|
letterAt.put(idx, (char) (64 | letter));
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,8 +6,6 @@ public final class LongArrayList {
|
|||||||
private long[] a;
|
private long[] a;
|
||||||
private int size;
|
private int size;
|
||||||
|
|
||||||
public LongArrayList() { this(16); }
|
|
||||||
|
|
||||||
public LongArrayList(int initialCapacity) {
|
public LongArrayList(int initialCapacity) {
|
||||||
if (initialCapacity < 0) throw new IllegalArgumentException();
|
if (initialCapacity < 0) throw new IllegalArgumentException();
|
||||||
a = new long[initialCapacity];
|
a = new long[initialCapacity];
|
||||||
|
|||||||
@@ -104,10 +104,10 @@ public class Main {
|
|||||||
System.out.print(indentLines(res.clues().gridToString(), " "));
|
System.out.print(indentLines(res.clues().gridToString(), " "));
|
||||||
|
|
||||||
section("Grid (raw)");
|
section("Grid (raw)");
|
||||||
System.out.print(indentLines(res.filled().grid().gridToString(res.clues().c()), " "));
|
System.out.print(indentLines(res.grid().gridToString(res.clues().c()), " "));
|
||||||
|
|
||||||
section("Grid (human)");
|
section("Grid (human)");
|
||||||
System.out.print(indentLines(res.filled().grid().renderHuman(res.clues().c()), " "));
|
System.out.print(indentLines(res.grid().renderHuman(res.clues().c()), " "));
|
||||||
|
|
||||||
var exported = res.exportFormatFromFilled(1, new Rewards(50, 2, 1));
|
var exported = res.exportFormatFromFilled(1, new Rewards(50, 2, 1));
|
||||||
|
|
||||||
@@ -384,13 +384,14 @@ public class Main {
|
|||||||
static PuzzleResult _attempt(Rng rng, Dict dict, Opts opts) {
|
static PuzzleResult _attempt(Rng rng, Dict dict, Opts opts) {
|
||||||
long t0 = System.currentTimeMillis();
|
long t0 = System.currentTimeMillis();
|
||||||
TOTAL_ATTEMPTS.incrementAndGet();
|
TOTAL_ATTEMPTS.incrementAndGet();
|
||||||
var swe = new SwedishGenerator(rng, new int[STACK_SIZE], Clues.createEmpty());
|
var masker = new Masker(rng, new int[STACK_SIZE], Masker.Clues.createEmpty());
|
||||||
var mask = swe.generateMask(opts.clueSize, opts.pop, opts.gens, opts.offspring);
|
var mask = masker.generateMask(opts.clueSize, opts.pop, opts.gens, opts.offspring);
|
||||||
if (mask == null) return null;
|
if (mask == null) return null;
|
||||||
val multiThreaded = Thread.currentThread().getName().contains("pool");
|
val multiThreaded = Thread.currentThread().getName().contains("pool");
|
||||||
var slots = extractSlots(mask, dict.index());
|
var slots = Masker.extractSlots(mask, dict.index());
|
||||||
val slotInfo = scoreSlots(new int[slots.length], slots);
|
val slotInfo = Masker.scoreSlots(new int[slots.length], slots);
|
||||||
var filled = fillMask(rng, slotInfo, mask.toGrid(), multiThreaded);
|
var grid = mask.toGrid();
|
||||||
|
var filled = fillMask(rng, slotInfo, grid, multiThreaded);
|
||||||
|
|
||||||
TOTAL_NODES.addAndGet(filled.stats().nodes);
|
TOTAL_NODES.addAndGet(filled.stats().nodes);
|
||||||
TOTAL_BACKTRACKS.addAndGet(filled.stats().backtracks);
|
TOTAL_BACKTRACKS.addAndGet(filled.stats().backtracks);
|
||||||
@@ -412,7 +413,7 @@ public class Main {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (filled.ok() && (opts.minSimplicity <= 0 || filled.stats().simplicity >= opts.minSimplicity)) {
|
if (filled.ok() && (opts.minSimplicity <= 0 || filled.stats().simplicity >= opts.minSimplicity)) {
|
||||||
return new PuzzleResult(new Clued(mask), slotInfo, filled);
|
return new PuzzleResult(new Clued(mask), new Gridded(grid), slotInfo, filled);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (opts.verbose && filled.ok()) {
|
if (opts.verbose && filled.ok()) {
|
||||||
|
|||||||
520
src/main/java/puzzle/Masker.java
Normal file
520
src/main/java/puzzle/Masker.java
Normal file
@@ -0,0 +1,520 @@
|
|||||||
|
package puzzle;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.val;
|
||||||
|
import puzzle.Export.Gridded;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.stream.IntStream;
|
||||||
|
|
||||||
|
import static java.lang.Long.*;
|
||||||
|
import static puzzle.SwedishGenerator.*;
|
||||||
|
|
||||||
|
public record Masker(Rng rng, int[] stack, Clues cache) {
|
||||||
|
|
||||||
|
public static final int[][] MUTATE_RI = new int[SwedishGenerator.SIZE][625];
|
||||||
|
|
||||||
|
static {
|
||||||
|
for (int i = 0; i < SwedishGenerator.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(SwedishGenerator.clamp(ti.r() + dr1 + dr2, 0, R - 1),
|
||||||
|
SwedishGenerator.clamp(ti.c() + dc1 + dc2, 0, C - 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 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)
|
||||||
|
private static void processSlotRev(Clues c, SlotVisitor visitor, int key) {
|
||||||
|
long rayLo = PATH_LO[key];
|
||||||
|
long rayHi = PATH_HI[key];
|
||||||
|
// only consider clue cells
|
||||||
|
long hitsLo = rayLo & c.lo;
|
||||||
|
long hitsHi = rayHi & c.hi;
|
||||||
|
|
||||||
|
if (hitsHi != X) {
|
||||||
|
int msb = 63 - numberOfLeadingZeros(hitsHi);
|
||||||
|
long stop = 1L << msb;
|
||||||
|
rayHi &= -(stop << 1); // keep bits > stop
|
||||||
|
rayLo = 0; // lo indices are below stop
|
||||||
|
} else if (hitsLo != X) {
|
||||||
|
int msb = 63 - numberOfLeadingZeros(hitsLo);
|
||||||
|
long stop = 1L << msb;
|
||||||
|
rayLo &= -(stop << 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
visitor.visit(key, rayLo, rayHi);
|
||||||
|
}
|
||||||
|
private static void processSlot(Clues c, SlotVisitor visitor, int key) {
|
||||||
|
long rayLo = PATH_LO[key];
|
||||||
|
long rayHi = PATH_HI[key];
|
||||||
|
long hitsLo = rayLo & c.lo;
|
||||||
|
long hitsHi = rayHi & c.hi;
|
||||||
|
|
||||||
|
if (hitsLo != X) {
|
||||||
|
long stop = 1L << numberOfTrailingZeros(hitsLo);
|
||||||
|
rayLo &= (stop - 1);
|
||||||
|
rayHi = 0; // any hi is beyond the stop
|
||||||
|
} else if (hitsHi != X) {
|
||||||
|
long stop = 1L << numberOfTrailingZeros(hitsHi);
|
||||||
|
// keep all lo (lo indices are < any hi index), but cut hi below stop
|
||||||
|
rayHi &= (stop - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
visitor.visit(key, rayLo, rayHi);
|
||||||
|
}
|
||||||
|
public 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;
|
||||||
|
}
|
||||||
|
public static Slotinfo[] scoreSlots(int[] slotScores, Slot[] slots) {
|
||||||
|
val count = new byte[SwedishGenerator.SIZE];
|
||||||
|
Slotinfo[] slotInfo = new Slotinfo[slots.length];
|
||||||
|
for (var s : slots) {
|
||||||
|
for (long b = s.lo; b != X; b &= b - 1) count[numberOfTrailingZeros(b)]++;
|
||||||
|
for (long b = s.hi; b != X; b &= b - 1) count[64 | numberOfTrailingZeros(b)]++;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < slots.length; i++) {
|
||||||
|
var slot = slots[i];
|
||||||
|
slotScores[i] = slotScore(count, slot.lo, slot.hi);
|
||||||
|
slotInfo[i] = new Slotinfo(slot.key, slot.lo, slot.hi, slotScores[i], new Assign(), slot.entry);
|
||||||
|
}
|
||||||
|
return slotInfo;
|
||||||
|
}
|
||||||
|
public static int slotScore(byte[] count, long lo, long hi) {
|
||||||
|
int cross = 0;
|
||||||
|
for (long b = lo; b != X; b &= b - 1) cross += (count[numberOfTrailingZeros(b)] - 1);
|
||||||
|
for (long b = hi; b != X; b &= b - 1) cross += (count[64 | numberOfTrailingZeros(b)] - 1);
|
||||||
|
return cross * 10 + Slot.length(lo, hi);
|
||||||
|
}
|
||||||
|
|
||||||
|
public long maskFitness(final Clues grid, int clueSize) {
|
||||||
|
|
||||||
|
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() - clueSize)) * 16000L);
|
||||||
|
boolean hasSlots = false;
|
||||||
|
|
||||||
|
for (long bits = lo_cl; bits != X; bits &= bits - 1) {
|
||||||
|
long lsb = bits & -bits;
|
||||||
|
int clueIdx = numberOfTrailingZeros(lsb);
|
||||||
|
int v = (grid.vlo & lsb) != 0 ? 1 : 0;
|
||||||
|
int r = (grid.rlo & lsb) != 0 ? 1 : 0;
|
||||||
|
int key = Slot.packSlotKey(clueIdx, (r << 1) | v);
|
||||||
|
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) {
|
||||||
|
int msb = 63 - numberOfLeadingZeros(hHi);
|
||||||
|
rHi &= -(1L << msb << 1);
|
||||||
|
rLo = 0;
|
||||||
|
} else if (hLo != X) {
|
||||||
|
int msb = 63 - numberOfLeadingZeros(hLo);
|
||||||
|
rLo &= -(1L << msb << 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ((rLo | rHi) != X) {
|
||||||
|
hasSlots = true;
|
||||||
|
if (Slot.horiz(key)) {
|
||||||
|
cHLo |= rLo;
|
||||||
|
cHHi |= rHi;
|
||||||
|
} else {
|
||||||
|
cVLo |= rLo;
|
||||||
|
cVHi |= rHi;
|
||||||
|
}
|
||||||
|
if ((bitCount(rLo) + 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 = numberOfTrailingZeros(lsb);
|
||||||
|
int v = (grid.vhi & lsb) != 0 ? 1 : 0;
|
||||||
|
int r = (grid.rhi & lsb) != 0 ? 1 : 0;
|
||||||
|
int key = Slot.packSlotKey(64 | clueIdx, (r << 1) | v);
|
||||||
|
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) {
|
||||||
|
int msb = 63 - numberOfLeadingZeros(hHi);
|
||||||
|
rHi &= -(1L << msb << 1);
|
||||||
|
rLo = 0;
|
||||||
|
} else if (hLo != X) {
|
||||||
|
int msb = 63 - numberOfLeadingZeros(hLo);
|
||||||
|
rLo &= -(1L << msb << 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ((rLo | rHi) != X) {
|
||||||
|
hasSlots = true;
|
||||||
|
if (Slot.horiz(key)) {
|
||||||
|
cHLo |= rLo;
|
||||||
|
cHHi |= rHi;
|
||||||
|
} else {
|
||||||
|
cVLo |= rLo;
|
||||||
|
cVHi |= rHi;
|
||||||
|
}
|
||||||
|
if ((bitCount(rLo) + 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, size, sp, cur; 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, nLo, nHi; bits != X; bits &= bits - 1) {
|
||||||
|
int clueIdx = base | numberOfTrailingZeros(bits);
|
||||||
|
|
||||||
|
// start nieuwe component
|
||||||
|
size = 0;
|
||||||
|
stack[0] = clueIdx;
|
||||||
|
sp = 1;
|
||||||
|
|
||||||
|
// mark seen
|
||||||
|
if ((clueIdx & 64) == 0) seenLo |= 1L << clueIdx;
|
||||||
|
else seenHi |= 1L << (clueIdx & 63);
|
||||||
|
|
||||||
|
// flood fill / bfs
|
||||||
|
while (sp > 0) {
|
||||||
|
cur = stack[--sp];
|
||||||
|
size++;
|
||||||
|
|
||||||
|
// neighbors als 2x long masks
|
||||||
|
nLo = NBR8_PACKED_LO[cur];
|
||||||
|
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 = 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 | numberOfTrailingZeros(nHi); // 64..127
|
||||||
|
seenHi |= lsb;
|
||||||
|
|
||||||
|
stack[sp++] = nidx;
|
||||||
|
|
||||||
|
nHi &= nHi - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (size >= 2) penalty += (size - 1L) * 120L;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
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 & 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;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Clues randomMask(final int clueSize) {
|
||||||
|
var g = Clues.createEmpty();
|
||||||
|
for (int placed = 0, guard = 0, ri; placed < clueSize && guard < 4000; guard++) {
|
||||||
|
|
||||||
|
ri = rng.randint0_SIZE();
|
||||||
|
if (isLo(ri)) {
|
||||||
|
if (g.isClueLo(ri)) continue;
|
||||||
|
var d_idx = rng.randint2bitByte();
|
||||||
|
if (g.hasRoomForClue(OFFSETS_D_IDX[Slot.packSlotKey(ri, d_idx)])) {
|
||||||
|
g.setClueLo(1L << ri, d_idx);
|
||||||
|
placed++;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (g.isClueHi(ri)) continue;
|
||||||
|
var d_idx = rng.randint2bitByte();
|
||||||
|
if (g.hasRoomForClue(OFFSETS_D_IDX[Slot.packSlotKey(ri, d_idx)])) {
|
||||||
|
g.setClueHi(1L << (ri & 63), d_idx);
|
||||||
|
placed++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return g;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Clues mutate(Clues c) {
|
||||||
|
var bytes = MUTATE_RI[rng.randint0_SIZE()];
|
||||||
|
for (int k = 0, ri; k < 4; k++) {
|
||||||
|
ri = bytes[rng.randint0_624()];
|
||||||
|
if (isLo(ri)) {
|
||||||
|
if (!c.cluelessLo(ri)) {
|
||||||
|
var d_idx = rng.randint2bitByte();
|
||||||
|
if (c.hasRoomForClue(OFFSETS_D_IDX[Slot.packSlotKey(ri, d_idx)])) c.setClueLo(1L << ri, d_idx);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!c.cluelessHi(ri)) {
|
||||||
|
var d_idx = rng.randint2bitByte();
|
||||||
|
if (c.hasRoomForClue(OFFSETS_D_IDX[Slot.packSlotKey(ri, d_idx)])) c.setClueHi(1L << (ri & 63), d_idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Clues crossover(Clues a, Clues other) {
|
||||||
|
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var c = new Clues(
|
||||||
|
(a.lo & ~maskLo) | (other.lo & maskLo),
|
||||||
|
(a.hi & ~maskHi) | (other.hi & maskHi),
|
||||||
|
(a.vlo & ~maskLo) | (other.vlo & maskLo),
|
||||||
|
(a.vhi & ~maskHi) | (other.vhi & maskHi),
|
||||||
|
(a.rlo & ~maskLo) | (other.rlo & maskLo),
|
||||||
|
(a.rhi & ~maskHi) | (other.rhi & maskHi));
|
||||||
|
|
||||||
|
for (var l = c.lo & ~c.rlo & ~c.vlo; l != X; l &= l - 1) clearCluesLo(c, numberOfTrailingZeros(l), 0);
|
||||||
|
for (var l = c.lo & ~c.rlo & c.vlo; l != X; l &= l - 1) clearCluesLo(c, numberOfTrailingZeros(l), 1);
|
||||||
|
for (var l = c.lo & c.rlo & ~c.vlo; l != X; l &= l - 1) clearCluesLo(c, numberOfTrailingZeros(l), 2);
|
||||||
|
for (var l = c.lo & c.rlo & c.vlo; l != X; l &= l - 1) clearCluesLo(c, numberOfTrailingZeros(l), 3);
|
||||||
|
for (var h = c.hi & ~c.rhi & ~c.vhi; h != X; h &= h - 1) clearCluesHi(c, numberOfTrailingZeros(h), 0);
|
||||||
|
for (var h = c.hi & ~c.rhi & c.vhi; h != X; h &= h - 1) clearCluesHi(c, numberOfTrailingZeros(h), 1);
|
||||||
|
for (var h = c.hi & c.rhi & ~c.vhi; h != X; h &= h - 1) clearCluesHi(c, (numberOfTrailingZeros(h)), 2);
|
||||||
|
for (var h = c.hi & c.rhi & ~c.vhi; h != X; h &= h - 1) clearCluesHi(c, (numberOfTrailingZeros(h)), 3);
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void clearCluesLo(Clues out, int idx, int d) { if (!out.hasRoomForClue(OFFSETS_D_IDX[Slot.packSlotKey(idx, d)])) out.clearClueLo(~(1L << idx)); }
|
||||||
|
|
||||||
|
public static void clearCluesHi(Clues out, int idx, int d) { if (!out.hasRoomForClue(OFFSETS_D_IDX[Slot.packSlotKey(64 | idx, d)])) out.clearClueHi(~(1L << idx)); }
|
||||||
|
|
||||||
|
public Clues hillclimb(Clues start, int clue_size, int limit) {
|
||||||
|
var best = start;
|
||||||
|
var bestF = maskFitness(best, clue_size);
|
||||||
|
var fails = 0;
|
||||||
|
|
||||||
|
while (fails < limit) {
|
||||||
|
cache.from(best);
|
||||||
|
var cand = mutate(best);
|
||||||
|
var f = maskFitness(cand, clue_size);
|
||||||
|
if (f < bestF) {
|
||||||
|
best = cand;
|
||||||
|
bestF = f;
|
||||||
|
fails = 0;
|
||||||
|
} else {
|
||||||
|
best.from(cache);
|
||||||
|
fails++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return best;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Clues generateMask(int clueSize, int popSize, int gens, int offspring) {
|
||||||
|
class GridAndFit {
|
||||||
|
|
||||||
|
Clues grid;
|
||||||
|
long fite = -1;
|
||||||
|
GridAndFit(Clues grid) { this.grid = grid; }
|
||||||
|
long fit() {
|
||||||
|
if (fite == -1) this.fite = maskFitness(grid, clueSize);
|
||||||
|
return this.fite;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Main.VERBOSE) System.out.println("generateMask init pop: " + popSize + " clueSize: " + clueSize);
|
||||||
|
var pop = new ArrayList<GridAndFit>();
|
||||||
|
for (var i = 0; i < popSize; i++) {
|
||||||
|
if (Thread.currentThread().isInterrupted()) return null;
|
||||||
|
pop.add(new GridAndFit(hillclimb(randomMask(clueSize), clueSize, 180)));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var gen = 0; gen < gens; gen++) {
|
||||||
|
if (Thread.currentThread().isInterrupted()) break;
|
||||||
|
var children = new ArrayList<GridAndFit>();
|
||||||
|
|
||||||
|
for (var k = 0; k < offspring; k++) {
|
||||||
|
if (Thread.currentThread().isInterrupted()) break;
|
||||||
|
var p1 = pop.get(rng.randint(pop.size() - 1));
|
||||||
|
var p2 = pop.get(rng.randint(pop.size() - 1));
|
||||||
|
var child = crossover(p1.grid, p2.grid);
|
||||||
|
children.add(new GridAndFit(hillclimb(child, clueSize, 70)));
|
||||||
|
}
|
||||||
|
|
||||||
|
pop.addAll(children);
|
||||||
|
pop.sort(Comparator.comparingLong(GridAndFit::fit));
|
||||||
|
|
||||||
|
var next = new ArrayList<GridAndFit>();
|
||||||
|
for (var cand : pop) {
|
||||||
|
if (next.size() >= offspring) 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());
|
||||||
|
}
|
||||||
|
if (pop.isEmpty()) return null;
|
||||||
|
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;
|
||||||
|
}//@formatter:off
|
||||||
|
@FunctionalInterface public interface SlotVisitor { void visit(int key, long lo, long hi); }
|
||||||
|
@AllArgsConstructor
|
||||||
|
public static class Clues {
|
||||||
|
|
||||||
|
long lo, hi, vlo, vhi, rlo, rhi;
|
||||||
|
public long lo() { return lo; }
|
||||||
|
public long hi() { return hi; }
|
||||||
|
public long vlo() { return vlo; }
|
||||||
|
public long vhi() { return vhi; }
|
||||||
|
public long rlo() { return rlo; }
|
||||||
|
public long rhi() { return rhi; }
|
||||||
|
|
||||||
|
public static Clues createEmpty() { return new Clues(0, 0, 0, 0, 0, 0); }
|
||||||
|
public boolean cluelessLo(int idx) {
|
||||||
|
if (!isClueLo(idx)) return false;
|
||||||
|
clearClueLo(~(1L << idx));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
public boolean cluelessHi(int idx) {
|
||||||
|
if (!isClueHi(idx)) return false;
|
||||||
|
clearClueHi(~(1L << (idx & 63)));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
public boolean hasRoomForClue(long packed) { return (packed) != X && notClue(packed & 0x7FL) && notClue((packed >>> 7) & 0x7FL); }
|
||||||
|
|
||||||
|
public void setClueLo(long mask, byte idx) {
|
||||||
|
lo |= mask;
|
||||||
|
if ((idx & 1) != 0) vlo |= mask;
|
||||||
|
else vlo &= ~mask;
|
||||||
|
if ((idx & 2) != 0) rlo |= mask;
|
||||||
|
else rlo &= ~mask;
|
||||||
|
}
|
||||||
|
public void setClueHi(long mask, byte idx) {
|
||||||
|
hi |= mask;
|
||||||
|
if ((idx & 1) != 0) vhi |= mask;
|
||||||
|
else vhi &= ~mask;
|
||||||
|
if ((idx & 2) != 0) rhi |= mask;
|
||||||
|
else rhi &= ~mask;
|
||||||
|
}
|
||||||
|
public void clearClueLo(long mask) {
|
||||||
|
lo &= mask;
|
||||||
|
vlo &= mask;
|
||||||
|
rlo &= mask;
|
||||||
|
}
|
||||||
|
public void clearClueHi(long mask) {
|
||||||
|
hi &= mask;
|
||||||
|
vhi &= mask;
|
||||||
|
rhi &= mask;
|
||||||
|
}
|
||||||
|
public boolean isClueLo(int index) { return ((lo >>> index) & 1L) != X; }
|
||||||
|
public boolean isClueHi(int index) { return ((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 bitCount(lo) + 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 (bitCount(matchLo & MASK_LO) + bitCount(matchHi & MASK_HI)) / SIZED;
|
||||||
|
}
|
||||||
|
public Grid toGrid() { return new Grid(new byte[SwedishGenerator.SIZE], lo, hi); }
|
||||||
|
public void forEachSlot(SlotVisitor visitor) {
|
||||||
|
for (var l = lo & ~rlo & vlo; l != X; l &= l - 1) processSlot(this, visitor, Slot.packSlotKey(numberOfTrailingZeros(l), 1));
|
||||||
|
for (var l = lo & ~rlo & ~vlo; l != X; l &= l - 1) processSlot(this, visitor, Slot.packSlotKey(numberOfTrailingZeros(l), 0));
|
||||||
|
for (var l = lo & rlo & ~vlo; l != X; l &= l - 1) processSlotRev(this, visitor, Slot.packSlotKey(numberOfTrailingZeros(l), 2));
|
||||||
|
for (var l = lo & rlo & vlo; l != X; l &= l - 1) processSlotRev(this, visitor, Slot.packSlotKey(numberOfTrailingZeros(l), 3));
|
||||||
|
for (var h = hi & ~rhi & vhi; h != X; h &= h - 1) processSlot(this, visitor, Slot.packSlotKey(64 | numberOfTrailingZeros(h), 1));
|
||||||
|
for (var h = hi & ~rhi & ~vhi; h != X; h &= h - 1) processSlot(this, visitor, Slot.packSlotKey(64 | numberOfTrailingZeros(h), 0));
|
||||||
|
for (var h = hi & rhi & ~vhi; h != X; h &= h - 1) processSlotRev(this, visitor, Slot.packSlotKey((64 | numberOfTrailingZeros(h)), 2));
|
||||||
|
for (var h = hi & rhi & vhi; h != X; h &= h - 1) processSlotRev(this, visitor, Slot.packSlotKey((64 | numberOfTrailingZeros(h)), 3));
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 bitCount(lo) + bitCount(hi); }
|
||||||
|
public static int clueIndex(int key) { return key >>> BIT_FOR_DIR; }
|
||||||
|
public static int dir(int key) { return key & 3; }
|
||||||
|
public IntStream walk() { return Gridded.walk((byte) key, lo, hi); }
|
||||||
|
public static boolean horiz(int d) { return (d & 1) != 0; }
|
||||||
|
public static int packSlotKey(int idx, int d) { return (idx << BIT_FOR_DIR) | d; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,11 +17,8 @@ import java.io.IOException;
|
|||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.stream.IntStream;
|
|
||||||
import static java.lang.Long.*;
|
import static java.lang.Long.*;
|
||||||
import static java.lang.Long.numberOfTrailingZeros;
|
import static java.lang.Long.numberOfTrailingZeros;
|
||||||
import static java.nio.charset.StandardCharsets.US_ASCII;
|
import static java.nio.charset.StandardCharsets.US_ASCII;
|
||||||
@@ -43,77 +40,48 @@ import static java.nio.charset.StandardCharsets.US_ASCII;
|
|||||||
* java SwedishGenerator [--seed N] [--pop N] [--gens N] [--tries N] [--words word-list.txt]
|
* java SwedishGenerator [--seed N] [--pop N] [--gens N] [--tries N] [--words word-list.txt]
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("ALL")
|
@SuppressWarnings("ALL")
|
||||||
public record SwedishGenerator(Rng rng, int[] stack, Clues cache) {
|
public class SwedishGenerator {
|
||||||
|
|
||||||
record CandidateInfo(int[] indices, int count) { }
|
public static final long GT_1_OFFSET_53_BIT = 0x3E00000000000000L;
|
||||||
|
public static final long X = 0L;
|
||||||
//@formatter:off
|
public static final int LOG_EVERY_MS = 200;
|
||||||
@FunctionalInterface interface SlotVisitor { void visit(int key, long lo, long hi); }
|
public static final int BAR_LEN = 22;
|
||||||
//@formatter:on
|
public static final int C = Config.PUZZLE_COLS;
|
||||||
static final long GT_1_OFFSET_53_BIT = 0x3E00000000000000L;
|
public static final double CROSS_R = (C - 1) / 2.0;
|
||||||
static final long X = 0L;
|
public static final int R = Config.PUZZLE_ROWS;
|
||||||
static final int LOG_EVERY_MS = 200;
|
public static final double CROSS_C = (R - 1) / 2.0;
|
||||||
static final int BAR_LEN = 22;
|
public static final int SIZE = C * R;// ~18
|
||||||
static final int C = Config.PUZZLE_COLS;
|
public static final int SIZE_MIN_1 = SIZE - 1;// ~18
|
||||||
static final double CROSS_R = (C - 1) / 2.0;
|
public static final double SIZED = (double) SIZE;// ~18
|
||||||
static final int R = Config.PUZZLE_ROWS;
|
public static final long MASK_LO = (SIZE >= 64) ? -1L : (1L << SIZE) - 1;
|
||||||
static final double CROSS_C = (R - 1) / 2.0;
|
public static final long MASK_HI = (SIZE <= 64) ? 0L : (SIZE >= 128 ? -1L : (1L << (SIZE - 64)) - 1);
|
||||||
static final int SIZE = C * R;// ~18
|
public static final int MAX_WORD_LENGTH = C <= R ? C : R;
|
||||||
static final int SIZE_MIN_1 = SIZE - 1;// ~18
|
public static final int MAX_WORD_LENGTH_PLUS_ONE = MAX_WORD_LENGTH + 1;
|
||||||
static final double SIZED = (double) SIZE;// ~18
|
public static final int MIN_LEN = Config.MIN_LEN;
|
||||||
static final long MASK_LO = (SIZE >= 64) ? -1L : (1L << SIZE) - 1;
|
public static final int MAX_TRIES_PER_SLOT = Config.MAX_TRIES_PER_SLOT;
|
||||||
static final long MASK_HI = (SIZE <= 64) ? 0L : (SIZE >= 128 ? -1L : (1L << (SIZE - 64)) - 1);
|
public static final int STACK_SIZE = 64;
|
||||||
static final int MAX_WORD_LENGTH = C <= R ? C : R;
|
public static final char C_DASH = '\0';
|
||||||
static final int MAX_WORD_LENGTH_PLUS_ONE = MAX_WORD_LENGTH + 1;
|
public static final byte DASH = (byte) C_DASH;
|
||||||
static final int MIN_LEN = Config.MIN_LEN;
|
public static final long RANGE_0_SIZE = (long) SIZE_MIN_1 - 0L + 1L;
|
||||||
static final int MAX_TRIES_PER_SLOT = Config.MAX_TRIES_PER_SLOT;
|
public static final long RANGE_0_624 = 624L - 0L + 1L;
|
||||||
static final int STACK_SIZE = 64;
|
public static final int CLUE_INDEX_MAX_SIZE = (288 | 3) + 1;
|
||||||
static final char C_DASH = '\0';
|
public static int clamp(int x, int a, int b) { return Math.max(a, Math.min(b, x)); }
|
||||||
static final byte _1 = 49, _9 = 57, A = 65, Z = 90, DASH = (byte) C_DASH;
|
|
||||||
static final long RANGE_0_SIZE = (long) SIZE_MIN_1 - 0L + 1L;
|
|
||||||
static final long RANGE_0_624 = 624L - 0L + 1L;
|
|
||||||
//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
|
@AllArgsConstructor
|
||||||
static class Pick {
|
public static class Pick {
|
||||||
|
|
||||||
Slotinfo slot;
|
public Slotinfo slot;
|
||||||
int[] indices;
|
public int[] indices;
|
||||||
int count;
|
public int count;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 0b11
|
public static final long[] OFFSETS_D_IDX = Neighbors9x8.OFFSET_D_IDX_0_BASE;
|
||||||
//0b00
|
public static final rci[] IT = Neighbors9x8.IT;
|
||||||
// 0b01
|
public static final long[] NBR8_PACKED_LO = Neighbors9x8.NBR8_PACKED_LO;
|
||||||
// 0b10
|
public static final long[] NBR8_PACKED_HI = Neighbors9x8.NBR8_PACKED_HI;
|
||||||
|
public static final long[] PATH_LO = Neighbors9x8.PATH_LO;
|
||||||
static final byte B0 = (byte) 0;
|
public static final long[] PATH_HI = Neighbors9x8.PATH_HI;
|
||||||
static final byte B64 = (byte) 64;
|
public static final Pick PICK_DONE = null;//new Pick(null, null, 0, true);
|
||||||
static final long[] OFFSETS_D_IDX = Neighbors9x8.OFFSET_D_IDX_0_BASE;
|
public static final Pick PICK_NOT_DONE = new Pick(null, null, 0);
|
||||||
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
|
@RequiredArgsConstructor
|
||||||
@Getter
|
@Getter
|
||||||
@@ -128,7 +96,7 @@ public record SwedishGenerator(Rng rng, int[] stack, Clues cache) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static record FillResult(boolean ok,
|
public static record FillResult(boolean ok,
|
||||||
Gridded grid,
|
|
||||||
@Delegate FillStats stats) {
|
@Delegate FillStats stats) {
|
||||||
|
|
||||||
static public long calcSimpel(Slotinfo[] slots) {
|
static public long calcSimpel(Slotinfo[] slots) {
|
||||||
@@ -145,15 +113,15 @@ public record SwedishGenerator(Rng rng, int[] stack, Clues cache) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static final class Rng {
|
public static final class Rng {
|
||||||
|
|
||||||
@Getter private int x;
|
@Getter private int x;
|
||||||
Rng(int seed) {
|
public Rng(int seed) {
|
||||||
var s = seed;
|
var s = seed;
|
||||||
if (s == 0) s = 1;
|
if (s == 0) s = 1;
|
||||||
this.x = s;
|
this.x = s;
|
||||||
}
|
}
|
||||||
int nextU32() {
|
public int nextU32() {
|
||||||
var y = x;
|
var y = x;
|
||||||
y ^= (y << 13);
|
y ^= (y << 13);
|
||||||
y ^= (y >>> 17);
|
y ^= (y >>> 17);
|
||||||
@@ -161,99 +129,24 @@ public record SwedishGenerator(Rng rng, int[] stack, Clues cache) {
|
|||||||
x = y;
|
x = y;
|
||||||
return y;
|
return y;
|
||||||
}
|
}
|
||||||
int randint2bit() { return nextU32() & 3; }
|
public int randint2bit() { return nextU32() & 3; }
|
||||||
byte randint2bitByte() { return (byte) (nextU32() & 3); }
|
public byte randint2bitByte() { return (byte) (nextU32() & 3); }
|
||||||
int randint(int max) { return (int) (((nextU32() & 0xFFFFFFFFL) % ((long) max - 0L + 1L))); }
|
public int randint(int max) { return (int) (((nextU32() & 0xFFFFFFFFL) % ((long) max - 0L + 1L))); }
|
||||||
int randint0_SIZE() { return (int) (((nextU32() & 0xFFFFFFFFL) % RANGE_0_SIZE)); }
|
public int randint0_SIZE() { return (int) (((nextU32() & 0xFFFFFFFFL) % RANGE_0_SIZE)); }
|
||||||
int randint0_624() { return (int) (((nextU32() & 0xFFFFFFFFL) % RANGE_0_624)); }
|
public int randint0_624() { return (int) (((nextU32() & 0xFFFFFFFFL) % RANGE_0_624)); }
|
||||||
double nextFloat() { return (nextU32() & 0xFFFFFFFFL) / 4294967295.0; }
|
public double nextFloat() { return (nextU32() & 0xFFFFFFFFL) / 4294967295.0; }
|
||||||
int biasedIndexPow3(int N) { return (int) (((Math.min(nextU32(), Math.min(nextU32(), nextU32())) & 0xFFFFFFFFL) * (long) N) >>> 32); }
|
public int biasedIndexPow3(int N) { return (int) (((Math.min(nextU32(), Math.min(nextU32(), nextU32())) & 0xFFFFFFFFL) * (long) N) >>> 32); }
|
||||||
}
|
}
|
||||||
|
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
static class Clues {
|
public static class Grid {
|
||||||
|
|
||||||
long lo, hi, vlo, vhi, rlo, rhi;
|
public final byte[] g;
|
||||||
public static Clues createEmpty() { return new Clues(0, 0, 0, 0, 0, 0); }
|
|
||||||
boolean cluelessLo(int idx) {
|
|
||||||
if (!isClueLo(idx)) return false;
|
|
||||||
clearClueLo(~(1L << idx));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
boolean cluelessHi(int idx) {
|
|
||||||
if (!isClueHi(idx)) return false;
|
|
||||||
clearClueHi(~(1L << (idx & 63)));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
public boolean hasRoomForClue(long packed) { return (packed) != X && notClue(packed & 0x7FL) && notClue((packed >>> 7) & 0x7FL); }
|
|
||||||
|
|
||||||
public void setClueLo(long mask, byte idx) {
|
|
||||||
lo |= mask;
|
|
||||||
if ((idx & 1) != 0) vlo |= mask;
|
|
||||||
else vlo &= ~mask;
|
|
||||||
if ((idx & 2) != 0) rlo |= mask;
|
|
||||||
else rlo &= ~mask;
|
|
||||||
}
|
|
||||||
public void setClueHi(long mask, byte idx) {
|
|
||||||
hi |= mask;
|
|
||||||
if ((idx & 1) != 0) vhi |= mask;
|
|
||||||
else vhi &= ~mask;
|
|
||||||
if ((idx & 2) != 0) rhi |= mask;
|
|
||||||
else rhi &= ~mask;
|
|
||||||
}
|
|
||||||
void clearClueLo(long mask) {
|
|
||||||
lo &= mask;
|
|
||||||
vlo &= mask;
|
|
||||||
rlo &= mask;
|
|
||||||
}
|
|
||||||
void clearClueHi(long mask) {
|
|
||||||
hi &= mask;
|
|
||||||
vhi &= mask;
|
|
||||||
rhi &= mask;
|
|
||||||
}
|
|
||||||
public boolean isClueLo(int index) { return ((lo >>> index) & 1L) != X; }
|
|
||||||
public boolean isClueHi(int index) { return ((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 bitCount(lo) + 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 (bitCount(matchLo & MASK_LO) + 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 & ~rlo & vlo; l != X; l &= l - 1) processSlot(this, visitor, Slot.packSlotKey(numberOfTrailingZeros(l), 1));
|
|
||||||
for (var l = lo & ~rlo & ~vlo; l != X; l &= l - 1) processSlot(this, visitor, Slot.packSlotKey(numberOfTrailingZeros(l), 0));
|
|
||||||
for (var l = lo & rlo & ~vlo; l != X; l &= l - 1) processSlotRev(this, visitor, Slot.packSlotKey(numberOfTrailingZeros(l), 2));
|
|
||||||
for (var l = lo & rlo & vlo; l != X; l &= l - 1) processSlotRev(this, visitor, Slot.packSlotKey(numberOfTrailingZeros(l), 3));
|
|
||||||
for (var h = hi & ~rhi & vhi; h != X; h &= h - 1) processSlot(this, visitor, Slot.packSlotKey(64 | numberOfTrailingZeros(h), 1));
|
|
||||||
for (var h = hi & ~rhi & ~vhi; h != X; h &= h - 1) processSlot(this, visitor, Slot.packSlotKey(64 | numberOfTrailingZeros(h), 0));
|
|
||||||
for (var h = hi & rhi & ~vhi; h != X; h &= h - 1) processSlotRev(this, visitor, Slot.packSlotKey((64 | numberOfTrailingZeros(h)), 2));
|
|
||||||
for (var h = hi & rhi & vhi; h != X; h &= h - 1) processSlotRev(this, visitor, Slot.packSlotKey((64 | numberOfTrailingZeros(h)), 3));
|
|
||||||
}
|
|
||||||
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;
|
public long lo, hi;
|
||||||
static int offset(int r, int c) { return r | (c << 3); }
|
public static int offset(int r, int c) { return r | (c << 3); }
|
||||||
}
|
}
|
||||||
|
|
||||||
static record DictEntry(long[] words, long[][] posBitsets, int length, int numlong) { }
|
public static record DictEntry(long[] words, long[][] posBitsets, int length, int numlong) { }
|
||||||
|
|
||||||
public static interface Lemma {
|
public static interface Lemma {
|
||||||
|
|
||||||
@@ -343,399 +236,15 @@ public record SwedishGenerator(Rng rng, int[] stack, Clues cache) {
|
|||||||
for (var n = 1; n < arr.length; n++) if (arr[n].assign.w != X) k++;
|
for (var n = 1; n < arr.length; n++) if (arr[n].assign.w != X) k++;
|
||||||
return k;
|
return k;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
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 bitCount(lo) + 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 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 packSlotKey(int idx, int d) { return (idx << BIT_FOR_DIR) | d; }
|
|
||||||
}
|
|
||||||
// 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)
|
|
||||||
private static void processSlotRev(Clues c, SlotVisitor visitor, int key) {
|
|
||||||
long rayLo = PATH_LO[key];
|
|
||||||
long rayHi = PATH_HI[key];
|
|
||||||
// only consider clue cells
|
|
||||||
long hitsLo = rayLo & c.lo;
|
|
||||||
long hitsHi = rayHi & c.hi;
|
|
||||||
|
|
||||||
if (hitsHi != X) {
|
|
||||||
int msb = 63 - 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 - numberOfLeadingZeros(hitsLo);
|
|
||||||
long stop = 1L << msb;
|
|
||||||
rayLo &= ~((stop << 1) - 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
visitor.visit(key, rayLo, rayHi);
|
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) {
|
||||||
private static void processSlot(Clues c, SlotVisitor visitor, int key) {
|
|
||||||
long rayLo = PATH_LO[key];
|
|
||||||
long rayHi = PATH_HI[key];
|
|
||||||
long hitsLo = rayLo & c.lo;
|
|
||||||
long hitsHi = rayHi & c.hi;
|
|
||||||
|
|
||||||
if (hitsLo != X) {
|
|
||||||
long stop = 1L << numberOfTrailingZeros(hitsLo);
|
|
||||||
rayLo &= (stop - 1);
|
|
||||||
rayHi = 0; // any hi is beyond the stop
|
|
||||||
} else if (hitsHi != X) {
|
|
||||||
long stop = 1L << numberOfTrailingZeros(hitsHi);
|
|
||||||
// keep all lo (lo indices are < any hi index), but cut hi below stop
|
|
||||||
rayHi &= (stop - 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, int clueSize) {
|
|
||||||
|
|
||||||
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() - clueSize)) * 16000L);
|
|
||||||
boolean hasSlots = false;
|
|
||||||
|
|
||||||
for (long bits = lo_cl; bits != X; bits &= bits - 1) {
|
|
||||||
long lsb = bits & -bits;
|
|
||||||
int clueIdx = numberOfTrailingZeros(lsb);
|
|
||||||
int v = (grid.vlo & lsb) != 0 ? 1 : 0;
|
|
||||||
int r = (grid.rlo & lsb) != 0 ? 1 : 0;
|
|
||||||
int key = Slot.packSlotKey(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 << numberOfTrailingZeros(hLo)) - 1);
|
|
||||||
rHi = 0;
|
|
||||||
} else if (hHi != X) { rHi &= ((1L << numberOfTrailingZeros(hHi)) - 1); }
|
|
||||||
} else {
|
|
||||||
if (hHi != X) {
|
|
||||||
int msb = 63 - numberOfLeadingZeros(hHi);
|
|
||||||
rHi &= ~((1L << msb << 1) - 1);
|
|
||||||
rLo = 0;
|
|
||||||
} else if (hLo != X) {
|
|
||||||
int msb = 63 - 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 ((bitCount(rLo) + 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 = numberOfTrailingZeros(lsb);
|
|
||||||
int v = (grid.vhi & lsb) != 0 ? 1 : 0;
|
|
||||||
int r = (grid.rhi & lsb) != 0 ? 1 : 0;
|
|
||||||
int key = Slot.packSlotKey(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 << numberOfTrailingZeros(hLo)) - 1);
|
|
||||||
rHi = 0;
|
|
||||||
} else if (hHi != X) { rHi &= ((1L << numberOfTrailingZeros(hHi)) - 1); }
|
|
||||||
} else {
|
|
||||||
if (hHi != X) {
|
|
||||||
int msb = 63 - numberOfLeadingZeros(hHi);
|
|
||||||
rHi &= ~((1L << msb << 1) - 1);
|
|
||||||
rLo = 0;
|
|
||||||
} else if (hLo != X) {
|
|
||||||
int msb = 63 - 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 ((bitCount(rLo) + 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, size, sp, cur; 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, nLo, nHi; bits != X; bits &= bits - 1) {
|
|
||||||
int clueIdx = base | numberOfTrailingZeros(bits);
|
|
||||||
|
|
||||||
// start nieuwe component
|
|
||||||
size = 0;
|
|
||||||
stack[0] = clueIdx;
|
|
||||||
sp = 1;
|
|
||||||
|
|
||||||
// mark seen
|
|
||||||
if ((clueIdx & 64) == 0) seenLo |= 1L << clueIdx;
|
|
||||||
else seenHi |= 1L << (clueIdx & 63);
|
|
||||||
|
|
||||||
// flood fill / bfs
|
|
||||||
while (sp > 0) {
|
|
||||||
cur = stack[--sp];
|
|
||||||
size++;
|
|
||||||
|
|
||||||
// neighbors als 2x long masks
|
|
||||||
nLo = NBR8_PACKED_LO[cur];
|
|
||||||
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 = 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 | 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 = numberOfTrailingZeros(bits);
|
|
||||||
var rci = IT[clueIdx];
|
|
||||||
if ((4 - rci.nbrCount()) + bitCount(rci.n1() & lo_cl) + 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 = numberOfTrailingZeros(bits);
|
|
||||||
var rci = IT[64 | clueIdx];
|
|
||||||
if ((4 - rci.nbrCount()) + bitCount(rci.n1() & lo_cl) + 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(final int clueSize) {
|
|
||||||
var g = Clues.createEmpty();
|
|
||||||
for (int placed = 0, guard = 0, ri; placed < clueSize && guard < 4000; guard++) {
|
|
||||||
|
|
||||||
ri = rng.randint0_SIZE();
|
|
||||||
if (isLo(ri)) {
|
|
||||||
if (g.isClueLo(ri)) continue;
|
|
||||||
var d_idx = rng.randint2bitByte();
|
|
||||||
if (g.hasRoomForClue(OFFSETS_D_IDX[Slot.packSlotKey(ri, d_idx)])) {
|
|
||||||
g.setClueLo(1L << ri, d_idx);
|
|
||||||
placed++;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (g.isClueHi(ri)) continue;
|
|
||||||
var d_idx = rng.randint2bitByte();
|
|
||||||
if (g.hasRoomForClue(OFFSETS_D_IDX[Slot.packSlotKey(ri, d_idx)])) {
|
|
||||||
g.setClueHi(1L << (ri & 63), d_idx);
|
|
||||||
placed++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
return g;
|
|
||||||
}
|
|
||||||
static boolean isLo(int n) { return (n & 64) == 0; }
|
|
||||||
Clues mutate(Clues c) {
|
|
||||||
var bytes = MUTATE_RI[rng.randint0_SIZE()];
|
|
||||||
for (int k = 0, ri; k < 4; k++) {
|
|
||||||
ri = bytes[rng.randint0_624()];
|
|
||||||
if (isLo(ri)) {
|
|
||||||
if (!c.cluelessLo(ri)) {
|
|
||||||
var d_idx = rng.randint2bitByte();
|
|
||||||
if (c.hasRoomForClue(OFFSETS_D_IDX[Slot.packSlotKey(ri, d_idx)])) c.setClueLo(1L << ri, d_idx);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (!c.cluelessHi(ri)) {
|
|
||||||
var d_idx = rng.randint2bitByte();
|
|
||||||
if (c.hasRoomForClue(OFFSETS_D_IDX[Slot.packSlotKey(ri, d_idx)])) c.setClueHi(1L << (ri & 63), d_idx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
return c;
|
|
||||||
}
|
|
||||||
Clues crossover(Clues a, Clues other) {
|
|
||||||
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var c = new Clues(
|
|
||||||
(a.lo & ~maskLo) | (other.lo & maskLo),
|
|
||||||
(a.hi & ~maskHi) | (other.hi & maskHi),
|
|
||||||
(a.vlo & ~maskLo) | (other.vlo & maskLo),
|
|
||||||
(a.vhi & ~maskHi) | (other.vhi & maskHi),
|
|
||||||
(a.rlo & ~maskLo) | (other.rlo & maskLo),
|
|
||||||
(a.rhi & ~maskHi) | (other.rhi & maskHi));
|
|
||||||
|
|
||||||
for (var l = c.lo & ~c.rlo & ~c.vlo; l != X; l &= l - 1) clearCluesLo(c, numberOfTrailingZeros(l), 0);
|
|
||||||
for (var l = c.lo & ~c.rlo & c.vlo; l != X; l &= l - 1) clearCluesLo(c, numberOfTrailingZeros(l), 1);
|
|
||||||
for (var l = c.lo & c.rlo & ~c.vlo; l != X; l &= l - 1) clearCluesLo(c, numberOfTrailingZeros(l), 2);
|
|
||||||
for (var l = c.lo & c.rlo & c.vlo; l != X; l &= l - 1) clearCluesLo(c, numberOfTrailingZeros(l), 3);
|
|
||||||
for (var h = c.hi & ~c.rhi & ~c.vhi; h != X; h &= h - 1) clearCluesHi(c, numberOfTrailingZeros(h), 0);
|
|
||||||
for (var h = c.hi & ~c.rhi & c.vhi; h != X; h &= h - 1) clearCluesHi(c, numberOfTrailingZeros(h), 1);
|
|
||||||
for (var h = c.hi & c.rhi & ~c.vhi; h != X; h &= h - 1) clearCluesHi(c, (numberOfTrailingZeros(h)), 2);
|
|
||||||
for (var h = c.hi & c.rhi & ~c.vhi; h != X; h &= h - 1) clearCluesHi(c, (numberOfTrailingZeros(h)), 3);
|
|
||||||
return c;
|
|
||||||
}
|
|
||||||
public static void clearCluesLo(Clues out, int idx, int d) { if (!out.hasRoomForClue(OFFSETS_D_IDX[Slot.packSlotKey(idx, d)])) out.clearClueLo(~(1L << idx)); }
|
|
||||||
public static void clearCluesHi(Clues out, int idx, int d) { if (!out.hasRoomForClue(OFFSETS_D_IDX[Slot.packSlotKey(64 | idx, d)])) out.clearClueHi(~(1L << idx)); }
|
|
||||||
|
|
||||||
Clues hillclimb(Clues start, int clue_size, int limit) {
|
|
||||||
var best = start;
|
|
||||||
var bestF = maskFitness(best, clue_size);
|
|
||||||
var fails = 0;
|
|
||||||
|
|
||||||
while (fails < limit) {
|
|
||||||
cache.from(best);
|
|
||||||
var cand = mutate(best);
|
|
||||||
var f = maskFitness(cand, clue_size);
|
|
||||||
if (f < bestF) {
|
|
||||||
best = cand;
|
|
||||||
bestF = f;
|
|
||||||
fails = 0;
|
|
||||||
} else {
|
|
||||||
best.from(cache);
|
|
||||||
fails++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return best;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Clues generateMask(int clueSize, int popSize, int gens, int offspring) {
|
|
||||||
class GridAndFit {
|
|
||||||
|
|
||||||
Clues grid;
|
|
||||||
long fite = -1;
|
|
||||||
GridAndFit(Clues grid) { this.grid = grid; }
|
|
||||||
long fit() {
|
|
||||||
if (fite == -1) this.fite = maskFitness(grid, clueSize);
|
|
||||||
return this.fite;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (Main.VERBOSE) System.out.println("generateMask init pop: " + popSize + " clueSize: " + clueSize);
|
|
||||||
var pop = new ArrayList<GridAndFit>();
|
|
||||||
for (var i = 0; i < popSize; i++) {
|
|
||||||
if (Thread.currentThread().isInterrupted()) return null;
|
|
||||||
pop.add(new GridAndFit(hillclimb(randomMask(clueSize), clueSize, 180)));
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var gen = 0; gen < gens; gen++) {
|
|
||||||
if (Thread.currentThread().isInterrupted()) break;
|
|
||||||
var children = new ArrayList<GridAndFit>();
|
|
||||||
|
|
||||||
for (var k = 0; k < offspring; k++) {
|
|
||||||
if (Thread.currentThread().isInterrupted()) break;
|
|
||||||
var p1 = pop.get(rng.randint(pop.size() - 1));
|
|
||||||
var p2 = pop.get(rng.randint(pop.size() - 1));
|
|
||||||
var child = crossover(p1.grid, p2.grid);
|
|
||||||
children.add(new GridAndFit(hillclimb(child, clueSize, 70)));
|
|
||||||
}
|
|
||||||
|
|
||||||
pop.addAll(children);
|
|
||||||
pop.sort(Comparator.comparingLong(GridAndFit::fit));
|
|
||||||
|
|
||||||
var next = new ArrayList<GridAndFit>();
|
|
||||||
for (var cand : pop) {
|
|
||||||
if (next.size() >= offspring) 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());
|
|
||||||
}
|
|
||||||
if (pop.isEmpty()) return null;
|
|
||||||
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(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;
|
if (((lo & glo) | (hi & ghi)) == X) return 0;
|
||||||
long p = 0;
|
long p = 0;
|
||||||
int n = 0;
|
int n = 0;
|
||||||
if (Slot.increasing(key)) {
|
if (Slotinfo.increasing(key)) {
|
||||||
for (long b = lo & glo; b != X; b &= b - 1) {
|
for (long b = lo & glo; b != X; b &= b - 1) {
|
||||||
int idx = numberOfTrailingZeros(b);
|
int idx = numberOfTrailingZeros(b);
|
||||||
int i = bitCount(lo & ((1L << idx) - 1));
|
int i = bitCount(lo & ((1L << idx) - 1));
|
||||||
@@ -762,15 +271,9 @@ public record SwedishGenerator(Rng rng, int[] stack, Clues cache) {
|
|||||||
}
|
}
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
static int slotScore(byte[] count, long lo, long hi) {
|
public static boolean placeWord(final Grid grid, final byte[] g, final int key, final long lo, final long hi, final long w) {
|
||||||
int cross = 0;
|
|
||||||
for (long b = lo; b != X; b &= b - 1) cross += (count[numberOfTrailingZeros(b)] - 1);
|
|
||||||
for (long b = hi; b != X; b &= b - 1) cross += (count[64 | numberOfTrailingZeros(b)] - 1);
|
|
||||||
return cross * 10 + Slot.length(lo, hi);
|
|
||||||
}
|
|
||||||
static boolean placeWord(final Grid grid, final byte[] g, final int key, final long lo, final long hi, final long w) {
|
|
||||||
final long glo = grid.lo, ghi = grid.hi;
|
final long glo = grid.lo, ghi = grid.hi;
|
||||||
if (Slot.increasing(key)) {
|
if (Slotinfo.increasing(key)) {
|
||||||
for (long b = lo & glo; b != X; b &= b - 1) {
|
for (long b = lo & glo; b != X; b &= b - 1) {
|
||||||
int idx = numberOfTrailingZeros(b);
|
int idx = numberOfTrailingZeros(b);
|
||||||
if (g[idx] != Lemma.byteAt(w, bitCount(lo & ((1L << idx) - 1)))) return false;
|
if (g[idx] != Lemma.byteAt(w, bitCount(lo & ((1L << idx) - 1)))) return false;
|
||||||
@@ -823,7 +326,7 @@ public record SwedishGenerator(Rng rng, int[] stack, Clues cache) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// pattern cannot be X
|
/// pattern cannot be X
|
||||||
static int[] candidateInfoForPattern(long[] res, long pattern, long[][] posBitsets, int numLongs) {
|
public static int[] candidateInfoForPattern(long[] res, long pattern, long[][] posBitsets, int numLongs) {
|
||||||
System.arraycopy(posBitsets[(int) (pattern & 0xFF) - 1], 0, res, 0, numLongs);
|
System.arraycopy(posBitsets[(int) (pattern & 0xFF) - 1], 0, res, 0, numLongs);
|
||||||
for (long p = pattern >>> 8; p != X; p >>>= 8) {
|
for (long p = pattern >>> 8; p != X; p >>>= 8) {
|
||||||
long[] bs = posBitsets[(int) (p & 0xFF) - 1];
|
long[] bs = posBitsets[(int) (p & 0xFF) - 1];
|
||||||
@@ -841,7 +344,7 @@ public record SwedishGenerator(Rng rng, int[] stack, Clues cache) {
|
|||||||
return indices;
|
return indices;
|
||||||
}
|
}
|
||||||
/// pattern cannot be X
|
/// pattern cannot be X
|
||||||
static int candidateCountForPattern(final long[] res, final long pattern, final long[][] posBitsets, final int numLongs) {
|
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);
|
System.arraycopy(posBitsets[(int) (pattern & 0xFF) - 1], 0, res, 0, numLongs);
|
||||||
for (long p = pattern >>> 8; p != X; p >>>= 8) {
|
for (long p = pattern >>> 8; p != X; p >>>= 8) {
|
||||||
long[] bs = posBitsets[(int) (p & 0xFF) - 1];
|
long[] bs = posBitsets[(int) (p & 0xFF) - 1];
|
||||||
@@ -853,20 +356,6 @@ public record SwedishGenerator(Rng rng, int[] stack, Clues cache) {
|
|||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Slotinfo[] scoreSlots(int[] slotScores, Slot[] slots) {
|
|
||||||
val count = new byte[SIZE];
|
|
||||||
Slotinfo[] slotInfo = new Slotinfo[slots.length];
|
|
||||||
for (var s : slots) {
|
|
||||||
for (long b = s.lo; b != X; b &= b - 1) count[numberOfTrailingZeros(b)]++;
|
|
||||||
for (long b = s.hi; b != X; b &= b - 1) count[64 | numberOfTrailingZeros(b)]++;
|
|
||||||
}
|
|
||||||
for (int i = 0; i < slots.length; i++) {
|
|
||||||
var slot = slots[i];
|
|
||||||
slotScores[i] = slotScore(count, slot.lo, slot.hi);
|
|
||||||
slotInfo[i] = new Slotinfo(slot.key, slot.lo, slot.hi, slotScores[i], new Assign(), slot.entry);
|
|
||||||
}
|
|
||||||
return slotInfo;
|
|
||||||
}
|
|
||||||
public static FillResult fillMask(final Rng rng, final Slotinfo[] slots,
|
public static FillResult fillMask(final Rng rng, final Slotinfo[] slots,
|
||||||
final Grid grid,
|
final Grid grid,
|
||||||
final boolean multiThreaded) {
|
final boolean multiThreaded) {
|
||||||
@@ -907,7 +396,7 @@ public record SwedishGenerator(Rng rng, int[] stack, Clues cache) {
|
|||||||
}
|
}
|
||||||
boolean placeWord(final int key, final long lo, final long hi, final long w) {
|
boolean placeWord(final int key, final long lo, final long hi, final long w) {
|
||||||
int idx;
|
int idx;
|
||||||
if (Slot.increasing(key)) {
|
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;
|
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);
|
int bcLo = bitCount(lo);
|
||||||
for (long b = hi & ghi; b != X; b &= b - 1)
|
for (long b = hi & ghi; b != X; b &= b - 1)
|
||||||
@@ -1059,7 +548,7 @@ public record SwedishGenerator(Rng rng, int[] stack, Clues cache) {
|
|||||||
// final progress line
|
// final progress line
|
||||||
grid.lo = solver.glo;
|
grid.lo = solver.glo;
|
||||||
grid.hi = solver.ghi;
|
grid.hi = solver.ghi;
|
||||||
var res = new FillResult(ok, new Gridded(grid),
|
var res = new FillResult(ok,
|
||||||
new FillStats(solver.nodes, solver.backtracks, (System.currentTimeMillis() - t0) / 1000.0, solver.lastMRV));
|
new FillStats(solver.nodes, solver.backtracks, (System.currentTimeMillis() - t0) / 1000.0, solver.lastMRV));
|
||||||
if (!multiThreaded) {
|
if (!multiThreaded) {
|
||||||
System.out.print("\r" + Strings.padRight("", 120) + "\r");
|
System.out.print("\r" + Strings.padRight("", 120) + "\r");
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import puzzle.Export.PuzzleResult;
|
|||||||
import puzzle.Export.Rewards;
|
import puzzle.Export.Rewards;
|
||||||
import puzzle.SwedishGenerator.Assign;
|
import puzzle.SwedishGenerator.Assign;
|
||||||
import puzzle.SwedishGenerator.FillResult;
|
import puzzle.SwedishGenerator.FillResult;
|
||||||
import puzzle.SwedishGenerator.Rng;
|
|
||||||
import puzzle.SwedishGenerator.Slotinfo;
|
import puzzle.SwedishGenerator.Slotinfo;
|
||||||
import puzzle.SwedishGeneratorTest.Idx;
|
import puzzle.SwedishGeneratorTest.Idx;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -20,17 +19,15 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
|
|||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
import static puzzle.ExportFormatTest.Clue.RIGHT;
|
import static puzzle.ExportFormatTest.Clue.RIGHT;
|
||||||
import static puzzle.SwedishGenerator.C;
|
import static puzzle.SwedishGenerator.C;
|
||||||
import static puzzle.SwedishGenerator.Clues;
|
import static puzzle.Masker.Clues;
|
||||||
import static puzzle.SwedishGenerator.FillStats;
|
import static puzzle.SwedishGenerator.FillStats;
|
||||||
import static puzzle.SwedishGenerator.R;
|
import static puzzle.SwedishGenerator.R;
|
||||||
import static puzzle.SwedishGenerator.STACK_SIZE;
|
import static puzzle.Masker.Slot;
|
||||||
import static puzzle.SwedishGenerator.Slot;
|
|
||||||
import static puzzle.SwedishGenerator.placeWord;
|
import static puzzle.SwedishGenerator.placeWord;
|
||||||
import static puzzle.SwedishGeneratorTest.OFF_0_1;
|
import static puzzle.SwedishGeneratorTest.OFF_0_1;
|
||||||
import static puzzle.SwedishGeneratorTest.OFF_0_2;
|
import static puzzle.SwedishGeneratorTest.OFF_0_2;
|
||||||
import static puzzle.SwedishGeneratorTest.OFF_0_3;
|
import static puzzle.SwedishGeneratorTest.OFF_0_3;
|
||||||
import static puzzle.SwedishGeneratorTest.OFF_0_4;
|
import static puzzle.SwedishGeneratorTest.OFF_0_4;
|
||||||
import static puzzle.SwedishGeneratorTest.OFF_0_5;
|
|
||||||
import static puzzle.SwedishGeneratorTest.TEST;
|
import static puzzle.SwedishGeneratorTest.TEST;
|
||||||
|
|
||||||
public class ExportFormatTest {
|
public class ExportFormatTest {
|
||||||
@@ -56,8 +53,6 @@ public class ExportFormatTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testExportFormatFromFilled() {
|
void testExportFormatFromFilled() {
|
||||||
var swe = new SwedishGenerator(new Rng(0), new int[STACK_SIZE], Clues.createEmpty());
|
|
||||||
|
|
||||||
val clues = Clues.createEmpty();
|
val clues = Clues.createEmpty();
|
||||||
// Place a RIGHT clue at (0,0)
|
// Place a RIGHT clue at (0,0)
|
||||||
clues.setClueLo(Idx.IDX_0_0.lo, RIGHT.dir);
|
clues.setClueLo(Idx.IDX_0_0.lo, RIGHT.dir);
|
||||||
@@ -72,8 +67,8 @@ public class ExportFormatTest {
|
|||||||
|
|
||||||
assertTrue(placeWord(grid.grid(), grid.grid().g, key, lo, 0L, TEST));
|
assertTrue(placeWord(grid.grid(), grid.grid().g, key, lo, 0L, TEST));
|
||||||
|
|
||||||
var fillResult = new FillResult(true, grid, new FillStats(0, 0, 0, 0));
|
var fillResult = new FillResult(true, new FillStats(0, 0, 0, 0));
|
||||||
var puzzleResult = new PuzzleResult(new Clued(clues), new Slotinfo[]{
|
var puzzleResult = new PuzzleResult(new Clued(clues), grid, new Slotinfo[]{
|
||||||
new Slotinfo(key, lo, 0L, 0, new Assign(TEST), null)
|
new Slotinfo(key, lo, 0L, 0, new Assign(TEST), null)
|
||||||
}, fillResult);
|
}, fillResult);
|
||||||
|
|
||||||
@@ -114,8 +109,8 @@ public class ExportFormatTest {
|
|||||||
void testExportFormatEmpty() {
|
void testExportFormatEmpty() {
|
||||||
var grid = SwedishGeneratorTest.createEmpty();
|
var grid = SwedishGeneratorTest.createEmpty();
|
||||||
val clues = Clues.createEmpty();
|
val clues = Clues.createEmpty();
|
||||||
var fillResult = new FillResult(true, new Gridded(grid), new FillStats(0, 0, 0, 0));
|
var fillResult = new FillResult(true, new FillStats(0, 0, 0, 0));
|
||||||
var puzzleResult = new PuzzleResult(new Clued(clues), new Slotinfo[0], fillResult);
|
var puzzleResult = new PuzzleResult(new Clued(clues), new Gridded(grid), new Slotinfo[0], fillResult);
|
||||||
|
|
||||||
var exported = puzzleResult.exportFormatFromFilled(1, new Rewards(0, 0, 0));
|
var exported = puzzleResult.exportFormatFromFilled(1, new Rewards(0, 0, 0));
|
||||||
|
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ import puzzle.Export.LetterVisit.LetterAt;
|
|||||||
import puzzle.Export.PuzzleResult;
|
import puzzle.Export.PuzzleResult;
|
||||||
import puzzle.Export.Rewards;
|
import puzzle.Export.Rewards;
|
||||||
import puzzle.Main.Opts;
|
import puzzle.Main.Opts;
|
||||||
|
import puzzle.Masker.Clues;
|
||||||
import puzzle.SwedishGenerator.Rng;
|
import puzzle.SwedishGenerator.Rng;
|
||||||
import puzzle.SwedishGenerator.Slot;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
@@ -46,17 +46,17 @@ public class MainTest {
|
|||||||
@Test
|
@Test
|
||||||
void testExtractSlots() {
|
void testExtractSlots() {
|
||||||
|
|
||||||
var clues = Clues.createEmpty();
|
var clues = Masker.Clues.createEmpty();
|
||||||
val key = Slot.packSlotKey(OFF_0_0, CLUE_RIGHT);
|
val key = Masker.Slot.packSlotKey(OFF_0_0, CLUE_RIGHT);
|
||||||
clues.setClueLo(IDX_0_0.lo, CLUE_RIGHT);
|
clues.setClueLo(IDX_0_0.lo, CLUE_RIGHT);
|
||||||
var grid = new Gridded(clues.toGrid());
|
var grid = new Gridded(clues.toGrid());
|
||||||
val g = grid.grid().g;
|
val g = grid.grid().g;
|
||||||
placeWord(grid.grid(), g, key, (1L << OFF_0_1) | (1L << OFF_0_2), 0, AB);
|
placeWord(grid.grid(), g, key, (1L << OFF_0_1) | (1L << OFF_0_2), 0, AB);
|
||||||
|
|
||||||
var slots = extractSlots(clues, dict.index());
|
var slots = Masker.extractSlots(clues, dict.index());
|
||||||
assertEquals(1, slots.length);
|
assertEquals(1, slots.length);
|
||||||
var s = slots[0];
|
var s = slots[0];
|
||||||
assertEquals(8, Slot.length(s.lo(), s.hi()));
|
assertEquals(8, Masker.Slot.length(s.lo(), s.hi()));
|
||||||
var cells = s.walk().toArray();
|
var cells = s.walk().toArray();
|
||||||
assertEquals(0, SwedishGenerator.IT[cells[0]].r());
|
assertEquals(0, SwedishGenerator.IT[cells[0]].r());
|
||||||
assertEquals(1, SwedishGenerator.IT[cells[0]].c());
|
assertEquals(1, SwedishGenerator.IT[cells[0]].c());
|
||||||
@@ -68,14 +68,14 @@ public class MainTest {
|
|||||||
void testStaticSlotMethods() {
|
void testStaticSlotMethods() {
|
||||||
// Test static horiz
|
// Test static horiz
|
||||||
// dir 1 (right) is horizontal
|
// dir 1 (right) is horizontal
|
||||||
assertTrue(Slot.horiz(1));
|
assertTrue(Masker.Slot.horiz(1));
|
||||||
// dir 0 (down) is vertical
|
// dir 0 (down) is vertical
|
||||||
assertFalse(Slot.horiz(0));
|
assertFalse(Masker.Slot.horiz(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testForEachSlot() {
|
void testForEachSlot() {
|
||||||
var clues = Clues.createEmpty();
|
var clues = Masker.Clues.createEmpty();
|
||||||
clues.setClueLo(IDX_0_0.lo, CLUE_RIGHT);
|
clues.setClueLo(IDX_0_0.lo, CLUE_RIGHT);
|
||||||
var count = new AtomicInteger(0);
|
var count = new AtomicInteger(0);
|
||||||
clues.forEachSlot((key, lo, hi) -> {
|
clues.forEachSlot((key, lo, hi) -> {
|
||||||
@@ -88,15 +88,15 @@ public class MainTest {
|
|||||||
}
|
}
|
||||||
@Test
|
@Test
|
||||||
public void testHoriz() {
|
public void testHoriz() {
|
||||||
assertTrue(Slot.horiz(1)); // Right
|
assertTrue(Masker.Slot.horiz(1)); // Right
|
||||||
assertTrue(Slot.horiz(3)); // Left
|
assertTrue(Masker.Slot.horiz(3)); // Left
|
||||||
assertFalse(Slot.horiz(0)); // Down
|
assertFalse(Masker.Slot.horiz(0)); // Down
|
||||||
assertFalse(Slot.horiz(2)); // Up
|
assertFalse(Masker.Slot.horiz(2)); // Up
|
||||||
}
|
}
|
||||||
@Test
|
@Test
|
||||||
public void testGridBasics() {
|
public void testGridBasics() {
|
||||||
var clues = new Clued(Clues.createEmpty());
|
var clues = new Clued(Masker.Clues.createEmpty());
|
||||||
val key = Slot.packSlotKey(OFF_2_1, CLUE_UP);
|
val key = Masker.Slot.packSlotKey(OFF_2_1, CLUE_UP);
|
||||||
clues.setClueLo(IDX_2_1.lo, CLUE_UP);
|
clues.setClueLo(IDX_2_1.lo, CLUE_UP);
|
||||||
var grid = new Gridded(clues.toGrid());
|
var grid = new Gridded(clues.toGrid());
|
||||||
|
|
||||||
@@ -130,7 +130,7 @@ public class MainTest {
|
|||||||
}
|
}
|
||||||
@Test
|
@Test
|
||||||
public void testCluesDeepCopy() {
|
public void testCluesDeepCopy() {
|
||||||
var clues = new Clued(Clues.createEmpty());
|
var clues = new Clued(Masker.Clues.createEmpty());
|
||||||
clues.setClueLo(IDX_0_0.lo, RIGHT.dir);
|
clues.setClueLo(IDX_0_0.lo, RIGHT.dir);
|
||||||
clues.setClueLo(IDX_0_1.lo, UP.dir);
|
clues.setClueLo(IDX_0_1.lo, UP.dir);
|
||||||
clues.setClueLo(IDX_1_0.lo, LEFT.dir);
|
clues.setClueLo(IDX_1_0.lo, LEFT.dir);
|
||||||
@@ -148,14 +148,14 @@ public class MainTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testMini() {
|
public void testMini() {
|
||||||
val idx = IDX_1_1;
|
val idx = IDX_1_1;
|
||||||
var clues = Clues.createEmpty();
|
var clues = Masker.Clues.createEmpty();
|
||||||
clues.setClueLo(idx.lo, CLUE_LEFT);
|
clues.setClueLo(idx.lo, CLUE_LEFT);
|
||||||
Assertions.assertTrue(clues.isClueLo(idx.index));
|
Assertions.assertTrue(clues.isClueLo(idx.index));
|
||||||
}
|
}
|
||||||
@Test
|
@Test
|
||||||
void testMaskerCreation() {
|
void testMaskerCreation() {
|
||||||
var swe = new SwedishGenerator(new Rng(12348), new int[STACK_SIZE], Clues.createEmpty());
|
var masker = new Masker(new Rng(12348), new int[STACK_SIZE], Masker.Clues.createEmpty());
|
||||||
var mask = swe.generateMask(opts.clueSize, opts.pop, opts.gens, opts.offspring);
|
var mask = masker.generateMask(opts.clueSize, opts.pop, opts.gens, opts.offspring);
|
||||||
val clued = new Clued(mask);
|
val clued = new Clued(mask);
|
||||||
val test = clued.gridToString();
|
val test = clued.gridToString();
|
||||||
val RESULT = "1 \n" +
|
val RESULT = "1 \n" +
|
||||||
@@ -180,16 +180,18 @@ public class MainTest {
|
|||||||
128L,
|
128L,
|
||||||
422762372923520L,
|
422762372923520L,
|
||||||
192L);
|
192L);
|
||||||
var slots = extractSlots(mask, dict.index());
|
var slots = Masker.extractSlots(mask, dict.index());
|
||||||
val slotInfo = scoreSlots(new int[slots.length], slots);
|
val slotInfo = Masker.scoreSlots(new int[slots.length], slots);
|
||||||
var filled = fillMask(rng, slotInfo, mask.toGrid(), false);
|
var grid = mask.toGrid();
|
||||||
|
var filled = fillMask(rng, slotInfo, grid, false);
|
||||||
Assertions.assertTrue(filled.ok(), "Puzzle generation failed (not ok)");
|
Assertions.assertTrue(filled.ok(), "Puzzle generation failed (not ok)");
|
||||||
Assertions.assertEquals(17, Slotinfo.wordCount(0, slotInfo), "Number of assigned words changed");
|
Assertions.assertEquals(17, Slotinfo.wordCount(0, slotInfo), "Number of assigned words changed");
|
||||||
Assertions.assertEquals("POENIGE", Lemma.asWord(slotInfo[0].assign().w));
|
Assertions.assertEquals("POENIGE", Lemma.asWord(slotInfo[0].assign().w));
|
||||||
Assertions.assertEquals(-1L, filled.grid().grid().lo);
|
Assertions.assertEquals(-1L, grid.lo);
|
||||||
Assertions.assertEquals(255L, filled.grid().grid().hi);
|
Assertions.assertEquals(255L, grid.hi);
|
||||||
filled.grid().gridToString(mask);
|
var g = new Gridded(grid);
|
||||||
var aa = new PuzzleResult(new Clued(mask), slotInfo, filled).exportFormatFromFilled(1, new Rewards(1, 1, 1));
|
g.gridToString(mask);
|
||||||
|
var aa = new PuzzleResult(new Clued(mask), g, slotInfo, filled).exportFormatFromFilled(1, new Rewards(1, 1, 1));
|
||||||
|
|
||||||
}
|
}
|
||||||
@Test
|
@Test
|
||||||
@@ -205,9 +207,9 @@ public class MainTest {
|
|||||||
System.out.println("[DEBUG_LOG] Simplicity: " + res.filled().stats().simplicity);
|
System.out.println("[DEBUG_LOG] Simplicity: " + res.filled().stats().simplicity);
|
||||||
System.out.println("[DEBUG_LOG] ClueMap Size: " + Slotinfo.wordCount(0, res.slots()));
|
System.out.println("[DEBUG_LOG] ClueMap Size: " + Slotinfo.wordCount(0, res.slots()));
|
||||||
System.out.println("[DEBUG_LOG] Grid:");
|
System.out.println("[DEBUG_LOG] Grid:");
|
||||||
System.out.println(res.filled().grid().renderHuman(res.clues().c()));
|
System.out.println(res.grid().renderHuman(res.clues().c()));
|
||||||
System.out.println(res.filled().grid().gridToString(res.clues().c()));
|
System.out.println(res.grid().gridToString(res.clues().c()));
|
||||||
System.out.println(res.filled().grid().renderHuman(res.clues().c()));
|
System.out.println(res.grid().renderHuman(res.clues().c()));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -138,8 +138,8 @@ public class SwedishGeneratorTest {
|
|||||||
@Test
|
@Test
|
||||||
void testPatternForSlotAllLetters() {
|
void testPatternForSlotAllLetters() {
|
||||||
var grid = new Gridded(createEmpty());
|
var grid = new Gridded(createEmpty());
|
||||||
var key = Slot.packSlotKey(OFF_0_0, CLUE_RIGHT);
|
var key = Masker.Slot.packSlotKey(OFF_0_0, CLUE_RIGHT);
|
||||||
val clues = Clues.createEmpty();
|
val clues = Masker.Clues.createEmpty();
|
||||||
clues.setClueLo(IDX_0_0.lo, CLUE_RIGHT);
|
clues.setClueLo(IDX_0_0.lo, CLUE_RIGHT);
|
||||||
placeWord(grid.grid(), grid.grid().g, key, (1L << OFF_0_1) | (1L << OFF_0_2) | (1L << OFF_0_3), 0L, ABC);
|
placeWord(grid.grid(), grid.grid().g, key, (1L << OFF_0_1) | (1L << OFF_0_2) | (1L << OFF_0_3), 0L, ABC);
|
||||||
val map = grid.stream(clues).collect(Collectors.toMap(LetterAt::index, LetterAt::letter));
|
val map = grid.stream(clues).collect(Collectors.toMap(LetterAt::index, LetterAt::letter));
|
||||||
@@ -151,9 +151,9 @@ public class SwedishGeneratorTest {
|
|||||||
@Test
|
@Test
|
||||||
void testPatternForSlotMixed() {
|
void testPatternForSlotMixed() {
|
||||||
var grid = createEmpty();
|
var grid = createEmpty();
|
||||||
placeWord(grid, grid.g, Slot.packSlotKey(0, CLUE_RIGHT), 1L << OFF_0_0, 0, Lemma.from(0, "A"));
|
placeWord(grid, grid.g, Masker.Slot.packSlotKey(0, CLUE_RIGHT), 1L << OFF_0_0, 0, Lemma.from(0, "A"));
|
||||||
placeWord(grid, grid.g, Slot.packSlotKey(0, CLUE_RIGHT), 1L << OFF_2_0, 0, Lemma.from(0, "C"));
|
placeWord(grid, grid.g, Masker.Slot.packSlotKey(0, CLUE_RIGHT), 1L << OFF_2_0, 0, Lemma.from(0, "C"));
|
||||||
var key = Slot.packSlotKey(OFF_1_0, CLUE_RIGHT);
|
var key = Masker.Slot.packSlotKey(OFF_1_0, CLUE_RIGHT);
|
||||||
var pattern = patternForSlot(grid.lo, grid.hi, grid.g, key, 7L, 0L);
|
var pattern = patternForSlot(grid.lo, grid.hi, grid.g, key, 7L, 0L);
|
||||||
assertEquals(14081L, pattern);
|
assertEquals(14081L, pattern);
|
||||||
}
|
}
|
||||||
@@ -161,7 +161,7 @@ public class SwedishGeneratorTest {
|
|||||||
@Test
|
@Test
|
||||||
void testPatternForSlotAllDashes() {
|
void testPatternForSlotAllDashes() {
|
||||||
var grid = createEmpty();
|
var grid = createEmpty();
|
||||||
var key = Slot.packSlotKey(1 << Slot.BIT_FOR_DIR, CLUE_RIGHT);
|
var key = Masker.Slot.packSlotKey(1 << Masker.Slot.BIT_FOR_DIR, CLUE_RIGHT);
|
||||||
var pattern = patternForSlot(grid.lo, grid.hi, grid.g, key, 7L, 0L);
|
var pattern = patternForSlot(grid.lo, grid.hi, grid.g, key, 7L, 0L);
|
||||||
assertEquals(0L, pattern);
|
assertEquals(0L, pattern);
|
||||||
}
|
}
|
||||||
@@ -169,8 +169,8 @@ public class SwedishGeneratorTest {
|
|||||||
@Test
|
@Test
|
||||||
void testPatternForSlotSingleLetter() {
|
void testPatternForSlotSingleLetter() {
|
||||||
var grid = createEmpty();
|
var grid = createEmpty();
|
||||||
placeWord(grid, grid.g, Slot.packSlotKey(0, CLUE_RIGHT), 1L << OFF_0_0, 0, Lemma.from(0, "A"));
|
placeWord(grid, grid.g, Masker.Slot.packSlotKey(0, CLUE_RIGHT), 1L << OFF_0_0, 0, Lemma.from(0, "A"));
|
||||||
var key = Slot.packSlotKey(1, CLUE_RIGHT);
|
var key = Masker.Slot.packSlotKey(1, CLUE_RIGHT);
|
||||||
var pattern = patternForSlot(grid.lo, grid.hi, grid.g, key, 7L, 0L);
|
var pattern = patternForSlot(grid.lo, grid.hi, grid.g, key, 7L, 0L);
|
||||||
assertEquals(1L, pattern);
|
assertEquals(1L, pattern);
|
||||||
}
|
}
|
||||||
@@ -195,8 +195,8 @@ public class SwedishGeneratorTest {
|
|||||||
@Test
|
@Test
|
||||||
void testGrid() {
|
void testGrid() {
|
||||||
var grid = new Gridded(createEmpty());
|
var grid = new Gridded(createEmpty());
|
||||||
placeWord(grid.grid(), grid.grid().g, Slot.packSlotKey(0, CLUE_RIGHT), 1L << OFF_0_0, 0, Lemma.from(0, "A"));
|
placeWord(grid.grid(), grid.grid().g, Masker.Slot.packSlotKey(0, CLUE_RIGHT), 1L << OFF_0_0, 0, Lemma.from(0, "A"));
|
||||||
val arr = grid.stream(Clues.createEmpty()).collect(Collectors.toMap(LetterAt::index, LetterAt::letter));
|
val arr = grid.stream(Masker.Clues.createEmpty()).collect(Collectors.toMap(LetterAt::index, LetterAt::letter));
|
||||||
assertEquals(1, arr.size());
|
assertEquals(1, arr.size());
|
||||||
assertEquals(LETTER_A, arr.get(OFF_0_0));
|
assertEquals(LETTER_A, arr.get(OFF_0_0));
|
||||||
}
|
}
|
||||||
@@ -231,11 +231,11 @@ public class SwedishGeneratorTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testSlot() {
|
void testSlot() {
|
||||||
System.out.println("[DEBUG_LOG] Slot.BIT_FOR_DIR = " + Slot.BIT_FOR_DIR);
|
System.out.println("[DEBUG_LOG] Slot.BIT_FOR_DIR = " + Masker.Slot.BIT_FOR_DIR);
|
||||||
// key = (r << 8) | (c << 4) | d
|
// key = (r << 8) | (c << 4) | d
|
||||||
var offset = OFF_2_3;
|
var offset = OFF_2_3;
|
||||||
System.out.println("[DEBUG_LOG] Grid.offset(2, 3) = " + offset);
|
System.out.println("[DEBUG_LOG] Grid.offset(2, 3) = " + offset);
|
||||||
var key = Slot.packSlotKey(offset, CLUE_DOWN);
|
var key = Masker.Slot.packSlotKey(offset, CLUE_DOWN);
|
||||||
System.out.println("[DEBUG_LOG] key = " + key);
|
System.out.println("[DEBUG_LOG] key = " + key);
|
||||||
long lo = 0;
|
long lo = 0;
|
||||||
// pos 0: (2, 5)
|
// pos 0: (2, 5)
|
||||||
@@ -245,10 +245,10 @@ public class SwedishGeneratorTest {
|
|||||||
// pos 2: (4, 5)
|
// pos 2: (4, 5)
|
||||||
lo |= 1L << OFF_4_5;
|
lo |= 1L << OFF_4_5;
|
||||||
|
|
||||||
System.out.println("[DEBUG_LOG] s.dir() = " + Slot.dir(key));
|
System.out.println("[DEBUG_LOG] s.dir() = " + Masker.Slot.dir(key));
|
||||||
assertEquals(OFF_2_3, Slot.clueIndex(key));
|
assertEquals(OFF_2_3, Masker.Slot.clueIndex(key));
|
||||||
assertEquals(CLUE_DOWN, Slot.dir(key));
|
assertEquals(CLUE_DOWN, Masker.Slot.dir(key));
|
||||||
assertFalse(Slot.horiz(key));
|
assertFalse(Masker.Slot.horiz(key));
|
||||||
var cells = Gridded.walk((byte) key, lo, 0L).toArray();
|
var cells = Gridded.walk((byte) key, lo, 0L).toArray();
|
||||||
assertEquals(2, SwedishGenerator.IT[cells[0]].r());
|
assertEquals(2, SwedishGenerator.IT[cells[0]].r());
|
||||||
assertEquals(3, SwedishGenerator.IT[cells[1]].r());
|
assertEquals(3, SwedishGenerator.IT[cells[1]].r());
|
||||||
@@ -257,8 +257,8 @@ public class SwedishGeneratorTest {
|
|||||||
assertEquals(5, SwedishGenerator.IT[cells[1]].c());
|
assertEquals(5, SwedishGenerator.IT[cells[1]].c());
|
||||||
assertEquals(5, SwedishGenerator.IT[cells[2]].c());
|
assertEquals(5, SwedishGenerator.IT[cells[2]].c());
|
||||||
|
|
||||||
assertTrue(Slot.horiz(CLUE_RIGHT)); // right
|
assertTrue(Masker.Slot.horiz(CLUE_RIGHT)); // right
|
||||||
assertFalse(Slot.horiz(CLUE_DOWN)); // down
|
assertFalse(Masker.Slot.horiz(CLUE_DOWN)); // down
|
||||||
}
|
}
|
||||||
|
|
||||||
static long packPattern(String s) {
|
static long packPattern(String s) {
|
||||||
@@ -287,22 +287,22 @@ public class SwedishGeneratorTest {
|
|||||||
@Test
|
@Test
|
||||||
void testForEachSlotAndExtractSlots() {
|
void testForEachSlotAndExtractSlots() {
|
||||||
// This should detect a slot starting at 0,1 with length 2 (0,1 and 0,2)
|
// This should detect a slot starting at 0,1 with length 2 (0,1 and 0,2)
|
||||||
var clues = Clues.createEmpty();
|
var clues = Masker.Clues.createEmpty();
|
||||||
clues.setClueLo(IDX_0_0.lo, CLUE_RIGHT);
|
clues.setClueLo(IDX_0_0.lo, CLUE_RIGHT);
|
||||||
var dict = new Dict(WORDS2);
|
var dict = new Dict(WORDS2);
|
||||||
var slots = extractSlots(clues, dict.index());
|
var slots = Masker.extractSlots(clues, dict.index());
|
||||||
assertEquals(1, slots.length);
|
assertEquals(1, slots.length);
|
||||||
var s = slots[0];
|
var s = slots[0];
|
||||||
|
|
||||||
assertTrue(Slot.length(s.lo(), s.hi()) >= 2);
|
assertTrue(Masker.Slot.length(s.lo(), s.hi()) >= 2);
|
||||||
assertEquals(OFF_0_0, Slot.clueIndex(s.key()));
|
assertEquals(OFF_0_0, Masker.Slot.clueIndex(s.key()));
|
||||||
assertEquals(CLUE_RIGHT, Slot.dir(s.key()));
|
assertEquals(CLUE_RIGHT, Masker.Slot.dir(s.key()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testMaskFitnessBasic() {
|
void testMaskFitnessBasic() {
|
||||||
var gen = new SwedishGenerator(new Rng(0), new int[STACK_SIZE], Clues.createEmpty());
|
var gen = new Masker(new Rng(0), new int[STACK_SIZE], Masker.Clues.createEmpty());
|
||||||
var grid = Clues.createEmpty();
|
var grid = Masker.Clues.createEmpty();
|
||||||
// Empty grid should have high penalty (no slots)
|
// Empty grid should have high penalty (no slots)
|
||||||
var f1 = gen.maskFitness(grid, 18);
|
var f1 = gen.maskFitness(grid, 18);
|
||||||
assertTrue(f1 >= 1_000_000_000L);
|
assertTrue(f1 >= 1_000_000_000L);
|
||||||
@@ -316,7 +316,7 @@ public class SwedishGeneratorTest {
|
|||||||
@Test
|
@Test
|
||||||
void testGeneticAlgorithmComponents() {
|
void testGeneticAlgorithmComponents() {
|
||||||
var rng = new Rng(42);
|
var rng = new Rng(42);
|
||||||
var gen = new SwedishGenerator(rng, new int[STACK_SIZE], Clues.createEmpty());
|
var gen = new Masker(rng, new int[STACK_SIZE], Masker.Clues.createEmpty());
|
||||||
|
|
||||||
var c1 = new Clued(gen.randomMask(18));
|
var c1 = new Clued(gen.randomMask(18));
|
||||||
assertNotNull(c1);
|
assertNotNull(c1);
|
||||||
@@ -335,14 +335,14 @@ public class SwedishGeneratorTest {
|
|||||||
void testPlaceWord() {
|
void testPlaceWord() {
|
||||||
var grid = new Gridded(createEmpty());
|
var grid = new Gridded(createEmpty());
|
||||||
// Slot at OFF_0_0 length 3, horizontal (right)
|
// Slot at OFF_0_0 length 3, horizontal (right)
|
||||||
var key = Slot.packSlotKey(0, CLUE_RIGHT);
|
var key = Masker.Slot.packSlotKey(0, CLUE_RIGHT);
|
||||||
var lo = (1L << OFF_0_0) | (1L << OFF_0_1) | (1L << OFF_0_2);
|
var lo = (1L << OFF_0_0) | (1L << OFF_0_1) | (1L << OFF_0_2);
|
||||||
val hi = 0L;
|
val hi = 0L;
|
||||||
var w1 = ABC;
|
var w1 = ABC;
|
||||||
|
|
||||||
// 1. Successful placement in empty grid
|
// 1. Successful placement in empty grid
|
||||||
assertTrue(placeWord(grid.grid(), grid.grid().g, key, lo, hi, w1));
|
assertTrue(placeWord(grid.grid(), grid.grid().g, key, lo, hi, w1));
|
||||||
var map = grid.stream(Clues.createEmpty()).collect(Collectors.toMap(LetterAt::index, LetterAt::letter));
|
var map = grid.stream(Masker.Clues.createEmpty()).collect(Collectors.toMap(LetterAt::index, LetterAt::letter));
|
||||||
assertEquals(3, map.size());
|
assertEquals(3, map.size());
|
||||||
assertEquals(LETTER_A, map.get(OFF_0_0));
|
assertEquals(LETTER_A, map.get(OFF_0_0));
|
||||||
assertEquals(LETTER_B, map.get(OFF_0_1));
|
assertEquals(LETTER_B, map.get(OFF_0_1));
|
||||||
@@ -353,7 +353,7 @@ public class SwedishGeneratorTest {
|
|||||||
// 3. Conflict: place "ABD" where "ABC" is
|
// 3. Conflict: place "ABD" where "ABC" is
|
||||||
assertFalse(placeWord(grid.grid(), grid.grid().g, key, lo, hi, ABD));
|
assertFalse(placeWord(grid.grid(), grid.grid().g, key, lo, hi, ABD));
|
||||||
// Verify grid is unchanged (still "ABC")
|
// Verify grid is unchanged (still "ABC")
|
||||||
map = grid.stream(Clues.createEmpty()).collect(Collectors.toMap(LetterAt::index, LetterAt::letter));
|
map = grid.stream(Masker.Clues.createEmpty()).collect(Collectors.toMap(LetterAt::index, LetterAt::letter));
|
||||||
assertEquals(3, map.size());
|
assertEquals(3, map.size());
|
||||||
assertEquals(LETTER_A, map.get(OFF_0_0));
|
assertEquals(LETTER_A, map.get(OFF_0_0));
|
||||||
assertEquals(LETTER_B, map.get(OFF_0_1));
|
assertEquals(LETTER_B, map.get(OFF_0_1));
|
||||||
@@ -361,9 +361,9 @@ public class SwedishGeneratorTest {
|
|||||||
|
|
||||||
// 4. Partial placement then conflict (rollback)
|
// 4. Partial placement then conflict (rollback)
|
||||||
grid = new Gridded(createEmpty());
|
grid = new Gridded(createEmpty());
|
||||||
placeWord(grid.grid(), grid.grid().g, Slot.packSlotKey(0, CLUE_RIGHT), 1L << OFF_0_2, 0, Lemma.from(0, "X")); // Conflict at the end
|
placeWord(grid.grid(), grid.grid().g, Masker.Slot.packSlotKey(0, CLUE_RIGHT), 1L << OFF_0_2, 0, Lemma.from(0, "X")); // Conflict at the end
|
||||||
assertFalse(placeWord(grid.grid(), grid.grid().g, key, lo, hi, w1));
|
assertFalse(placeWord(grid.grid(), grid.grid().g, key, lo, hi, w1));
|
||||||
map = grid.stream(Clues.createEmpty()).collect(Collectors.toMap(LetterAt::index, LetterAt::letter));
|
map = grid.stream(Masker.Clues.createEmpty()).collect(Collectors.toMap(LetterAt::index, LetterAt::letter));
|
||||||
assertEquals(1, map.size());
|
assertEquals(1, map.size());
|
||||||
assertEquals(LETTER_X, map.get(OFF_0_2));
|
assertEquals(LETTER_X, map.get(OFF_0_2));
|
||||||
}
|
}
|
||||||
@@ -372,21 +372,21 @@ public class SwedishGeneratorTest {
|
|||||||
void testBacktrackingHelpers() {
|
void testBacktrackingHelpers() {
|
||||||
var grid = new Gridded(createEmpty());
|
var grid = new Gridded(createEmpty());
|
||||||
// Slot at 0,1 length 2
|
// Slot at 0,1 length 2
|
||||||
var key = Slot.packSlotKey(0, CLUE_RIGHT);
|
var key = Masker.Slot.packSlotKey(0, CLUE_RIGHT);
|
||||||
var lo = (1L << OFF_0_1) | (1L << OFF_0_2);
|
var lo = (1L << OFF_0_1) | (1L << OFF_0_2);
|
||||||
var w = AZ;
|
var w = AZ;
|
||||||
val low = grid.grid().lo;
|
val low = grid.grid().lo;
|
||||||
val top = grid.grid().hi;
|
val top = grid.grid().hi;
|
||||||
var placed = placeWord(grid.grid(), grid.grid().g, key, lo, 0L, w);
|
var placed = placeWord(grid.grid(), grid.grid().g, key, lo, 0L, w);
|
||||||
assertTrue(placed);
|
assertTrue(placed);
|
||||||
var map = grid.stream(Clues.createEmpty()).collect(Collectors.toMap(LetterAt::index, LetterAt::letter));
|
var map = grid.stream(Masker.Clues.createEmpty()).collect(Collectors.toMap(LetterAt::index, LetterAt::letter));
|
||||||
assertEquals(2, map.size());
|
assertEquals(2, map.size());
|
||||||
assertEquals(LETTER_A, map.get(OFF_0_1));
|
assertEquals(LETTER_A, map.get(OFF_0_1));
|
||||||
assertEquals(LETTER_Z, map.get(OFF_0_2));
|
assertEquals(LETTER_Z, map.get(OFF_0_2));
|
||||||
|
|
||||||
grid.grid().hi = top;
|
grid.grid().hi = top;
|
||||||
grid.grid().lo = low;
|
grid.grid().lo = low;
|
||||||
map = grid.stream(Clues.createEmpty()).collect(Collectors.toMap(LetterAt::index, LetterAt::letter));
|
map = grid.stream(Masker.Clues.createEmpty()).collect(Collectors.toMap(LetterAt::index, LetterAt::letter));
|
||||||
assertEquals(0, map.size());
|
assertEquals(0, map.size());
|
||||||
assertEquals(DASH, map.getOrDefault(OFF_0_1, DASH));
|
assertEquals(DASH, map.getOrDefault(OFF_0_1, DASH));
|
||||||
assertEquals(DASH, map.getOrDefault(OFF_0_2, DASH));
|
assertEquals(DASH, map.getOrDefault(OFF_0_2, DASH));
|
||||||
@@ -395,13 +395,13 @@ public class SwedishGeneratorTest {
|
|||||||
@Test
|
@Test
|
||||||
void testInnerWorkings() {
|
void testInnerWorkings() {
|
||||||
// 1. Test Slot.increasing
|
// 1. Test Slot.increasing
|
||||||
assertFalse(Slot.increasing(CLUE_LEFT)); // Left
|
assertFalse(Slotinfo.increasing(CLUE_LEFT)); // Left
|
||||||
assertTrue(Slot.increasing(CLUE_RIGHT)); // Right
|
assertTrue(Slotinfo.increasing(CLUE_RIGHT)); // Right
|
||||||
assertTrue(Slot.increasing(CLUE_DOWN)); // Down
|
assertTrue(Slotinfo.increasing(CLUE_DOWN)); // Down
|
||||||
assertFalse(Slot.increasing(CLUE_UP)); // Up
|
assertFalse(Slotinfo.increasing(CLUE_UP)); // Up
|
||||||
|
|
||||||
assertTrue(Slot.increasing(Slot.packSlotKey(0, CLUE_RIGHT)));
|
assertTrue(Slotinfo.increasing(Masker.Slot.packSlotKey(0, CLUE_RIGHT)));
|
||||||
assertFalse(Slot.increasing(Slot.packSlotKey(0, CLUE_LEFT)));
|
assertFalse(Slotinfo.increasing(Masker.Slot.packSlotKey(0, CLUE_LEFT)));
|
||||||
|
|
||||||
// 2. Test slotScore
|
// 2. Test slotScore
|
||||||
val counts = new byte[SIZE];
|
val counts = new byte[SIZE];
|
||||||
@@ -411,7 +411,7 @@ public class SwedishGeneratorTest {
|
|||||||
var entry5 = dict.index()[5];
|
var entry5 = dict.index()[5];
|
||||||
// cross = (counts[1]-1) + (counts[2]-1) = 1 + 2 = 3
|
// cross = (counts[1]-1) + (counts[2]-1) = 1 + 2 = 3
|
||||||
// score = 3 * 10 + len(2) = 32
|
// score = 3 * 10 + len(2) = 32
|
||||||
assertEquals(32, slotScore(counts, (1L << 1) | (1L << 2), 0L));
|
assertEquals(32, Masker.slotScore(counts, (1L << 1) | (1L << 2), 0L));
|
||||||
// 3. Test candidateCountForPattern
|
// 3. Test candidateCountForPattern
|
||||||
|
|
||||||
var ctx = Context.get();
|
var ctx = Context.get();
|
||||||
@@ -427,8 +427,8 @@ public class SwedishGeneratorTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testMaskFitnessDetailed() {
|
void testMaskFitnessDetailed() {
|
||||||
var gen = new SwedishGenerator(new Rng(42), new int[STACK_SIZE], Clues.createEmpty());
|
var gen = new Masker(new Rng(42), new int[STACK_SIZE], Masker.Clues.createEmpty());
|
||||||
var grid = Clues.createEmpty();
|
var grid = Masker.Clues.createEmpty();
|
||||||
// Empty grid: huge penalty
|
// Empty grid: huge penalty
|
||||||
var fitEmpty = gen.maskFitness(grid, 18);
|
var fitEmpty = gen.maskFitness(grid, 18);
|
||||||
assertTrue(fitEmpty >= 1_000_000_000L);
|
assertTrue(fitEmpty >= 1_000_000_000L);
|
||||||
|
|||||||
Reference in New Issue
Block a user