Gather data

This commit is contained in:
mike
2026-01-09 00:39:43 +01:00
parent fb99a7aab4
commit a269584ad6
3 changed files with 93 additions and 29 deletions

View File

@@ -1,6 +1,7 @@
package puzzle; package puzzle;
import puzzle.Main.PuzzleResult; import puzzle.Main.PuzzleResult;
import puzzle.SwedishGenerator.Grid;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
@@ -78,11 +79,11 @@ public record ExportFormat() {
int maxR = Integer.MIN_VALUE, maxC = Integer.MIN_VALUE; int maxR = Integer.MIN_VALUE, maxC = Integer.MIN_VALUE;
for (var rc : placed) { for (var rc : placed) {
for (var r : rc.cells) { for (var c : rc.cells) {
minR = Math.min(minR, r[0]); minR = Math.min(minR, Grid.r(c));
minC = Math.min(minC, r[1]); minC = Math.min(minC, Grid.c(c));
maxR = Math.max(maxR, r[0]); maxR = Math.max(maxR, Grid.r(c));
maxC = Math.max(maxC, r[1]); maxC = Math.max(maxC, Grid.c(c));
} }
minR = Math.min(minR, rc.arrowRow); minR = Math.min(minR, rc.arrowRow);
minC = Math.min(minC, rc.arrowCol); minC = Math.min(minC, rc.arrowCol);
@@ -93,8 +94,8 @@ public record ExportFormat() {
// 3) map of only used letter cells (everything else becomes '#') // 3) map of only used letter cells (everything else becomes '#')
var letterAt = new HashMap<Long, Character>(); var letterAt = new HashMap<Long, Character>();
for (var p : placed) { for (var p : placed) {
for (var rc : p.cells) { for (var c : p.cells) {
int rr = rc[0], cc = rc[1]; int rr = Grid.r(c), cc = Grid.c(c);
if (inBounds(rr, cc) && g.isLetterAt(rr, cc)) { if (inBounds(rr, cc) && g.isLetterAt(rr, cc)) {
letterAt.put(pack(rr, cc), g.getCharAt(rr, cc)); letterAt.put(pack(rr, cc), g.getCharAt(rr, cc));
} }
@@ -134,40 +135,34 @@ public record ExportFormat() {
int c = s.clueC(); int c = s.clueC();
int d = s.dir(); int d = s.dir();
int[][] cells = new int[s.len()][]; int[] cells = new int[s.len()];
for (int i = 0; i < s.len(); i++) { for (int i = 0; i < s.len(); i++) {
cells[i] = new int[]{ s.r(i), s.c(i) }; cells[i] = s.pos(i);
} }
// Canonicalize: always output right/down // Canonicalize: always output right/down
int startRow, startCol, arrowRow, arrowCol; int startRow, startCol, arrowRow, arrowCol;
String direction; String direction;
boolean isReversed = false; boolean isReversed = false;
startRow = Grid.r(cells[0]);
startCol = Grid.c(cells[0]);
if (d == 2) { // right -> horizontal if (d == 2) { // right -> horizontal
direction = HORIZONTAL; direction = HORIZONTAL;
startRow = cells[0][0];
startCol = cells[0][1]; arrowRow = r;
arrowRow = r; arrowCol = c;
arrowCol = c;
} else if (d == 3 || d == 5) { // down or down-bent -> vertical } else if (d == 3 || d == 5) { // down or down-bent -> vertical
direction = VERTICAL; direction = VERTICAL;
startRow = cells[0][0];
startCol = cells[0][1];
arrowRow = r; arrowRow = r;
arrowCol = c; arrowCol = c;
} else if (d == 4) { // left -> horizontal (REVERSED) } else if (d == 4) { // left -> horizontal (REVERSED)
direction = HORIZONTAL; direction = HORIZONTAL;
isReversed = true; isReversed = true;
startRow = cells[0][0];
startCol = cells[0][1];
arrowRow = r; arrowRow = r;
arrowCol = c; arrowCol = c;
} else if (d == 1) { // up -> vertical (REVERSED) } else if (d == 1) { // up -> vertical (REVERSED)
direction = VERTICAL; direction = VERTICAL;
isReversed = true; isReversed = true;
startRow = cells[0][0];
startCol = cells[0][1];
arrowRow = r; arrowRow = r;
arrowCol = c; arrowCol = c;
} else { } else {
@@ -187,7 +182,7 @@ public record ExportFormat() {
} }
private static long pack(int r, int c) { return (((long) r) << 32) ^ (c & 0xFFFFFFFFL); } private static long pack(int r, int c) { return (((long) r) << 32) ^ (c & 0xFFFFFFFFL); }
private record Placed(Lemma lemma, int startRow, int startCol, String direction, int arrowRow, int arrowCol, int[][] cells, boolean isReversed) { } private record Placed(Lemma lemma, int startRow, int startCol, String direction, int arrowRow, int arrowCol, int[] cells, boolean isReversed) { }
public record Rewards(int coins, int stars, int hints) { } public record Rewards(int coins, int stars, int hints) { }

View File

@@ -125,9 +125,13 @@ public record SwedishGenerator(int[] buff) {
static int offset(int r, int c) { return r | (c << 3); } static int offset(int r, int c) { return r | (c << 3); }
Grid deepCopyGrid() { return new Grid(g.clone()); } Grid deepCopyGrid() { return new Grid(g.clone()); }
char getCharAt(int r, int c) { return (char) (g[offset(r, c)]); } char getCharAt(int r, int c) { return (char) (g[offset(r, c)]); }
char getCharAt(int pos) { return (char) (g[pos]); }
int digitAt(int r, int c) { return g[offset(r, c)] - 48; } int digitAt(int r, int c) { return g[offset(r, c)] - 48; }
byte byteAt(int r, int c) { return g[offset(r, c)]; } byte byteAt(int r, int c) { return g[offset(r, c)]; }
void setCharAt(int r, int c, char ch) { g[offset(r, c)] = (byte) ch; } void setCharAt(int r, int c, char ch) { g[offset(r, c)] = (byte) ch; }
void setCharAt(int idx, char ch) { g[idx] = (byte) ch; }
void clear(int r, int c) { g[offset(r, c)] = 0; }
void clear(int idx) { g[idx] = 0; }
boolean isLetterAt(int r, int c) { return ((g[offset(r, c)] & 64) != 0); } boolean isLetterAt(int r, int c) { return ((g[offset(r, c)] & 64) != 0); }
boolean isDigitAt(int r, int c) { return (g[offset(r, c)] & 48) == 48; } boolean isDigitAt(int r, int c) { return (g[offset(r, c)] & 48) == 48; }
boolean isDigitAt(int index) { return (g[index] & 48) == 48; } boolean isDigitAt(int index) { return (g[index] & 48) == 48; }
@@ -339,6 +343,7 @@ public record SwedishGenerator(int[] buff) {
public int dir() { return key & 15; } public int dir() { return key & 15; }
public boolean horiz() { return horiz(key); } public boolean horiz() { return horiz(key); }
public int r(int i) { return Grid.r(offset(packedPos, i)); } public int r(int i) { return Grid.r(offset(packedPos, i)); }
public int pos(int i) { return offset(packedPos, i); }
public int c(int i) { return Grid.c(offset(packedPos, i)); } public int c(int i) { return Grid.c(offset(packedPos, i)); }
public static boolean horiz(int key) { return ((key & 15) & 1) == 0; } public static boolean horiz(int key) { return ((key & 15) & 1) == 0; }
public static int offset(long packedPos, int i) { return (int) ((packedPos >> (i * 7)) & 127); } public static int offset(long packedPos, int i) { return (int) ((packedPos >> (i * 7)) & 127); }
@@ -346,7 +351,7 @@ public record SwedishGenerator(int[] buff) {
static void undoPlace(Grid grid, long[] undoBuffer, int offset, int n) { static void undoPlace(Grid grid, long[] undoBuffer, int offset, int n) {
for (var i = 0; i < n; i++) { for (var i = 0; i < n; i++) {
long v = undoBuffer[offset + i]; long v = undoBuffer[offset + i];
grid.setCharAt((int) (v >> 16) & 0xFF, (int) (v >> 8) & 0xFF, (char) (v & 0xFF)); grid.clear((int)v);
} }
} }
@FunctionalInterface @FunctionalInterface
@@ -724,32 +729,53 @@ public record SwedishGenerator(int[] buff) {
static void patternForSlot(Grid grid, Slot s, char[] pat) { static void patternForSlot(Grid grid, Slot s, char[] pat) {
for (var i = 0; i < s.len(); i++) { for (var i = 0; i < s.len(); i++) {
var ch = grid.getCharAt(s.r(i), s.c(i)); var ch = grid.getCharAt(s.pos(i));
pat[i] = isLetter(ch) ? ch : C_DASH; pat[i] = isLetter(ch) ? ch : C_DASH;
} }
} }
static int slotScore(int[] cellCount, Slot s, Grid grid) { static int slotScore(int[] cellCount, Slot s, Grid grid) {
var cross = 0; var cross = 0;
for (var i = 0; i < s.len(); i++) cross += (cellCount[Grid.offset(s.r(i), s.c(i))] - 1); for (var i = 0; i < s.len(); i++) cross += (cellCount[s.pos(i)] - 1);
return cross * 10 + s.len(); return cross * 10 + s.len();
} }
static int placeWord1(Grid grid, Slot s, Lemma w, long[] undoBuffer, int offset) {
static int placeWord(Grid grid, Slot s, Lemma w, long[] undoBuffer, int offset) {
int n = 0; int n = 0;
for (var i = 0; i < s.len(); i++) { for (var i = 0; i < s.len(); i++) {
int r = s.r(i), c = s.c(i); int r = s.r(i), c = s.c(i);
char cur = grid.getCharAt(r, c); char cur = grid.getCharAt(r, c);
var ch = w.charAt(i); var ch = w.charAt(i);
if (cur == C_DASH) { if (cur == C_DASH) {
undoBuffer[offset + n] = ((long) r << 16) | ((long) c << 8) | (long) C_DASH; undoBuffer[offset + n] = ((long) r << 16) | ((long) c << 8);
n++; n++;
grid.setCharAt(r, c, ch); grid.setCharAt(r, c, ch);
} else { } else {
if (cur != ch) { if (cur != ch) {
for (var j = 0; j < n; j++) { for (var j = 0; j < n; j++) {
long v = undoBuffer[offset + j]; long v = undoBuffer[offset + j];
grid.setCharAt((int) (v >> 16) & 0xFF, (int) (v >> 8) & 0xFF, (char) (v & 0xFF)); grid.clear((int) (v >> 16) & 0xFF, (int) (v >> 8) & 0xFF);
}
return -1;
}
}
}
return n;
}
static int placeWord(Grid grid, Slot s, Lemma w, long[] undoBuffer, int offset) {
int n = 0;
for (var i = 0; i < s.len(); i++) {
int idx = s.pos(i);
char cur = grid.getCharAt(idx);
var ch = w.charAt(i);
if (cur == C_DASH) {
undoBuffer[offset + n] = idx;
n++;
grid.setCharAt(idx, ch);
} else {
if (cur != ch) {
for (var j = 0; j < n; j++) {
long v = undoBuffer[offset + j];
grid.clear((int) v);
} }
return -1; return -1;
} }
@@ -770,7 +796,7 @@ public record SwedishGenerator(int[] buff) {
var ctx = CTX.get(); var ctx = CTX.get();
var cellCount = ctx.cellCount; var cellCount = ctx.cellCount;
Arrays.fill(cellCount, 0, SIZE, 0); Arrays.fill(cellCount, 0, SIZE, 0);
for (var s : slots) for (var i = 0; i < s.len(); i++) cellCount[Grid.offset(s.r(i), s.c(i))]++; for (var s : slots) for (var i = 0; i < s.len(); i++) cellCount[s.pos(i)]++;
var t0 = System.currentTimeMillis(); var t0 = System.currentTimeMillis();
final var lastLog = new AtomicLong(t0); final var lastLog = new AtomicLong(t0);

View File

@@ -255,6 +255,49 @@ public class SwedishGeneratorTest {
assertNotNull(g4); assertNotNull(g4);
} }
@Test
void testPlaceWord() {
var grid = SwedishGenerator.makeEmptyGrid();
// Slot at (0,0) length 3, horizontal (right)
// key = (r << 8) | (c << 4) | d. Here we just need a valid slot for placeWord.
// r(i) and c(i) are used by placeWord.
var packedPos = ((long) Grid.offset(0, 0)) | (((long) Grid.offset(0, 1)) << 7) | (((long) Grid.offset(0, 2)) << 14);
var s = Slot.from(0, packedPos, 3);
var w1 = new Lemma("ABC", 1, "test");
var undoBuffer = new long[10];
// 1. Successful placement in empty grid
int placed = SwedishGenerator.placeWord(grid, s, w1, undoBuffer, 0);
assertEquals(3, placed);
assertEquals('A', grid.getCharAt(0, 0));
assertEquals('B', grid.getCharAt(0, 1));
assertEquals('C', grid.getCharAt(0, 2));
// 2. Successful placement with partial overlap (same characters)
// Clear grid first or use another slot. Let's just verify it works if we place it again.
placed = SwedishGenerator.placeWord(grid, s, w1, undoBuffer, 0);
assertEquals(0, placed); // 0 new characters placed
// 3. Conflict: place "ABD" where "ABC" is
var w2 = new Lemma("ABD", 1, "conflict");
placed = SwedishGenerator.placeWord(grid, s, w2, undoBuffer, 0);
assertEquals(-1, placed);
// Verify grid is unchanged (still "ABC")
assertEquals('A', grid.getCharAt(0, 0));
assertEquals('B', grid.getCharAt(0, 1));
assertEquals('C', grid.getCharAt(0, 2));
// 4. Partial placement then conflict (rollback)
grid = SwedishGenerator.makeEmptyGrid();
grid.setCharAt(0, 2, 'X'); // Conflict at the end
placed = SwedishGenerator.placeWord(grid, s, w1, undoBuffer, 0);
assertEquals(-1, placed);
// Verify grid is still empty (except for 'X')
assertEquals(SwedishGenerator.C_DASH, grid.getCharAt(0, 0));
assertEquals(SwedishGenerator.C_DASH, grid.getCharAt(0, 1));
assertEquals('X', grid.getCharAt(0, 2));
}
@Test @Test
void testBacktrackingHelpers() { void testBacktrackingHelpers() {
var grid = SwedishGenerator.makeEmptyGrid(); var grid = SwedishGenerator.makeEmptyGrid();