introduce bitloops
This commit is contained in:
@@ -5,6 +5,7 @@ import lombok.experimental.Accessors;
|
||||
import lombok.experimental.Delegate;
|
||||
import lombok.val;
|
||||
import puzzle.Export.Gridded.Replacar.Cell;
|
||||
import puzzle.Export.LetterVisit.LetterAt;
|
||||
import puzzle.SwedishGenerator.Clues;
|
||||
import puzzle.SwedishGenerator.DictEntry;
|
||||
import puzzle.SwedishGenerator.FillResult;
|
||||
@@ -13,7 +14,9 @@ import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.function.IntSupplier;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
import java.util.stream.Stream;
|
||||
import static puzzle.SwedishGenerator.R;
|
||||
import static puzzle.SwedishGenerator.Lemma;
|
||||
import static puzzle.SwedishGenerator.Slot;
|
||||
@@ -38,30 +41,46 @@ public record Export() {
|
||||
}
|
||||
}
|
||||
|
||||
static final String INIT = IntStream.range(0, R).mapToObj(l_ -> " ").collect(Collectors.joining("\n"));
|
||||
|
||||
public record Clued(@Delegate Clues mask) {
|
||||
|
||||
String gridToString() {
|
||||
var sb = new StringBuilder();
|
||||
for (var r = 0; r < R; r++) {
|
||||
if (r > 0) sb.append('\n');
|
||||
for (var c = 0; c < C; c++) {
|
||||
val idx = Grid.offset(r, c);
|
||||
if (mask.isClue(idx))
|
||||
sb.append((char) (mask.digitAt(idx) | 48));
|
||||
else {
|
||||
sb.append(' ');
|
||||
}
|
||||
}
|
||||
}
|
||||
var sb = new StringBuilder(INIT);
|
||||
forEachSlot((s, l, a) -> {
|
||||
val idx = Slot.clueIndex(s);
|
||||
val r = idx & 7;
|
||||
val c = idx >>> 3;
|
||||
val dir = Slot.dir(s);
|
||||
sb.setCharAt(r * (C + 1) + c, (char) (dir | 48));
|
||||
});
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
interface LetterVisit {
|
||||
|
||||
record LetterAt(int index, byte letter) {
|
||||
|
||||
static LetterAt from(int index, byte[] bytes) { return new LetterAt(index, bytes[index]); }
|
||||
}
|
||||
|
||||
void visit(int index, byte letter);
|
||||
default void visit(int index, byte[] letters) { visit(index, letters[index]); }
|
||||
}
|
||||
|
||||
record Gridded(@Delegate Grid grid) {
|
||||
public boolean lisLetterAt(int pos) {
|
||||
if ((pos & 64) == 0)
|
||||
return lisLetterAtLo(pos);
|
||||
return lisLetterAtHi(pos);
|
||||
|
||||
public Stream<LetterAt> stream(Clues clues) {
|
||||
val stream = Stream.<LetterAt>builder();
|
||||
for (var l = grid.lo & ~clues.lo; l != SwedishGenerator.X; l &= l - 1) stream.accept(LetterAt.from(Long.numberOfTrailingZeros(l), grid.g));
|
||||
for (var h = grid.hi & ~clues.hi; h != SwedishGenerator.X; h &= h - 1) stream.accept(LetterAt.from(64 | Long.numberOfTrailingZeros(h), grid.g));
|
||||
return stream.build();
|
||||
}
|
||||
public void forEachLetter(Clues clues, LetterVisit visitor) {
|
||||
for (var l = grid.lo & ~clues.lo; l != SwedishGenerator.X; l &= l - 1) visitor.visit(Long.numberOfTrailingZeros(l), grid.g);
|
||||
for (var h = grid.hi & ~clues.hi; h != SwedishGenerator.X; h &= h - 1) visitor.visit(64 | Long.numberOfTrailingZeros(h), grid.g);
|
||||
}
|
||||
public static IntStream walk(byte base, long lo, long hi) {
|
||||
if (Slot.increasing(base)) {
|
||||
@@ -116,19 +135,19 @@ public record Export() {
|
||||
return (char) (64 | b);
|
||||
}
|
||||
String gridToString(Clues clues) {
|
||||
var sb = new StringBuilder();
|
||||
for (var r = 0; r < R; r++) {
|
||||
if (r > 0) sb.append('\n');
|
||||
for (var c = 0; c < C; c++) {
|
||||
var offset = Grid.offset(r, c);
|
||||
if (clues.isClue(offset))
|
||||
sb.append((char) (48 | clues.digitAt(offset)));
|
||||
else if (lisLetterAt(offset))
|
||||
sb.append((char) (64 | grid.letter32At(offset)));
|
||||
else
|
||||
sb.append(' ');
|
||||
}
|
||||
}
|
||||
var sb = new StringBuilder(INIT);
|
||||
clues.forEachSlot((s, l, a) -> {
|
||||
val idx = Slot.clueIndex(s);
|
||||
val r = idx & 7;
|
||||
val c = idx >>> 3;
|
||||
val dir = Slot.dir(s);
|
||||
sb.setCharAt(r * (C + 1) + c, (char) (dir | 48));
|
||||
});
|
||||
forEachLetter(clues, (idx, letter) -> {
|
||||
val r = idx & 7;
|
||||
val c = idx >>> 3;
|
||||
sb.setCharAt(r * (C + 1) + c, (char) (letter | 64));
|
||||
});
|
||||
return sb.toString();
|
||||
}
|
||||
public String renderHuman(Clues clues) {
|
||||
@@ -141,6 +160,21 @@ public record Export() {
|
||||
char replace(Cell c);
|
||||
}
|
||||
public String[] exportGrid(Clues clues, Replacar clueChar, char emptyFallback) {
|
||||
var sb = new StringBuilder(INIT);
|
||||
clues.forEachSlot((s, l, a) -> {
|
||||
val idx = Slot.clueIndex(s);
|
||||
val r = idx & 7;
|
||||
val c = idx >>> 3;
|
||||
val dir = Slot.dir(s);
|
||||
sb.setCharAt(r * (C + 1) + c, clueChar.replace(new Cell(grid, clues, idx, (byte) (dir | 48))));
|
||||
});
|
||||
forEachLetter(clues, (idx, letter) -> {
|
||||
val r = idx & 7;
|
||||
val c = idx >>> 3;
|
||||
sb.setCharAt(r * (C + 1) + c, (char) (letter | 64));
|
||||
});
|
||||
return sb.toString().replaceAll(" ", "" + emptyFallback).split("\n");
|
||||
/*
|
||||
var out = new String[R];
|
||||
for (var r = 0; r < R; r++) {
|
||||
var sb = new StringBuilder(C);
|
||||
@@ -156,7 +190,7 @@ public record Export() {
|
||||
}
|
||||
out[r] = sb.toString();
|
||||
}
|
||||
return out;
|
||||
return out;*/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,7 +228,7 @@ public record Export() {
|
||||
|
||||
public record ExportedPuzzle(String[] grid, WordOut[] words, int difficulty, Rewards rewards) { }
|
||||
|
||||
public record PuzzleResult(Clued mask, FillResult filled) {
|
||||
public record PuzzleResult(Clued clues, FillResult filled) {
|
||||
|
||||
Placed extractPlacedFromSlot(Slot s, long lemma) { return new Placed(lemma, s.key(), s.walk().toArray()); }
|
||||
public ExportedPuzzle exportFormatFromFilled(int difficulty, Rewards rewards) {
|
||||
@@ -202,7 +236,7 @@ public record Export() {
|
||||
var placed = new ArrayList<Placed>();
|
||||
var clueMap = filled().clueMap();
|
||||
val entries = new DictEntry[10];
|
||||
mask.forEachSlot((int key, long lo, long hi) -> {
|
||||
clues.forEachSlot((int key, long lo, long hi) -> {
|
||||
var word = clueMap[key];
|
||||
if (word != 0L) {
|
||||
placed.add(extractPlacedFromSlot(Slot.from(key, lo, hi, entries[Slot.length(lo, hi)]), word));
|
||||
@@ -213,7 +247,7 @@ public record Export() {
|
||||
|
||||
// If nothing placed: return full grid mapped to letters/# only
|
||||
if (placed.isEmpty()) {
|
||||
return new ExportedPuzzle(g.exportGrid(mask.mask, _ -> '#', '#'), new WordOut[0], difficulty, rewards);
|
||||
return new ExportedPuzzle(g.exportGrid(clues.mask, _ -> '#', '#'), new WordOut[0], difficulty, rewards);
|
||||
}
|
||||
|
||||
// 2) bounding box around all word cells + arrow cells, with 1-cell margin
|
||||
@@ -237,13 +271,17 @@ public record Export() {
|
||||
|
||||
// 3) map of only used letter cells (everything else becomes '#')
|
||||
var letterAt = new HashMap<Integer, Character>();
|
||||
for (var p : placed) {
|
||||
g.forEachLetter(clues.mask(), (idx, letter) -> {
|
||||
if (letter == 0) return;
|
||||
letterAt.put(idx, (char) (64 | letter));
|
||||
});
|
||||
/* for (var p : placed) {
|
||||
for (var c : p.cells) {
|
||||
if (mask.notClue(c) && g.lisLetterAt(c)) {
|
||||
if (clues.notClue(c) && g.lisLetterAt(c)) {
|
||||
letterAt.put(c, (char) (64 | g.letter32At(c)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
// 4) render gridv2 over cropped bounds (out-of-bounds become '#')
|
||||
var gridv2 = new String[Math.max(0, maxR - minR + 1)];
|
||||
|
||||
@@ -93,13 +93,13 @@ public class Main {
|
||||
info(String.format(Locale.ROOT, "simplicity : %.2f", res.filled().stats().simplicity));
|
||||
|
||||
section("Mask");
|
||||
System.out.print(indentLines(res.mask().gridToString(), " "));
|
||||
System.out.print(indentLines(res.clues().gridToString(), " "));
|
||||
|
||||
section("Grid (raw)");
|
||||
System.out.print(indentLines(res.filled().grid().gridToString(res.mask().mask()), " "));
|
||||
System.out.print(indentLines(res.filled().grid().gridToString(res.clues().mask()), " "));
|
||||
|
||||
section("Grid (human)");
|
||||
System.out.print(indentLines(res.filled().grid().renderHuman(res.mask().mask()), " "));
|
||||
System.out.print(indentLines(res.filled().grid().renderHuman(res.clues().mask()), " "));
|
||||
|
||||
var exported = res.exportFormatFromFilled(1, new Rewards(50, 2, 1));
|
||||
|
||||
|
||||
@@ -194,7 +194,11 @@ public record SwedishGenerator(Rng rng, int[] stack, Clues cache) {
|
||||
public Clues deepCopyGrid() { return new Clues(lo, hi, vlo, vhi, rlo, rhi); }
|
||||
boolean clueless(int idx) {
|
||||
if (!isClue(idx)) return false;
|
||||
clearClue(idx);
|
||||
if ((idx & 64) == 0) {
|
||||
clearClueLo(idx);
|
||||
}else{
|
||||
clearClueHi(idx);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
public boolean hasRoomForClue(long packed) { return (packed) != X && notClue(packed & 0x7FL) && notClue((packed >>> 7) & 0x7FL); }
|
||||
@@ -216,6 +220,10 @@ public record SwedishGenerator(Rng rng, int[] stack, Clues cache) {
|
||||
else rhi &= ~mask;
|
||||
}
|
||||
}
|
||||
public byte digitAtRevLo(int idx) { return (byte) (2 | ((vlo >>> idx) & 1L)); }
|
||||
public byte digitAtRevHi(int idx) { return (byte) (2 | ((vhi >>> (idx & 63)) & 1L)); }
|
||||
public byte digitAtDevLo(int idx) { return (byte) ((vlo >>> idx) & 1L); }
|
||||
public byte digitAtDevHi(int idx) { return (byte) ((vhi >>> (idx & 63)) & 1L); }
|
||||
public byte digitAt(int idx) {
|
||||
if ((idx & 64) == 0) {
|
||||
int v = (int) ((vlo >>> idx) & 1L);
|
||||
@@ -227,19 +235,18 @@ public record SwedishGenerator(Rng rng, int[] stack, Clues cache) {
|
||||
return (byte) ((r << 1) | v);
|
||||
}
|
||||
}
|
||||
public void clearClue(int idx) {
|
||||
if ((idx & 64) == 0) {
|
||||
public void clearClueLo(int idx) {
|
||||
long mask = ~(1L << idx);
|
||||
lo &= mask;
|
||||
vlo &= mask;
|
||||
rlo &= mask;
|
||||
} else {
|
||||
}
|
||||
public void clearClueHi(int idx) {
|
||||
long mask = ~(1L << (idx & 63));
|
||||
hi &= mask;
|
||||
vhi &= mask;
|
||||
rhi &= mask;
|
||||
}
|
||||
}
|
||||
public boolean isClue(long index) { return ((index & 64) == 0) ? ((lo >>> index) & 1L) != X : ((hi >>> (index & 63)) & 1L) != X; }
|
||||
public boolean isClue(int index) { return ((index & 64) == 0) ? ((lo >>> index) & 1L) != X : ((hi >>> (index & 63)) & 1L) != X; }
|
||||
public boolean notClue(long index) { return ((index & 64) == 0) ? ((lo >>> index) & 1L) == X : ((hi >>> (index & 63)) & 1L) == X; }
|
||||
@@ -254,8 +261,14 @@ public record SwedishGenerator(Rng rng, int[] stack, Clues cache) {
|
||||
}
|
||||
public Grid toGrid() { return new Grid(new byte[SIZE], lo, hi); }
|
||||
public void forEachSlot(SlotVisitor visitor) {
|
||||
for (var l = lo; l != X; l &= l - 1) processSlot(this, visitor, Long.numberOfTrailingZeros(l));
|
||||
for (var h = hi; h != X; h &= h - 1) processSlot(this, visitor, 64 | Long.numberOfTrailingZeros(h));
|
||||
for (var l = lo & ~rlo & vlo; l != X; l &= l - 1) processSlot(this, visitor, Slot.packSlotKey(Long.numberOfTrailingZeros(l), 1));
|
||||
for (var l = lo & ~rlo & ~vlo; l != X; l &= l - 1) processSlot(this, visitor, Slot.packSlotKey(Long.numberOfTrailingZeros(l), 0));
|
||||
for (var l = lo & rlo & ~vlo; l != X; l &= l - 1) processSlotRev(this, visitor, Slot.packSlotKey(Long.numberOfTrailingZeros(l), 2));
|
||||
for (var l = lo & rlo & vlo; l != X; l &= l - 1) processSlotRev(this, visitor, Slot.packSlotKey(Long.numberOfTrailingZeros(l), 3));
|
||||
for (var h = hi & ~rhi & vlo; h != X; h &= h - 1) processSlot(this, visitor, Slot.packSlotKey(64 | Long.numberOfTrailingZeros(h), 1));
|
||||
for (var h = hi & ~rhi & ~vlo; h != X; h &= h - 1) processSlot(this, visitor, Slot.packSlotKey(64 | Long.numberOfTrailingZeros(h), 0));
|
||||
for (var h = hi & rhi & ~vhi; h != X; h &= h - 1) processSlotRev(this, visitor, Slot.packSlotKey((64 | Long.numberOfTrailingZeros(h)), 2));
|
||||
for (var h = hi & rhi & vhi; h != X; h &= h - 1) processSlotRev(this, visitor, Slot.packSlotKey((64 | Long.numberOfTrailingZeros(h)), 3));
|
||||
}
|
||||
public Clues from(Clues best) {
|
||||
lo = best.lo;
|
||||
@@ -275,17 +288,9 @@ public record SwedishGenerator(Rng rng, int[] stack, Clues cache) {
|
||||
public long lo, hi;
|
||||
static int offset(int r, int c) { return r | (c << 3); }
|
||||
/// the pos will never target a clue
|
||||
public byte letter32At(int pos) { return g[pos]; }
|
||||
public boolean lisLetterAtLo(int pos) { return (lo & (1L << pos)) != X; }
|
||||
public boolean lisLetterAtHi(int pos) { return (hi & (1L << (pos & 63))) != X; }
|
||||
void setLetterLo(int idx, byte ch) {
|
||||
lo |= (1L << idx);
|
||||
|
||||
g[idx] = ch;
|
||||
}
|
||||
void setLetterHi(int idx, byte ch) {
|
||||
hi |= (1L << (idx & 63));
|
||||
|
||||
g[idx] = ch;
|
||||
}
|
||||
}
|
||||
@@ -382,33 +387,16 @@ public record SwedishGenerator(Rng rng, int[] stack, Clues cache) {
|
||||
public static boolean horiz(int d) { return (d & 1) != 0; }
|
||||
public static int packSlotKey(int idx, int d) { return (idx << BIT_FOR_DIR) | d; }
|
||||
}
|
||||
|
||||
private static void processSlot(Clues grid, SlotVisitor visitor, int idx) {
|
||||
int key = Slot.packSlotKey(idx, grid.digitAt(idx)); // 0..3
|
||||
|
||||
long rayLo = PATH_LO[key];
|
||||
long rayHi = PATH_HI[key];
|
||||
|
||||
// only consider clue cells
|
||||
long hitsLo = rayLo & grid.lo;
|
||||
long hitsHi = rayHi & grid.hi;
|
||||
|
||||
// slice ray to stop before first clue, depending on direction monotonicity
|
||||
// right/down => increasing indices; up/left => decreasing indices
|
||||
|
||||
if (Slot.increasing(key)) {
|
||||
// first clue is lowest index among hits (lo first, then hi)
|
||||
if (hitsLo != X) {
|
||||
long stop = 1L << Long.numberOfTrailingZeros(hitsLo);
|
||||
rayLo &= (stop - 1);
|
||||
rayHi = 0; // any hi is beyond the stop
|
||||
} else if (hitsHi != X) {
|
||||
long stop = 1L << Long.numberOfTrailingZeros(hitsHi);
|
||||
// keep all lo (lo indices are < any hi index), but cut hi below stop
|
||||
rayHi &= (stop - 1);
|
||||
}
|
||||
} else {
|
||||
// first clue is highest index among hits (hi first, then lo)
|
||||
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 - Long.numberOfLeadingZeros(hitsHi);
|
||||
long stop = 1L << msb;
|
||||
@@ -419,6 +407,23 @@ public record SwedishGenerator(Rng rng, int[] stack, Clues cache) {
|
||||
long stop = 1L << msb;
|
||||
rayLo &= ~((stop << 1) - 1);
|
||||
}
|
||||
|
||||
visitor.visit(key, rayLo, rayHi);
|
||||
}
|
||||
private static void processSlot(Clues c, SwedishGenerator.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 << Long.numberOfTrailingZeros(hitsLo);
|
||||
rayLo &= (stop - 1);
|
||||
rayHi = 0; // any hi is beyond the stop
|
||||
} else if (hitsHi != X) {
|
||||
long stop = 1L << Long.numberOfTrailingZeros(hitsHi);
|
||||
// keep all lo (lo indices are < any hi index), but cut hi below stop
|
||||
rayHi &= (stop - 1);
|
||||
}
|
||||
|
||||
visitor.visit(key, rayLo, rayHi);
|
||||
@@ -646,11 +651,12 @@ public record SwedishGenerator(Rng rng, int[] stack, Clues cache) {
|
||||
out.rlo = (out.rlo & ~maskLo) | (other.rlo & maskLo);
|
||||
out.rhi = (out.rhi & ~maskHi) | (other.rhi & maskHi);
|
||||
|
||||
for (var lo = out.lo; lo != X; lo &= lo - 1L) clearClues(out, Long.numberOfTrailingZeros(lo));
|
||||
for (var hi = out.hi; hi != X; hi &= hi - 1L) clearClues(out, 64 | Long.numberOfTrailingZeros(hi));
|
||||
for (var lo = out.lo; lo != X; lo &= lo - 1L) clearCluesLo(out, Long.numberOfTrailingZeros(lo));
|
||||
for (var hi = out.hi; hi != X; hi &= hi - 1L) clearCluesHi(out, 64 | Long.numberOfTrailingZeros(hi));
|
||||
return out;
|
||||
}
|
||||
public static void clearClues(Clues out, int idx) { if (!out.hasRoomForClue(OFFSETS_D_IDX[Slot.packSlotKey(idx, out.digitAt(idx))])) out.clearClue(idx); }
|
||||
public static void clearCluesLo(Clues out, int idx) { if (!out.hasRoomForClue(OFFSETS_D_IDX[Slot.packSlotKey(idx, out.digitAt(idx))])) out.clearClueLo(idx); }
|
||||
public static void clearCluesHi(Clues out, int idx) { if (!out.hasRoomForClue(OFFSETS_D_IDX[Slot.packSlotKey(idx, out.digitAt(idx))])) out.clearClueHi(idx); }
|
||||
|
||||
Clues hillclimb(Clues start, int clue_size, int limit) {
|
||||
var best = start;
|
||||
|
||||
@@ -5,20 +5,28 @@ import org.junit.jupiter.api.Test;
|
||||
import puzzle.Export.Clued;
|
||||
import puzzle.Export.Gridded;
|
||||
import puzzle.Export.Placed;
|
||||
import puzzle.Export.Rewards;
|
||||
import puzzle.Export.PuzzleResult;
|
||||
import puzzle.Export.Rewards;
|
||||
import puzzle.SwedishGenerator.FillResult;
|
||||
import puzzle.SwedishGenerator.Grid;
|
||||
import puzzle.SwedishGenerator.Rng;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static puzzle.MainTest.*;
|
||||
import static puzzle.MainTest.LETTER_E;
|
||||
import static puzzle.MainTest.LETTER_T;
|
||||
import static puzzle.SwedishGenerator.*;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static puzzle.SwedishGenerator.C;
|
||||
import static puzzle.SwedishGenerator.Clues;
|
||||
import static puzzle.SwedishGenerator.FillStats;
|
||||
import static puzzle.SwedishGenerator.R;
|
||||
import static puzzle.SwedishGenerator.STACK_SIZE;
|
||||
import static puzzle.SwedishGenerator.Slot;
|
||||
import static puzzle.SwedishGenerator.placeWord;
|
||||
import static puzzle.SwedishGeneratorTest.OFF_0_1;
|
||||
import static puzzle.SwedishGeneratorTest.OFF_0_2;
|
||||
import static puzzle.SwedishGeneratorTest.OFF_0_3;
|
||||
import static puzzle.SwedishGeneratorTest.OFF_0_4;
|
||||
import static puzzle.SwedishGeneratorTest.OFF_0_5;
|
||||
import static puzzle.SwedishGeneratorTest.TEST;
|
||||
|
||||
public class ExportFormatTest {
|
||||
|
||||
@@ -36,21 +44,17 @@ public class ExportFormatTest {
|
||||
clues.setClue(0, CLUE_RIGHT);
|
||||
// This creates a slot starting at (0,1)
|
||||
// Terminate the slot at (0,5) with another digit to avoid it extending to MAX_WORD_LENGTH
|
||||
clues.setClue(Grid.offset(0, 5), CLUE_LEFT);
|
||||
var grid = clues.toGrid();
|
||||
clues.setClue(OFF_0_5, CLUE_LEFT);
|
||||
var grid = new Gridded(clues.toGrid());
|
||||
|
||||
var clueMap = new long[300];
|
||||
// key = (cellIndex << 2) | (direction)
|
||||
var key = (0) | (CLUE_RIGHT);
|
||||
clueMap[key] = SwedishGeneratorTest.TEST;
|
||||
var key = Slot.packSlotKey(0, CLUE_RIGHT);
|
||||
var lo = (1L << OFF_0_1) | (1L << OFF_0_2) | (1L << OFF_0_3) | (1L << OFF_0_4);
|
||||
clueMap[key] = TEST;
|
||||
assertTrue(placeWord(grid.grid(), key, lo, 0L, TEST));
|
||||
|
||||
// Manually fill the grid letters for "TEST" at (0,1), (0,2), (0,3), (0,4)
|
||||
grid.setLetterLo(OFF_0_1, LETTER_T);
|
||||
grid.setLetterLo(OFF_0_2, LETTER_E);
|
||||
grid.setLetterLo(OFF_0_3, LETTER_S);
|
||||
grid.setLetterLo(OFF_0_4, LETTER_T);
|
||||
|
||||
var fillResult = new FillResult(true, new Gridded(grid), clueMap, new FillStats(0, 0, 0, 0));
|
||||
var fillResult = new FillResult(true, grid, clueMap, new FillStats(0, 0, 0, 0));
|
||||
var puzzleResult = new PuzzleResult(new Clued(clues), fillResult);
|
||||
|
||||
var rewards = new Rewards(10, 5, 1);
|
||||
|
||||
@@ -4,38 +4,27 @@ import lombok.val;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import puzzle.Export.Clued;
|
||||
import puzzle.Export.Gridded;
|
||||
import puzzle.Export.LetterVisit.LetterAt;
|
||||
import puzzle.Export.PuzzleResult;
|
||||
import puzzle.Export.Rewards;
|
||||
import puzzle.Main.Opts;
|
||||
import puzzle.SwedishGenerator.Rng;
|
||||
import puzzle.SwedishGenerator.Slot;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.stream.Collectors;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static puzzle.SwedishGenerator.*;
|
||||
import static puzzle.SwedishGeneratorTest.*;
|
||||
import static puzzle.SwedishGeneratorTest.OFF_0_0;
|
||||
import static puzzle.SwedishGeneratorTest.OFF_0_1;
|
||||
import static puzzle.SwedishGeneratorTest.OFF_0_2;
|
||||
|
||||
public class MainTest {
|
||||
|
||||
static final byte LETTER_A = (byte) 'A';
|
||||
static final byte LETTER_B = (byte) 'B';
|
||||
static final byte LETTER_Z = (byte) 'Z';
|
||||
static final byte LETTER_T = (byte) 'T';
|
||||
static final byte LETTER_E = (byte) 'E';
|
||||
static final byte LETTER_S = (byte) 'S';
|
||||
static final byte CLUE_DOWN = 0;
|
||||
static final byte CLUE_RIGHT = 1;
|
||||
static final byte CLUE_UP = 2;
|
||||
static final byte CLUE_LEFT = 3;
|
||||
|
||||
static final int OFF_0_0 = Grid.offset(0, 0);
|
||||
static final int OFF_0_1 = Grid.offset(0, 1);
|
||||
static final int OFF_0_2 = Grid.offset(0, 2);
|
||||
static final int OFF_0_3 = Grid.offset(0, 3);
|
||||
static final int OFF_0_4 = Grid.offset(0, 4);
|
||||
static final int OFF_1_1 = Grid.offset(1, 1);
|
||||
static final int OFF_1_2 = Grid.offset(1, 2);
|
||||
static final int OFF_2_3 = Grid.offset(2, 3);
|
||||
static final Opts opts = new Main.Opts() {{
|
||||
this.seed = 12348;
|
||||
this.clueSize = 4;
|
||||
@@ -52,10 +41,11 @@ public class MainTest {
|
||||
void testExtractSlots() {
|
||||
|
||||
var clues = Clues.createEmpty();
|
||||
val key = Slot.packSlotKey(OFF_0_0, CLUE_RIGHT);
|
||||
clues.setClue(OFF_0_0, CLUE_RIGHT);
|
||||
var grid = clues.toGrid();
|
||||
grid.setLetterLo(OFF_0_1, LETTER_A);
|
||||
grid.setLetterLo(OFF_0_2, LETTER_B);
|
||||
var grid = new Gridded(clues.toGrid());
|
||||
|
||||
placeWord(grid.grid(), key, (1L << OFF_0_1) | (1L << OFF_0_2), 0, AB);
|
||||
|
||||
var slots = extractSlots(clues, dict.index());
|
||||
assertEquals(1, slots.length);
|
||||
@@ -100,40 +90,33 @@ public class MainTest {
|
||||
@Test
|
||||
public void testGridBasics() {
|
||||
var clues = Clues.createEmpty();
|
||||
clues.setClue(OFF_1_2, CLUE_UP);
|
||||
var grid = clues.toGrid();
|
||||
val key = Slot.packSlotKey(OFF_2_1, CLUE_UP);
|
||||
clues.setClue(OFF_2_1, CLUE_UP);
|
||||
var grid = new Gridded(clues.toGrid());
|
||||
|
||||
// Test set/get
|
||||
grid.setLetterLo(OFF_0_0, LETTER_A);
|
||||
grid.setLetterLo(OFF_2_3, LETTER_Z);
|
||||
|
||||
Assertions.assertEquals(LETTER_A, grid.letter32At(OFF_0_0));
|
||||
Assertions.assertEquals(CLUE_UP, clues.digitAt(OFF_1_2));
|
||||
Assertions.assertEquals(LETTER_Z, grid.letter32At(OFF_2_3));
|
||||
Assertions.assertFalse(grid.lisLetterAtLo(OFF_1_1));
|
||||
|
||||
// Verify letter mask
|
||||
Assertions.assertTrue((grid.lo & (1L << OFF_0_0)) != 0);
|
||||
Assertions.assertTrue((grid.lo & (1L << OFF_2_3)) != 0);
|
||||
Assertions.assertTrue((grid.lo & (1L << OFF_1_2)) != 0); // Clue also in lo
|
||||
assertEquals(0, (grid.lo & (1L << OFF_1_1))); // Empty letter cell
|
||||
placeWord(grid.grid(), key, (1L << OFF_1_1) | (1L << OFF_0_1), 0, AZ);
|
||||
val arr = grid.stream(clues).collect(Collectors.toMap(LetterAt::index, LetterAt::letter));
|
||||
Assertions.assertEquals(LETTER_A, arr.get(OFF_1_1));
|
||||
Assertions.assertEquals(LETTER_Z, arr.get(OFF_0_1));
|
||||
Assertions.assertEquals(CLUE_UP, clues.digitAt(OFF_2_1));
|
||||
|
||||
// Test isLetterAt
|
||||
Assertions.assertTrue(clues.notClue(OFF_0_0));
|
||||
Assertions.assertFalse(clues.notClue(OFF_1_2));
|
||||
Assertions.assertTrue(clues.notClue(OFF_1_2));
|
||||
Assertions.assertTrue(clues.notClue(OFF_2_3));
|
||||
Assertions.assertFalse(clues.isClue(OFF_1_1));
|
||||
|
||||
// Test isDigitAt
|
||||
Assertions.assertFalse(clues.isClue(0));
|
||||
Assertions.assertTrue(clues.isClue(OFF_1_2));
|
||||
Assertions.assertEquals(CLUE_UP, clues.digitAt(OFF_1_2));
|
||||
Assertions.assertTrue(clues.isClue(OFF_2_1));
|
||||
Assertions.assertEquals(CLUE_UP, clues.digitAt(OFF_2_1));
|
||||
Assertions.assertFalse(clues.isClue(OFF_2_3));
|
||||
Assertions.assertFalse(clues.isClue(OFF_1_1));
|
||||
|
||||
// Test isLettercell
|
||||
Assertions.assertTrue(clues.notClue(OFF_0_0)); // 'A' is letter
|
||||
Assertions.assertTrue(clues.isClue(OFF_1_2)); // digit
|
||||
Assertions.assertTrue(clues.isClue(OFF_2_1)); // digit
|
||||
Assertions.assertTrue(clues.notClue(OFF_1_1)); // '#' is lettercell
|
||||
}
|
||||
@Test
|
||||
@@ -192,7 +175,7 @@ public class MainTest {
|
||||
Assertions.assertEquals("SLEDE", Lemma.asWord(filled.clueMap()[282]));
|
||||
Assertions.assertEquals(-1L, filled.grid().grid().lo);
|
||||
Assertions.assertEquals(255L, filled.grid().grid().hi);
|
||||
|
||||
filled.grid().gridToString(mask);
|
||||
var aa = new PuzzleResult(new Clued(mask), filled).exportFormatFromFilled(1, new Rewards(1, 1, 1));
|
||||
|
||||
}
|
||||
@@ -209,7 +192,7 @@ public class MainTest {
|
||||
System.out.println("[DEBUG_LOG] Simplicity: " + res.filled().stats().simplicity);
|
||||
System.out.println("[DEBUG_LOG] ClueMap Size: " + res.filled().wordCount());
|
||||
System.out.println("[DEBUG_LOG] Grid:");
|
||||
System.out.println(res.filled().grid().renderHuman(res.mask().mask()));
|
||||
System.out.println(res.filled().grid().renderHuman(res.clues().mask()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,8 +5,10 @@ import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import puzzle.Export.Gridded;
|
||||
import puzzle.Export.IntListDTO;
|
||||
import puzzle.Export.LetterVisit.LetterAt;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static puzzle.SwedishGenerator.*;
|
||||
@@ -54,6 +56,7 @@ public class SwedishGeneratorTest {
|
||||
static final long ABC = Lemma.from(26, "ABC");
|
||||
static final long ABD = Lemma.from(27, "ABD");
|
||||
static final long AZ = Lemma.from(28, "AZ");
|
||||
static final long AB = Lemma.from(29, "AB");
|
||||
static final byte LETTER_A = ((byte) 'A') & 31;
|
||||
static final byte LETTER_B = ((byte) 'B') & 31;
|
||||
static final byte LETTER_C = ((byte) 'C') & 31;
|
||||
@@ -64,13 +67,18 @@ public class SwedishGeneratorTest {
|
||||
static final byte CLUE_UP = 2;
|
||||
static final byte CLUE_LEFT = 3;
|
||||
|
||||
static final int OFF_0_0 = Grid.offset(0, 0);
|
||||
static final int OFF_0_1 = Grid.offset(0, 1);
|
||||
static final int OFF_0_2 = Grid.offset(0, 2);
|
||||
static final int OFF_1_0 = Grid.offset(1, 0);
|
||||
static final int OFF_1_1 = Grid.offset(1, 1);
|
||||
static final int OFF_1_2 = Grid.offset(1, 2);
|
||||
static final int OFF_2_0 = Grid.offset(2, 0);
|
||||
static final int OFF_2_1 = Grid.offset(2, 1);
|
||||
static final int OFF_2_3 = Grid.offset(2, 3);
|
||||
static final int OFF_0_0 = Grid.offset(0, 0);
|
||||
static final int OFF_0_4 = Grid.offset(0, 4);
|
||||
static final int OFF_0_5 = Grid.offset(0, 5);
|
||||
static final int OFF_0_1 = Grid.offset(0, 1);
|
||||
static final int OFF_0_2 = Grid.offset(0, 2);
|
||||
static final int OFF_0_3 = Grid.offset(0, 3);
|
||||
static final int OFF_2_0 = Grid.offset(2, 0);
|
||||
static final int OFF_2_5 = Grid.offset(2, 5);
|
||||
static final int OFF_3_5 = Grid.offset(3, 5);
|
||||
static final int OFF_4_5 = Grid.offset(4, 5);
|
||||
@@ -78,21 +86,23 @@ public class SwedishGeneratorTest {
|
||||
static final byte D_BYTE_2 = CLUE_RIGHT;
|
||||
@Test
|
||||
void testPatternForSlotAllLetters() {
|
||||
var grid = createEmpty();
|
||||
grid.setLetterLo(0, LETTER_A);
|
||||
grid.setLetterLo(1, LETTER_B);
|
||||
grid.setLetterLo(2, LETTER_C);
|
||||
var key = Slot.packSlotKey(18, CLUE_RIGHT);
|
||||
var pattern = patternForSlot(grid, key, 7L, 0L);
|
||||
assertEquals(1L | (28L << 8) | (55L << 16), pattern);
|
||||
var grid = new Gridded(createEmpty());
|
||||
var key = Slot.packSlotKey(OFF_0_0, CLUE_RIGHT);
|
||||
val clues = Clues.createEmpty();
|
||||
clues.setClue(OFF_0_0, CLUE_RIGHT);
|
||||
placeWord(grid.grid(), 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));
|
||||
assertEquals(LETTER_A, map.get(OFF_0_1));
|
||||
assertEquals(LETTER_B, map.get(OFF_0_2));
|
||||
assertEquals(LETTER_C, map.get(OFF_0_3));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPatternForSlotMixed() {
|
||||
var grid = createEmpty();
|
||||
grid.setLetterLo(OFF_0_0, LETTER_A);
|
||||
grid.setLetterLo(2, LETTER_C);
|
||||
var key = Slot.packSlotKey(1, CLUE_RIGHT);
|
||||
grid.setLetterLo(OFF_2_0, LETTER_C);
|
||||
var key = Slot.packSlotKey(OFF_1_0, CLUE_RIGHT);
|
||||
var pattern = patternForSlot(grid, key, 7L, 0L);
|
||||
assertEquals(1L | (0L) | (55L << 16), pattern);
|
||||
}
|
||||
@@ -133,9 +143,11 @@ public class SwedishGeneratorTest {
|
||||
|
||||
@Test
|
||||
void testGrid() {
|
||||
var grid = createEmpty();
|
||||
grid.setLetterLo(OFF_0_0, LETTER_A);
|
||||
assertEquals(LETTER_A, grid.letter32At(OFF_0_0));
|
||||
var grid = new Gridded(createEmpty());
|
||||
grid.grid().setLetterLo(OFF_0_0, LETTER_A);
|
||||
val arr = grid.stream(Clues.createEmpty()).collect(Collectors.toMap(LetterAt::index, LetterAt::letter));
|
||||
assertEquals(1, arr.size());
|
||||
assertEquals(LETTER_A, arr.get(OFF_0_0));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -270,7 +282,7 @@ public class SwedishGeneratorTest {
|
||||
|
||||
@Test
|
||||
void testPlaceWord() {
|
||||
var grid = createEmpty();
|
||||
var grid = new Gridded(createEmpty());
|
||||
// Slot at OFF_0_0 length 3, horizontal (right)
|
||||
var key = Slot.packSlotKey(0, CLUE_RIGHT);
|
||||
var lo = (1L << OFF_0_0) | (1L << OFF_0_1) | (1L << OFF_0_2);
|
||||
@@ -278,51 +290,55 @@ public class SwedishGeneratorTest {
|
||||
var w1 = ABC;
|
||||
|
||||
// 1. Successful placement in empty grid
|
||||
assertTrue(placeWord(grid, key, lo, hi, w1));
|
||||
assertEquals(LETTER_A, grid.letter32At(OFF_0_0));
|
||||
assertEquals(LETTER_B, grid.letter32At(OFF_0_1));
|
||||
assertEquals(LETTER_C, grid.letter32At(OFF_0_2));
|
||||
assertTrue(placeWord(grid.grid(), key, lo, hi, w1));
|
||||
var map = grid.stream(Clues.createEmpty()).collect(Collectors.toMap(LetterAt::index, LetterAt::letter));
|
||||
assertEquals(3, map.size());
|
||||
assertEquals(LETTER_A, map.get(OFF_0_0));
|
||||
assertEquals(LETTER_B, map.get(OFF_0_1));
|
||||
assertEquals(LETTER_C, map.get(OFF_0_2));
|
||||
|
||||
// 2. Successful placement with partial overlap (same characters)
|
||||
assertTrue(placeWord(grid, key, lo, hi, w1));
|
||||
|
||||
assertTrue(placeWord(grid.grid(), key, lo, hi, w1));
|
||||
// 3. Conflict: place "ABD" where "ABC" is
|
||||
var w2 = ABD;
|
||||
assertFalse(placeWord(grid, key, lo, hi, w2));
|
||||
assertFalse(placeWord(grid.grid(), key, lo, hi, ABD));
|
||||
// Verify grid is unchanged (still "ABC")
|
||||
assertEquals(LETTER_A, grid.letter32At(OFF_0_0));
|
||||
assertEquals(LETTER_B, grid.letter32At(OFF_0_1));
|
||||
assertEquals(LETTER_C, grid.letter32At(OFF_0_2));
|
||||
map = grid.stream(Clues.createEmpty()).collect(Collectors.toMap(LetterAt::index, LetterAt::letter));
|
||||
assertEquals(3, map.size());
|
||||
assertEquals(LETTER_A, map.get(OFF_0_0));
|
||||
assertEquals(LETTER_B, map.get(OFF_0_1));
|
||||
assertEquals(LETTER_C, map.get(OFF_0_2));
|
||||
|
||||
// 4. Partial placement then conflict (rollback)
|
||||
grid = createEmpty();
|
||||
grid.setLetterLo(OFF_0_2, LETTER_X); // Conflict at the end
|
||||
assertFalse(placeWord(grid, key, lo, hi, w1));
|
||||
// Verify grid is still empty (except for 'X')
|
||||
assertFalse(grid.lisLetterAtLo(OFF_0_0));
|
||||
assertFalse(grid.lisLetterAtLo(OFF_0_1));
|
||||
assertEquals(LETTER_X, grid.letter32At(OFF_0_2));
|
||||
grid = new Gridded(createEmpty());
|
||||
grid.grid().setLetterLo(OFF_0_2, LETTER_X); // Conflict at the end
|
||||
assertFalse(placeWord(grid.grid(), key, lo, hi, w1));
|
||||
map = grid.stream(Clues.createEmpty()).collect(Collectors.toMap(LetterAt::index, LetterAt::letter));
|
||||
assertEquals(1, map.size());
|
||||
assertEquals(LETTER_X, map.get(OFF_0_2));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBacktrackingHelpers() {
|
||||
var grid = createEmpty();
|
||||
var grid = new Gridded(createEmpty());
|
||||
// Slot at 0,1 length 2
|
||||
var key = Slot.packSlotKey(0, CLUE_RIGHT);
|
||||
var lo = (1L << OFF_0_1) | (1L << OFF_0_2);
|
||||
var w = AZ;
|
||||
val low = grid.lo;
|
||||
val top = grid.hi;
|
||||
var placed = placeWord(grid, key, lo, 0L, w);
|
||||
val low = grid.grid().lo;
|
||||
val top = grid.grid().hi;
|
||||
var placed = placeWord(grid.grid(), key, lo, 0L, w);
|
||||
assertTrue(placed);
|
||||
assertEquals(LETTER_A, grid.letter32At(OFF_0_1));
|
||||
assertEquals(LETTER_Z, grid.letter32At(OFF_0_2));
|
||||
var map = grid.stream(Clues.createEmpty()).collect(Collectors.toMap(LetterAt::index, LetterAt::letter));
|
||||
assertEquals(2, map.size());
|
||||
assertEquals(LETTER_A, map.get(OFF_0_1));
|
||||
assertEquals(LETTER_Z, map.get(OFF_0_2));
|
||||
|
||||
grid.hi = top;
|
||||
grid.lo = low;
|
||||
//grid.undoPlace(undoBuffer[0], undoBuffer[1]);
|
||||
assertFalse(grid.lisLetterAtLo(OFF_0_1));
|
||||
assertFalse(grid.lisLetterAtLo(OFF_0_2));
|
||||
grid.grid().hi = top;
|
||||
grid.grid().lo = low;
|
||||
map = grid.stream(Clues.createEmpty()).collect(Collectors.toMap(LetterAt::index, LetterAt::letter));
|
||||
assertEquals(0, map.size());
|
||||
assertEquals(DASH, map.getOrDefault(OFF_0_1, DASH));
|
||||
assertEquals(DASH, map.getOrDefault(OFF_0_2, DASH));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user