Gather data
This commit is contained in:
222
src/main/java/puzzle/Export.java
Normal file
222
src/main/java/puzzle/Export.java
Normal file
@@ -0,0 +1,222 @@
|
||||
package puzzle;
|
||||
|
||||
import lombok.experimental.Delegate;
|
||||
import puzzle.SwedishGenerator.Dict;
|
||||
import puzzle.SwedishGenerator.FillResult;
|
||||
import puzzle.SwedishGenerator.Grid;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import static puzzle.SwedishGenerator.R;
|
||||
import static puzzle.SwedishGenerator.Lemma;
|
||||
import static puzzle.SwedishGenerator.Slot;
|
||||
import static puzzle.SwedishGenerator.C;
|
||||
|
||||
/**
|
||||
* ExportFormat.java
|
||||
*
|
||||
* Direct port of export_format.js:
|
||||
* - scans filled grid for clue digits '1'..'4'
|
||||
* - extracts placed words in canonical direction (horizontal=right, vertical=down)
|
||||
* - crops to bounding box (words + arrow cells) with 1-cell margin
|
||||
* - outputs gridv2 + words[] (+ difficulty, rewards)
|
||||
*/
|
||||
public record Export() {
|
||||
|
||||
record Strings() {
|
||||
|
||||
static String padRight(String s, int n) {
|
||||
if (s.length() >= n) return s;
|
||||
return s + " ".repeat(n - s.length());
|
||||
}
|
||||
}
|
||||
|
||||
record Gridded(@Delegate Grid grid) {
|
||||
|
||||
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++) sb.append((char) grid.byteAt(Grid.offset(r, c)));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
public String renderHuman() {
|
||||
var sb = new StringBuilder();
|
||||
for (var r = 0; r < R; r++) {
|
||||
if (r > 0) sb.append('\n');
|
||||
for (var c = 0; c < C; c++) {
|
||||
sb.append(grid.isDigitAt(r, c) ? ' ' : (char) grid.byteAt(Grid.offset(r, c)));
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
static class Bit {
|
||||
|
||||
static long pack(int r, int c) { return (((long) r) << 32) ^ (c & 0xFFFFFFFFL); }
|
||||
long l1, l2;
|
||||
public Bit() {
|
||||
l1 = 0L;
|
||||
l2 = 0L;
|
||||
}
|
||||
public boolean get(int bitIndex) {
|
||||
if (bitIndex < 64) return (l1 & (1L << bitIndex)) != 0L;
|
||||
return (l2 & (1L << (bitIndex & 63))) != 0L;
|
||||
}
|
||||
public void set(int bitIndex) {
|
||||
if (bitIndex < 64) this.l1 |= 1L << bitIndex;
|
||||
else this.l2 |= 1L << (bitIndex & 63);
|
||||
}
|
||||
public void clear() {
|
||||
l1 = 0L;
|
||||
l2 = 0L;
|
||||
}
|
||||
}
|
||||
|
||||
record Bit1029(long[] bits) {
|
||||
|
||||
public Bit1029() { this(new long[1029]); }
|
||||
static int wordIndex(int bitIndex) { return bitIndex >> 6; }
|
||||
public boolean get(int bitIndex) { return (this.bits[wordIndex(bitIndex)] & 1L << bitIndex) != 0L; }
|
||||
public void set(int bitIndex) { bits[wordIndex(bitIndex)] |= 1L << bitIndex; }
|
||||
public void clear(int bitIndex) { this.bits[wordIndex(bitIndex)] &= ~(1L << bitIndex); }
|
||||
public void clear() { Arrays.fill(bits, 0L); }
|
||||
}
|
||||
|
||||
private record Placed(Lemma lemma, int startRow, int startCol, String direction, int arrowRow, int arrowCol, int[] cells, boolean isReversed) {
|
||||
|
||||
static final String HORIZONTAL = "h";
|
||||
static final String VERTICAL = "v";
|
||||
}
|
||||
|
||||
public record Rewards(int coins, int stars, int hints) { }
|
||||
|
||||
public record WordOut(Lemma lemma, int startRow, int startCol, String direction, int arrowRow, int arrowCol, boolean isReversed, int complex) {
|
||||
|
||||
public String word() { return new String(lemma().word(), java.nio.charset.StandardCharsets.US_ASCII); }
|
||||
public String[] clue() { return lemma.clue(); }
|
||||
}
|
||||
|
||||
public record ExportedPuzzle(List<String> gridv2, WordOut[] words, int difficulty, Rewards rewards) { }
|
||||
|
||||
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; }
|
||||
Placed extractPlacedFromSlot(Slot s, Lemma lemma) {
|
||||
var d = s.dir();
|
||||
|
||||
var cells = new int[s.len()];
|
||||
for (int i = 0, len = s.len(); i < len; i++) cells[i] = s.pos(i);
|
||||
|
||||
String direction;
|
||||
var isReversed = false;
|
||||
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;
|
||||
isReversed = true;
|
||||
} else if (d == 1) { // up -> vertical (REVERSED)
|
||||
direction = Placed.VERTICAL;
|
||||
isReversed = true;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new Placed(
|
||||
lemma,
|
||||
startRow,
|
||||
startCol,
|
||||
direction,
|
||||
s.clueR(),
|
||||
s.clueC(),
|
||||
cells,
|
||||
isReversed
|
||||
);
|
||||
}
|
||||
public ExportedPuzzle exportFormatFromFilled(int difficulty, Rewards rewards) {
|
||||
var g = filled().grid();
|
||||
var placed = new ArrayList<Placed>();
|
||||
var clueMap = filled().clueMap();
|
||||
g.grid().forEachSlot((int key, long packedPos, int len) -> {
|
||||
var word = clueMap.get(key);
|
||||
if (word != null) {
|
||||
var p = extractPlacedFromSlot(Slot.from(key, packedPos, len), word);
|
||||
if (p != null) placed.add(p);
|
||||
}
|
||||
});
|
||||
|
||||
// If nothing placed: return full grid mapped to letters/# only
|
||||
if (placed.isEmpty()) {
|
||||
var gridv2 = new ArrayList<String>(R);
|
||||
for (var r = 0; r < R; r++) {
|
||||
var sb = new StringBuilder(C);
|
||||
for (var c = 0; c < C; c++) {
|
||||
sb.append(g.isLetterAt(r, c) ? (char) g.byteAt(Grid.offset(r, c)) : '#');
|
||||
}
|
||||
gridv2.add(sb.toString());
|
||||
}
|
||||
return new ExportedPuzzle(gridv2, new WordOut[0], difficulty, rewards);
|
||||
}
|
||||
|
||||
// 2) bounding box around all word cells + arrow cells, with 1-cell margin
|
||||
|
||||
int minR = Integer.MAX_VALUE, minC = Integer.MAX_VALUE;
|
||||
int maxR = Integer.MIN_VALUE, maxC = Integer.MIN_VALUE;
|
||||
|
||||
for (var rc : placed) {
|
||||
for (var c : rc.cells) {
|
||||
minR = Math.min(minR, Grid.r(c));
|
||||
minC = Math.min(minC, Grid.c(c));
|
||||
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);
|
||||
}
|
||||
|
||||
// 3) map of only used letter cells (everything else becomes '#')
|
||||
var letterAt = new HashMap<Long, Character>();
|
||||
for (var p : placed) {
|
||||
for (var c : p.cells) {
|
||||
int rr = Grid.r(c), cc = Grid.c(c);
|
||||
if (inBounds(rr, cc) && g.isLetterAt(rr, cc)) {
|
||||
letterAt.put(Bit.pack(rr, cc), (char) g.byteAt(Grid.offset(rr, cc)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 4) render gridv2 over cropped bounds (out-of-bounds become '#')
|
||||
var gridv2 = new ArrayList<String>(Math.max(0, maxR - minR + 1));
|
||||
for (var r = minR; r <= maxR; r++) {
|
||||
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), '#'));
|
||||
}
|
||||
gridv2.add(row.toString());
|
||||
}
|
||||
|
||||
// 5) words output with cropped coordinates
|
||||
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.direction,
|
||||
p.arrowRow - MIN_R,
|
||||
p.arrowCol - MIN_C,
|
||||
p.isReversed,
|
||||
p.lemma.simpel()
|
||||
)).toArray(WordOut[]::new);
|
||||
return new ExportedPuzzle(gridv2, wordsOut, difficulty, rewards);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user