Gather data
This commit is contained in:
205
src/main/java/puzzle/ExportFormat.java
Normal file
205
src/main/java/puzzle/ExportFormat.java
Normal file
@@ -0,0 +1,205 @@
|
||||
package puzzle;
|
||||
|
||||
import java.util.*;
|
||||
import static puzzle.SwedishGenerator.*;
|
||||
|
||||
/**
|
||||
* 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 final class ExportFormat {
|
||||
|
||||
private ExportFormat() { }
|
||||
|
||||
private static boolean isLetter(char ch) { return ch >= 'A' && ch <= 'Z'; }
|
||||
|
||||
private static boolean inBounds(int H, int W, int r, int c) {
|
||||
return r >= 0 && r < H && c >= 0 && c < W;
|
||||
}
|
||||
|
||||
// ---------- Public API ----------
|
||||
|
||||
public static ExportedPuzzle exportFormatFromFilled(PuzzleResult puz, int difficulty, Rewards rewards) {
|
||||
Objects.requireNonNull(puz, "puz");
|
||||
var g = puz.filled().grid;
|
||||
var H = g.length;
|
||||
var W = g[0].length;
|
||||
|
||||
// 1) extract "placed" list from all clue digits in the filled grid
|
||||
List<Placed> placed = new ArrayList<>();
|
||||
var allSlots = extractSlots(g);
|
||||
var clueMap = puz.filled().clueMap;
|
||||
|
||||
for (var s : allSlots) {
|
||||
var word = clueMap.get(s.key());
|
||||
if (word == null) continue;
|
||||
|
||||
var p = extractPlacedFromSlot(s, word);
|
||||
if (p == null) continue;
|
||||
placed.add(p);
|
||||
}
|
||||
|
||||
// If nothing placed: return full grid mapped to letters/# only
|
||||
if (placed.isEmpty()) {
|
||||
List<String> gridv2 = new ArrayList<>(H);
|
||||
for (var chars : g) {
|
||||
var sb = new StringBuilder(W);
|
||||
for (var c = 0; c < W; c++) {
|
||||
var ch = chars[c];
|
||||
sb.append(isLetter(ch) ? ch : '#');
|
||||
}
|
||||
gridv2.add(sb.toString());
|
||||
}
|
||||
return new ExportedPuzzle(gridv2, List.of(), difficulty, rewards);
|
||||
}
|
||||
|
||||
// 2) bounding box around all word cells + arrow cells, with 1-cell margin
|
||||
List<int[]> allCells = new ArrayList<>();
|
||||
for (var p : placed) {
|
||||
allCells.addAll(p.cells);
|
||||
allCells.add(p.arrow);
|
||||
}
|
||||
|
||||
int minR = Integer.MAX_VALUE, minC = Integer.MAX_VALUE;
|
||||
int maxR = Integer.MIN_VALUE, maxC = Integer.MIN_VALUE;
|
||||
|
||||
for (var rc : allCells) {
|
||||
int rr = rc[0], cc = rc[1];
|
||||
minR = Math.min(minR, rr);
|
||||
minC = Math.min(minC, cc);
|
||||
maxR = Math.max(maxR, rr);
|
||||
maxC = Math.max(maxC, cc);
|
||||
}
|
||||
|
||||
// 3) map of only used letter cells (everything else becomes '#')
|
||||
Map<Long, Character> letterAt = new HashMap<>();
|
||||
for (var p : placed) {
|
||||
for (var rc : p.cells) {
|
||||
int rr = rc[0], cc = rc[1];
|
||||
if (inBounds(H, W, rr, cc) && isLetter(g[rr][cc])) {
|
||||
letterAt.put(pack(rr, cc), g[rr][cc]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 4) render gridv2 over cropped bounds (out-of-bounds become '#')
|
||||
List<String> gridv2 = new ArrayList<>(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++) {
|
||||
var ch = letterAt.get(pack(r, c));
|
||||
row.append(ch != null ? ch : '#');
|
||||
}
|
||||
gridv2.add(row.toString());
|
||||
}
|
||||
|
||||
// 5) words output with cropped coordinates
|
||||
List<WordOut> wordsOut = new ArrayList<>(placed.size());
|
||||
for (var p : placed) {
|
||||
wordsOut.add(new WordOut(
|
||||
p.word,
|
||||
p.clue, // placeholder = word (same as JS)
|
||||
p.startRow - minR,
|
||||
p.startCol - minC,
|
||||
p.direction,
|
||||
p.word, // answer
|
||||
p.arrowRow - minR,
|
||||
p.arrowCol - minC,
|
||||
p.isReversed,
|
||||
puz.dict().words().get(p.word).cross()
|
||||
));
|
||||
}
|
||||
|
||||
return new ExportedPuzzle(gridv2, wordsOut, difficulty, rewards);
|
||||
}
|
||||
static final String HORIZONTAL = "h", VERTICAL = "v";
|
||||
/**
|
||||
* Convert a generator Slot + assigned word into a Placed object for export.
|
||||
*/
|
||||
private static Placed extractPlacedFromSlot(Slot s, String word) {
|
||||
int r = s.clueR();
|
||||
int c = s.clueC();
|
||||
char d = s.dir();
|
||||
|
||||
List<int[]> cells = new ArrayList<>();
|
||||
for (int i = 0; i < s.len(); i++) {
|
||||
cells.add(new int[]{ s.rs()[i], s.cs()[i] });
|
||||
}
|
||||
|
||||
// Canonicalize: always output right/down
|
||||
int startRow, startCol, arrowRow, arrowCol;
|
||||
String direction;
|
||||
boolean isReversed = false;
|
||||
|
||||
if (d == '2') { // right -> horizontal
|
||||
direction = HORIZONTAL;
|
||||
startRow = cells.get(0)[0];
|
||||
startCol = cells.get(0)[1];
|
||||
arrowRow = r;
|
||||
arrowCol = c;
|
||||
} else if (d == '3' || d == '5') { // down or down-bent -> vertical
|
||||
direction = VERTICAL;
|
||||
startRow = cells.get(0)[0];
|
||||
startCol = cells.get(0)[1];
|
||||
arrowRow = r;
|
||||
arrowCol = c;
|
||||
} else if (d == '4') { // left -> horizontal (REVERSED)
|
||||
direction = HORIZONTAL;
|
||||
isReversed = true;
|
||||
startRow = cells.get(0)[0];
|
||||
startCol = cells.get(0)[1];
|
||||
arrowRow = r;
|
||||
arrowCol = c;
|
||||
} else if (d == '1') { // up -> vertical (REVERSED)
|
||||
direction = VERTICAL;
|
||||
isReversed = true;
|
||||
startRow = cells.get(0)[0];
|
||||
startCol = cells.get(0)[1];
|
||||
arrowRow = r;
|
||||
arrowCol = c;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new Placed(
|
||||
word,
|
||||
word, // clue placeholder
|
||||
startRow,
|
||||
startCol,
|
||||
direction,
|
||||
word, // answer
|
||||
arrowRow,
|
||||
arrowCol,
|
||||
cells,
|
||||
new int[]{ arrowRow, arrowCol },
|
||||
isReversed
|
||||
);
|
||||
}
|
||||
|
||||
// pack (r,c) into one long key (handles negatives too)
|
||||
private static long pack(int r, int c) {
|
||||
return (((long) r) << 32) ^ (c & 0xFFFFFFFFL);
|
||||
}
|
||||
|
||||
// ---------- Data models ----------
|
||||
|
||||
/**
|
||||
* @param direction "horizontal" | "vertical"
|
||||
* @param cells word cells
|
||||
* @param arrow [arrowRow, arrowCol] */
|
||||
private record Placed(String word, String clue, int startRow, int startCol, String direction, String answer, int arrowRow, int arrowCol, List<int[]> cells, int[] arrow,
|
||||
boolean isReversed) { }
|
||||
|
||||
public record Rewards(int coins, int stars, int hints) { }
|
||||
|
||||
/**
|
||||
* @param direction "horizontal" | "vertical" */
|
||||
public record WordOut(String word, String clue, int startRow, int startCol, String direction, String answer, int arrowRow, int arrowCol, boolean isReversed, int complex) { }
|
||||
|
||||
public record ExportedPuzzle(List<String> gridv2, List<WordOut> words, int difficulty, Rewards rewards) { }
|
||||
}
|
||||
Reference in New Issue
Block a user