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.experimental.Accessors;
import lombok.experimental.Delegate;
import puzzle.Export.Gridded.Replacar.Cell;
import puzzle.SwedishGenerator.Dict;
import puzzle.SwedishGenerator.FillResult;
import puzzle.SwedishGenerator.Grid;
@@ -98,17 +99,26 @@ public record Export() {
return sb.toString();
}
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];
for (var r = 0; r < R; r++) {
var sb = new StringBuilder(C);
for (var c = 0; c < C; c++) {
if (grid.isClue(Grid.offset(r, c))) {
sb.append(clueChar);
var offset = Grid.offset(r, c);
if (grid.isClue(offset)) {
sb.append(clueChar.replace(new Cell(grid, offset, grid.byteAt(offset))));
} 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();
@@ -149,10 +159,15 @@ public record Export() {
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';
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) { }
@@ -168,36 +183,19 @@ public record Export() {
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) {
var d = s.dir();
var cells = s.walk().toArray();
char direction;
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;
}
char[] DIRECTION = { '\0', Placed.VERTICAL, Placed.HORIZONTAL, Placed.VERTICAL, Placed.HORIZONTAL, Placed.VERTICAL };
return new Placed(
lemma,
startRow,
startCol,
direction,
s.clueR(),
s.clueC(),
cells,
!s.increasing()
DIRECTION[d],
s.key(),
cells
);
}
public ExportedPuzzle exportFormatFromFilled(int difficulty, Rewards rewards) {
@@ -207,14 +205,15 @@ public record Export() {
g.grid().forEachSlot((int key, long lo, long hi) -> {
var word = clueMap.get(key);
if (word != null) {
var p = extractPlacedFromSlot(Slot.from(key, lo, hi), word);
if (p != null) placed.add(p);
placed.add(extractPlacedFromSlot(Slot.from(key, lo, hi), word));
} else {
System.err.println("Could not find clue for slot: " + key);
}
});
// If nothing placed: return full grid mapped to letters/# only
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
@@ -229,20 +228,18 @@ public record Export() {
maxR = Math.max(maxR, Grid.r(c));
maxC = Math.max(maxC, Grid.c(c));
}
minR = Math.min(minR, rc.arrowRow);
minC = Math.min(minC, rc.arrowCol);
maxR = Math.max(maxR, rc.arrowRow);
maxC = Math.max(maxC, rc.arrowCol);
minR = Math.min(minR, rc.arrowRow());
minC = Math.min(minC, rc.arrowCol());
maxR = Math.max(maxR, rc.arrowRow());
maxC = Math.max(maxC, rc.arrowCol());
}
// 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 c : p.cells) {
int rr = Grid.r(c), cc = Grid.c(c);
int idx = Grid.offset(rr, cc);
if (inBounds(rr, cc) && g.isLetterSet(idx)) {
letterAt.put(Bit.pack(rr, cc), (char) g.byteAt(idx));
if (inBounds(c) && g.notClue(c)) {
letterAt.put(c, (char) g.byteAt(c));
}
}
}
@@ -251,7 +248,7 @@ public record Export() {
var gridv2 = new String[Math.max(0, maxR - minR + 1)];
for (int r = minR, i = 0; r <= maxR; r++, i++) {
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();
}
@@ -259,12 +256,12 @@ public record Export() {
int MIN_R = minR, MIN_C = minC;
var wordsOut = placed.stream().map(p -> new WordOut(
p.lemma,
p.startRow - MIN_R,
p.startCol - MIN_C,
p.startRow() - MIN_R,
p.startCol() - MIN_C,
p.direction,
p.arrowRow - MIN_R,
p.arrowCol - MIN_C,
p.isReversed
p.arrowRow() - MIN_R,
p.arrowCol() - MIN_C,
p.isReversed()
)).toArray(WordOut[]::new);
return new ExportedPuzzle(gridv2, wordsOut, difficulty, rewards);
}