introduce bitloops

This commit is contained in:
mike
2026-01-14 23:15:52 +01:00
parent 5849f543c5
commit 04e3844732
6 changed files with 254 additions and 207 deletions

View File

@@ -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)];

View File

@@ -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));

View File

@@ -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,18 +235,17 @@ public record SwedishGenerator(Rng rng, int[] stack, Clues cache) {
return (byte) ((r << 1) | v);
}
}
public void clearClue(int idx) {
if ((idx & 64) == 0) {
long mask = ~(1L << idx);
lo &= mask;
vlo &= mask;
rlo &= mask;
} else {
long mask = ~(1L << (idx & 63));
hi &= mask;
vhi &= mask;
rhi &= mask;
}
public void clearClueLo(int idx) {
long mask = ~(1L << idx);
lo &= mask;
vlo &= mask;
rlo &= mask;
}
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; }
@@ -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,43 +387,43 @@ 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
// 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 & grid.lo;
long hitsHi = rayHi & grid.hi;
long hitsLo = rayLo & c.lo;
long hitsHi = rayHi & c.hi;
// slice ray to stop before first clue, depending on direction monotonicity
// right/down => increasing indices; up/left => decreasing indices
if (hitsHi != X) {
int msb = 63 - Long.numberOfLeadingZeros(hitsHi);
long stop = 1L << msb;
rayHi &= ~((stop << 1) - 1); // keep bits > stop
rayLo = 0; // lo indices are below stop
} else if (hitsLo != X) {
int msb = 63 - Long.numberOfLeadingZeros(hitsLo);
long stop = 1L << msb;
rayLo &= ~((stop << 1) - 1);
}
if (Slot.increasing(key)) {
// first clue is lowest index among hits (lo first, then hi)
if (hitsLo != X) {
long stop = 1L << Long.numberOfTrailingZeros(hitsLo);
rayLo &= (stop - 1);
rayHi = 0; // any hi is beyond the stop
} else if (hitsHi != X) {
long stop = 1L << Long.numberOfTrailingZeros(hitsHi);
// keep all lo (lo indices are < any hi index), but cut hi below stop
rayHi &= (stop - 1);
}
} else {
// first clue is highest index among hits (hi first, then lo)
if (hitsHi != X) {
int msb = 63 - Long.numberOfLeadingZeros(hitsHi);
long stop = 1L << msb;
rayHi &= ~((stop << 1) - 1); // keep bits > stop
rayLo = 0; // lo indices are below stop
} else if (hitsLo != X) {
int msb = 63 - Long.numberOfLeadingZeros(hitsLo);
long stop = 1L << msb;
rayLo &= ~((stop << 1) - 1);
}
visitor.visit(key, rayLo, rayHi);
}
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;