introduce bitloops

This commit is contained in:
mike
2026-01-14 00:57:41 +01:00
parent 2430dbdfb9
commit 1d731334d9
6 changed files with 144 additions and 149 deletions

View File

@@ -163,17 +163,8 @@ public record Export() {
public record PuzzleResult(SwedishGenerator swe, Dict dict, Gridded mask, FillResult filled) { public record PuzzleResult(SwedishGenerator swe, Dict dict, Gridded mask, FillResult filled) {
boolean inBounds(int idx) { return idx >= 0 && idx < SwedishGenerator.SIZE; } boolean inBounds(int idx) { return idx >= 0 && idx < SwedishGenerator.SIZE; }
Placed extractPlacedFromSlot(Slot s, long lemma) { Placed extractPlacedFromSlot(Slot s, long lemma) { return new Placed(lemma, s.key(), s.walk().toArray()); }
var cells = s.walk().toArray();
return new Placed(
lemma,
s.key(),
cells
);
}
public ExportedPuzzle exportFormatFromFilled(int difficulty, Rewards rewards) { public ExportedPuzzle exportFormatFromFilled(int difficulty, Rewards rewards) {
var g = filled().grid(); var g = filled().grid();
var placed = new ArrayList<Placed>(); var placed = new ArrayList<Placed>();

View File

@@ -1,6 +1,7 @@
package puzzle; package puzzle;
import lombok.Data; import lombok.Data;
import lombok.val;
import puzzle.SwedishGenerator.Rng; import puzzle.SwedishGenerator.Rng;
import java.io.IOException; import java.io.IOException;
@@ -346,7 +347,7 @@ public class Main {
static PuzzleResult attempt(Rng rng, Dict dict, Opts opts) { static PuzzleResult attempt(Rng rng, Dict dict, Opts opts) {
try { try {
return _attempt(rng, dict, opts); return _attempt(rng, dict, opts);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
System.err.println("Failed to operate" + e.getMessage()); System.err.println("Failed to operate" + e.getMessage());
@@ -356,8 +357,9 @@ public class Main {
static PuzzleResult _attempt(Rng rng, Dict dict, Opts opts) { static PuzzleResult _attempt(Rng rng, Dict dict, Opts opts) {
TOTAL_ATTEMPTS.incrementAndGet(); TOTAL_ATTEMPTS.incrementAndGet();
var swe = new SwedishGenerator(rng); var swe = new SwedishGenerator(rng);
var mask = swe.generateMask(opts.pop, opts.gens, Math.max(opts.pop, (int) Math.floor(opts.pop * 1.5))); val stack = new int[STACK_SIZE];
var filled = swe.fillMask(mask, dict.index(), opts.fillTimeout); var mask = swe.generateMask(stack, opts.pop, opts.gens, Math.max(opts.pop, (int) Math.floor(opts.pop * 1.5)));
var filled = fillMask(rng, mask, dict.index());
TOTAL_NODES.addAndGet(filled.stats().nodes); TOTAL_NODES.addAndGet(filled.stats().nodes);
TOTAL_BACKTRACKS.addAndGet(filled.stats().backtracks); TOTAL_BACKTRACKS.addAndGet(filled.stats().backtracks);

View File

@@ -1,6 +1,9 @@
package puzzle; package puzzle;
import lombok.Getter; import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.experimental.Accessors;
import lombok.experimental.Delegate;
import lombok.val; import lombok.val;
import precomp.Neighbors9x8; import precomp.Neighbors9x8;
import precomp.Neighbors9x8.rci; import precomp.Neighbors9x8.rci;
@@ -59,9 +62,11 @@ public record SwedishGenerator(Rng rng) {
static final int MAX_WORD_LENGTH_PLUS_ONE = MAX_WORD_LENGTH + 1; static final int MAX_WORD_LENGTH_PLUS_ONE = MAX_WORD_LENGTH + 1;
static final int MIN_LEN = Config.MIN_LEN; static final int MIN_LEN = Config.MIN_LEN;
static final int MAX_TRIES_PER_SLOT = Config.MAX_TRIES_PER_SLOT; static final int MAX_TRIES_PER_SLOT = Config.MAX_TRIES_PER_SLOT;
static final int STACK_SIZE = 64;
static final char C_DASH = '\0'; static final char C_DASH = '\0';
static final byte _1 = 49, _9 = 57, A = 65, Z = 90, DASH = (byte) C_DASH; static final byte _1 = 49, _9 = 57, A = 65, Z = 90, DASH = (byte) C_DASH;
//72 << 3;
static final int CLUE_INDEX_MAX_SIZE = (288 | 3) + 1;
static int clamp(int x, int a, int b) { return Math.max(a, Math.min(b, x)); } static int clamp(int x, int a, int b) { return Math.max(a, Math.min(b, x)); }
record Pick(Slot slot, CandidateInfo info, boolean done) { } record Pick(Slot slot, CandidateInfo info, boolean done) { }
// 0b11 // 0b11
@@ -94,19 +99,22 @@ public record SwedishGenerator(Rng rng) {
static final Pick PICK_DONE = new Pick(null, null, true); static final Pick PICK_DONE = new Pick(null, null, true);
static final Pick PICK_NOT_DONE = new Pick(null, null, false); static final Pick PICK_NOT_DONE = new Pick(null, null, false);
@RequiredArgsConstructor
@Getter
@Accessors(fluent = true)
public static final class FillStats { public static final class FillStats {
public long nodes; final public long nodes;
public long backtracks; final public long backtracks;
public double seconds; final public double seconds;
public int lastMRV; final public int lastMRV;
public double simplicity; public double simplicity;
} }
public static record FillResult(boolean ok, public static record FillResult(boolean ok,
Gridded grid, Gridded grid,
long[] clueMap, long[] clueMap,
FillStats stats) { @Delegate FillStats stats) {
public void calcSimpel() { public void calcSimpel() {
if (ok) { if (ok) {
@@ -131,9 +139,9 @@ public record SwedishGenerator(Rng rng) {
} }
} }
static final record Context(int[] stack, long[] undo, long[] bitset) { static final record Context(long[] bitset) {
public Context() { this(new int[SIZE], new long[128], new long[2500]); } public Context() { this(new long[2500]); }
private static final ThreadLocal<Context> CTX = ThreadLocal.withInitial(Context::new); private static final ThreadLocal<Context> CTX = ThreadLocal.withInitial(Context::new);
public static Context get() { return CTX.get(); } public static Context get() { return CTX.get(); }
@@ -381,18 +389,18 @@ public record SwedishGenerator(Rng rng) {
} }
} }
if ((rayLo | rayHi) != 0) { visitor.visit(key, rayLo, rayHi);
visitor.visit(key, rayLo, rayHi); //if ((rayLo | rayHi) == 0L) throw new RuntimeException()
}
} }
static ArrayList<Slot> extractSlots(Grid grid) { static Slot[] extractSlots(Grid grid) {
var slots = new ArrayList<Slot>(32); var slots = new Slot[grid.clueCount()];
grid.forEachSlot((key, lo, hi) -> slots.add(Slot.from(key, lo, hi))); int[] N = new int[]{ 0 };
grid.forEachSlot((key, lo, hi) -> slots[N[0]++] = Slot.from(key, lo, hi));
return slots; return slots;
} }
long maskFitness(Grid grid) { long maskFitness(Grid grid, int[] stack) {
long cHLo = 0L, cHHi = 0L, cVLo = 0L, cVHi = 0L; long cHLo = 0L, cHHi = 0L, cVLo = 0L, cVHi = 0L;
long lo_cl = grid.lo, hi_cl = grid.hi; long lo_cl = grid.lo, hi_cl = grid.hi;
@@ -437,8 +445,6 @@ public record SwedishGenerator(Rng rng) {
} }
if (!hasSlots) return 1_000_000_000L; if (!hasSlots) return 1_000_000_000L;
var ctx = Context.get();
var stack = ctx.stack;
long seenLo = 0L, seenHi = 0L; long seenLo = 0L, seenHi = 0L;
// loop over beide helften // loop over beide helften
@@ -448,7 +454,7 @@ public record SwedishGenerator(Rng rng) {
// "unseen clues" in deze helft // "unseen clues" in deze helft
for (long bits = clueMask & ~seenMask; bits != 0L; bits &= bits - 1) { for (long bits = clueMask & ~seenMask; bits != 0L; bits &= bits - 1) {
int clueIdx = base + Long.numberOfTrailingZeros(bits); int clueIdx = base | Long.numberOfTrailingZeros(bits);
// start nieuwe component // start nieuwe component
int size = 0; int size = 0;
@@ -543,7 +549,7 @@ public record SwedishGenerator(Rng rng) {
for (var k = 0; k < 4; k++) { for (var k = 0; k < 4; k++) {
ri = bytes[rng.randint(0, 624)]; ri = bytes[rng.randint(0, 624)];
if (!g.clueless(ri)) { if (!g.clueless(ri)) {
var d_idx = rng.randint2bitByte(); var d_idx = rng.randint2bitByte();
if (g.hasRoomForClue(OFFSETS_D_IDX[Slot.packSlotDir(ri, d_idx)])) g.setClue(ri, d_idx); if (g.hasRoomForClue(OFFSETS_D_IDX[Slot.packSlotDir(ri, d_idx)])) g.setClue(ri, d_idx);
} }
} }
@@ -580,14 +586,14 @@ public record SwedishGenerator(Rng rng) {
} }
public static void clearClues(Grid out, int idx) { if (!out.hasRoomForClue(OFFSETS_D_IDX[Slot.packSlotDir(idx, out.digitAt(idx))])) out.clearClue(idx); } public static void clearClues(Grid out, int idx) { if (!out.hasRoomForClue(OFFSETS_D_IDX[Slot.packSlotDir(idx, out.digitAt(idx))])) out.clearClue(idx); }
Grid hillclimb(Grid start, int limit) { Grid hillclimb(int[] stack, Grid start, int limit) {
var best = start; var best = start;
var bestF = maskFitness(best); var bestF = maskFitness(best, stack);
var fails = 0; var fails = 0;
while (fails < limit) { while (fails < limit) {
var cand = mutate(best); var cand = mutate(best);
var f = maskFitness(cand); var f = maskFitness(cand, stack);
if (f < bestF) { if (f < bestF) {
best = cand; best = cand;
bestF = f; bestF = f;
@@ -599,21 +605,21 @@ public record SwedishGenerator(Rng rng) {
return best; return best;
} }
public Grid generateMask(int popSize, int gens, int pairs) { public Grid generateMask(int[] stack, int popSize, int gens, int pairs) {
class GridAndFit { class GridAndFit {
Grid grid; Grid grid;
long fite = -1; long fite = -1;
GridAndFit(Grid grid) { this.grid = grid; } GridAndFit(Grid grid) { this.grid = grid; }
long fit() { long fit() {
if (fite == -1) this.fite = maskFitness(grid); if (fite == -1) this.fite = maskFitness(grid, stack);
return this.fite; return this.fite;
} }
} }
if (Main.VERBOSE) System.out.println("generateMask init pop: " + popSize); if (Main.VERBOSE) System.out.println("generateMask init pop: " + popSize);
var pop = new ArrayList<GridAndFit>(); var pop = new ArrayList<GridAndFit>();
for (var i = 0; i < popSize; i++) { for (var i = 0; i < popSize; i++) {
pop.add(new GridAndFit(hillclimb(randomMask(), 180))); pop.add(new GridAndFit(hillclimb(stack, randomMask(), 180)));
} }
for (var gen = 0; gen < gens; gen++) { for (var gen = 0; gen < gens; gen++) {
@@ -624,7 +630,7 @@ public record SwedishGenerator(Rng rng) {
var p1 = pop.get(rng.randint(0, pop.size() - 1)); var p1 = pop.get(rng.randint(0, pop.size() - 1));
var p2 = pop.get(rng.randint(0, pop.size() - 1)); var p2 = pop.get(rng.randint(0, pop.size() - 1));
var child = crossover(p1.grid, p2.grid); var child = crossover(p1.grid, p2.grid);
children.add(new GridAndFit(hillclimb(child, 70))); children.add(new GridAndFit(hillclimb(stack, child, 70)));
} }
pop.addAll(children); pop.addAll(children);
@@ -696,7 +702,7 @@ public record SwedishGenerator(Rng rng) {
} }
return p; return p;
} }
static int slotScore(int[] count, Slot s) { static int slotScore(byte[] count, Slot s) {
int cross = 0; int cross = 0;
for (long b = s.lo; b != 0; b &= b - 1) cross += (count[Long.numberOfTrailingZeros(b)] - 1); for (long b = s.lo; b != 0; b &= b - 1) cross += (count[Long.numberOfTrailingZeros(b)] - 1);
for (long b = s.hi; b != 0; b &= b - 1) cross += (count[64 | Long.numberOfTrailingZeros(b)] - 1); for (long b = s.hi; b != 0; b &= b - 1) cross += (count[64 | Long.numberOfTrailingZeros(b)] - 1);
@@ -761,9 +767,8 @@ public record SwedishGenerator(Rng rng) {
return true; return true;
} }
static CandidateInfo candidateInfoForPattern(Context ctx, long pattern, DictEntry entry, int lenb) { static CandidateInfo candidateInfoForPattern(long[] res, long pattern, DictEntry entry, int lenb) {
int numLongs = entry.numlong; int numLongs = entry.numlong;
long[] res = ctx.bitset;
boolean first = true; boolean first = true;
for (long p = pattern; p != 0; ) { for (long p = pattern; p != 0; ) {
@@ -799,15 +804,15 @@ public record SwedishGenerator(Rng rng) {
return new CandidateInfo(indices, count); return new CandidateInfo(indices, count);
} }
static int candidateCountForPattern(Context ctx, long pattern, DictEntry entry, int lenb) { static int candidateCountForPattern(final long[] res, final long pattern, final DictEntry entry, final int lenb) {
int numLongs = entry.numlong; val numLongs = entry.numlong;
long[] res = ctx.bitset; val posBitsets = entry.posBitsets;
boolean first = true; boolean first = true;
for (long p = pattern; p != 0; ) { for (long p = pattern; p != 0; ) {
int combined = (int) (p & 0xFF); int combined = (int) (p & 0xFF);
if (combined != 0) { if (combined != 0) {
long[] bs = entry.posBitsets[combined - 1]; long[] bs = posBitsets[combined - 1];
if (first) { if (first) {
System.arraycopy(bs, 0, res, 0, numLongs); System.arraycopy(bs, 0, res, 0, numLongs);
first = false; first = false;
@@ -824,36 +829,40 @@ public record SwedishGenerator(Rng rng) {
for (int k = 0; k < numLongs; k++) count += Long.bitCount(res[k]); for (int k = 0; k < numLongs; k++) count += Long.bitCount(res[k]);
return count; return count;
} }
//72 << 3;
static final int BIGG = (288 | 3) + 1;
public FillResult fillMask(Grid mask, DictEntry[] dictIndex,
int timeLimitMs) {
val multiThreaded = Thread.currentThread().getName().contains("pool");
val grid = mask.deepCopyGrid();
val used = new Bit1029();
long[] assigned = new long[BIGG]; static void scoreSlots(int[] slotScores, Slot[] slots) {
val ctx = Context.get(); val count = new byte[SIZE];
val count = new int[SIZE];
val slots = extractSlots(grid);
for (var s : slots) { for (var s : slots) {
for (long b = s.lo; b != 0; b &= b - 1) count[Long.numberOfTrailingZeros(b)]++; for (long b = s.lo; b != 0; b &= b - 1) count[Long.numberOfTrailingZeros(b)]++;
for (long b = s.hi; b != 0; b &= b - 1) count[64 | Long.numberOfTrailingZeros(b)]++; for (long b = s.hi; b != 0; b &= b - 1) count[64 | Long.numberOfTrailingZeros(b)]++;
} }
for (int i = 0; i < slots.length; i++) slotScores[i] = slotScore(count, slots[i]);
val slotScores = new int[slots.size()]; }
for (int i = 0; i < slots.size(); i++) slotScores[i] = slotScore(count, slots.get(i)); public static FillResult fillMask(Rng rng, Grid mask, DictEntry[] dictIndex) {
val multiThreaded = Thread.currentThread().getName().contains("pool");
val NO_LOG = (!Main.VERBOSE || multiThreaded);
val grid = mask.deepCopyGrid();
val used = new Bit1029();
val assigned = new long[CLUE_INDEX_MAX_SIZE];
val bitset = new long[2500];
val undo = new long[64];
val t0 = System.currentTimeMillis(); val slots = extractSlots(grid);
val stats = new FillStats(); val TOTAL = slots.length;
val TOTAL = slots.size(); val slotScores = new int[TOTAL];
scoreSlots(slotScores, slots);
val t0 = System.currentTimeMillis();
class Solver { class Solver {
long nodes;
long backtracks;
int lastMRV;
long lastLog = t0; long lastLog = t0;
void renderProgress() { void renderProgress() {
if (!Main.VERBOSE || multiThreaded) return;
var now = System.currentTimeMillis(); var now = System.currentTimeMillis();
if ((now - lastLog) < LOG_EVERY_MS) return; if ((now - lastLog) < LOG_EVERY_MS) return;
lastLog = (now); lastLog = (now);
@@ -869,19 +878,19 @@ public record SwedishGenerator(Rng rng) {
var msg = String.format( var msg = String.format(
Locale.ROOT, Locale.ROOT,
"%s %d/%d slots | nodes=%d | backtracks=%d | mrv=%d | %s", "%s %d/%d slots | nodes=%d | backtracks=%d | mrv=%d | %s",
bar, done, TOTAL, stats.nodes, stats.backtracks, stats.lastMRV, elapsed bar, done, TOTAL, nodes, backtracks, lastMRV, elapsed
); );
System.out.print("\r" + Strings.padRight(msg, 120)); System.out.print("\r" + Strings.padRight(msg, 120));
System.out.flush(); System.out.flush();
} }
Pick chooseMRV() { Pick chooseMRV() {
Slot best = null; Slot best = null;
for (int i = 0, count, count2 = -1, bestScore = -1, n = slots.size(); i < n; i++) { for (int i = 0, count, count2 = -1, bestScore = -1, n = TOTAL; i < n; i++) {
var s = slots.get(i); var s = slots[i];
if (assigned[s.key] != X) continue; if (assigned[s.key] != X) continue;
var pattern = patternForSlot(grid, s); var pattern = patternForSlot(grid, s);
var index = dictIndex[s.length()]; var index = dictIndex[s.length()];
count = pattern == X ? index.length : candidateCountForPattern(ctx, pattern, index, s.length()); count = pattern == X ? index.length : candidateCountForPattern(bitset, pattern, index, s.length());
if (count == 0) return PICK_NOT_DONE; if (count == 0) return PICK_NOT_DONE;
if (best == null if (best == null
@@ -898,28 +907,27 @@ public record SwedishGenerator(Rng rng) {
var pattern = patternForSlot(grid, best); var pattern = patternForSlot(grid, best);
var index = dictIndex[best.length()]; var index = dictIndex[best.length()];
if (pattern == X) return new Pick(best, index.empty, false); if (pattern == X) return new Pick(best, index.empty, false);
return new Pick(best, candidateInfoForPattern(ctx, pattern, index, best.length()), false); return new Pick(best, candidateInfoForPattern(bitset, pattern, index, best.length()), false);
} }
boolean backtrack(int depth) { boolean backtrack(int depth) {
if (Thread.currentThread().isInterrupted()) return false; if (Thread.currentThread().isInterrupted()) return false;
stats.nodes++; nodes++;
if (timeLimitMs > 0 && (System.currentTimeMillis() - t0) > timeLimitMs) return false; if (20_000 > 0 && (System.currentTimeMillis() - t0) > 20_000) return false;
var pick = chooseMRV(); var pick = chooseMRV();
if (pick.done) return true; if (pick.done) return true;
if (pick.slot == null) { if (pick.slot == null) {
stats.backtracks++; backtracks++;
return false; return false;
} }
val info = pick.info; val info = pick.info;
stats.lastMRV = info.count; lastMRV = info.count;
renderProgress(); if (!NO_LOG) renderProgress();
var s = pick.slot; val s = pick.slot;
var k = s.key; val k = s.key;
int patLen = s.length(); val entry = dictIndex[s.length()];
var entry = dictIndex[patLen];
if (info.indices != null && info.indices.length > 0) { if (info.indices != null && info.indices.length > 0) {
var idxs = info.indices; var idxs = info.indices;
@@ -935,7 +943,7 @@ public record SwedishGenerator(Rng rng) {
var lemIdx = Lemma.unpackIndex(w); var lemIdx = Lemma.unpackIndex(w);
if (used.get(lemIdx)) continue; if (used.get(lemIdx)) continue;
if (!placeWord(grid, s, w, ctx.undo, depth)) continue; if (!placeWord(grid, s, w, undo, depth)) continue;
used.set(lemIdx); used.set(lemIdx);
assigned[k] = w; assigned[k] = w;
@@ -944,9 +952,9 @@ public record SwedishGenerator(Rng rng) {
assigned[k] = X; assigned[k] = X;
used.clear(lemIdx); used.clear(lemIdx);
grid.undoPlace(ctx.undo[depth << 1], ctx.undo[(depth << 1) | 1]); grid.undoPlace(undo[depth << 1], undo[(depth << 1) | 1]);
} }
stats.backtracks++; backtracks++;
return false; return false;
} }
@@ -960,7 +968,7 @@ public record SwedishGenerator(Rng rng) {
var lemIdx = Lemma.unpackIndex(w); var lemIdx = Lemma.unpackIndex(w);
if (used.get(lemIdx)) continue; if (used.get(lemIdx)) continue;
if (!placeWord(grid, s, w, ctx.undo, depth)) continue; if (!placeWord(grid, s, w, undo, depth)) continue;
used.set(lemIdx); used.set(lemIdx);
assigned[k] = w; assigned[k] = w;
@@ -969,33 +977,31 @@ public record SwedishGenerator(Rng rng) {
assigned[k] = X; assigned[k] = X;
used.clear(lemIdx); used.clear(lemIdx);
grid.undoPlace(ctx.undo[depth << 1], ctx.undo[(depth << 1) | 1]); grid.undoPlace(undo[depth << 1], undo[(depth << 1) | 1]);
} }
stats.backtracks++; backtracks++;
return false; return false;
} }
} }
// initial render (same feel) // initial render (same feel)
var solver = new Solver(); var solver = new Solver();
solver.renderProgress(); if (!NO_LOG) solver.renderProgress();
var ok = solver.backtrack(0); var ok = solver.backtrack(0);
// final progress line // final progress line
var res = new FillResult(ok, new Gridded(grid), assigned, new FillStats(solver.nodes, solver.backtracks, (System.currentTimeMillis() - t0) / 1000.0, solver.lastMRV));
if (!multiThreaded) { if (!multiThreaded) {
System.out.print("\r" + Strings.padRight("", 120) + "\r"); System.out.print("\r" + Strings.padRight("", 120) + "\r");
System.out.flush(); System.out.flush();
} }
stats.seconds = (System.currentTimeMillis() - t0) / 1000.0;
var res = new FillResult(ok, new Gridded(grid), assigned, stats);
// print a final progress line // print a final progress line
if (Main.VERBOSE && !multiThreaded) { if (Main.VERBOSE && !multiThreaded) {
System.out.println( System.out.println(
String.format(Locale.ROOT, String.format(Locale.ROOT,
"[######################] %d/%d slots | nodes=%d | backtracks=%d | mrv=%d | %.1fs", "[######################] %d/%d slots | nodes=%d | backtracks=%d | mrv=%d | %.1fs",
res.wordCount(), TOTAL, stats.nodes, stats.backtracks, stats.lastMRV, stats.seconds res.wordCount(), TOTAL, res.nodes(), res.backtracks(), res.lastMRV(), res.seconds()
) )
); );
} }

View File

@@ -35,7 +35,7 @@ public class ExportFormatTest {
var clueMap = new long[300]; var clueMap = new long[300];
// key = (cellIndex << 2) | (direction) // key = (cellIndex << 2) | (direction)
var key = (0 << 2) | (CLUE_RIGHT); var key = (0) | (CLUE_RIGHT);
clueMap[key] = Lemma.from("TEST"); clueMap[key] = Lemma.from("TEST");
// Manually fill the grid letters for "TEST" at (0,1), (0,2), (0,3), (0,4) // Manually fill the grid letters for "TEST" at (0,1), (0,2), (0,3), (0,4)
@@ -44,9 +44,9 @@ public class ExportFormatTest {
grid.setLetter(Grid.offset(0, 3), (byte) 'S'); grid.setLetter(Grid.offset(0, 3), (byte) 'S');
grid.setLetter(Grid.offset(0, 4), (byte) 'T'); grid.setLetter(Grid.offset(0, 4), (byte) 'T');
// Terminate the slot at (0,5) with another digit to avoid it extending to MAX_WORD_LENGTH // Terminate the slot at (0,5) with another digit to avoid it extending to MAX_WORD_LENGTH
grid.setClue(Grid.offset(0, 5), CLUE_UP); grid.setClue(Grid.offset(0, 5), CLUE_LEFT);
var fillResult = new FillResult(true, new Gridded(grid), clueMap, new FillStats()); var fillResult = new FillResult(true, new Gridded(grid), clueMap, new FillStats(0, 0, 0, 0));
var puzzleResult = new PuzzleResult(swe, null, null, fillResult); var puzzleResult = new PuzzleResult(swe, null, null, fillResult);
var rewards = new Rewards(10, 5, 1); var rewards = new Rewards(10, 5, 1);
@@ -86,7 +86,7 @@ public class ExportFormatTest {
void testExportFormatEmpty() { void testExportFormatEmpty() {
var swe = new SwedishGenerator(new Rng(0)); var swe = new SwedishGenerator(new Rng(0));
var grid = Grid.createEmpty(); var grid = Grid.createEmpty();
var fillResult = new FillResult(true, new Gridded(grid), new long[300], new FillStats()); var fillResult = new FillResult(true, new Gridded(grid), new long[300], new FillStats(0, 0, 0, 0));
var puzzleResult = new PuzzleResult(swe, null, null, fillResult); var puzzleResult = new PuzzleResult(swe, null, null, fillResult);
var exported = puzzleResult.exportFormatFromFilled(1, new Rewards(0, 0, 0)); var exported = puzzleResult.exportFormatFromFilled(1, new Rewards(0, 0, 0));

View File

@@ -42,8 +42,8 @@ public class MainTest {
grid.setLetter(OFF_0_2, LETTER_B); grid.setLetter(OFF_0_2, LETTER_B);
var slots = extractSlots(grid); var slots = extractSlots(grid);
assertEquals(1, slots.size()); assertEquals(1, slots.length);
var s = slots.getFirst(); var s = slots[0];
assertEquals(8, s.length()); assertEquals(8, s.length());
var cells = s.walk().toArray(); var cells = s.walk().toArray();
assertEquals(0, Grid.r(cells[0])); assertEquals(0, Grid.r(cells[0]));

View File

@@ -1,5 +1,6 @@
package puzzle; package puzzle;
import lombok.val;
import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import puzzle.Export.IntListDTO; import puzzle.Export.IntListDTO;
@@ -35,36 +36,36 @@ public class SwedishGeneratorTest {
static final byte D_BYTE_2 = CLUE_RIGHT; static final byte D_BYTE_2 = CLUE_RIGHT;
@Test @Test
void testPatternForSlotAllLetters() { void testPatternForSlotAllLetters() {
var grid = new Grid(new byte[]{ LETTER_A, LETTER_B, LETTER_C }); var grid = new Grid(new byte[]{ LETTER_A, LETTER_B, LETTER_C });
var slot = Slot.from(18 << Slot.BIT_FOR_DIR | (CLUE_RIGHT), 7L, 0L); var slot = Slot.from(18 << Slot.BIT_FOR_DIR | (CLUE_RIGHT), 7L, 0L);
long pattern = patternForSlot(grid, slot); var pattern = patternForSlot(grid, slot);
assertEquals(1L | (28L << 8) | (55L << 16), pattern); assertEquals(1L | (28L << 8) | (55L << 16), pattern);
} }
@Test @Test
void testPatternForSlotMixed() { void testPatternForSlotMixed() {
var grid = new Grid(new byte[]{ LETTER_A, DASH, LETTER_C }); var grid = new Grid(new byte[]{ LETTER_A, DASH, LETTER_C });
var slot = Slot.from(1 << Slot.BIT_FOR_DIR | (CLUE_RIGHT), 7L, 0L); var slot = Slot.from(1 << Slot.BIT_FOR_DIR | (CLUE_RIGHT), 7L, 0L);
long pattern = patternForSlot(grid, slot); var pattern = patternForSlot(grid, slot);
assertEquals(1L | (0L << 8) | (55L << 16), pattern); assertEquals(1L | (0L << 8) | (55L << 16), pattern);
} }
@Test @Test
void testPatternForSlotAllDashes() { void testPatternForSlotAllDashes() {
var grid = new Grid(new byte[]{ DASH, DASH, DASH }); // - - - var grid = new Grid(new byte[]{ DASH, DASH, DASH }); // - - -
var slot = Slot.from(1 << Slot.BIT_FOR_DIR | (CLUE_RIGHT), 7L, 0L); var slot = Slot.from(1 << Slot.BIT_FOR_DIR | (CLUE_RIGHT), 7L, 0L);
long pattern = patternForSlot(grid, slot); var pattern = patternForSlot(grid, slot);
assertEquals(0L, pattern); assertEquals(0L, pattern);
} }
@Test @Test
void testPatternForSlotSingleLetter() { void testPatternForSlotSingleLetter() {
var grid = new Grid(new byte[]{ LETTER_A, DASH, DASH }); var grid = new Grid(new byte[]{ LETTER_A, DASH, DASH });
var slot = Slot.from(1 << Slot.BIT_FOR_DIR | (CLUE_RIGHT), 7L, 0L); var slot = Slot.from(1 << Slot.BIT_FOR_DIR | (CLUE_RIGHT), 7L, 0L);
long pattern = patternForSlot(grid, slot); var pattern = patternForSlot(grid, slot);
assertEquals(1L, pattern); assertEquals(1L, pattern);
} }
@@ -186,17 +187,17 @@ public class SwedishGeneratorTest {
static int intersectSorted(int[] a, int aLen, int[] b, int bLen, int[] out) { static int intersectSorted(int[] a, int aLen, int[] b, int bLen, int[] out) {
if (aLen == 0 || bLen == 0) return 0; if (aLen == 0 || bLen == 0) return 0;
if (aLen < bLen >>> 4) { if (aLen < bLen >>> 4) {
int k = 0; var k = 0;
for (int i = 0; i < aLen; i++) { for (var i = 0; i < aLen; i++) {
int x = a[i]; var x = a[i];
if (Arrays.binarySearch(b, 0, bLen, x) >= 0) out[k++] = x; if (Arrays.binarySearch(b, 0, bLen, x) >= 0) out[k++] = x;
} }
return k; return k;
} }
if (bLen < aLen >>> 4) { if (bLen < aLen >>> 4) {
int k = 0; var k = 0;
for (int i = 0; i < bLen; i++) { for (var i = 0; i < bLen; i++) {
int y = b[i]; var y = b[i];
if (Arrays.binarySearch(a, 0, aLen, y) >= 0) out[k++] = y; if (Arrays.binarySearch(a, 0, aLen, y) >= 0) out[k++] = y;
} }
return k; return k;
@@ -233,10 +234,10 @@ public class SwedishGeneratorTest {
} }
static long packPattern(String s) { static long packPattern(String s) {
long p = 0; long p = 0;
byte[] b = s.getBytes(StandardCharsets.US_ASCII); var b = s.getBytes(StandardCharsets.US_ASCII);
for (int i = 0; i < b.length; i++) { for (var i = 0; i < b.length; i++) {
int val = b[i] & 31; var val = b[i] & 31;
if (val != 0) { if (val != 0) {
p |= (long) (i * 26 + val) << (i << 3); p |= (long) (i * 26 + val) << (i << 3);
} }
@@ -258,7 +259,7 @@ public class SwedishGeneratorTest {
var dict = new Dict(new long[]{ l0, l1, l2, l3, l3a, l4a, l6a, l7a, l8a }); var dict = new Dict(new long[]{ l0, l1, l2, l3, l3a, l4a, l6a, l7a, l8a });
// Pattern "APP--" for length 5 // Pattern "APP--" for length 5
var info = candidateInfoForPattern(Context.get(), packPattern("APP"), dict.index()[5], 5); var info = candidateInfoForPattern(Context.get().bitset(), packPattern("APP"), dict.index()[5], 5);
assertEquals(2, info.count()); assertEquals(2, info.count());
assertNotNull(info.indices()); assertNotNull(info.indices());
@@ -274,8 +275,8 @@ public class SwedishGeneratorTest {
// 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 slots = extractSlots(grid); var slots = extractSlots(grid);
assertEquals(1, slots.size()); assertEquals(1, slots.length);
var s = slots.getFirst(); var s = slots[0];
assertTrue(s.length() >= 2); assertTrue(s.length() >= 2);
assertEquals(OFF_0_0, s.clueIndex()); assertEquals(OFF_0_0, s.clueIndex());
@@ -284,19 +285,16 @@ public class SwedishGeneratorTest {
@Test @Test
void testMaskFitnessBasic() { void testMaskFitnessBasic() {
var gen = new SwedishGenerator(new Rng(0)); var gen = new SwedishGenerator(new Rng(0));
var grid = Grid.createEmpty(); var grid = Grid.createEmpty();
var lenCounts = new int[12]; var stack = new int[STACK_SIZE];
lenCounts[2] = 10;
lenCounts[8] = 10; // In case MAX_WORD_LENGTH is 8
// Empty grid should have high penalty (no slots) // Empty grid should have high penalty (no slots)
var f1 = gen.maskFitness(grid); var f1 = gen.maskFitness(grid, stack);
assertTrue(f1 >= 1_000_000_000L); assertTrue(f1 >= 1_000_000_000L);
// Add a slot // Add a slot
grid.setClue(OFF_0_0, D_BYTE_2); grid.setClue(OFF_0_0, D_BYTE_2);
var f2 = gen.maskFitness(grid); var f2 = gen.maskFitness(grid, stack);
assertTrue(f2 < f1); assertTrue(f2 < f1);
} }
@@ -312,12 +310,10 @@ public class SwedishGeneratorTest {
assertNotNull(g2); assertNotNull(g2);
assertNotSame(g1, g2); assertNotSame(g1, g2);
var g3 = gen.crossover(g1, g2); assertNotNull(gen.crossover(g1, g2));
assertNotNull(g3);
var lenCounts = new int[12]; val stack = new int[STACK_SIZE];
Arrays.fill(lenCounts, 10); var g4 = gen.hillclimb(stack, g1, 10);
var g4 = gen.hillclimb(g1, 10);
assertNotNull(g4); assertNotNull(g4);
} }
@@ -395,7 +391,7 @@ public class SwedishGeneratorTest {
assertFalse(sDec.increasing()); assertFalse(sDec.increasing());
// 2. Test slotScore // 2. Test slotScore
int[] counts = new int[SIZE]; val counts = new byte[SIZE];
counts[1] = 2; counts[1] = 2;
counts[2] = 3; counts[2] = 3;
var sScore = Slot.from(0, (1L << 1) | (1L << 2), 0L); var sScore = Slot.from(0, (1L << 1) | (1L << 2), 0L);
@@ -419,29 +415,29 @@ public class SwedishGeneratorTest {
var dict = new Dict(words); var dict = new Dict(words);
var entry5 = dict.index()[5]; var entry5 = dict.index()[5];
var ctx = Context.get(); var ctx = Context.get();
long pattern = packPattern("APP"); var pattern = packPattern("APP");
assertEquals(2, candidateCountForPattern(ctx, pattern, entry5, 3)); assertEquals(2, candidateCountForPattern(ctx.bitset(), pattern, entry5, 3));
pattern = packPattern("BAN"); pattern = packPattern("BAN");
assertEquals(1, candidateCountForPattern(ctx, pattern, entry5, 3)); assertEquals(1, candidateCountForPattern(ctx.bitset(), pattern, entry5, 3));
pattern = packPattern("CAT"); pattern = packPattern("CAT");
assertEquals(0, candidateCountForPattern(ctx, pattern, entry5, 3)); assertEquals(0, candidateCountForPattern(ctx.bitset(), pattern, entry5, 3));
} }
@Test @Test
void testMaskFitnessDetailed() { void testMaskFitnessDetailed() {
var gen = new SwedishGenerator(new Rng(42)); var gen = new SwedishGenerator(new Rng(42));
var grid = Grid.createEmpty(); var grid = Grid.createEmpty();
val stack = new int[STACK_SIZE];
// Empty grid: huge penalty // Empty grid: huge penalty
long fitEmpty = gen.maskFitness(grid); var fitEmpty = gen.maskFitness(grid, stack);
assertTrue(fitEmpty >= 1_000_000_000L); assertTrue(fitEmpty >= 1_000_000_000L);
// Grid with one short slot: still high penalty but less than empty // Grid with one short slot: still high penalty but less than empty
grid.setClue(0, D_BYTE_2); // Right from 0,0. Len 2 if 3x3. grid.setClue(0, D_BYTE_2); // Right from 0,0. Len 2 if 3x3.
long fitOne = gen.maskFitness(grid); var fitOne = gen.maskFitness(grid, stack);
assertTrue(fitOne < fitEmpty); assertTrue(fitOne < fitEmpty);
// Test penalty for TARGET_CLUES // Test penalty for TARGET_CLUES