142 lines
6.9 KiB
Java
142 lines
6.9 KiB
Java
package puzzle;
|
|
|
|
import module java.base;
|
|
import anno.GenerateShapedCopies;
|
|
import anno.Shaped;
|
|
import lombok.experimental.Delegate;
|
|
import lombok.val;
|
|
import precomp.Const9x8;
|
|
import precomp.Mask;
|
|
import puzzle.Riddle.ClueSign;
|
|
import puzzle.Riddle.ExportedPuzzle;
|
|
import puzzle.Riddle.Placed;
|
|
import puzzle.Riddle.Rewards;
|
|
import puzzle.Riddle.Signa;
|
|
import puzzle.Riddle.WordOut;
|
|
import puzzle.SwedishGenerator.FillResult;
|
|
import puzzle.SwedishGenerator.Grid;
|
|
import puzzle.SwedishGenerator.Slotinfo;
|
|
import static puzzle.Masker.Slot;
|
|
import static puzzle.SwedishGenerator.X;
|
|
import java.util.stream.Stream;
|
|
import java.util.Arrays;
|
|
|
|
@GenerateShapedCopies(
|
|
packageName = "puzzle",
|
|
className = "ExportX",
|
|
shapes = { "precomp.Const9x8", "precomp.Const3x4" }
|
|
)
|
|
public record Export() {
|
|
|
|
public record ExportTemplates(byte[] table, byte[] dashTable, byte[] wordBytes) { }
|
|
|
|
@Shaped static final byte SPACE = Const9x8.SPACE;
|
|
@Shaped static final byte LINE_BREAK = Const9x8.LINE_BREAK;
|
|
@Shaped static final byte DASH = Const9x8.DASH;
|
|
@Shaped static final int SIZE = Const9x8.SIZE;
|
|
@Shaped static final byte[] INIT_GRID_OUTPUT_ARR = Const9x8.INIT_GRID_OUTPUT_ARR;
|
|
@Shaped static final byte[] INIT_GRID_OUTPUT_DASH_ARR = Const9x8.INIT_GRID_OUTPUT_DASH_ARR;
|
|
@Shaped static final long MASK_HI = Const9x8.MASK_HI;
|
|
@Shaped static final long MASK_LO = Const9x8.MASK_LO;
|
|
@Shaped static final Mask[] CELLS = Const9x8.CELLS;
|
|
|
|
public static final ThreadLocal<ExportTemplates> BYTES = ThreadLocal.withInitial(
|
|
() -> new ExportTemplates(INIT_GRID_OUTPUT_ARR, INIT_GRID_OUTPUT_DASH_ARR, new byte[8]));
|
|
static int HI(int in) { return in | 64; }
|
|
|
|
public static String gridToString(Clues clues) {
|
|
val chars = BYTES.get().table();
|
|
var signa = new Signa(clues).map(v -> CELLS[v.cellIndex()]).toArray(Mask[]::new);
|
|
Arrays.stream(signa).forEach(v -> chars[v.place()] = v.clueChar());
|
|
val result = new String(chars);
|
|
Arrays.stream(signa).forEach(v -> chars[v.place()] = SPACE);
|
|
return result;
|
|
}
|
|
|
|
record Puzzle(@Delegate Grid grid, Mask[] cells, Clues cl)
|
|
implements Stream<Mask> {
|
|
|
|
public Puzzle {
|
|
for (var l = grid.lo & MASK_LO & ~cl.lo; l != X; l &= l - 1) set(Long.numberOfTrailingZeros(l), cells, grid.g);
|
|
for (var h = grid.hi & MASK_HI & ~cl.hi; h != X; h &= h - 1) set(HI(Long.numberOfTrailingZeros(h)), cells, grid.g);
|
|
new Signa(cl).forEach(v -> cells[v.index()] = CELLS[v.cellIndex()]);
|
|
}
|
|
static void set(int idx, Mask[] cells, byte[] read) { cells[idx] = CELLS[idx * 33 + read[idx]]; }
|
|
public Puzzle(Grid grid, Clues cl) { this(grid, new Mask[grid.g.length], cl); }
|
|
public Puzzle(Clues clues) { this(new Grid(new byte[SIZE], clues.lo, clues.hi), new Mask[SIZE], clues); }
|
|
public Puzzle(Signa clues) { this(clues.c()); }
|
|
public @Delegate Stream<Mask> stream() {
|
|
val stream = Stream.<Mask>builder();
|
|
for (var l = grid.lo & MASK_LO & ~cl.lo; l != X; l &= l - 1) stream.accept(cells[Long.numberOfTrailingZeros(l)]);
|
|
for (var h = grid.hi & MASK_HI & ~cl.hi; h != X; h &= h - 1) stream.accept(cells[HI(Long.numberOfTrailingZeros(h))]);
|
|
return stream.build();
|
|
}
|
|
public Puzzle sync() {
|
|
for (var l = grid.lo & MASK_LO & ~cl.lo; l != X; l &= l - 1) set(Long.numberOfTrailingZeros(l), cells, grid.g);
|
|
for (var h = grid.hi & MASK_HI & ~cl.hi; h != X; h &= h - 1) set(HI(Long.numberOfTrailingZeros(h)), cells, grid.g);
|
|
new Signa(cl).forEach(v -> cells[v.index()] = CELLS[v.cellIndex()]);
|
|
return this;
|
|
}
|
|
}
|
|
|
|
public record PuzzleResult(Signa clues, Puzzle puzzle, Slotinfo[] slots, FillResult filled) {
|
|
|
|
public String exportGrid(ClueSign clueChar, byte[] sb) {
|
|
Arrays.stream(slots).map(s -> puzzle.cells[Slot.clueIndex(s.key())]).forEach(c -> sb[c.place()] = clueChar.replace(c.clueChar()));
|
|
puzzle.forEach((l) -> sb[l.place()] = l.letter());
|
|
return new String(sb);
|
|
}
|
|
public String cluesGridToString() { return gridToString(clues.c()); }
|
|
public String gridRenderHuman() { return exportGrid(_ -> SPACE, INIT_GRID_OUTPUT_DASH_ARR.clone()); }
|
|
public String gridGridToString() { return exportGrid(d1 -> d1, INIT_GRID_OUTPUT_ARR.clone()); }
|
|
public ExportedPuzzle exportFormatFromFilled(Rewards rewards) {
|
|
if (slots.length == 0) {
|
|
return new ExportedPuzzle(new String(INIT_GRID_OUTPUT_DASH_ARR).split("\n"), new WordOut[0], 1, rewards);
|
|
}
|
|
|
|
var placed = Arrays.stream(slots)
|
|
.map(slot -> new Placed(slot.assign().w, slot.key(), Riddle.cellWalk(slot.key(), slot.lo(), slot.hi())
|
|
.mapToObj(idx -> puzzle.cells[idx])
|
|
.toArray(Mask[]::new)))
|
|
.toArray(Placed[]::new);
|
|
|
|
// 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 it : rc.cells()) {
|
|
minR = Math.min(minR, it.r());
|
|
minC = Math.min(minC, it.c());
|
|
maxR = Math.max(maxR, it.r());
|
|
maxC = Math.max(maxC, it.c());
|
|
}
|
|
}
|
|
|
|
// 3) render grid over cropped bounds (out-of-bounds become '#')
|
|
final int MINR = minR, MINC = minC;
|
|
int height = Math.max(0, maxR - minR + 1);
|
|
int width = Math.max(0, maxC - minC + 1);
|
|
byte[] template = new byte[height * (width + 1)];
|
|
Arrays.fill(template, DASH);
|
|
for (int i = width; i < template.length; i += width + 1) template[i] = LINE_BREAK;
|
|
|
|
puzzle.forEach(l -> l.letter(template, MINR, MINC, height, width));
|
|
var grid = new String(template).split("\n");
|
|
// 5) words output with cropped coordinates
|
|
|
|
val bytes = BYTES.get().wordBytes();
|
|
var wordsOut = Arrays.stream(placed).map(p -> new WordOut(
|
|
p.lemma(),
|
|
p.startRow() - MINR,
|
|
p.startCol() - MINC,
|
|
p.direction(),
|
|
p.arrowRow() - MINR,
|
|
p.arrowCol() - MINC,
|
|
p.isReversed(), bytes
|
|
)).toArray(WordOut[]::new);
|
|
var total = 0.0001d + Arrays.stream(wordsOut).mapToDouble(Riddle.WordOut::complex).sum();
|
|
return new ExportedPuzzle(grid, wordsOut, (int) (total / wordsOut.length), rewards);
|
|
}
|
|
}
|
|
} |