introduce bitloops
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
package puzzle;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
@@ -13,13 +14,14 @@ import puzzle.SwedishGenerator.DictEntry;
|
||||
import puzzle.SwedishGenerator.FillResult;
|
||||
import puzzle.SwedishGenerator.Grid;
|
||||
import puzzle.SwedishGenerator.Slotinfo;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.function.IntSupplier;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
import java.util.stream.Stream;
|
||||
import static puzzle.Export.Clue.DOWN;
|
||||
import static puzzle.Export.Clue.RIGHT;
|
||||
import static puzzle.Masker.Clues.createEmpty;
|
||||
import static puzzle.SwedishGenerator.R;
|
||||
import static puzzle.SwedishGenerator.Lemma;
|
||||
import static puzzle.Masker.Slot;
|
||||
@@ -37,42 +39,78 @@ import static puzzle.SwedishGenerator.X;
|
||||
*/
|
||||
public record Export() {
|
||||
|
||||
record Strings() {
|
||||
|
||||
static String padRight(String s, int n) {
|
||||
if (s.length() >= n) return s;
|
||||
return s + " ".repeat(n - s.length());
|
||||
}
|
||||
public static final ThreadLocal<byte[]> BYTES = ThreadLocal.withInitial(() -> new byte[SwedishGenerator.MAX_WORD_LENGTH]);
|
||||
static final byte CLUE_DOWN = 0;
|
||||
static final byte CLUE_RIGHT = 1;
|
||||
static final byte CLUE_UP = 2;
|
||||
static final byte CLUE_LEFT = 3;
|
||||
static final byte CLUE_LEFT_TOP = 4;
|
||||
static int HI(int in) { return in | 64; }
|
||||
static char LETTER(int in) { return (char) (in | 64); }
|
||||
static char CLUE_CHAR(int s) { return (char) (s | 48); }
|
||||
static int INDEX_ROW(int idx) { return idx & 7; }
|
||||
static int INDEX_COL(int idx) { return idx >>> 3; }
|
||||
static int INDEX(int r, int cols, int c) { return r * cols + c; }
|
||||
@AllArgsConstructor
|
||||
enum Clue {
|
||||
DOWN(CLUE_DOWN),
|
||||
RIGHT(CLUE_RIGHT),
|
||||
UP(CLUE_UP),
|
||||
LEFT(CLUE_LEFT);
|
||||
final byte dir;
|
||||
}
|
||||
|
||||
static final String INIT = IntStream.range(0, R).mapToObj(l_ -> " ").collect(Collectors.joining("\n"));
|
||||
record Strings() {
|
||||
|
||||
static String padRight(String s, int n) { return s.length() >= n ? s : s + " ".repeat(n - s.length()); }
|
||||
}
|
||||
|
||||
static final String INIT = IntStream.range(0, Config.PUZZLE_ROWS).mapToObj(l_ -> " ").collect(Collectors.joining("\n"));
|
||||
|
||||
public record ClueAt(int index, int clue) { }
|
||||
|
||||
public record Clued(@Delegate Clues c) {
|
||||
|
||||
public Clued deepCopyGrid() { return new Clued(new Clues(c.lo, c.hi, c.vlo, c.vhi, c.rlo, c.rhi, c.xlo, c.xhi)); }
|
||||
public static Clued parse(String s) {
|
||||
var c = createEmpty();
|
||||
var lines = s.split("\n");
|
||||
for (int r = 0; r < Math.min(lines.length, R); r++) {
|
||||
var line = lines[r];
|
||||
for (int col = 0; col < Math.min(line.length(), C); col++) {
|
||||
char ch = line.charAt(col);
|
||||
if (ch >= '0' && ch <= '4') {
|
||||
int idx = Masker.offset(r, col);
|
||||
byte dir = (byte) (ch - '0');
|
||||
if ((idx & 64) == 0) c.setClueLo(1L << idx, dir);
|
||||
else c.setClueHi(1L << (idx & 63), dir);
|
||||
}
|
||||
}
|
||||
}
|
||||
return new Clued(c);
|
||||
}
|
||||
String gridToString() {
|
||||
var sb = new StringBuilder(INIT);
|
||||
forEachSlot((s, _, _) -> {
|
||||
val idx = Slot.clueIndex(s);
|
||||
val r = idx & 7;
|
||||
val c = idx >>> 3;
|
||||
val dir = Slot.dir(s);
|
||||
sb.setCharAt(r * (C + 1) + c, (char) (dir | 48));
|
||||
sb.setCharAt(INDEX(INDEX_ROW(idx), C + 1, INDEX_COL(idx)), CLUE_CHAR(dir));
|
||||
});
|
||||
return sb.toString();
|
||||
}
|
||||
public Stream<ClueAt> stream() {
|
||||
val stream = Stream.<ClueAt>builder();
|
||||
for (var l = c.lo & ~c.rlo & c.vlo; l != X; l &= l - 1) stream.accept(new ClueAt(Long.numberOfTrailingZeros(l), 1));
|
||||
for (var l = c.lo & ~c.rlo & ~c.vlo; l != X; l &= l - 1) stream.accept(new ClueAt(Long.numberOfTrailingZeros(l), 0));
|
||||
for (var l = c.lo & c.rlo & ~c.vlo; l != X; l &= l - 1) stream.accept(new ClueAt(Long.numberOfTrailingZeros(l), 2));
|
||||
for (var l = c.lo & c.rlo & c.vlo; l != X; l &= l - 1) stream.accept(new ClueAt(Long.numberOfTrailingZeros(l), 3));
|
||||
for (var h = c.hi & ~c.rhi & c.vhi; h != X; h &= h - 1) stream.accept(new ClueAt(64 | Long.numberOfTrailingZeros(h), 1));
|
||||
for (var h = c.hi & ~c.rhi & ~c.vhi; h != X; h &= h - 1) stream.accept(new ClueAt(64 | Long.numberOfTrailingZeros(h), 0));
|
||||
for (var h = c.hi & c.rhi & ~c.vhi; h != X; h &= h - 1) stream.accept(new ClueAt((64 | Long.numberOfTrailingZeros(h)), 2));
|
||||
for (var h = c.hi & c.rhi & c.vhi; h != X; h &= h - 1) stream.accept(new ClueAt((64 | Long.numberOfTrailingZeros(h)), 3));
|
||||
for (var l = c.lo & ~c.xlo & ~c.rlo & c.vlo; l != X; l &= l - 1) stream.accept(new ClueAt(Long.numberOfTrailingZeros(l), RIGHT.dir));
|
||||
for (var l = c.lo & ~c.xlo & ~c.rlo & ~c.vlo; l != X; l &= l - 1) stream.accept(new ClueAt(Long.numberOfTrailingZeros(l), DOWN.dir));
|
||||
for (var l = c.lo & ~c.xlo & c.rlo & ~c.vlo; l != X; l &= l - 1) stream.accept(new ClueAt(Long.numberOfTrailingZeros(l), CLUE_UP));
|
||||
for (var l = c.lo & ~c.xlo & c.rlo & c.vlo; l != X; l &= l - 1) stream.accept(new ClueAt(Long.numberOfTrailingZeros(l), CLUE_LEFT));
|
||||
for (var l = c.lo & c.xlo; l != X; l &= l - 1) stream.accept(new ClueAt(Long.numberOfTrailingZeros(l), CLUE_LEFT_TOP));
|
||||
|
||||
for (var h = c.hi & ~c.xhi & ~c.rhi & c.vhi; h != X; h &= h - 1) stream.accept(new ClueAt(HI(Long.numberOfTrailingZeros(h)), CLUE_RIGHT));
|
||||
for (var h = c.hi & ~c.xhi & ~c.rhi & ~c.vhi; h != X; h &= h - 1) stream.accept(new ClueAt(HI(Long.numberOfTrailingZeros(h)), CLUE_DOWN));
|
||||
for (var h = c.hi & ~c.xhi & c.rhi & ~c.vhi; h != X; h &= h - 1) stream.accept(new ClueAt(HI(Long.numberOfTrailingZeros(h)), CLUE_UP));
|
||||
for (var h = c.hi & ~c.xhi & c.rhi & c.vhi; h != X; h &= h - 1) stream.accept(new ClueAt(HI(Long.numberOfTrailingZeros(h)), CLUE_LEFT));
|
||||
for (var h = c.hi & c.xhi; h != X; h &= h - 1) stream.accept(new ClueAt(HI(Long.numberOfTrailingZeros(h)), CLUE_LEFT_TOP));
|
||||
|
||||
return stream.build();
|
||||
}
|
||||
@@ -83,29 +121,51 @@ public record Export() {
|
||||
|
||||
record LetterAt(int index, byte letter) {
|
||||
|
||||
public char human() {
|
||||
return (char) (letter | 64);
|
||||
}
|
||||
public int row() { return INDEX_ROW(index); }
|
||||
public int col() { return INDEX_COL(index); }
|
||||
public char human() { return LETTER(letter); }
|
||||
static LetterAt from(int index, byte[] bytes) { return new LetterAt(index, bytes[index]); }
|
||||
public int index(int cols) { return (row() * cols) + col(); }
|
||||
}
|
||||
|
||||
void visit(int index, byte letter);
|
||||
default void visit(int index, byte[] letters) { visit(index, letters[index]); }
|
||||
}
|
||||
|
||||
record Gridded(@Delegate Grid grid) {
|
||||
record Gridded(@Delegate Grid grid, Clues cl) {
|
||||
|
||||
public Gridded(Clues clues) { this(clues.toGrid(), clues); }
|
||||
public Stream<LetterAt> stream(Clues clues) {
|
||||
val stream = Stream.<LetterAt>builder();
|
||||
for (var l = grid.lo & ~clues.lo; l != X; l &= l - 1) stream.accept(LetterAt.from(Long.numberOfTrailingZeros(l), grid.g));
|
||||
for (var h = grid.hi & ~clues.hi & 0xFF; h != X; h &= h - 1) stream.accept(LetterAt.from(64 | Long.numberOfTrailingZeros(h), grid.g));
|
||||
return stream.build();
|
||||
}
|
||||
public void forEachLetter(Clues clues, LetterVisit visitor) {
|
||||
for (var l = grid.lo & ~clues.lo; l != X; l &= l - 1) visitor.visit(Long.numberOfTrailingZeros(l), grid.g);
|
||||
for (var h = grid.hi & ~clues.hi & 0xFF; h != X; h &= h - 1) visitor.visit(64 | Long.numberOfTrailingZeros(h), grid.g);
|
||||
String gridToString(Clues clues) {
|
||||
var sb = new StringBuilder(INIT);
|
||||
clues.forEachSlot((s, _, _) -> {
|
||||
val idx = Slot.clueIndex(s);
|
||||
val r = idx & 7;
|
||||
val c = idx >>> 3;
|
||||
val dir = Slot.dir(s);
|
||||
sb.setCharAt(r * (C + 1) + c, (char) (dir | 48));
|
||||
});
|
||||
stream(clues).forEach((l) -> sb.setCharAt(l.index(C + 1), l.human()));
|
||||
return sb.toString();
|
||||
}
|
||||
public static IntStream walk(byte base, long lo, long hi) {
|
||||
public String[] exportGrid(Clues clues, Replacar clueChar, char emptyFallback) {
|
||||
var sb = new StringBuilder(INIT);
|
||||
clues.forEachSlot((s, l, a) -> {
|
||||
val idx = Slot.clueIndex(s);
|
||||
val r = idx & 7;
|
||||
val c = idx >>> 3;
|
||||
val dir = Slot.dir(s);
|
||||
sb.setCharAt(r * (C + 1) + c, clueChar.replace(new Cell(grid, clues, idx, (byte) (dir | 48))));
|
||||
});
|
||||
stream(clues).forEach((l) -> sb.setCharAt(l.index(C + 1), l.human()));
|
||||
return sb.toString().replaceAll(" ", "" + emptyFallback).split("\n");
|
||||
}
|
||||
public static IntStream cellWalk(byte base, long lo, long hi) {
|
||||
if (Slotinfo.increasing(base)) {
|
||||
return IntStream.concat(
|
||||
IntStream.generate(new IntSupplier() {
|
||||
@@ -136,9 +196,8 @@ public record Export() {
|
||||
@Override
|
||||
public int getAsInt() {
|
||||
int msb = 63 - Long.numberOfLeadingZeros(temp);
|
||||
int res = 64 | msb;
|
||||
temp &= ~(1L << msb);
|
||||
return res;
|
||||
return 64 | msb;
|
||||
}
|
||||
}).limit(Long.bitCount(hi)),
|
||||
IntStream.generate(new IntSupplier() {
|
||||
@@ -147,65 +206,33 @@ public record Export() {
|
||||
@Override
|
||||
public int getAsInt() {
|
||||
int msb = 63 - Long.numberOfLeadingZeros(temp);
|
||||
int res = msb;
|
||||
temp &= ~(1L << msb);
|
||||
return res;
|
||||
return msb;
|
||||
}
|
||||
}).limit(Long.bitCount(lo)));
|
||||
}
|
||||
}
|
||||
String gridToString(Clues clues) {
|
||||
var sb = new StringBuilder(INIT);
|
||||
clues.forEachSlot((s, _, _) -> {
|
||||
val idx = Slot.clueIndex(s);
|
||||
val r = idx & 7;
|
||||
val c = idx >>> 3;
|
||||
val dir = Slot.dir(s);
|
||||
sb.setCharAt(r * (C + 1) + c, (char) (dir | 48));
|
||||
});
|
||||
forEachLetter(clues, (idx, letter) -> {
|
||||
val r = idx & 7;
|
||||
val c = idx >>> 3;
|
||||
sb.setCharAt(r * (C + 1) + c, (char) (letter | 64));
|
||||
});
|
||||
return sb.toString();
|
||||
}
|
||||
public String renderHuman(Clues clues) {
|
||||
return String.join("\n", exportGrid(clues, _ -> ' ', '#'));
|
||||
}
|
||||
|
||||
public String renderHuman(Clues clues) { return String.join("\n", exportGrid(clues, _ -> ' ', '#')); }
|
||||
@FunctionalInterface
|
||||
interface Replacar {
|
||||
|
||||
record Cell(Grid grid, Clues clues, int index, byte data) { }
|
||||
char replace(Cell c);
|
||||
}
|
||||
public String[] exportGrid(Clues clues, Replacar clueChar, char emptyFallback) {
|
||||
var sb = new StringBuilder(INIT);
|
||||
clues.forEachSlot((s, l, a) -> {
|
||||
val idx = Slot.clueIndex(s);
|
||||
val r = idx & 7;
|
||||
val c = idx >>> 3;
|
||||
val dir = Slot.dir(s);
|
||||
sb.setCharAt(r * (C + 1) + c, clueChar.replace(new Cell(grid, clues, idx, (byte) (dir | 48))));
|
||||
});
|
||||
forEachLetter(clues, (idx, letter) -> {
|
||||
val r = idx & 7;
|
||||
val c = idx >>> 3;
|
||||
sb.setCharAt(r * (C + 1) + c, (char) (letter | 64));
|
||||
});
|
||||
return sb.toString().replaceAll(" ", "" + emptyFallback).split("\n");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
record Placed(long lemma, int slotKey, int[] cells) {
|
||||
|
||||
static final char[] DIRECTION = { Placed.VERTICAL, Placed.HORIZONTAL, Placed.VERTICAL, Placed.HORIZONTAL, Placed.VERTICAL };
|
||||
public static final char HORIZONTAL = 'h';
|
||||
static final char VERTICAL = 'v';
|
||||
public int arrowCol() { return SwedishGenerator.IT[Slot.clueIndex(slotKey)].c(); }
|
||||
public int arrowRow() { return SwedishGenerator.IT[Slot.clueIndex(slotKey)].r(); }
|
||||
public int startRow() { return SwedishGenerator.IT[cells[0]].r(); }
|
||||
public int startCol() { return SwedishGenerator.IT[cells[0]].c(); }
|
||||
static final char[] DIRECTION = { Placed.VERTICAL, Placed.HORIZONTAL, Placed.VERTICAL, Placed.HORIZONTAL, Placed.VERTICAL };
|
||||
|
||||
public int arrowCol() { return Masker.IT[Slot.clueIndex(slotKey)].c(); }
|
||||
public int arrowRow() { return Masker.IT[Slot.clueIndex(slotKey)].r(); }
|
||||
public int startRow() { return Masker.IT[cells[0]].r(); }
|
||||
public int startCol() { return Masker.IT[cells[0]].c(); }
|
||||
public boolean isReversed() { return !Slotinfo.increasing(slotKey); }
|
||||
public char direction() { return DIRECTION[Slot.dir(slotKey)]; }
|
||||
}
|
||||
@@ -214,9 +241,9 @@ public record Export() {
|
||||
|
||||
public record WordOut(String word, int[] cell, int startRow, int startCol, char direction, int arrowRow, int arrowCol, boolean isReversed, int complex, String[] clue) {
|
||||
|
||||
public WordOut(long l, int startRow, int startCol, char d, int arrowRow, int arrowCol, boolean isReversed) {
|
||||
val meta = Meta.readRecord(Meta.shardKey(l), Lemma.unpackShardIndex(l));
|
||||
this(Lemma.asWord(l), new int[]{ arrowRow, arrowCol, startRow, startCol }, startRow, startCol, d, arrowRow, arrowCol, isReversed,
|
||||
public WordOut(long l, int startRow, int startCol, char d, int arrowRow, int arrowCol, boolean isReversed, byte[] bytes) {
|
||||
val meta = Meta.readRecord(Meta.shardKey(l), Lemma.unpackShardIndex(l));
|
||||
this(Lemma.asWord(l, bytes), new int[]{ arrowRow, arrowCol, startRow, startCol }, startRow, startCol, d, arrowRow, arrowCol, isReversed,
|
||||
meta.simpel(), meta.clues());
|
||||
}
|
||||
}
|
||||
@@ -243,7 +270,7 @@ public record Export() {
|
||||
return new ExportedPuzzle(grid.exportGrid(clues.c, _ -> '#', '#'), new WordOut[0], difficulty, rewards);
|
||||
}
|
||||
|
||||
var placed = Arrays.stream(slots).map(slot -> new Placed(slot.assign().w, slot.key(), Gridded.walk((byte) slot.key(), slot.lo(), slot.hi()).toArray())).toArray(
|
||||
var placed = Arrays.stream(slots).map(slot -> new Placed(slot.assign().w, slot.key(), Gridded.cellWalk((byte) slot.key(), slot.lo(), slot.hi()).toArray())).toArray(
|
||||
Placed[]::new);
|
||||
|
||||
// 2) bounding box around all word cells + arrow cells, with 1-cell margin
|
||||
@@ -252,7 +279,7 @@ public record Export() {
|
||||
|
||||
for (var rc : placed) {
|
||||
for (var c : rc.cells) {
|
||||
val it = SwedishGenerator.IT[c];
|
||||
val it = Masker.IT[c];
|
||||
minR = Math.min(minR, it.r());
|
||||
minC = Math.min(minC, it.c());
|
||||
maxR = Math.max(maxR, it.r());
|
||||
@@ -270,12 +297,13 @@ 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(map.getOrDefault(Grid.offset(r, c), '#'));
|
||||
for (var c = minC; c <= maxC; c++) row.append(map.getOrDefault(Masker.offset(r, c), '#'));
|
||||
gridv2[i] = row.toString();
|
||||
}
|
||||
|
||||
// 5) words output with cropped coordinates
|
||||
int MIN_R = minR, MIN_C = minC;
|
||||
val bytes = BYTES.get();
|
||||
var wordsOut = Arrays.stream(placed).map(p -> new WordOut(
|
||||
p.lemma,
|
||||
p.startRow() - MIN_R,
|
||||
@@ -283,7 +311,7 @@ public record Export() {
|
||||
p.direction(),
|
||||
p.arrowRow() - MIN_R,
|
||||
p.arrowCol() - MIN_C,
|
||||
p.isReversed()
|
||||
p.isReversed(), bytes
|
||||
)).toArray(WordOut[]::new);
|
||||
return new ExportedPuzzle(gridv2, wordsOut, difficulty, rewards);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user