introduce bitloops

This commit is contained in:
mike
2026-01-12 00:47:38 +01:00
parent 986c2f85a9
commit 84ba4c9c63
7 changed files with 164 additions and 143 deletions

View File

@@ -8,19 +8,18 @@ import java.nio.channels.FileChannel;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.util.Arrays;
import java.util.Locale;
import java.util.function.Consumer;
public final class CsvIndexService
implements Closeable {
static final ScopedValue<CsvIndexService> SC = ScopedValue.newInstance();
static final Gson GSON = new Gson();
private static final int MAGIC = 0x4C494458; // "LIDX"
private static final int VERSION = 1;
private final Path csvPath;
private final Path idxPath;
static final ScopedValue<CsvIndexService> SC = ScopedValue.newInstance();
static final Gson GSON = new Gson();
private static final int MAGIC = 0x4C494458; // "LIDX"
private static final int VERSION = 1;
static int SIMPEL_IDX = 3;
private final Path csvPath;
private final Path idxPath;
private volatile long[] offsets; // lazy
private volatile FileChannel csvChannel; // open once
@@ -30,7 +29,10 @@ public final class CsvIndexService
this.csvPath = csvPath;
this.idxPath = idxPath;
}
public static int lineToSimpel(String line) {
var parts = line.split(",", 5);
return Integer.parseInt(parts[SIMPEL_IDX].trim());
}
public static String[] lineToClue(String line) {
if (line.isBlank()) throw new RuntimeException("Empty line");
var parts = line.split(",", 5);
@@ -51,16 +53,23 @@ public final class CsvIndexService
throw new RuntimeException("Invalid word:" + line);
}
// CSV has level 1-10. llmScores use 10-level.
int score = Integer.parseInt(parts[2].trim());
if (score < 1) {
if (Main.VERBOSE) System.err.println("Word too complex: " + line);
return;
}
int simpel = Integer.parseInt(parts[3].trim());
ok.accept(new Lemma(id, word, simpel));
ok.accept(new Lemma(id, word));
}
public static int simpel(int index) {
try {
if (SC.isBound())
return lineToSimpel(SC.get().getLine(index));
return -1;
} catch (Exception e) {
throw new RuntimeException("Failed to get clues for index " + index, e);
}
}
public static String[] clues(int index) {
try {
if (SC.isBound())

View File

@@ -7,7 +7,6 @@ import puzzle.SwedishGenerator.Grid;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import static java.nio.charset.StandardCharsets.*;
import static puzzle.SwedishGenerator.R;
import static puzzle.SwedishGenerator.Lemma;
import static puzzle.SwedishGenerator.Slot;
@@ -82,10 +81,10 @@ public record Export() {
public void clear() { Arrays.fill(bits, 0L); }
}
record Placed(Lemma lemma, int startRow, int startCol, char direction, int arrowRow, int arrowCol, int[] cells, boolean isReversed) {
record Placed(Lemma lemma, int startRow, int startCol, char direction, int arrowRow, int arrowCol, int[] cells, boolean isReversed) {
public static final char HORIZONTAL = 'h';
static final char VERTICAL = 'v';
static final char VERTICAL = 'v';
}
public record Rewards(int coins, int stars, int hints) { }
@@ -109,9 +108,9 @@ public record Export() {
for (int i = 0, len = s.len(); i < len; i++) cells[i] = s.pos(i);
char direction;
var isReversed = false;
var startRow = Grid.r(cells[0]);
var startCol = Grid.c(cells[0]);
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
@@ -215,4 +214,25 @@ public record Export() {
return new ExportedPuzzle(gridv2, wordsOut, difficulty, rewards);
}
}
static record DictEntryDTO(ArrayList<Lemma> words, IntListDTO[][] pos) {
public DictEntryDTO(int L) {
this(new ArrayList<>(), new IntListDTO[L][26]);
for (var i = 0; i < L; i++) for (var j = 0; j < 26; j++) pos[i][j] = new IntListDTO();
}
}
static final class IntListDTO {
int[] a = new int[8];
int n = 0;
void add(int v) {
if (n >= a.length) a = Arrays.copyOf(a, a.length * 2);
a[n++] = v;
}
int size() { return n; }
int[] data() { return a; }
}
}

View File

@@ -74,8 +74,6 @@ public class Main {
section("Settings");
printSettings(opts);
var csv = Paths.get("nl_score_hints_v3.csv");
var idx = Paths.get("nl_score_hints_v3.idx");
var res = generatePuzzle(opts);
if (res == null) {
@@ -86,6 +84,7 @@ public class Main {
}
section("Result");
res.filled().calcSimpel();
info(String.format(Locale.ROOT, "simplicity : %.2f", res.filled().stats().simplicity));
section("Mask");
@@ -353,6 +352,7 @@ public class Main {
TOTAL_NODES.addAndGet(filled.stats().nodes);
TOTAL_BACKTRACKS.addAndGet(filled.stats().backtracks);
if (filled.ok()) {
filled.calcSimpel();
TOTAL_SUCCESS.incrementAndGet();
TOTAL_SIMPLICITY.addAndGet((long) (filled.stats().simplicity * 100));
}

View File

@@ -8,10 +8,10 @@ import precomp.Neighbors9x8.nbrs_8;
import precomp.Neighbors9x8.rci;
import puzzle.Export.Bit;
import puzzle.Export.Bit1029;
import puzzle.Export.DictEntryDTO;
import puzzle.Export.Gridded;
import puzzle.Export.Strings;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
@@ -37,6 +37,8 @@ public record SwedishGenerator(Rng rng) {
public CandidateInfo(int n) { this(null, n); }
}
static final CandidateInfo[] CANDIDATES = IntStream.range(0, 10192 << 2).mapToObj(CandidateInfo::new).toArray(CandidateInfo[]::new);
//@formatter:off
@FunctionalInterface interface SlotVisitor { void visit(int key, long packedPos, int len); }
//@formatter:on
@@ -67,6 +69,9 @@ public record SwedishGenerator(Rng rng) {
static int clamp(int x, int a, int b) { return Math.max(a, Math.min(b, x)); }
record Pick(Slot slot, CandidateInfo info, boolean done) { }
static final byte B0 = (byte) 0;
static final byte B64 = (byte) 64;
static final byte B48 = (byte) 48;
// Directions for '1'..'6'
static final nbrs_16[] OFFSETS = Neighbors9x8.OFFSETS;
static final nbrs_16[] OFFSETS_FOUR = Neighbors9x8.OFFSETS_FOUR;
@@ -75,6 +80,7 @@ public record SwedishGenerator(Rng rng) {
static final rci[] IT = Neighbors9x8.IT;
static final long[] INBR8_PACKEDT = Neighbors9x8.NBR8_PACKED;
static final int[][] MUTATE_RI = new int[SIZE][625];
static final long[] NBR8_PACKED = Neighbors9x8.NBR8_PACKED;
static {
for (int i = 0; i < SIZE; i++) {
@@ -87,28 +93,6 @@ public record SwedishGenerator(Rng rng) {
clamp(Grid.c(i) + dc1 + dc2, 0, C - 1));
}
}
/*static {
Rng trng = new Rng(1);
for (int i = 0; i < SIZE; i++) {
int[] neighborhood = new int[625];
int k = 0;
for (int dr1 = -2; dr1 <= 2; dr1++)
for (int dr2 = -2; dr2 <= 2; dr2++)
for (int dc1 = -2; dc1 <= 2; dc1++)
for (int dc2 = -2; dc2 <= 2; dc2++)
neighborhood[k++] = Grid.offset(clamp(Grid.r(i) + dr1 + dr2, 0, R - 1),
clamp(Grid.c(i) + dc1 + dc2, 0, C - 1));
for (k = 0; k < 625; k++) {
long packed = 0;
for (int s = 0; s < 4; s++) {
int ri = neighborhood[trng.randint(0, 624)];
int d = trng.randint(1, 4);
packed |= ((long) ri | ((long) d << 8)) << (s << 4);
}
MUTATE_RI[i][k] = packed;
}
}
}*/
static final Pick PICK_DONE = new Pick(null, null, true);
static final Pick PICK_NOT_DONE = new Pick(null, null, false);
@@ -127,9 +111,9 @@ public record SwedishGenerator(Rng rng) {
HashMap<Integer, Lemma> clueMap,
FillStats stats) {
public FillResult {
public void calcSimpel() {
if (ok) {
clueMap.forEach((k, v) -> stats.simplicity += v.simpel);
clueMap.forEach((k, v) -> stats.simplicity += v.simpel());
stats.simplicity = clueMap.isEmpty() ? 0 : stats.simplicity / clueMap.size();
}
}
@@ -247,9 +231,7 @@ public record SwedishGenerator(Rng rng) {
}
return true;
}
static final byte B0 = (byte) 0;
static final byte B64 = (byte) 64;
static final byte B48 = (byte) 48;
static boolean isLetter(byte b) { return (b & B64) != B0; }
public boolean isLetterSet(int idx) { return isLetter(g[idx]); }
static boolean notDigit(byte b) { return (b & B48) != B48; }
@@ -268,27 +250,11 @@ public record SwedishGenerator(Rng rng) {
}
}
static final class IntList {
int[] a = new int[8];
int n = 0;
void add(int v) {
if (n >= a.length) a = Arrays.copyOf(a, a.length * 2);
a[n++] = v;
}
int size() { return n; }
int[] data() { return a; }
}
static final record IntList(int[] data, int size) { }
static record DictEntry(ArrayList<Lemma> words, IntList[][] pos) {
public DictEntry(int L) {
this(new ArrayList<>(), new IntList[L][26]);
for (var i = 0; i < L; i++) for (var j = 0; j < 26; j++) pos[i][j] = new IntList();
}
}
static record DictEntry(Lemma[] words, IntList[][] pos) { }
public static record Lemma(int index, long word, byte len, short simpel) {
public static record Lemma(int index, long word, byte len) {
static int LEMMA_COUNTER = 0;
static long pack(byte[] b) {
@@ -296,15 +262,17 @@ public record SwedishGenerator(Rng rng) {
for (var i = 0; i < b.length; i++) w |= ((long) b[i] & ~64) << (i * 5);
return w;
}
public Lemma(int index, String word, int simpel) { this(index, pack(word.getBytes(US_ASCII)), (byte) word.length(), (short) simpel); }
public Lemma(String word, int simpel) { this(LEMMA_COUNTER++, word, simpel); }
byte byteAt(int idx) { return (byte) ((word >>> (idx * 5)) & 0b11111 | Grid.B64); }// word[]; }
@Override public int hashCode() { return index; }
@Override public boolean equals(Object o) { return (o == this) || (o instanceof Lemma l && l.index == index); }
String[] clue() { return CsvIndexService.clues(index); }
public Lemma(int index, String word) { this(index, pack(word.getBytes(US_ASCII)), (byte) word.length()); }
public Lemma(String word) { this(LEMMA_COUNTER++, word); }
byte byteAt(int idx) { return (byte) ((word >>> (idx * 5)) & 0b11111 | B64); }// word[]; }
int intAt(int idx) { return (int) (((word >>> (idx * 5))) & 0b11111); }// word[]; }
@Override public int hashCode() { return index; }
@Override public boolean equals(Object o) { return (o == this) || (o instanceof Lemma l && l.index == index); }
String[] clue() { return CsvIndexService.clues(index); }
int simpel() { return CsvIndexService.simpel(index); }
public String asWord() {
var b = new byte[len];
for (var i = 0; i < len; i++) b[i] = (byte) ((word >>> (i * 5)) & 0b11111 | Grid.B64);
for (var i = 0; i < len; i++) b[i] = (byte) ((word >>> (i * 5)) & 0b11111 | B64);
return new String(b, US_ASCII);
}
}
@@ -314,23 +282,29 @@ public record SwedishGenerator(Rng rng) {
int length) {
public Dict(Lemma[] wordz) {
var index = new DictEntry[MAX_WORD_LENGTH_PLUS_ONE];
Arrays.setAll(index, i -> new DictEntry(i));
var index = new DictEntryDTO[MAX_WORD_LENGTH_PLUS_ONE];
Arrays.setAll(index, i -> new DictEntryDTO(i));
for (var lemma : wordz) {
var L = lemma.len;
var entry = index[L];
var idx = entry.words.size();
entry.words.add(lemma);
var idx = entry.words().size();
entry.words().add(lemma);
for (var i = 0; i < L; i++) {
var letter = lemma.byteAt(i) - 'A';
var letter = lemma.intAt(i) - 1;
if (letter < 0 || letter >= 26) throw new RuntimeException("Illegal letter: " + letter + " in word " + lemma);
entry.pos[i][letter].add(idx);
entry.pos()[i][letter].add(idx);
}
}
for (int i = MIN_LEN; i < index.length; i++) if (index[i].words.size() <= 0) throw new RuntimeException("No words for length " + i);
this(index, Arrays.stream(index).mapToInt(i -> i.words.size()).sum());
for (int i = MIN_LEN; i < index.length; i++) if (index[i].words().size() <= 0) throw new RuntimeException("No words for length " + i);
this(Arrays.stream(index).map(i -> new DictEntry(i.words().toArray(Lemma[]::new),
Arrays.stream(i.pos())
.map(ii -> Arrays.stream(ii).map(dto -> new IntList(dto.data(), dto.size()))
.toArray(IntList[]::new))
.toArray(IntList[][]::new)))
.toArray(DictEntry[]::new),
Arrays.stream(index).mapToInt(i -> i.words().size()).sum());
}
static Dict loadDict(String wordsPath) {
try {
@@ -394,12 +368,12 @@ public record SwedishGenerator(Rng rng) {
private static void processSlot(Grid grid, SlotVisitor visitor, int idx) {
var d = grid.digitAt(idx);
var packed = OFFSETS[d].path()[idx];
long packedPos = 0;
long packedPos = 0L;
int k = 0;
for (int n = (int) (packed >>> 56), iidx; k < n && k < MAX_WORD_LENGTH; k++) {
iidx = (int) ((packed >>> (k * 7)) & 0x7F);
for (long n = (packed >>> 56), offset = 0L, iidx; k < n; k++, offset += 7L) {
iidx = ((packed >>> offset) & 0x7FL);
if (grid.isClue(iidx)) break;
packedPos |= (long) iidx << (k * 7);
packedPos |= iidx << offset;
}
if (k > 0) {
visitor.visit((idx << 4) | d, packedPos, k);
@@ -412,52 +386,68 @@ public record SwedishGenerator(Rng rng) {
return slots;
}
static long fitting(Grid grid, int clueIdx, int[] covH, int[] covV) {
var d = grid.digitAt(clueIdx);
var nbrs16 = OFFSETS[d];
long packed = nbrs16.path()[clueIdx];
int n = (int) (packed >>> 56) * 7, k, idx;
var horiz = Slot.horiz(d) ? covH : covV;
for (k = 0; k < n && k < MAX_WORD_LENGTH7; k += 7) {
idx = (int) ((packed >>> (k)) & 0x7F);
if (grid.isClue(idx)) break;
horiz[idx] += 1;
}
if (k > 0) {
if (k < MIN_LEN7) return 8001L;
return 1L;
}
return 0L;
}
long maskFitness(Grid grid) {
var ctx = CTX.get();
var covH = ctx.covH;
var covV = ctx.covV;
Arrays.fill(covH, 0, SIZE, 0);
Arrays.fill(covV, 0, SIZE, 0);
long lo_cl = grid.lo, hi_cl = grid.hi;
long penalty = (((long) Math.abs(grid.clueCount() - TARGET_CLUES)) << 3);
boolean hasSlots = false;
int clueIdx;
long lo_cl = grid.lo, hi_cl = grid.hi;
long penalty = 0L;
for (int i = 0; i < 65; i += 64) {
for (long bits = (i == 0 ? lo_cl : hi_cl); bits != X; bits &= bits - 1) {
int clueIdx = i + Long.numberOfTrailingZeros(bits);
var d = grid.digitAt(clueIdx);
var nbrs16 = OFFSETS[d];
long packed = nbrs16.path()[clueIdx];
int n = (int) (packed >>> 56) * 7, k, idx;
var horiz = Slot.horiz(d) ? covH : covV;
clueIdx = i + Long.numberOfTrailingZeros(bits);
var d = grid.digitAt(clueIdx);
var nbrs16 = OFFSETS[d];
long packed = nbrs16.path()[clueIdx];
int n = (int) (packed >>> 56) * 7, k, idx;
var horiz = Slot.horiz(d) ? covH : covV;
for (k = 0; k < n && k < MAX_WORD_LENGTH7; k += 7) {
idx = (int) ((packed >>> (k)) & 0x7F);
if (grid.isClue(idx)) break;
horiz[idx] += 1;
}
if (k > 0) {
hasSlots = true;
if (k < MIN_LEN7) penalty += 8000;
penalty |= 1;
}
}
}
if (!hasSlots) return 1_000_000_000L;
if ((penalty & 1) == 0) return 1_000_000_000L;
penalty = (penalty & ~3L) + (((long) Math.abs(grid.clueCount() - TARGET_CLUES)) << 3);
var seen = ctx.seen;
var stack = ctx.stack;
seen.clear();
for (int i = 0; i < 65; i += 64) {
for (long bits = (i == 0 ? lo_cl : hi_cl); bits != X; bits &= bits - 1) {
int clueIdx = i + Long.numberOfTrailingZeros(bits);
clueIdx = i + Long.numberOfTrailingZeros(bits);
if (seen.get(clueIdx)) continue;
int size = 0;
stack[0] = clueIdx;
seen.set(clueIdx);
for (int sp = 1; sp > 0; size++) {
long packed = Neighbors9x8.NBR8_PACKED[stack[--sp]];
long packed = NBR8_PACKED[stack[--sp]];
for (int k = 0, n = (int) (packed >>> 56) * 7; k < n; k += 7) {
int nidx = (int) ((packed >>> k) & 0x7F);
if (seen.get(nidx) || grid.notClue(nidx)) continue;
@@ -472,11 +462,11 @@ public record SwedishGenerator(Rng rng) {
for (int i = 0; i < 65; i += 64) {
long bits = (i == 0 ? ~lo_cl : (~hi_cl & 0xFFL));
for (; bits != X; bits &= bits - 1) {
int idx = i + Long.numberOfTrailingZeros(bits);
var rci = IT[idx];
clueIdx = i + Long.numberOfTrailingZeros(bits);
var rci = IT[clueIdx];
if ((4 - rci.nbrCount()) + Long.bitCount(rci.n1() & lo_cl) + Long.bitCount(rci.n2() & hi_cl) >= 3) penalty += 400;
var h = covH[idx];
var v = covV[idx];
var h = covH[clueIdx];
var v = covV[clueIdx];
if (h == 0 && v == 0) penalty += 1500;
else if (h > 0 && v > 0) { /* ok */ } else if (h + v == 1) penalty += 200;
else penalty += 600;
@@ -650,7 +640,7 @@ public record SwedishGenerator(Rng rng) {
undoBuffer[offset] = mask;
return true;
}
static final CandidateInfo[] CANDIDATES = IntStream.range(0, 10192 << 2).mapToObj(CandidateInfo::new).toArray(CandidateInfo[]::new);
static CandidateInfo candidateInfoForPattern(Context ctx, DictEntry entry, int len) {
var pattern = ctx.pattern;
var listBuffer = ctx.intListBuffer;
@@ -665,7 +655,7 @@ public record SwedishGenerator(Rng rng) {
}
if (listCount == 0) {
return CANDIDATES[entry.words.size()];
return CANDIDATES[entry.words.length];
}
// Sort constraints by size to optimize intersection
@@ -801,26 +791,26 @@ public record SwedishGenerator(Rng rng) {
double r = rng.nextFloat();
int idxInArray = (int) (r * r * r * L);
var idx = idxs[idxInArray];
var w = entry.words.get(idx);
var w = entry.words[idx];
if (used.get(w.index())) continue;
if (used.get(w.index)) continue;
if (!placeWord(grid, s, w, ctx.undo, depth)) continue;
used.set(w.index());
used.set(w.index);
assigned.put(k, w);
if (backtrack(depth + 1)) return true;
assigned.remove(k);
used.clear(w.index());
used.clear(w.index);
s.undoPlace(grid, ctx.undo[depth]);
}
stats.backtracks++;
return false;
}
var N = entry.words.size();
var N = entry.words.length;
if (N == 0) {
stats.backtracks++;
return false;
@@ -830,13 +820,13 @@ public record SwedishGenerator(Rng rng) {
for (var t = 0; t < tries; t++) {
double r = rng.nextFloat();
int idxInArray = (int) (r * r * r * N);
var w = entry.words.get(idxInArray);
var w = entry.words[idxInArray];
if (used.get(w.index())) continue;
if (used.get(w.index)) continue;
if (!placeWord(grid, s, w, ctx.undo, depth)) continue;
used.set(w.index());
used.set(w.index);
assigned.put(k, w);
if (backtrack(depth + 1)) return true;

View File

@@ -31,7 +31,7 @@ public class ExportFormatTest {
var clueMap = new HashMap<Integer, Lemma>();
// key = (cellIndex << 4) | direction
var key = (0 << 4) | 2;
var lemma = new Lemma("TEST", 1);
var lemma = new Lemma("TEST");
clueMap.put(key, lemma);
// Manually fill the grid letters for "TEST" at (0,1), (0,2), (0,3), (0,4)

View File

@@ -167,8 +167,9 @@ public class MainTest {
// Regression baseline for seed search starting at 12347, pop 4, gens 20
Assertions.assertEquals(12348, foundSeed, "Found seed changed");
Assertions.assertEquals(22, res.filled().clueMap().size(), "Number of assigned words changed");
Assertions.assertEquals(747.5454545454545, res.filled().stats().simplicity, 1e-9, "Simplicity value changed");
Assertions.assertEquals(Lemma.pack(new byte[]{ 'I', 'E', 'M', 'A', 'N', 'D', 'S' }), res.filled().clueMap().get(515).word());
Assertions.assertEquals(648985643903632391L, res.filled().grid().grid().lo);
Assertions.assertEquals(140L, res.filled().grid().grid().hi);
}
@Test
public void testIsLetterA() {

View File

@@ -2,6 +2,7 @@ package puzzle;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import puzzle.Export.IntListDTO;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
@@ -91,7 +92,7 @@ public class SwedishGeneratorTest {
@Test
void testIntList() {
var list = new IntList();
var list = new IntListDTO();
assertEquals(0, list.size());
for (var i = 0; i < 10; i++) {
list.add(i);
@@ -103,27 +104,27 @@ public class SwedishGeneratorTest {
@Test
void testLemmaAndDict() {
var l2a = new Lemma("IN", 1);
var l4a = new Lemma("INER", 1);
var l6a = new Lemma("INEREN", 1);
var l7a = new Lemma("INERENA", 1);
var l8a = new Lemma("INERENAE", 1);
var l2a = new Lemma("IN");
var l4a = new Lemma("INER");
var l6a = new Lemma("INEREN");
var l7a = new Lemma("INERENA");
var l8a = new Lemma("INERENAE");
var l1 = new Lemma("APPLE", 5);
var l1 = new Lemma("APPLE");
Assertions.assertEquals(Lemma.pack("APPLE".getBytes(StandardCharsets.US_ASCII)), l1.word());
assertEquals(5, l1.len());
assertEquals(5, l1.simpel());
assertEquals((byte) 'A', l1.byteAt(0));
assertEquals(1, l1.intAt(0));
var l2 = new Lemma("AXE", 2);
var l2 = new Lemma("AXE");
var dict = new Dict(new Lemma[]{ l1, l2, l2a, l4a, l6a, l7a, l8a });
assertEquals(1, dict.index()[3].words().size());
assertEquals(1, dict.index()[5].words().size());
assertEquals(1, dict.index()[3].words().length);
assertEquals(1, dict.index()[5].words().length);
var entry3 = dict.index()[3];
assertEquals(1, entry3.words().size());
assertEquals(Lemma.pack("AXE".getBytes(StandardCharsets.US_ASCII)), entry3.words().getFirst().word());
assertEquals(1, entry3.words().length);
assertEquals(Lemma.pack("AXE".getBytes(StandardCharsets.US_ASCII)), entry3.words()[0].word());
// Check pos indexing
// AXE: A at 0, X at 1, E at 2
@@ -179,15 +180,15 @@ public class SwedishGeneratorTest {
@Test
void testCandidateInfoForPattern() {
var l0 = new Lemma("IN", 1);
var l3a = new Lemma("INE", 1);
var l4a = new Lemma("INER", 1);
var l6a = new Lemma("INEREN", 1);
var l7a = new Lemma("INERENA", 1);
var l8a = new Lemma("INERENAE", 1);
var l1 = new Lemma("APPLE", 1);
var l2 = new Lemma("APPLY", 1);
var l3 = new Lemma("BANAN", 1);
var l0 = new Lemma("IN");
var l3a = new Lemma("INE");
var l4a = new Lemma("INER");
var l6a = new Lemma("INEREN");
var l7a = new Lemma("INERENA");
var l8a = new Lemma("INERENAE");
var l1 = new Lemma("APPLE");
var l2 = new Lemma("APPLY");
var l3 = new Lemma("BANAN");
var dict = new Dict(new Lemma[]{ l0, l1, l2, l3, l3a, l4a, l6a, l7a, l8a });
// Pattern "APP--" for length 5
@@ -274,7 +275,7 @@ public class SwedishGeneratorTest {
// 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);
var w1 = new Lemma("ABC");
var undoBuffer = new int[10];
// 1. Successful placement in empty grid
@@ -289,7 +290,7 @@ public class SwedishGeneratorTest {
assertEquals(0L, undoBuffer[1]); // 0 new characters placed
// 3. Conflict: place "ABD" where "ABC" is
var w2 = new Lemma("ABD", 1);
var w2 = new Lemma("ABD");
assertFalse(placeWord(grid, s, w2, undoBuffer, 2));
// Verify grid is unchanged (still "ABC")
assertEquals('A', grid.byteAt(Grid.offset(0, 0)));
@@ -312,7 +313,7 @@ public class SwedishGeneratorTest {
// Slot at 0,1 length 2
var packedPos = ((long) Grid.offset(0, 1)) | (((long) Grid.offset(0, 2)) << 7);
var s = Slot.from((0 << 8) | (1 << 4) | 2, packedPos, 2);
var w = new Lemma("AZ", 1);
var w = new Lemma("AZ");
var undoBuffer = new int[10];
var placed = placeWord(grid, s, w, undoBuffer, 0);