introduce bitloops

This commit is contained in:
mike
2026-01-12 21:12:38 +01:00
parent b3b1921414
commit 4784fa7180
3 changed files with 52 additions and 55 deletions

View File

@@ -3,6 +3,7 @@ package puzzle;
import lombok.Getter; import lombok.Getter;
import lombok.experimental.Accessors; import lombok.experimental.Accessors;
import lombok.experimental.Delegate; import lombok.experimental.Delegate;
import puzzle.Export.Gridded.Replacar.Cell;
import puzzle.SwedishGenerator.Dict; import puzzle.SwedishGenerator.Dict;
import puzzle.SwedishGenerator.FillResult; import puzzle.SwedishGenerator.FillResult;
import puzzle.SwedishGenerator.Grid; import puzzle.SwedishGenerator.Grid;
@@ -98,17 +99,26 @@ public record Export() {
return sb.toString(); return sb.toString();
} }
public String renderHuman() { public String renderHuman() {
return String.join("\n", exportGrid(' ', '#')); return String.join("\n", exportGrid(_ -> ' ', '#'));
} }
public String[] exportGrid(char clueChar, char emptyFallback) { public boolean notClue(int c) { return grid.notClue(c); }
@FunctionalInterface
interface Replacar {
record Cell(Grid grid, int index, byte data) { }
char replace(Cell c);
}
public String[] exportGrid(Replacar clueChar, char emptyFallback) {
var out = new String[R]; var out = new String[R];
for (var r = 0; r < R; r++) { for (var r = 0; r < R; r++) {
var sb = new StringBuilder(C); var sb = new StringBuilder(C);
for (var c = 0; c < C; c++) { for (var c = 0; c < C; c++) {
if (grid.isClue(Grid.offset(r, c))) { var offset = Grid.offset(r, c);
sb.append(clueChar); if (grid.isClue(offset)) {
sb.append(clueChar.replace(new Cell(grid, offset, grid.byteAt(offset))));
} else { } else {
sb.append(NOT_CLUE_NOT_LETTER_TO(grid.byteAt(Grid.offset(r, c)), emptyFallback)); sb.append(NOT_CLUE_NOT_LETTER_TO(grid.byteAt(offset), emptyFallback));
} }
} }
out[r] = sb.toString(); out[r] = sb.toString();
@@ -149,10 +159,15 @@ public record Export() {
public void clear() { Arrays.fill(bits, 0L); } public void clear() { Arrays.fill(bits, 0L); }
} }
record Placed(long lemma, int startRow, int startCol, char direction, int arrowRow, int arrowCol, int[] cells, boolean isReversed) { record Placed(long lemma, char direction, int slotKey, int[] cells) {
public static final char HORIZONTAL = 'h'; public static final char HORIZONTAL = 'h';
static final char VERTICAL = 'v'; static final char VERTICAL = 'v';
public int arrowCol() { return Grid.c(Slot.clueIndex(slotKey)); }
public int arrowRow() { return Grid.r(Slot.clueIndex(slotKey)); }
public int startRow() { return Grid.r(cells[0]); }
public int startCol() { return Grid.c(cells[0]); }
public boolean isReversed() { return !Slot.increasing(slotKey); }
} }
public record Rewards(int coins, int stars, int hints) { } public record Rewards(int coins, int stars, int hints) { }
@@ -168,36 +183,19 @@ 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 r, int c) { return r >= 0 && r < SwedishGenerator.R && c >= 0 && c < SwedishGenerator.C; } boolean inBounds(int idx) { return idx >= 0 && idx < SwedishGenerator.SIZE; }
Placed extractPlacedFromSlot(Slot s, long lemma) { Placed extractPlacedFromSlot(Slot s, long lemma) {
var d = s.dir(); var d = s.dir();
var cells = s.walk().toArray(); var cells = s.walk().toArray();
char direction; char[] DIRECTION = { '\0', Placed.VERTICAL, Placed.HORIZONTAL, Placed.VERTICAL, Placed.HORIZONTAL, Placed.VERTICAL };
var startRow = Grid.r(cells[0]);
var startCol = Grid.c(cells[0]);
if (d == 2) { // right -> horizontal
direction = Placed.HORIZONTAL;
} else if (d == 3 || d == 5) { // down or down-bent -> vertical
direction = Placed.VERTICAL;
} else if (d == 4) { // left -> horizontal (REVERSED)
direction = Placed.HORIZONTAL;
} else if (d == 1) { // up -> vertical (REVERSED)
direction = Placed.VERTICAL;
} else {
return null;
}
return new Placed( return new Placed(
lemma, lemma,
startRow, DIRECTION[d],
startCol, s.key(),
direction, cells
s.clueR(),
s.clueC(),
cells,
!s.increasing()
); );
} }
public ExportedPuzzle exportFormatFromFilled(int difficulty, Rewards rewards) { public ExportedPuzzle exportFormatFromFilled(int difficulty, Rewards rewards) {
@@ -207,14 +205,15 @@ public record Export() {
g.grid().forEachSlot((int key, long lo, long hi) -> { g.grid().forEachSlot((int key, long lo, long hi) -> {
var word = clueMap.get(key); var word = clueMap.get(key);
if (word != null) { if (word != null) {
var p = extractPlacedFromSlot(Slot.from(key, lo, hi), word); placed.add(extractPlacedFromSlot(Slot.from(key, lo, hi), word));
if (p != null) placed.add(p); } else {
System.err.println("Could not find clue for slot: " + key);
} }
}); });
// 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('#', '#'), new WordOut[0], difficulty, rewards); return new ExportedPuzzle(g.exportGrid(_ -> '#', '#'), 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
@@ -229,20 +228,18 @@ public record Export() {
maxR = Math.max(maxR, Grid.r(c)); maxR = Math.max(maxR, Grid.r(c));
maxC = Math.max(maxC, Grid.c(c)); maxC = Math.max(maxC, Grid.c(c));
} }
minR = Math.min(minR, rc.arrowRow); minR = Math.min(minR, rc.arrowRow());
minC = Math.min(minC, rc.arrowCol); minC = Math.min(minC, rc.arrowCol());
maxR = Math.max(maxR, rc.arrowRow); maxR = Math.max(maxR, rc.arrowRow());
maxC = Math.max(maxC, rc.arrowCol); maxC = Math.max(maxC, rc.arrowCol());
} }
// 3) map of only used letter cells (everything else becomes '#') // 3) map of only used letter cells (everything else becomes '#')
var letterAt = new HashMap<Long, Character>(); var letterAt = new HashMap<Integer, Character>();
for (var p : placed) { for (var p : placed) {
for (var c : p.cells) { for (var c : p.cells) {
int rr = Grid.r(c), cc = Grid.c(c); if (inBounds(c) && g.notClue(c)) {
int idx = Grid.offset(rr, cc); letterAt.put(c, (char) g.byteAt(c));
if (inBounds(rr, cc) && g.isLetterSet(idx)) {
letterAt.put(Bit.pack(rr, cc), (char) g.byteAt(idx));
} }
} }
} }
@@ -251,7 +248,7 @@ public record Export() {
var gridv2 = new String[Math.max(0, maxR - minR + 1)]; var gridv2 = new String[Math.max(0, maxR - minR + 1)];
for (int r = minR, i = 0; r <= maxR; r++, i++) { for (int r = minR, i = 0; r <= maxR; r++, i++) {
var row = new StringBuilder(Math.max(0, maxC - minC + 1)); var row = new StringBuilder(Math.max(0, maxC - minC + 1));
for (var c = minC; c <= maxC; c++) row.append(letterAt.getOrDefault(Bit.pack(r, c), '#')); for (var c = minC; c <= maxC; c++) row.append(letterAt.getOrDefault(Grid.offset(r, c), '#'));
gridv2[i] = row.toString(); gridv2[i] = row.toString();
} }
@@ -259,12 +256,12 @@ public record Export() {
int MIN_R = minR, MIN_C = minC; int MIN_R = minR, MIN_C = minC;
var wordsOut = placed.stream().map(p -> new WordOut( var wordsOut = placed.stream().map(p -> new WordOut(
p.lemma, p.lemma,
p.startRow - MIN_R, p.startRow() - MIN_R,
p.startCol - MIN_C, p.startCol() - MIN_C,
p.direction, p.direction,
p.arrowRow - MIN_R, p.arrowRow() - MIN_R,
p.arrowCol - MIN_C, p.arrowCol() - MIN_C,
p.isReversed p.isReversed()
)).toArray(WordOut[]::new); )).toArray(WordOut[]::new);
return new ExportedPuzzle(gridv2, wordsOut, difficulty, rewards); return new ExportedPuzzle(gridv2, wordsOut, difficulty, rewards);
} }

View File

@@ -119,7 +119,7 @@ public record SwedishGenerator(Rng rng) {
final int[] stack = new int[SIZE]; final int[] stack = new int[SIZE];
final Bit seen = new Bit(); final Bit seen = new Bit();
long pattern; long pattern;
final long[] undo = new long[4096]; final long[] undo = new long[128];
final long[] bitset = new long[2500]; final long[] bitset = new long[2500];
void setPattern(long p) { this.pattern = p; } void setPattern(long p) { this.pattern = p; }
@@ -207,9 +207,6 @@ public record SwedishGenerator(Rng rng) {
return true; return true;
} }
static boolean isLetter(byte b) { return (b & B64) != B0; }
public boolean isLetterSet(int idx) { return isLetter(g[idx]); }
public double similarity(Grid b) { public double similarity(Grid b) {
var same = 0; var same = 0;
for (int i = 0; i < SIZE; i++) if (g[i] == b.g[i]) same++; for (int i = 0; i < SIZE; i++) if (g[i] == b.g[i]) same++;
@@ -318,7 +315,8 @@ public record SwedishGenerator(Rng rng) {
public int len() { return Long.bitCount(lo) + Long.bitCount(hi); } public int len() { return Long.bitCount(lo) + Long.bitCount(hi); }
public int clueR() { return Grid.r((key >>> BIT_FOR_DIR)); } public int clueR() { return Grid.r((key >>> BIT_FOR_DIR)); }
public int clueIndex() { return key >>> BIT_FOR_DIR; } public int clueIndex() { return clueIndex(key); }
public static int clueIndex(int key) { return key >>> BIT_FOR_DIR; }
public int clueC() { return Grid.c((key >>> BIT_FOR_DIR)); } public int clueC() { return Grid.c((key >>> BIT_FOR_DIR)); }
public int dir() { return key & 7; } public int dir() { return key & 7; }
public boolean horiz() { return horiz(key); } public boolean horiz() { return horiz(key); }

View File

@@ -4,6 +4,7 @@ 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.PuzzleResult; import puzzle.Export.PuzzleResult;
import puzzle.Export.Rewards;
import puzzle.SwedishGenerator.Rng; import puzzle.SwedishGenerator.Rng;
import puzzle.SwedishGenerator.Slot; import puzzle.SwedishGenerator.Slot;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
@@ -81,10 +82,10 @@ public class MainTest {
Assertions.assertEquals(DASH, grid.byteAt(Grid.offset(1, 1))); Assertions.assertEquals(DASH, grid.byteAt(Grid.offset(1, 1)));
// Test isLetterAt // Test isLetterAt
Assertions.assertTrue(grid.isLetterSet(Grid.offset(0, 0))); Assertions.assertTrue(grid.notClue(Grid.offset(0, 0)));
Assertions.assertFalse(grid.isLetterSet(Grid.offset(1, 2))); Assertions.assertFalse(grid.notClue(Grid.offset(1, 2)));
Assertions.assertTrue(grid.isLetterSet(Grid.offset(2, 3))); Assertions.assertTrue(grid.notClue(Grid.offset(2, 3)));
Assertions.assertFalse(grid.isLetterSet(Grid.offset(1, 1))); Assertions.assertFalse(grid.isClue(Grid.offset(1, 1)));
// Test isDigitAt // Test isDigitAt
Assertions.assertFalse(grid.isClue(0)); Assertions.assertFalse(grid.isClue(0));
@@ -150,6 +151,7 @@ public class MainTest {
System.out.println("[DEBUG_LOG] ClueMap Size: " + res.filled().clueMap().size()); System.out.println("[DEBUG_LOG] ClueMap Size: " + res.filled().clueMap().size());
System.out.println("[DEBUG_LOG] Grid:"); System.out.println("[DEBUG_LOG] Grid:");
System.out.println(res.filled().grid().renderHuman()); System.out.println(res.filled().grid().renderHuman());
var aa = res.exportFormatFromFilled(1, new Rewards(1, 1, 1));
break; break;
} }
} }