From b66437bb70d438edb51987b592c112953fc2897a Mon Sep 17 00:00:00 2001 From: mike Date: Tue, 20 Jan 2026 21:19:39 +0100 Subject: [PATCH] introduce bitloops --- src/main/generated-sources/puzzle/Config.java | 16 - src/main/java/puzzle/Export.java | 14 +- src/main/java/puzzle/Main.java | 8 +- src/main/java/puzzle/Masker.java | 361 +++++++++--------- src/main/java/puzzle/Meta.java | 52 +-- src/main/java/puzzle/TriggerConstants.java | 6 + src/main/java/puzzle/TriggerNeighbors4x3.java | 5 + ...{Trigger.java => TriggerNeighbors9x8.java} | 2 +- .../java/puzzle/BuildClueAndSimpelIndex.java | 2 - src/test/java/puzzle/ConnectivityTest.java | 199 +++++----- src/test/java/puzzle/CornerClueTest.java | 169 ++++---- src/test/java/puzzle/ExportFormatTest.java | 75 ++-- src/test/java/puzzle/MainTest.java | 22 +- src/test/java/puzzle/MaskerCluesTest.java | 63 +-- src/test/java/puzzle/PerformanceTest.java | 46 +-- .../java/puzzle/SwedishGeneratorTest.java | 26 +- 16 files changed, 502 insertions(+), 564 deletions(-) delete mode 100644 src/main/generated-sources/puzzle/Config.java create mode 100644 src/main/java/puzzle/TriggerConstants.java create mode 100644 src/main/java/puzzle/TriggerNeighbors4x3.java rename src/main/java/puzzle/{Trigger.java => TriggerNeighbors9x8.java} (80%) diff --git a/src/main/generated-sources/puzzle/Config.java b/src/main/generated-sources/puzzle/Config.java deleted file mode 100644 index d10450e..0000000 --- a/src/main/generated-sources/puzzle/Config.java +++ /dev/null @@ -1,16 +0,0 @@ -package puzzle; - -/** - * Generated constants from pom.xml during build via templating-maven-plugin. - */ -public final class Config { - public static final int CLUE_SIZE = 4; - public static final int MIN_LEN = 2; - public static final int MAX_TRIES_PER_SLOT = 1000; - public static final int MAX_LEN = 8; - public static final int PUZZLE_ROWS = 8; - public static final int PUZZLE_COLS = 9; - public static final int PUZZLE_SIZE = PUZZLE_ROWS*PUZZLE_COLS; - public static final int MAX_WORD_LENGTH = PUZZLE_ROWS; - public static final int MAX_WORD_LENGTH_MIN_1 = PUZZLE_ROWS-1; -} diff --git a/src/main/java/puzzle/Export.java b/src/main/java/puzzle/Export.java index b069da1..9f854ba 100644 --- a/src/main/java/puzzle/Export.java +++ b/src/main/java/puzzle/Export.java @@ -45,11 +45,15 @@ public record Export() { 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); + DOWN(CLUE_DOWN, 'B', 'b'), + RIGHT(CLUE_RIGHT, 'A', 'a'), + UP(CLUE_UP, 'C', 'c'), + LEFT(CLUE_LEFT, 'D', 'd'), + NONE(CLUE_LEFT, '?', '?'); final byte dir; + final char slotChar, clueChar; + private static final Clue[] CLUES = new Clue[]{ DOWN, RIGHT, UP, LEFT, NONE, NONE, NONE, NONE, NONE }; + public static Clue from(int dir) { return CLUES[dir]; } } record Strings() { @@ -295,7 +299,7 @@ public record Export() { p.arrowCol() - MIN_C, p.isReversed(), bytes )).toArray(WordOut[]::new); - var total = 0.0001d; + var total = 0.0001d; for (var word : wordsOut) { total += word.complex(); } diff --git a/src/main/java/puzzle/Main.java b/src/main/java/puzzle/Main.java index 6923aeb..9ec3d32 100644 --- a/src/main/java/puzzle/Main.java +++ b/src/main/java/puzzle/Main.java @@ -40,7 +40,6 @@ public class Main { public int pop = SSIZE * 2; public int offspring = SSIZE * 3; public int gens = 600; - public String wordsPath = "nl_score_hints_v4.csv"; public double minSimplicity = 0; // 0 means no limit public int threads = Math.max(1, Runtime.getRuntime().availableProcessors()); public int tries = threads; @@ -65,7 +64,6 @@ public class Main { section("Puzzle Generator"); info("OutputDir : " + OUT_DIR); - info("WordsFile : " + opts.wordsPath); section("Settings"); printSettings(opts); @@ -88,7 +86,7 @@ public class Main { System.out.print(indentLines(res.grid().renderHuman(res.clues().c()), " ")); var exported = res.exportFormatFromFilled(new Rewards(50, 2, 1)); - + section("Clues"); info("status : generating..."); info("generatedFor : " + exported.words().length); @@ -146,7 +144,6 @@ public class Main { System.out.printf(Locale.ROOT, " %-14s: %d%n", "population", o.pop); System.out.printf(Locale.ROOT, " %-14s: %d%n", "offspring", o.offspring); System.out.printf(Locale.ROOT, " %-14s: %d%n", "generations", o.gens); - System.out.printf(Locale.ROOT, " %-14s: %s%n", "wordsPath", o.wordsPath); System.out.printf(Locale.ROOT, " %-14s: %.2f%n", "minSimplicity", o.minSimplicity); System.out.printf(Locale.ROOT, " %-14s: %d%n", "threads", o.threads); } @@ -229,9 +226,6 @@ public class Main { } else if (a.equals("--tries")) { out.tries = Integer.parseInt(v); i++; - } else if (a.equals("--words")) { - out.wordsPath = v; - i++; } else if (a.equals("--min-simplicity")) { out.minSimplicity = Double.parseDouble(v); i++; diff --git a/src/main/java/puzzle/Masker.java b/src/main/java/puzzle/Masker.java index b80756f..8501caa 100644 --- a/src/main/java/puzzle/Masker.java +++ b/src/main/java/puzzle/Masker.java @@ -11,51 +11,46 @@ import static puzzle.SwedishGenerator.*; public final class Masker { - public static final rci[] IT = Neighbors9x8.IT; - private final Rng rng; - private final int[] stack; - private final Clues cache; - private final int[] activeCIdx = new int[SwedishGenerator.SIZE]; - private final long[] activeSLo = new long[SwedishGenerator.SIZE]; - private final long[] activeSHi = new long[SwedishGenerator.SIZE]; - private final long[] adjLo = new long[SwedishGenerator.SIZE]; - private final long[] adjHi = new long[SwedishGenerator.SIZE]; - private final int[] rCount = new int[8]; - private final int[] cCount = new int[9]; - private static final long[] NBR_LO = new long[SwedishGenerator.SIZE]; - private static final long[] NBR_HI = new long[SwedishGenerator.SIZE]; - static { - for (int i = 0; i < SwedishGenerator.SIZE; i++) { - NBR_LO[i] = IT[i].n1(); - NBR_HI[i] = IT[i].n2(); - } - } + public static final rci[] IT = Neighbors9x8.IT; + private final Rng rng; + private final int[] stack; + private final Clues cache; + private final int[] activeCIdx = new int[SwedishGenerator.SIZE]; + private final long[] activeSLo = new long[SwedishGenerator.SIZE]; + private final long[] activeSHi = new long[SwedishGenerator.SIZE]; + private final long[] adjLo = new long[SwedishGenerator.SIZE]; + private final long[] adjHi = new long[SwedishGenerator.SIZE]; + private final int[] rCount = new int[8]; + private final int[] cCount = new int[9]; + private static final long[] NBR_LO = Neighbors9x8.NBR_LO; + private static final long[] NBR_HI = Neighbors9x8.NBR_HI; + public Masker(Rng rng, int[] stack, Clues cache) { this.rng = rng; this.stack = stack; this.cache = cache; } - public boolean isValid(Clues c, int minLen) { - return findOffendingClue(c, minLen) == -1; + public boolean isValid(Clues c) { + return findOffendingClue(c) == -1; } - public int findOffendingClue(Clues grid, int minLen) { + public int findOffendingClue(Clues grid) { if (((grid.xlo & grid.rlo) & grid.lo) != X) return numberOfTrailingZeros((grid.xlo & grid.rlo) & grid.lo); if (((grid.xhi & grid.rhi) & grid.hi) != X) return 64 | numberOfTrailingZeros((grid.xhi & grid.rhi) & grid.hi); - int num = 0; - for (long bits = grid.lo; bits != X; bits &= bits - 1) activeCIdx[num++] = numberOfTrailingZeros(bits); - for (long bits = grid.hi; bits != X; bits &= bits - 1) activeCIdx[num++] = 64 | numberOfTrailingZeros(bits); + var num = 0; + for (var bits = grid.lo; bits != X; bits &= bits - 1) activeCIdx[num++] = numberOfTrailingZeros(bits); + for (var bits = grid.hi; bits != X; bits &= bits - 1) activeCIdx[num++] = 64 | numberOfTrailingZeros(bits); if (num == 0) return -1; - int start = rng.randint0_SIZE() % num; - int n = 0; - for (int i = 0; i < num; i++) { - int idx = activeCIdx[(start + i) % num]; - int dir = grid.getDir(idx); - int key = Slot.packSlotKey(idx, dir); + var start = rng.randint0_SIZE() % num; + var n = 0; + for (var i = 0; i < num; i++) { + var idx = activeCIdx[(start + i) % num]; + int dir = grid.getDir(idx); + var key = Slot.packSlotKey(idx, dir); long sLo = PATH_LO[key], sHi = PATH_HI[key]; long hLo = sLo & grid.lo, hHi = sHi & grid.hi; if (Slotinfo.increasing(key)) { @@ -69,8 +64,8 @@ public final class Masker { sLo = 0; } else if (hLo != X) { sLo &= -(1L << (63 - numberOfLeadingZeros(hLo)) << 1); } } - if (bitCount(sLo) + bitCount(sHi) < minLen) return idx; - for (int j = 0; j < n; j++) if (bitCount(sLo & activeSLo[j]) + bitCount(sHi & activeSHi[j]) > 1) return idx; + if (bitCount(sLo) + bitCount(sHi) < MIN_LEN) return idx; + for (var j = 0; j < n; j++) if (bitCount(sLo & activeSLo[j]) + bitCount(sHi & activeSHi[j]) > 1) return idx; activeSLo[n] = sLo; activeSHi[n] = sHi; n++; @@ -78,10 +73,10 @@ public final class Masker { return -1; } - public void cleanup(Clues c, int minLen) { - int guard = 0; + public void cleanup(Clues c) { + var guard = 0; while (guard++ < 50) { - int offending = findOffendingClue(c, minLen); + var offending = findOffendingClue(c); if (offending == -1) break; if ((offending & 64) == 0) c.clearClueLo(~(1L << offending)); else c.clearClueHi(~(1L << (offending & 63))); @@ -91,12 +86,12 @@ public final class Masker { public static final int[][] MUTATE_RI = new int[SwedishGenerator.SIZE][625]; static { - for (int i = 0; i < SwedishGenerator.SIZE; i++) { - 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++) { + for (var i = 0; i < SwedishGenerator.SIZE; i++) { + var k = 0; + for (var dr1 = -2; dr1 <= 2; dr1++) + for (var dr2 = -2; dr2 <= 2; dr2++) + for (var dc1 = -2; dc1 <= 2; dc1++) + for (var dc2 = -2; dc2 <= 2; dc2++) { val ti = IT[i]; MUTATE_RI[i][k++] = offset(clamp(ti.r() + dr1 + dr2, 0, R - 1), clamp(ti.c() + dc1 + dc2, 0, C - 1)); @@ -107,58 +102,58 @@ public final class Masker { // right/down => increasing indices; up/left => decreasing indices // first clue is highest index among hits (hi first, then lo) private static void processSlotRev(Clues c, SlotVisitor visitor, int key) { - long rayLo = PATH_LO[key]; - long rayHi = PATH_HI[key]; + var rayLo = PATH_LO[key]; + var rayHi = PATH_HI[key]; // only consider clue cells - long hitsLo = rayLo & c.lo; - long hitsHi = rayHi & c.hi; + var hitsLo = rayLo & c.lo; + var hitsHi = rayHi & c.hi; if (hitsHi != X) { - int msb = 63 - numberOfLeadingZeros(hitsHi); - long stop = 1L << msb; + var msb = 63 - numberOfLeadingZeros(hitsHi); + var stop = 1L << msb; rayHi &= -(stop << 1); // keep bits > stop rayLo = 0; // lo indices are below stop } else if (hitsLo != X) { - int msb = 63 - numberOfLeadingZeros(hitsLo); - long stop = 1L << msb; + var msb = 63 - numberOfLeadingZeros(hitsLo); + var stop = 1L << msb; rayLo &= -(stop << 1); } if (Long.bitCount(rayLo) + Long.bitCount(rayHi) >= MIN_LEN) visitor.visit(key, rayLo, rayHi); } private static boolean validSlotRev(long lo, long hi, int min, int key) { - long rayLo = PATH_LO[key]; - long rayHi = PATH_HI[key]; + var rayLo = PATH_LO[key]; + var rayHi = PATH_HI[key]; // only consider clue cells - long hitsLo = rayLo & lo; - long hitsHi = rayHi & hi; + var hitsLo = rayLo & lo; + var hitsHi = rayHi & hi; if (hitsHi != X) return (Long.bitCount(rayHi & -(1L << 63 - numberOfLeadingZeros(hitsHi) << 1)) >= min); else if (hitsLo != X) return (Long.bitCount(rayLo & -(1L << 63 - numberOfLeadingZeros(hitsLo) << 1)) + Long.bitCount(rayHi) >= min); else return (Long.bitCount(rayLo) + Long.bitCount(rayHi) >= min); } private static boolean validSlot(long lo, long hi, int min, int key) { - long rayLo = PATH_LO[key]; - long rayHi = PATH_HI[key]; - long hitsLo = rayLo & lo; - long hitsHi = rayHi & hi; + var rayLo = PATH_LO[key]; + var rayHi = PATH_HI[key]; + var hitsLo = rayLo & lo; + var hitsHi = rayHi & hi; if (hitsLo != X) return (Long.bitCount(rayLo & ((1L << numberOfTrailingZeros(hitsLo)) - 1)) >= min); else if (hitsHi != X) return (Long.bitCount(rayLo) + Long.bitCount(rayHi & ((1L << numberOfTrailingZeros(hitsHi)) - 1)) >= min); else return (Long.bitCount(rayLo) + Long.bitCount(rayHi) >= min); } private static void processSlot(Clues c, SlotVisitor visitor, int key) { - long rayLo = PATH_LO[key]; - long rayHi = PATH_HI[key]; - long hitsLo = rayLo & c.lo; - long hitsHi = rayHi & c.hi; + var rayLo = PATH_LO[key]; + var rayHi = PATH_HI[key]; + var hitsLo = rayLo & c.lo; + var hitsHi = rayHi & c.hi; if (hitsLo != X) { - long stop = 1L << numberOfTrailingZeros(hitsLo); + var stop = 1L << numberOfTrailingZeros(hitsLo); rayLo &= (stop - 1); rayHi = 0; } else if (hitsHi != X) { - long stop = 1L << numberOfTrailingZeros(hitsHi); + var stop = 1L << numberOfTrailingZeros(hitsHi); rayHi &= (stop - 1); } if (Long.bitCount(rayLo) + Long.bitCount(rayHi) >= MIN_LEN) @@ -174,13 +169,13 @@ public final class Masker { return Masker.scoreSlots(slots); } public static Slotinfo[] scoreSlots(Slot[] slots) { - val count = new byte[SwedishGenerator.SIZE]; - Slotinfo[] slotInfo = new Slotinfo[slots.length]; + val count = new byte[SwedishGenerator.SIZE]; + var slotInfo = new Slotinfo[slots.length]; for (var s : slots) { - for (long b = s.lo; b != X; b &= b - 1) count[numberOfTrailingZeros(b)]++; - for (long b = s.hi; b != X; b &= b - 1) count[64 | numberOfTrailingZeros(b)]++; + for (var b = s.lo; b != X; b &= b - 1) count[numberOfTrailingZeros(b)]++; + for (var b = s.hi; b != X; b &= b - 1) count[64 | numberOfTrailingZeros(b)]++; } - for (int i = 0; i < slots.length; i++) { + for (var i = 0; i < slots.length; i++) { var slot = slots[i]; slotInfo[i] = new Slotinfo(slot.key, slot.lo, slot.hi, slotScore(count, slot.lo, slot.hi), new Assign(), slot.entry, Math.min(slot.entry.words().length, MAX_TRIES_PER_SLOT)); @@ -188,9 +183,9 @@ public final class Masker { return slotInfo; } public static int slotScore(byte[] count, long lo, long hi) { - int cross = 0; - for (long b = lo; b != X; b &= b - 1) cross += (count[numberOfTrailingZeros(b)] - 1); - for (long b = hi; b != X; b &= b - 1) cross += (count[64 | numberOfTrailingZeros(b)] - 1); + var cross = 0; + for (var b = lo; b != X; b &= b - 1) cross += (count[numberOfTrailingZeros(b)] - 1); + for (var b = hi; b != X; b &= b - 1) cross += (count[64 | numberOfTrailingZeros(b)] - 1); return cross * 10 + Slot.length(lo, hi); } public static int clamp(int x, int a, int b) { return Math.max(a, Math.min(b, x)); } @@ -200,21 +195,21 @@ public final class Masker { long cHLo = 0L, cHHi = 0L, cVLo = 0L, cVHi = 0L; long cHLo2 = 0L, cHHi2 = 0L, cVLo2 = 0L, cVHi2 = 0L; - long lo_cl = grid.lo, hi_cl = grid.hi; - long penalty = (((long) Math.abs(grid.clueCount() - clueSize)) * 16000L); - boolean hasSlots = false; + long lo_cl = grid.lo, hi_cl = grid.hi; + var penalty = (((long) Math.abs(grid.clueCount() - clueSize)) * 16000L); + var hasSlots = false; /* if (!isValid(grid, 2)) { throw new RuntimeException("Invalid grid configuration for mask fitness calculation"); //return 1_000_000_000L; }*/ - int numClues = 0; - for (long bits = lo_cl; bits != X; bits &= bits - 1) { - long lsb = bits & -bits; - int clueIdx = numberOfTrailingZeros(lsb); + var numClues = 0; + for (var bits = lo_cl; bits != X; bits &= bits - 1) { + var lsb = bits & -bits; + var clueIdx = numberOfTrailingZeros(lsb); int dir = grid.getDir(clueIdx); - int key = Slot.packSlotKey(clueIdx, dir); - long rLo = PATH_LO[key], rHi = PATH_HI[key]; + var key = Slot.packSlotKey(clueIdx, dir); + long rLo = PATH_LO[key], rHi = PATH_HI[key]; long hLo = rLo & lo_cl, hHi = rHi & hi_cl; if (Slotinfo.increasing(key)) { if (hLo != X) { @@ -222,11 +217,11 @@ public final class Masker { rHi = 0; } else if (hHi != X) rHi &= ((1L << numberOfTrailingZeros(hHi)) - 1); } else if (hHi != X) { - int msb = 63 - numberOfLeadingZeros(hHi); + var msb = 63 - numberOfLeadingZeros(hHi); rHi &= -(1L << msb << 1); rLo = 0; } else if (hLo != X) { - int msb = 63 - numberOfLeadingZeros(hLo); + var msb = 63 - numberOfLeadingZeros(hLo); rLo &= -(1L << msb << 1); } @@ -249,16 +244,16 @@ public final class Masker { cVHi |= rHi; } if ((bitCount(rLo) + bitCount(rHi)) < MIN_LEN) penalty += 8000; - int wordLen = bitCount(rLo) + bitCount(rHi); + var wordLen = bitCount(rLo) + bitCount(rHi); if (wordLen > 6) penalty += (wordLen - 6) * 1000L; } else penalty += 25000; } - for (long bits = hi_cl; bits != X; bits &= bits - 1) { - long lsb = bits & -bits; - int clueIdx = numberOfTrailingZeros(lsb); + for (var bits = hi_cl; bits != X; bits &= bits - 1) { + var lsb = bits & -bits; + var clueIdx = numberOfTrailingZeros(lsb); int dir = grid.getDir(64 | clueIdx); - int key = Slot.packSlotKey(64 | clueIdx, dir); - long rLo = PATH_LO[key], rHi = PATH_HI[key]; + var key = Slot.packSlotKey(64 | clueIdx, dir); + long rLo = PATH_LO[key], rHi = PATH_HI[key]; long hLo = rLo & lo_cl, hHi = rHi & hi_cl; if (Slotinfo.increasing(key)) { if (hLo != X) { @@ -266,11 +261,11 @@ public final class Masker { rHi = 0; } else if (hHi != X) rHi &= ((1L << numberOfTrailingZeros(hHi)) - 1); } else if (hHi != X) { - int msb = 63 - numberOfLeadingZeros(hHi); + var msb = 63 - numberOfLeadingZeros(hHi); rHi &= -(1L << msb << 1); rLo = 0; } else if (hLo != X) { - int msb = 63 - numberOfLeadingZeros(hLo); + var msb = 63 - numberOfLeadingZeros(hLo); rLo &= -(1L << msb << 1); } @@ -293,7 +288,7 @@ public final class Masker { cVHi |= rHi; } if ((bitCount(rLo) + bitCount(rHi)) < MIN_LEN) penalty += 8000; - int wordLen = bitCount(rLo) + bitCount(rHi); + var wordLen = bitCount(rLo) + bitCount(rHi); if (wordLen > 6) penalty += (wordLen - 6) * 1000L; } else penalty += 25000; } @@ -302,22 +297,22 @@ public final class Masker { Arrays.fill(rCount, 0); Arrays.fill(cCount, 0); - for (int i = 0; i < numClues; i++) { - int idx = activeCIdx[i]; + for (var i = 0; i < numClues; i++) { + var idx = activeCIdx[i]; rCount[idx & 7]++; cCount[idx >> 3]++; } - for (int rc : rCount) if (rc < 2) penalty += (2 - rc) * 4000L; - for (int cc : cCount) if (cc < 2) penalty += (2 - cc) * 4000L; + for (var rc : rCount) if (rc < 2) penalty += (2 - rc) * 4000L; + for (var cc : cCount) if (cc < 2) penalty += (2 - cc) * 4000L; // Connectiviteitscheck - for (int i = 0; i < numClues; i++) { + for (var i = 0; i < numClues; i++) { adjLo[i] = 0; adjHi[i] = 0; } - for (int i = 0; i < numClues; i++) { - for (int j = i + 1; j < numClues; j++) { - boolean connected = false; + for (var i = 0; i < numClues; i++) { + for (var j = i + 1; j < numClues; j++) { + var connected = false; // 1. Intersectie if (((activeSLo[i] & activeSLo[j]) | (activeSHi[i] & activeSHi[j])) != 0) { connected = true; @@ -333,31 +328,31 @@ public final class Masker { } if (numClues > 0) { - int maxReached = 0; + var maxReached = 0; long totalReachedLo = 0, totalReachedHi = 0; - for (int i = 0; i < numClues; i++) { + for (var i = 0; i < numClues; i++) { if (i < 64) { if ((totalReachedLo & (1L << i)) != 0) continue; } else { if ((totalReachedHi & (1L << (i - 64))) != 0) continue; } - long currentReachedLo = (i < 64) ? (1L << i) : 0; - long currentReachedHi = (i >= 64) ? (1L << (i - 64)) : 0; + var currentReachedLo = (i < 64) ? (1L << i) : 0; + var currentReachedHi = (i >= 64) ? (1L << (i - 64)) : 0; stack[0] = i; - int sp = 1; - int count = 0; + var sp = 1; + var count = 0; while (sp > 0) { - int cur = stack[--sp]; + var cur = stack[--sp]; count++; - long nLo = adjLo[cur] & ~currentReachedLo; - long nHi = adjHi[cur] & ~currentReachedHi; + var nLo = adjLo[cur] & ~currentReachedLo; + var nHi = adjHi[cur] & ~currentReachedHi; while (nLo != 0) { - long lsb = nLo & -nLo; - int idx = numberOfTrailingZeros(lsb); + var lsb = nLo & -nLo; + var idx = numberOfTrailingZeros(lsb); currentReachedLo |= lsb; stack[sp++] = idx; nLo &= ~lsb; } while (nHi != 0) { - long lsb = nHi & -nHi; - int idx = 64 | numberOfTrailingZeros(lsb); + var lsb = nHi & -nHi; + var idx = 64 | numberOfTrailingZeros(lsb); currentReachedHi |= lsb; stack[sp++] = idx; nHi &= ~lsb; @@ -373,22 +368,22 @@ public final class Masker { } } - for (long bits = ~lo_cl & MASK_LO; bits != X; bits &= bits - 1) { - int clueIdx = numberOfTrailingZeros(bits); + for (var bits = ~lo_cl & MASK_LO; bits != X; bits &= bits - 1) { + var clueIdx = numberOfTrailingZeros(bits); var rci = IT[clueIdx]; if ((4 - rci.nbrCount()) + bitCount(rci.n1() & lo_cl) + bitCount(rci.n2() & hi_cl) >= 3) penalty += 400; - boolean h = (cHLo & (1L << clueIdx)) != X; - boolean v = (cVLo & (1L << clueIdx)) != X; + var h = (cHLo & (1L << clueIdx)) != X; + var v = (cVLo & (1L << clueIdx)) != X; if (!h && !v) penalty += 2000; else if (h && v) { /* ok */ } else if (((h ? cHLo2 : cVLo2) & (1L << clueIdx)) != X) penalty += 1000; else penalty += 1000; } - for (long bits = ~hi_cl & MASK_HI; bits != X; bits &= bits - 1) { - int clueIdx = numberOfTrailingZeros(bits); + for (var bits = ~hi_cl & MASK_HI; bits != X; bits &= bits - 1) { + var clueIdx = numberOfTrailingZeros(bits); var rci = IT[64 | clueIdx]; if ((4 - rci.nbrCount()) + bitCount(rci.n1() & lo_cl) + bitCount(rci.n2() & hi_cl) >= 3) penalty += 400; - boolean h = (cHHi & (1L << clueIdx)) != X; - boolean v = (cVHi & (1L << clueIdx)) != X; + var h = (cHHi & (1L << clueIdx)) != X; + var v = (cVHi & (1L << clueIdx)) != X; if (!h && !v) penalty += 2000; else if (h && v) { /* ok */ } else if (((h ? cHHi2 : cVHi2) & (1L << clueIdx)) != X) penalty += 1000; @@ -402,20 +397,23 @@ public final class Masker { else compHi = lowestOneBit(remHi); long lastLo, lastHi; do { - lastLo = compLo; lastHi = compHi; + lastLo = compLo; + lastHi = compHi; long expandedLo = 0, expandedHi = 0; - for (long bits = compLo; bits != X; bits &= bits - 1) { - int idx = numberOfTrailingZeros(bits); - expandedLo |= NBR_LO[idx]; expandedHi |= NBR_HI[idx]; + for (var bits = compLo; bits != X; bits &= bits - 1) { + var idx = numberOfTrailingZeros(bits); + expandedLo |= NBR_LO[idx]; + expandedHi |= NBR_HI[idx]; } - for (long bits = compHi; bits != X; bits &= bits - 1) { - int idx = 64 | numberOfTrailingZeros(bits); - expandedLo |= NBR_LO[idx]; expandedHi |= NBR_HI[idx]; + for (var bits = compHi; bits != X; bits &= bits - 1) { + var idx = 64 | numberOfTrailingZeros(bits); + expandedLo |= NBR_LO[idx]; + expandedHi |= NBR_HI[idx]; } compLo |= expandedLo & lo_cl; compHi |= expandedHi & hi_cl; } while (compLo != lastLo || compHi != lastHi); - int s = bitCount(compLo) + bitCount(compHi); + var s = bitCount(compLo) + bitCount(compHi); if (s >= 2) penalty += (long) (s - 1) * 520; remLo &= ~compLo; remHi &= ~compHi; @@ -432,19 +430,19 @@ public final class Masker { if (isLo(ri)) { if (g.isClueLo(ri)) continue; var d_idx = rng.randomClueDir(); - int key = Slot.packSlotKey(ri, d_idx); + var key = Slot.packSlotKey(ri, d_idx); if (g.hasRoomForClue(key)) { g.setClueLo(1L << ri, d_idx); - if (isValid(g, MIN_LEN)) placed++; + if (isValid(g)) placed++; else g.clearClueLo(~(1L << ri)); } } else { if (g.isClueHi(ri)) continue; var d_idx = rng.randomClueDir(); - int key = Slot.packSlotKey(ri, d_idx); + var key = Slot.packSlotKey(ri, d_idx); if (g.hasRoomForClue(key)) { g.setClueHi(1L << (ri & 63), d_idx); - if (isValid(g, MIN_LEN)) placed++; + if (isValid(g)) placed++; else g.clearClueHi(~(1L << (ri & 63))); } } @@ -458,62 +456,59 @@ public final class Masker { for (int k = 0, ri; k < 6; k++) { ri = bytes[rng.randint0_624()]; if (c.notClue(ri)) { // ADD - byte d = rng.randomClueDir(); - int key = Slot.packSlotKey(ri, d); + var d = rng.randomClueDir(); + var key = Slot.packSlotKey(ri, d); if (c.hasRoomForClue(key)) { if (isLo(ri)) { c.setClueLo(1L << ri, d); - if (!isValid(c, MIN_LEN)) c.clearClueLo(~(1L << ri)); + if (!isValid(c)) c.clearClueLo(~(1L << ri)); else continue; } else { c.setClueHi(1L << (ri & 63), d); - if (!isValid(c, MIN_LEN)) c.clearClueHi(~(1L << (ri & 63))); + if (!isValid(c)) c.clearClueHi(~(1L << (ri & 63))); else continue; } } } else { // HAS CLUE var op = rng.randomClueDir(); if (op < 2) { // REMOVE - byte oldD = c.getDir(ri); + var oldD = c.getDir(ri); if (isLo(ri)) { c.clearClueLo(~(1L << ri)); - if (!isValid(c, MIN_LEN)) c.setClueLo(1L << ri, oldD); + if (!isValid(c)) c.setClueLo(1L << ri, oldD); else continue; } else { c.clearClueHi(~(1L << (ri & 63))); - if (!isValid(c, MIN_LEN)) c.setClueHi(1L << (ri & 63), oldD); + if (!isValid(c)) c.setClueHi(1L << (ri & 63), oldD); else continue; } - - /* if (isLo(ri)) c.clearClueLo(~(1L << ri)); - else c.clearClueHi(~(1L << (ri & 63)));*/ } if (op < 4) { // CHANGE DIRECTION - byte d = rng.randomClueDir(); - int key = Slot.packSlotKey(ri, d); + var d = rng.randomClueDir(); + var key = Slot.packSlotKey(ri, d); if (c.hasRoomForClue(key)) { - byte oldD = c.getDir(ri); + var oldD = c.getDir(ri); if (isLo(ri)) { c.setClueLo(1L << ri, d); - if (!isValid(c, MIN_LEN)) c.setClueLo(1L << ri, oldD); + if (!isValid(c)) c.setClueLo(1L << ri, oldD); else continue; } else { c.setClueHi(1L << (ri & 63), d); - if (!isValid(c, MIN_LEN)) c.setClueHi(1L << (ri & 63), oldD); + if (!isValid(c)) c.setClueHi(1L << (ri & 63), oldD); else continue; } } } // MOVE - int nri = bytes[rng.randint0_624()]; + var nri = bytes[rng.randint0_624()]; if (c.notClue(nri)) { - byte d = c.getDir(ri); - int nkey = Slot.packSlotKey(nri, d); + var d = c.getDir(ri); + var nkey = Slot.packSlotKey(nri, d); if (c.hasRoomForClue(nkey)) { if (isLo(ri)) c.clearClueLo(~(1L << ri)); else c.clearClueHi(~(1L << (ri & 63))); if (isLo(nri)) c.setClueLo(1L << nri, d); else c.setClueHi(1L << (nri & 63), d); - if (!isValid(c, MIN_LEN)) { + if (!isValid(c)) { if (isLo(nri)) c.clearClueLo(~(1L << nri)); else c.clearClueHi(~(1L << (nri & 63))); if (isLo(ri)) c.setClueLo(1L << ri, d); @@ -535,7 +530,7 @@ public final class Masker { long maskLo = 0, maskHi = 0; for (var rci : IT) if ((rci.cross_r()) * nc + (rci.cross_c()) * nr < 0) { - int i = rci.i(); + var i = rci.i(); if ((i & 64) == 0) maskLo |= (1L << i); else maskHi |= (1L << (i - 64)); } @@ -548,7 +543,7 @@ public final class Masker { (a.rhi & ~maskHi) | (other.rhi & maskHi), (a.xlo & ~maskLo) | (other.xlo & maskLo), (a.xhi & ~maskHi) | (other.xhi & maskHi)); - cleanup(c, MIN_LEN); + cleanup(c); return c; } @@ -585,36 +580,36 @@ public final class Masker { } } if (Main.VERBOSE) System.out.println("generateMask init pop: " + popSize + " clueSize: " + clueSize); - GridAndFit[] pop = new GridAndFit[popSize]; - for (int i = 0; i < popSize; i++) { + var pop = new GridAndFit[popSize]; + for (var i = 0; i < popSize; i++) { if (Thread.currentThread().isInterrupted()) return null; pop[i] = new GridAndFit(hillclimb(randomMask(clueSize), clueSize, 180)); } - for (int gen = 0; gen < gens; gen++) { + for (var gen = 0; gen < gens; gen++) { if (Thread.currentThread().isInterrupted()) break; - GridAndFit[] children = new GridAndFit[offspring]; - int childCount = 0; - for (int k = 0; k < offspring; k++) { + var children = new GridAndFit[offspring]; + var childCount = 0; + for (var k = 0; k < offspring; k++) { if (Thread.currentThread().isInterrupted()) break; - GridAndFit p1 = rng.rand(pop); - GridAndFit p2 = rng.rand(pop); - Clues child = crossover(p1.grid, p2.grid); + var p1 = rng.rand(pop); + var p2 = rng.rand(pop); + var child = crossover(p1.grid, p2.grid); children[k] = new GridAndFit(hillclimb(child, clueSize, 70)); childCount++; } - GridAndFit[] combined = new GridAndFit[pop.length + childCount]; + var combined = new GridAndFit[pop.length + childCount]; System.arraycopy(pop, 0, combined, 0, pop.length); System.arraycopy(children, 0, combined, pop.length, childCount); Arrays.sort(combined, Comparator.comparingLong(GridAndFit::fit)); - GridAndFit[] next = new GridAndFit[popSize]; - int nextCount = 0; - for (GridAndFit cand : combined) { + var next = new GridAndFit[popSize]; + var nextCount = 0; + for (var cand : combined) { if (nextCount >= popSize) break; - boolean unique = true; - for (int i = 0; i < nextCount; i++) { + var unique = true; + for (var i = 0; i < nextCount; i++) { if (cand.grid.similarity(next[i].grid) > 0.92) { unique = false; break; @@ -624,10 +619,10 @@ public final class Masker { } if (nextCount < popSize) { - for (GridAndFit cand : combined) { + for (var cand : combined) { if (nextCount >= popSize) break; - boolean alreadyIn = false; - for (int i = 0; i < nextCount; i++) { + var alreadyIn = false; + for (var i = 0; i < nextCount; i++) { if (cand == next[i]) { alreadyIn = true; break; @@ -641,9 +636,9 @@ public final class Masker { if (Main.VERBOSE && (gen & 15) == 15) System.out.println(" gen " + gen + "/" + gens + " bestFitness=" + pop[0].fit()); } if (pop.length == 0) return null; - GridAndFit best = pop[0]; - for (int i = 1; i < pop.length; i++) { - GridAndFit x = pop[i]; + var best = pop[0]; + for (var i = 1; i < pop.length; i++) { + var x = pop[i]; if (x.fit() < best.fit()) best = x; } return best.grid; @@ -698,8 +693,8 @@ public final class Masker { public int clueCount() { return bitCount(lo) + bitCount(hi); } public double similarity(Clues b) { - long matchLo = (~(lo ^ b.lo)) & (~lo | (~(vlo ^ b.vlo) & ~(rlo ^ b.rlo) & ~(xlo ^ b.xlo))); - long matchHi = (~(hi ^ b.hi)) & (~hi | (~(vhi ^ b.vhi) & ~(rhi ^ b.rhi) & ~(xhi ^ b.xhi))); + var matchLo = (~(lo ^ b.lo)) & (~lo | (~(vlo ^ b.vlo) & ~(rlo ^ b.rlo) & ~(xlo ^ b.xlo))); + var matchHi = (~(hi ^ b.hi)) & (~hi | (~(vhi ^ b.vhi) & ~(rhi ^ b.rhi) & ~(xhi ^ b.xhi))); return (bitCount(matchLo & MASK_LO) + bitCount(matchHi & MASK_HI)) / SIZED; } @@ -733,14 +728,14 @@ public final class Masker { } public byte getDir(int index) { if ((index & 64) == 0) { - int v = (vlo & (1L << index)) != 0 ? 1 : 0; - int r = (rlo & (1L << index)) != 0 ? 1 : 0; - int x = (xlo & (1L << index)) != 0 ? 1 : 0; + var v = (vlo & (1L << index)) != 0 ? 1 : 0; + var r = (rlo & (1L << index)) != 0 ? 1 : 0; + var x = (xlo & (1L << index)) != 0 ? 1 : 0; return (byte) ((x << 2) | (r << 1) | v); } else { - int v = (vhi & (1L << (index & 63))) != 0 ? 1 : 0; - int r = (rhi & (1L << (index & 63))) != 0 ? 1 : 0; - int x = (xhi & (1L << (index & 63))) != 0 ? 1 : 0; + var v = (vhi & (1L << (index & 63))) != 0 ? 1 : 0; + var r = (rhi & (1L << (index & 63))) != 0 ? 1 : 0; + var x = (xhi & (1L << (index & 63))) != 0 ? 1 : 0; return (byte) ((x << 2) | (r << 1) | v); } } diff --git a/src/main/java/puzzle/Meta.java b/src/main/java/puzzle/Meta.java index 807fedc..09a5aa0 100644 --- a/src/main/java/puzzle/Meta.java +++ b/src/main/java/puzzle/Meta.java @@ -37,19 +37,19 @@ public class Meta { // --- Lookup: w -> i using mmap --- static int findIndexInMapMmap(Path mapFile, long target) throws IOException { - try (FileChannel ch = FileChannel.open(mapFile, StandardOpenOption.READ)) { - MappedByteBuffer mbb = (MappedByteBuffer) ch.map(FileChannel.MapMode.READ_ONLY, 0, ch.size()).order(ORDER); + try (var ch = FileChannel.open(mapFile, StandardOpenOption.READ)) { + var mbb = (MappedByteBuffer) ch.map(FileChannel.MapMode.READ_ONLY, 0, ch.size()).order(ORDER); - int magic = mbb.getInt(0); - int ver = mbb.getInt(4); - int n = mbb.getInt(8); + var magic = mbb.getInt(0); + var ver = mbb.getInt(4); + var n = mbb.getInt(8); if (magic != MAP_MAGIC || ver != VERSION) throw new IOException("Bad map file"); int lo = 0, hi = n - 1; while (lo <= hi) { - int mid = (lo + hi) >>> 1; - int off = 12 + mid * 8; - long key = mbb.getLong(off); + var mid = (lo + hi) >>> 1; + var off = 12 + mid * 8; + var key = mbb.getLong(off); if (key < target) lo = mid + 1; else if (key > target) hi = mid - 1; @@ -61,41 +61,41 @@ public class Meta { // --- Read record i from shard.data (your format) --- static ShardLem readRecord(Path shardFile, long w, int i) throws IOException { - try (FileChannel ch = FileChannel.open(shardFile, StandardOpenOption.READ)) { - ByteBuffer hdr = ByteBuffer.allocate(12).order(ORDER); + try (var ch = FileChannel.open(shardFile, StandardOpenOption.READ)) { + var hdr = ByteBuffer.allocate(12).order(ORDER); ch.read(hdr); hdr.flip(); - int magic = hdr.getInt(); - int ver = hdr.getInt(); - int n = hdr.getInt(); + var magic = hdr.getInt(); + var ver = hdr.getInt(); + var n = hdr.getInt(); if (magic != SHARD_MAGIC || ver != VERSION) throw new IOException("Bad shard file"); if (i < 0 || i >= n) throw new IndexOutOfBoundsException(); - long tableStart = 12L; - long dataStart = 12L + (long) n * 4L; + var tableStart = 12L; + var dataStart = 12L + (long) n * 4L; - int offI = readIntAt(ch, tableStart + (long) i * 4L); - int offIp = (i + 1 < n) + var offI = readIntAt(ch, tableStart + (long) i * 4L); + var offIp = (i + 1 < n) ? readIntAt(ch, tableStart + (long) (i + 1) * 4L) : (int) (ch.size() - dataStart); - int len = offIp - offI; - ByteBuffer buf = ByteBuffer.allocate(len); + var len = offIp - offI; + var buf = ByteBuffer.allocate(len); ch.position(dataStart + offI); ch.read(buf); buf.flip(); - String s = StandardCharsets.UTF_8.decode(buf).toString(); - String[] parts = s.split("\t", 3); + var s = StandardCharsets.UTF_8.decode(buf).toString(); + var parts = s.split("\t", 3); - int simpel = Integer.parseInt(parts[1]); - String[] clues = GSON.fromJson(parts[2], String[].class); + var simpel = Integer.parseInt(parts[1]); + var clues = GSON.fromJson(parts[2], String[].class); return new ShardLem(w, simpel, clues); } } static int readIntAt(FileChannel ch, long pos) throws IOException { - ByteBuffer b = ByteBuffer.allocate(4).order(ORDER); + var b = ByteBuffer.allocate(4).order(ORDER); ch.position(pos); ch.read(b); b.flip(); @@ -105,10 +105,10 @@ public class Meta { // --- Demo main --- public static ShardLem lookup(long w) { try { - int i = findIndexInMapMmap(shardMap, Lemma.pack43(w)); + var i = findIndexInMapMmap(shardMap, Lemma.pack43(w)); System.out.println("\nQuery: w=" + w + " -> i=" + i); if (i >= 0) { - ShardLem rec = readRecord(shardData, w, i); + var rec = readRecord(shardData, w, i); System.out.println(" simpel=" + rec.simpel()); System.out.println(" clues=" + Arrays.toString(rec.clues())); return rec; diff --git a/src/main/java/puzzle/TriggerConstants.java b/src/main/java/puzzle/TriggerConstants.java new file mode 100644 index 0000000..147fe24 --- /dev/null +++ b/src/main/java/puzzle/TriggerConstants.java @@ -0,0 +1,6 @@ +// file: app/Trigger.java +package puzzle; + +import gen.GenerateConst; +@GenerateConst(C = 9, R = 8, packageName = "precomp", className = "Const9x8") +public final class TriggerConstants { } diff --git a/src/main/java/puzzle/TriggerNeighbors4x3.java b/src/main/java/puzzle/TriggerNeighbors4x3.java new file mode 100644 index 0000000..6ff919d --- /dev/null +++ b/src/main/java/puzzle/TriggerNeighbors4x3.java @@ -0,0 +1,5 @@ +package puzzle; + +import gen.GenerateNeighbors; +@GenerateNeighbors(C = 4, R = 3, packageName = "precomp", className = "Neighbors4x3", MIN_LEN = 2) +public final class TriggerNeighbors4x3 { } diff --git a/src/main/java/puzzle/Trigger.java b/src/main/java/puzzle/TriggerNeighbors9x8.java similarity index 80% rename from src/main/java/puzzle/Trigger.java rename to src/main/java/puzzle/TriggerNeighbors9x8.java index f805e81..1858208 100644 --- a/src/main/java/puzzle/Trigger.java +++ b/src/main/java/puzzle/TriggerNeighbors9x8.java @@ -3,4 +3,4 @@ package puzzle; import gen.GenerateNeighbors; @GenerateNeighbors(C = 9, R = 8, packageName = "precomp", className = "Neighbors9x8", MIN_LEN = 2) -public final class Trigger { } +public final class TriggerNeighbors9x8 { } diff --git a/src/test/java/puzzle/BuildClueAndSimpelIndex.java b/src/test/java/puzzle/BuildClueAndSimpelIndex.java index de82a82..643dde4 100644 --- a/src/test/java/puzzle/BuildClueAndSimpelIndex.java +++ b/src/test/java/puzzle/BuildClueAndSimpelIndex.java @@ -15,9 +15,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.List; -import java.util.function.LongConsumer; import static java.nio.charset.StandardCharsets.US_ASCII; -import static puzzle.SwedishGenerator.*; public class BuildClueAndSimpelIndex { public static void main(String[] args) throws Exception { diff --git a/src/test/java/puzzle/ConnectivityTest.java b/src/test/java/puzzle/ConnectivityTest.java index 334c2f0..605c270 100644 --- a/src/test/java/puzzle/ConnectivityTest.java +++ b/src/test/java/puzzle/ConnectivityTest.java @@ -1,106 +1,111 @@ package puzzle; -import module java.base; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.*; import puzzle.Masker.Clues; import puzzle.SwedishGenerator.Rng; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static precomp.Const9x8.OFF_0_0; +import static precomp.Const9x8.OFF_1_1; +import static precomp.Const9x8.OFF_1_2; +import static precomp.Const9x8.OFF_2_0; +import static precomp.Const9x8.OFF_2_1; +import static precomp.Const9x8.OFF_2_2; +import static precomp.Const9x8.OFF_3_1; +import static precomp.Const9x8.OFF_7_7; import static puzzle.SwedishGenerator.STACK_SIZE; public class ConnectivityTest { - - @Test - void testConnectivityPenalty() { - Rng rng = new Rng(42); - Masker masker = new Masker(rng, new int[STACK_SIZE], Clues.createEmpty()); - - // 1. Maak een masker met één component van clues (bijv. 2 clues die elkaar kruisen) - Clues singleComp = Clues.createEmpty(); - // Clue 1: (0,0) Right. Slot: (0,1), (0,2), (0,3) - // Clue 2: (1,2) Up. Slot: (0,2) - // Ze zijn NIET 8-naburig, maar wel verbonden via slot op (0,2) - singleComp.setClueLo(1L << Masker.offset(0, 0), (byte)1); - singleComp.setClueLo(1L << Masker.offset(2, 2), (byte)2); // Up van (2,2) naar (1,2), (0,2) - - long fitnessSingle = masker.maskFitness(singleComp, 2); - - // 2. Maak een masker met twee eilandjes van clues - Clues twoIslands = Clues.createEmpty(); - twoIslands.setClueLo(1L << Masker.offset(0, 0), (byte)1); - twoIslands.setClueLo(Masker.offset(7, 7) < 64 ? 1L << Masker.offset(7, 7) : 0, (byte)1); - // Voor de zekerheid checken we of offset(7,7) in lo of hi zit - int off77 = Masker.offset(7, 7); - if (off77 < 64) twoIslands.setClueLo(1L << off77, (byte)1); - else twoIslands.setClueHi(1L << (off77 - 64), (byte)1); - - long fitnessIslands = masker.maskFitness(twoIslands, 2); - - System.out.println("[DEBUG_LOG] Fitness single component: " + fitnessSingle); - System.out.println("[DEBUG_LOG] Fitness two islands: " + fitnessIslands); - - assertTrue(fitnessIslands > fitnessSingle + 10000, "Islands should have much higher penalty"); - } - - @Test - void testPhysicalAdjacency() { - Rng rng = new Rng(42); - Masker masker = new Masker(rng, new int[STACK_SIZE], Clues.createEmpty()); - - Clues clues = Clues.createEmpty(); - // Twee clues naast elkaar, maar slots kruisen niet. - // Clue 1: (1,1) Right. Slot (1,2), (1,3), (1,4) - // Clue 2: (2,1) Right. Slot (2,2), (2,3), (2,4) - clues.setClueLo(1L << Masker.offset(1, 1), (byte)1); - clues.setClueLo(1L << Masker.offset(2, 1), (byte)1); - - long fitness = masker.maskFitness(clues, 2); - // Als 8-naburigheid NIET MEER meetelt, moet de penalty hoog zijn. - System.out.println("[DEBUG_LOG] Fitness physically adjacent: " + fitness); - assertTrue(fitness > 20000, "Should have island penalty even if physically adjacent"); - Clues clues2 = Clues.createEmpty(); - // Twee clues naast elkaar, maar slots kruisen niet. - // Clue 1: (1,1) Right. Slot (1,2), (1,3), (1,4) - // Clue 2: (2,1) Right. Slot (2,2), (2,3), (2,4) - clues2.setClueLo(1L << Masker.offset(1, 1), (byte)1); - clues2.setClueLo(1L << Masker.offset(3, 1), (byte)1); - long fitness2 = new Masker(rng, new int[STACK_SIZE], Clues.createEmpty()).maskFitness(clues2, 2); - // Als 8-naburigheid NIET MEER meetelt, moet de penalty hoog zijn. - System.out.println("[DEBUG_LOG] Fitness physically adjacent: " + fitness2); - assertTrue(fitness2 > 20000, "Should have island penalty even if physically adjacent"); - - - - } - - @Test - void testCornerClueConnectivity() { - Rng rng = new Rng(42); - Masker masker = new Masker(rng, new int[STACK_SIZE], Clues.createEmpty()); - - Clues clues = Clues.createEmpty(); - // Clue A: (2,0) Right. Slot: (2,1), (2,2), (2,3), ... - // Clue B: (1,2) Corner Down. Word starts at (1,3) en gaat omlaag: (1,3), (2,3), (3,3)... - // Ze kruisen op (2,3). - clues.setClueLo(1L << Masker.offset(2, 0), (byte)1); // Right - clues.setClueLo(1L << Masker.offset(1, 2), (byte)4); // Corner Down - - long fitness = masker.maskFitness(clues, 2); - System.out.println("[DEBUG_LOG] Fitness corner clue connected: " + fitness); - - // Als ze verbonden zijn, is de penalty voor eilandjes 0. - // We vergelijken met een island scenario (2 clues die elkaar NIET raken) - Clues island = Clues.createEmpty(); - int offA = Masker.offset(2, 0); - if (offA < 64) island.setClueLo(1L << offA, (byte)1); - else island.setClueHi(1L << (offA - 64), (byte)1); - - int offB = Masker.offset(7, 7); - if (offB < 64) island.setClueLo(1L << offB, (byte)1); - else island.setClueHi(1L << (offB - 64), (byte)1); - - long fitnessIsland = masker.maskFitness(island, 2); - System.out.println("[DEBUG_LOG] Fitness island: " + fitnessIsland); - - assertTrue(fitnessIsland > fitness + 20000, "Island should add significant penalty compared to connected corner clue"); - } + + @Test + void testConnectivityPenalty() { + Rng rng = new Rng(42); + Masker masker = new Masker(rng, new int[STACK_SIZE], Clues.createEmpty()); + + // 1. Maak een masker met één component van clues (bijv. 2 clues die elkaar kruisen) + Clues singleComp = Clues.createEmpty(); + // Clue 1: (0,0) Right. Slot: (0,1), (0,2), (0,3) + // Clue 2: (1,2) Up. Slot: (0,2) + // Ze zijn NIET 8-naburig, maar wel verbonden via slot op (0,2) + singleComp.setClueLo(1L << OFF_0_0, (byte) 1); + singleComp.setClueLo(1L << OFF_2_2, (byte) 2); // Up van (2,2) naar (1,2), (0,2) + + long fitnessSingle = masker.maskFitness(singleComp, 2); + + // 2. Maak een masker met twee eilandjes van clues + Clues twoIslands = Clues.createEmpty(); + twoIslands.setClueLo(1L << OFF_0_0, (byte) 1); + twoIslands.setClueLo(OFF_7_7 < 64 ? 1L << OFF_7_7 : 0, (byte) 1); + // Voor de zekerheid checken we of offset(7,7) in lo of hi zit + int off77 = OFF_7_7; + if (off77 < 64) twoIslands.setClueLo(1L << off77, (byte) 1); + else twoIslands.setClueHi(1L << (off77 - 64), (byte) 1); + + long fitnessIslands = masker.maskFitness(twoIslands, 2); + + System.out.println("[DEBUG_LOG] Fitness single component: " + fitnessSingle); + System.out.println("[DEBUG_LOG] Fitness two islands: " + fitnessIslands); + + assertTrue(fitnessIslands > fitnessSingle + 10000, "Islands should have much higher penalty"); + } + + @Test + void testPhysicalAdjacency() { + Rng rng = new Rng(42); + Masker masker = new Masker(rng, new int[STACK_SIZE], Clues.createEmpty()); + + Clues clues = Clues.createEmpty(); + // Twee clues naast elkaar, maar slots kruisen niet. + // Clue 1: (1,1) Right. Slot (1,2), (1,3), (1,4) + // Clue 2: (2,1) Right. Slot (2,2), (2,3), (2,4) + clues.setClueLo(1L << OFF_1_1, (byte) 1); + clues.setClueLo(1L << OFF_2_1, (byte) 1); + + long fitness = masker.maskFitness(clues, 2); + // Als 8-naburigheid NIET MEER meetelt, moet de penalty hoog zijn. + System.out.println("[DEBUG_LOG] Fitness physically adjacent: " + fitness); + assertTrue(fitness > 20000, "Should have island penalty even if physically adjacent"); + Clues clues2 = Clues.createEmpty(); + // Twee clues naast elkaar, maar slots kruisen niet. + // Clue 1: (1,1) Right. Slot (1,2), (1,3), (1,4) + // Clue 2: (2,1) Right. Slot (2,2), (2,3), (2,4) + clues2.setClueLo(1L << OFF_1_1, (byte) 1); + clues2.setClueLo(1L << OFF_3_1, (byte) 1); + long fitness2 = new Masker(rng, new int[STACK_SIZE], Clues.createEmpty()).maskFitness(clues2, 2); + // Als 8-naburigheid NIET MEER meetelt, moet de penalty hoog zijn. + System.out.println("[DEBUG_LOG] Fitness physically adjacent: " + fitness2); + assertTrue(fitness2 > 20000, "Should have island penalty even if physically adjacent"); + + } + + @Test + void testCornerClueConnectivity() { + Rng rng = new Rng(42); + Masker masker = new Masker(rng, new int[STACK_SIZE], Clues.createEmpty()); + + Clues clues = Clues.createEmpty(); + // Clue A: (2,0) Right. Slot: (2,1), (2,2), (2,3), ... + // Clue B: (1,2) Corner Down. Word starts at (1,3) en gaat omlaag: (1,3), (2,3), (3,3)... + // Ze kruisen op (2,3). + clues.setClueLo(1L << OFF_2_0, (byte) 1); // Right + clues.setClueLo(1L << OFF_1_2, (byte) 4); // Corner Down + + long fitness = masker.maskFitness(clues, 2); + System.out.println("[DEBUG_LOG] Fitness corner clue connected: " + fitness); + + // Als ze verbonden zijn, is de penalty voor eilandjes 0. + // We vergelijken met een island scenario (2 clues die elkaar NIET raken) + Clues island = Clues.createEmpty(); + int offA = OFF_2_0; + if (offA < 64) island.setClueLo(1L << offA, (byte) 1); + else island.setClueHi(1L << (offA - 64), (byte) 1); + + int offB = OFF_7_7; + if (offB < 64) island.setClueLo(1L << offB, (byte) 1); + else island.setClueHi(1L << (offB - 64), (byte) 1); + + long fitnessIsland = masker.maskFitness(island, 2); + System.out.println("[DEBUG_LOG] Fitness island: " + fitnessIsland); + + assertTrue(fitnessIsland > fitness + 20000, "Island should add significant penalty compared to connected corner clue"); + } } diff --git a/src/test/java/puzzle/CornerClueTest.java b/src/test/java/puzzle/CornerClueTest.java index c79f1f2..c4d330d 100644 --- a/src/test/java/puzzle/CornerClueTest.java +++ b/src/test/java/puzzle/CornerClueTest.java @@ -1,93 +1,90 @@ package puzzle; -import module java.base; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.*; import puzzle.Masker.Clues; -import puzzle.SwedishGenerator.DictEntry; -import puzzle.SwedishGenerator.Slotinfo; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static precomp.Const9x8.OFF_0_0; +import static precomp.Const9x8.OFF_0_1; +import static precomp.Const9x8.OFF_1_0; +import static precomp.Const9x8.OFF_1_1; public class CornerClueTest { - @Test - void testCornerDownSlot() { - Clues clues = Clues.createEmpty(); - // Clue op (0,0), type 4 (Corner Down) - int idx = Masker.offset(0, 0); - clues.setClueLo(1L << idx, (byte)4); - - assertEquals(4, clues.getDir(idx)); - - // Controleer of forEachSlot het slot vindt - final boolean[] found = {false}; - clues.forEachSlot((key, lo, hi) -> { - if (Masker.Slot.dir(key) == 4) { - found[0] = true; - // Woord zou moeten starten op (0,1) - int startIdx = Masker.offset(0, 1); - assertTrue((lo & (1L << startIdx)) != 0, "Slot should start at (0,1)"); - // En omlaag gaan - int secondIdx = Masker.offset(1, 1); - assertTrue((lo & (1L << secondIdx)) != 0, "Slot should continue to (1,1)"); - - // Lengte van het slot zou 8 moeten zijn (van rij 0 t/m 7 in kolom 1) - assertEquals(8, Masker.Slot.length(lo, hi)); - } - }); - assertTrue(found[0], "Corner Down slot should be found"); - } - - @Test - void testCornerDownExtraction() { - Clues clues = Clues.createEmpty(); - int idx = Masker.offset(0, 0); - clues.setClueLo(1L << idx, (byte)4); - - DictEntry[] dict = DictData.DICT.index(); - Slotinfo[] slots = Masker.slots(clues, dict); - - assertEquals(1, slots.length); - assertEquals(4, Masker.Slot.dir(slots[0].key())); - } - - @Test - void testCornerDownLeftSlot() { - Clues clues = Clues.createEmpty(); - // Clue op (0,1), type 5 (Corner Down Left) - // Should result in word starting at (0,0) going down. - int idx = Masker.offset(0, 1); - clues.setClueLo(1L << idx, (byte)5); - - assertEquals(5, clues.getDir(idx)); - - // Controleer of forEachSlot het slot vindt - final boolean[] found = {false}; - clues.forEachSlot((key, lo, hi) -> { - if (Masker.Slot.dir(key) == 5) { - found[0] = true; - // Woord zou moeten starten op (0,0) - int startIdx = Masker.offset(0, 0); - assertTrue((lo & (1L << startIdx)) != 0, "Slot should start at (0,0)"); - // En omlaag gaan - int secondIdx = Masker.offset(1, 0); - assertTrue((lo & (1L << secondIdx)) != 0, "Slot should continue to (1,0)"); - - // Lengte van het slot zou 8 moeten zijn (van rij 0 t/m 7 in kolom 0) - assertEquals(8, Masker.Slot.length(lo, hi)); - } - }); - assertTrue(found[0], "Corner Down Left slot should be found"); - } - - @Test - void testCornerDownLeftExtraction() { - Clues clues = Clues.createEmpty(); - int idx = Masker.offset(0, 1); - clues.setClueLo(1L << idx, (byte)5); - - DictEntry[] dict = DictData.DICT.index(); - Slotinfo[] slots = Masker.slots(clues, dict); - - assertEquals(1, slots.length); - assertEquals(5, Masker.Slot.dir(slots[0].key())); - } + + @Test + void testCornerDownSlot() { + var clues = Clues.createEmpty(); + // Clue op (0,0), type 4 (Corner Down) + var idx = OFF_0_0; + clues.setClueLo(1L << idx, (byte) 4); + + assertEquals(4, clues.getDir(idx)); + + // Controleer of forEachSlot het slot vindt + final var found = new boolean[]{ false }; + clues.forEachSlot((key, lo, hi) -> { + if (Masker.Slot.dir(key) == 4) { + found[0] = true; + // Woord zou moeten starten op (0,1) + assertTrue((lo & (1L << OFF_0_1)) != 0, "Slot should start at (0,1)"); + // En omlaag gaan + assertTrue((lo & (1L << OFF_1_1)) != 0, "Slot should continue to (1,1)"); + + // Lengte van het slot zou 8 moeten zijn (van rij 0 t/m 7 in kolom 1) + assertEquals(8, Masker.Slot.length(lo, hi)); + } + }); + assertTrue(found[0], "Corner Down slot should be found"); + } + + @Test + void testCornerDownExtraction() { + var clues = Clues.createEmpty(); + clues.setClueLo(1L << OFF_0_0, (byte) 4); + + var dict = DictData.DICT.index(); + var slots = Masker.slots(clues, dict); + + assertEquals(1, slots.length); + assertEquals(4, Masker.Slot.dir(slots[0].key())); + } + + @Test + void testCornerDownLeftSlot() { + var clues = Clues.createEmpty(); + // Clue op (0,1), type 5 (Corner Down Left) + // Should result in word starting at (0,0) going down. + var idx = OFF_0_1; + clues.setClueLo(1L << idx, (byte) 5); + + assertEquals(5, clues.getDir(idx)); + + // Controleer of forEachSlot het slot vindt + final var found = new boolean[]{ false }; + clues.forEachSlot((key, lo, hi) -> { + if (Masker.Slot.dir(key) == 5) { + found[0] = true; + // Woord zou moeten starten op (0,0) + assertTrue((lo & (1L << OFF_0_0)) != 0, "Slot should start at (0,0)"); + // En omlaag gaan + assertTrue((lo & (1L << OFF_1_0)) != 0, "Slot should continue to (1,0)"); + + // Lengte van het slot zou 8 moeten zijn (van rij 0 t/m 7 in kolom 0) + assertEquals(8, Masker.Slot.length(lo, hi)); + } + }); + assertTrue(found[0], "Corner Down Left slot should be found"); + } + + @Test + void testCornerDownLeftExtraction() { + var clues = Clues.createEmpty(); + clues.setClueLo(1L << OFF_0_1, (byte) 5); + + var dict = DictData.DICT.index(); + var slots = Masker.slots(clues, dict); + + assertEquals(1, slots.length); + assertEquals(5, Masker.Slot.dir(slots[0].key())); + } } diff --git a/src/test/java/puzzle/ExportFormatTest.java b/src/test/java/puzzle/ExportFormatTest.java index 333a603..2c1654e 100644 --- a/src/test/java/puzzle/ExportFormatTest.java +++ b/src/test/java/puzzle/ExportFormatTest.java @@ -2,6 +2,7 @@ package puzzle; import module java.base; import lombok.val; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import puzzle.Export.Clued; import puzzle.Export.Gridded; @@ -16,6 +17,10 @@ import puzzle.SwedishGeneratorTest.Idx; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; +import static precomp.Const9x8.OFF_0_1; +import static precomp.Const9x8.OFF_0_2; +import static precomp.Const9x8.OFF_0_3; +import static precomp.Const9x8.OFF_0_4; import static puzzle.Export.Clue.LEFT; import static puzzle.Export.Clue.RIGHT; import static puzzle.SwedishGenerator.C; @@ -24,10 +29,6 @@ import static puzzle.SwedishGenerator.FillStats; import static puzzle.SwedishGenerator.R; import static puzzle.Masker.Slot; import static puzzle.GridBuilder.placeWord; -import static puzzle.SwedishGeneratorTest.OFF_0_1; -import static puzzle.SwedishGeneratorTest.OFF_0_2; -import static puzzle.SwedishGeneratorTest.OFF_0_3; -import static puzzle.SwedishGeneratorTest.OFF_0_4; import static puzzle.SwedishGeneratorTest.TEST; public class ExportFormatTest { @@ -104,20 +105,17 @@ public class ExportFormatTest { assertTrue(row.matches("#+")); } } - Path shardKey(long word) { - return Path.of("src/main/generated-sources/puzzle").resolve(Lemma.unpackSize(word) + 1 + ".idx"); - } @Test void testShardToClue() { - for (int length = 2; length <= 8; length++) { + for (var length = 2; length <= 8; length++) { val entry = DictData.DICT.index()[length]; if (entry == null) continue; val words = entry.words(); - for (int i = 0; i < Math.min(words.length, 5); i++) { - val wordVal = words[i]; - val word = Lemma.asWord(wordVal, Export.BYTES.get()); - val clueRec = Meta.lookup(wordVal); + for (var i = 0; i < Math.min(words.length, 5); i++) { + val wordVal = words[i]; + val word = Lemma.asWord(wordVal, Export.BYTES.get()); + val clueRec = Meta.lookup(wordVal); assertNotNull(clueRec); assertEquals(word, Lemma.asWord(clueRec.w(), Export.BYTES.get())); @@ -130,43 +128,28 @@ public class ExportFormatTest { @Test void testSpecificWords() { // These words are known to be in the CSV and likely in the dictionary - String[] testWords = { "EEN", "NAAR", "IEDEREEN" }; - for (String wStr : testWords) { - long w = Lemma.from(wStr); - int L = wStr.length(); - var entry = DictData.DICT.index()[L]; - if (entry == null) continue; + var testWords = new String[]{ "EEN", "NAAR", "IEDEREEN" }; + for (var wStr : testWords) { + var w = Lemma.from(wStr); - // Find index of word in entry - int idx = -1; - long[] words = entry.words(); - for (int i = 0; i < words.length; i++) { - if (Lemma.asWord(words[i], Export.BYTES.get()).equals(wStr)) { - idx = i; - break; - } + val clueRec = Meta.lookup(w); + assertNotNull(clueRec); + assertEquals(wStr, Lemma.asWord(clueRec.w(), Export.BYTES.get())); + // Check some expected complexity values (from CSV head output, column 3) + if (wStr.equals("EEN")) { + assertEquals(451, clueRec.simpel()); + assertEquals("een geheel vormend", clueRec.clues()[0]); + } + if (wStr.equals("NAAR")) { + assertEquals(497, clueRec.simpel()); + assertEquals("onaangenaam, vervelend, rot, niet leuk", clueRec.clues()[0]); + } + if (wStr.equals("IEDEREEN")) { + assertEquals(501, clueRec.simpel()); + assertEquals("elke persoon", clueRec.clues()[0]); } - if (idx != -1) { - val clueRec = Meta.lookup(w); - assertNotNull(clueRec); - assertEquals(wStr, Lemma.asWord(clueRec.w(), Export.BYTES.get())); - // Check some expected complexity values (from CSV head output, column 3) - if (wStr.equals("EEN")) { - assertEquals(451, clueRec.simpel()); - assertEquals("een geheel vormend", clueRec.clues()[0]); - } - if (wStr.equals("NAAR")) { - assertEquals(497, clueRec.simpel()); - assertEquals("onaangenaam, vervelend, rot, niet leuk", clueRec.clues()[0]); - } - if (wStr.equals("IEDEREEN")) { - assertEquals(501, clueRec.simpel()); - assertEquals("elke persoon", clueRec.clues()[0]); - } - - assertTrue(clueRec.clues().length > 0); - } + assertTrue(clueRec.clues().length > 0); } } } diff --git a/src/test/java/puzzle/MainTest.java b/src/test/java/puzzle/MainTest.java index 711f08c..a5927dd 100644 --- a/src/test/java/puzzle/MainTest.java +++ b/src/test/java/puzzle/MainTest.java @@ -16,11 +16,17 @@ import puzzle.SwedishGenerator.Rng; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; +import static precomp.Const9x8.OFF_0_0; +import static precomp.Const9x8.OFF_0_1; +import static precomp.Const9x8.OFF_0_2; +import static precomp.Const9x8.OFF_1_1; +import static precomp.Const9x8.OFF_1_2; +import static precomp.Const9x8.OFF_2_1; +import static precomp.Const9x8.OFF_2_3; import static puzzle.Export.Clue.DOWN; import static puzzle.Export.Clue.LEFT; import static puzzle.Export.Clue.RIGHT; import static puzzle.Export.Clue.UP; -import static puzzle.SwedishGenerator.Dict; import static puzzle.SwedishGenerator.Lemma; import static puzzle.SwedishGenerator.Slotinfo; import static puzzle.SwedishGenerator.fillMask; @@ -36,13 +42,6 @@ import static puzzle.SwedishGeneratorTest.Idx.IDX_1_1; import static puzzle.SwedishGeneratorTest.Idx.IDX_2_1; import static puzzle.SwedishGeneratorTest.LETTER_A; import static puzzle.SwedishGeneratorTest.LETTER_Z; -import static puzzle.SwedishGeneratorTest.OFF_0_0; -import static puzzle.SwedishGeneratorTest.OFF_0_1; -import static puzzle.SwedishGeneratorTest.OFF_0_2; -import static puzzle.SwedishGeneratorTest.OFF_1_1; -import static puzzle.SwedishGeneratorTest.OFF_1_2; -import static puzzle.SwedishGeneratorTest.OFF_2_1; -import static puzzle.SwedishGeneratorTest.OFF_2_3; public class MainTest { @@ -57,7 +56,6 @@ public class MainTest { this.tries = 1; this.verbose = false; }}; - static final Dict dict = DictData.DICT;//loadDict(opts.wordsPath); @Test void testExtractSlots() { @@ -68,7 +66,7 @@ public class MainTest { val g = grid.grid().g; GridBuilder.placeWord(grid.grid(), g, key, (1L << OFF_0_1) | (1L << OFF_0_2), 0, AB); - var slots = Masker.extractSlots(clues, dict.index()); + var slots = Masker.extractSlots(clues, DictData.DICT.index()); assertEquals(1, slots.length); var s = slots[0]; assertEquals(8, Masker.Slot.length(s.lo(), s.hi())); @@ -184,7 +182,7 @@ public class MainTest { Assertions.assertEquals(20, mask.clueCount()); val map = mask.stream().collect(Collectors.toMap(ClueAt::index, ClueAt::clue)); Assertions.assertEquals(20, map.size()); - var slots = Masker.slots(mask.c(), dict.index()); + var slots = Masker.slots(mask.c(), DictData.DICT.index()); // var filled = fillMask(rng, slotInfo, grid, false); // val res = new PuzzleResult(new Clued(mask), new Gridded(grid), slotInfo, filled).exportFormatFromFilled(0, new Rewards(0, 0, 0)); } @@ -200,7 +198,7 @@ public class MainTest { " 1 \n" + " 1 2\n" + "21 22 3"); - var slotInfo = Masker.slots(mask.c(), dict.index()); + var slotInfo = Masker.slots(mask.c(), DictData.DICT.index()); var grid = Slotinfo.grid(slotInfo); var filled = fillMask(rng, slotInfo, grid); Assertions.assertTrue(filled.ok(), "Puzzle generation failed (not ok)"); diff --git a/src/test/java/puzzle/MaskerCluesTest.java b/src/test/java/puzzle/MaskerCluesTest.java index d37533e..0189bee 100644 --- a/src/test/java/puzzle/MaskerCluesTest.java +++ b/src/test/java/puzzle/MaskerCluesTest.java @@ -1,13 +1,22 @@ package puzzle; -import module java.base; import lombok.val; import org.junit.jupiter.api.Test; import puzzle.Export.Clued; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static precomp.Const9x8.OFF_0_0; +import static precomp.Const9x8.OFF_0_2; +import static precomp.Const9x8.OFF_0_3; +import static precomp.Const9x8.OFF_0_7; +import static precomp.Const9x8.OFF_0_8; +import static precomp.Const9x8.OFF_1_1; +import static precomp.Const9x8.OFF_2_2; import static puzzle.Masker.Clues; -import static puzzle.SwedishGenerator.*; import static puzzle.Masker.Slot; +import static puzzle.SwedishGenerator.Rng; +import static puzzle.SwedishGenerator.STACK_SIZE; public class MaskerCluesTest { @@ -18,7 +27,7 @@ public class MaskerCluesTest { for (int i = 0; i < 200; i++) { for (int j = 19; j < 24; j++) { var clues = masker.randomMask(j); - assertTrue(masker.isValid(clues, MIN_LEN), "Mask should be valid for length \n" + new Clued(clues).gridToString()); + assertTrue(masker.isValid(clues), "Mask should be valid for length \n" + new Clued(clues).gridToString()); } } } @@ -36,7 +45,7 @@ public class MaskerCluesTest { simCount++; masker.mutate(clues); sim += orig.similarity(clues); - assertTrue(masker.isValid(clues, MIN_LEN), "Mask should be valid for length \n" + new Clued(clues).gridToString()); + assertTrue(masker.isValid(clues), "Mask should be valid for length \n" + new Clued(clues).gridToString()); } } System.out.println("Average similarity: " + sim / simCount); @@ -55,7 +64,7 @@ public class MaskerCluesTest { simCount++; var cross = masker.crossover(clues, clues2); sim += Math.max(cross.similarity(clues), cross.similarity(clues2)); - assertTrue(masker.isValid(cross, MIN_LEN), "Mask should be valid for length \n" + new Clued(cross).gridToString()); + assertTrue(masker.isValid(cross), "Mask should be valid for length \n" + new Clued(cross).gridToString()); } } System.out.println("Average similarity: " + sim / simCount); @@ -90,16 +99,16 @@ public class MaskerCluesTest { void testIsValid() { Masker masker = new Masker(new Rng(42), new int[STACK_SIZE], Clues.createEmpty()); Clues g = Clues.createEmpty(); - assertTrue(masker.isValid(g, MIN_LEN)); + assertTrue(masker.isValid(g)); // Valid clue: Right from (0,0) in 9x8 grid. Length is 8. - g.setClueLo(1L << Masker.offset(0, 0), (byte) 1); - assertTrue(masker.isValid(g, MIN_LEN)); + g.setClueLo(1L << OFF_0_0, (byte) 1); + assertTrue(masker.isValid(g)); // Invalid clue: Right from (0,7) in 9x8 grid. Length is 1 (too short if MIN_LEN >= 2). Clues g2 = Clues.createEmpty(); - g2.setClueLo(1L << Masker.offset(0, 7), (byte) 1); - assertFalse(masker.isValid(g2, MIN_LEN)); + g2.setClueLo(1L << OFF_0_7, (byte) 1); + assertFalse(masker.isValid(g2)); } @Test @@ -107,22 +116,22 @@ public class MaskerCluesTest { Clues g = Clues.createEmpty(); // Room for Right clue at (0,0) (length 8) - assertTrue(g.hasRoomForClue(Slot.packSlotKey(Masker.offset(0, 0), 1))); + assertTrue(g.hasRoomForClue(Slot.packSlotKey(OFF_0_0, 1))); // No room for Right clue at (0,8) (length 0 < MIN_LEN) - assertFalse(g.hasRoomForClue(Slot.packSlotKey(Masker.offset(0, 8), 1))); + assertFalse(g.hasRoomForClue(Slot.packSlotKey(OFF_0_8, 1))); // Blocked room // Let's place a clue that leaves only 1 cell for another clue. - g.setClueLo(1L << Masker.offset(0, 2), (byte) 1); + g.setClueLo(1L << OFF_0_2, (byte) 1); // Now Right at (0,0) only has (0,1) available -> length 1 < MIN_LEN (which is 2) - assertFalse(g.hasRoomForClue(Slot.packSlotKey(Masker.offset(0, 0), 1))); + assertFalse(g.hasRoomForClue(Slot.packSlotKey(OFF_0_0, 1))); // But enough room g.clearClueLo(0L); - g.setClueLo(1L << Masker.offset(0, 3), (byte) 1); + g.setClueLo(1L << OFF_0_3, (byte) 1); // Now Right at (0,0) has (0,1), (0,2) -> length 2 == MIN_LEN - assertTrue(g.hasRoomForClue(Slot.packSlotKey(Masker.offset(0, 0), 1))); + assertTrue(g.hasRoomForClue(Slot.packSlotKey(OFF_0_0, 1))); } @Test @@ -130,17 +139,17 @@ public class MaskerCluesTest { Clues g = Clues.createEmpty(); Masker masker = new Masker(new Rng(42), new int[STACK_SIZE], Clues.createEmpty()); // Clue 1: (0,0) Right. Slot cells: (0,1), (0,2), (0,3), (0,4), (0,5), (0,6), (0,7), (0,8) - g.setClueLo(1L << Masker.offset(0, 0), (byte) 1); + g.setClueLo(1L << OFF_0_0, (byte) 1); // Clue 2: (1,2) Up. Slot cells: (0,2) // Intersection is exactly 1 cell (0,2). Valid. - g.setClueLo(1L << Masker.offset(2, 2), (byte) 2); - assertTrue(masker.isValid(g, MIN_LEN)); + g.setClueLo(1L << OFF_2_2, (byte) 2); + assertTrue(masker.isValid(g)); // Clue 3: (1,1) Right. Slot cells: (1,2), (1,3), ... // No intersection with Clue 1 or 2. Valid. - g.setClueLo(1L << Masker.offset(1, 1), (byte) 1); - assertTrue(masker.isValid(g, MIN_LEN)); + g.setClueLo(1L << OFF_1_1, (byte) 1); + assertTrue(masker.isValid(g)); // Now create a violation: two slots sharing 2 cells. // We can do this with Corner Down and another clue. @@ -149,9 +158,9 @@ public class MaskerCluesTest { // They share MANY cells starting from (0,1). Clues g3 = Clues.createEmpty(); - g3.setClueLo(1L << Masker.offset(0, 0), (byte) 4); // Corner Down - g3.setClueLo(1L << Masker.offset(0, 2), (byte) 5); // Corner Down Left - assertFalse(masker.isValid(g3, MIN_LEN)); + g3.setClueLo(1L << OFF_0_0, (byte) 4); // Corner Down + g3.setClueLo(1L << OFF_0_2, (byte) 5); // Corner Down Left + assertFalse(masker.isValid(g3)); } @Test @@ -160,11 +169,11 @@ public class MaskerCluesTest { Clues g = Clues.createEmpty(); // Dir 6 (x=1, r=1, v=0) is invalid g.setClueLo(1L << 0, (byte) 6); - assertFalse(masker.isValid(g,MIN_LEN)); + assertFalse(masker.isValid(g)); // Dir 7 (x=1, r=1, v=1) is invalid Clues g2 = Clues.createEmpty(); g2.setClueLo(1L << 0, (byte) 7); - assertFalse(masker.isValid(g2,MIN_LEN)); + assertFalse(masker.isValid(g2)); } } diff --git a/src/test/java/puzzle/PerformanceTest.java b/src/test/java/puzzle/PerformanceTest.java index fc17d14..e52e70f 100644 --- a/src/test/java/puzzle/PerformanceTest.java +++ b/src/test/java/puzzle/PerformanceTest.java @@ -3,6 +3,7 @@ package puzzle; import module java.base; import lombok.val; import org.junit.jupiter.api.Test; +import puzzle.Export.Clue; import puzzle.Export.Clued; import puzzle.Export.Gridded; import puzzle.Masker.Clues; @@ -12,12 +13,14 @@ import puzzle.SwedishGenerator.Slotinfo; import static org.junit.jupiter.api.Assertions.assertTrue; import static puzzle.SwedishGenerator.fillMask; +import static puzzle.dict800.DictData.DICT800; +import static puzzle.dict900.DictData.DICT900; public class PerformanceTest { - final DictEntry[] EN = DictData.DICT.index(); + final DictEntry[] EN = DICT800.index(); void main() { - testPerformance(); + testIncrementalComplexity(); } @Test void testPerformance() { @@ -80,7 +83,7 @@ public class PerformanceTest { " 1 \n" + "221 22\n"; val mask = Clued.parse(maskStr); - val allSlots = Masker.slots(mask.c(), EN); + val allSlots = Masker.slots(mask.c(), DICT900.index()); //mask.toGrid() System.out.println("[DEBUG_LOG] \n--- Incremental Complexity Test ---"); System.out.println("[DEBUG_LOG] Full Slot Layout:"); @@ -123,7 +126,9 @@ public class PerformanceTest { for (Slotinfo s : slots) s.assign().w = 0; val result = fillMask(rng, slots, Slotinfo.grid(slots)); - if (result.ok()) successCount++; + if (result.ok()) { + successCount++; + } totalNodes += result.nodes(); totalBacktracks += result.backtracks(); } @@ -132,6 +137,7 @@ public class PerformanceTest { System.out.printf(Locale.ROOT, "[DEBUG_LOG] %s: %d/%d SUCCESS | avg nodes=%d | avg backtracks=%d | total time=%.3fs%n", label, successCount, iterations, totalNodes / iterations, totalBacktracks / iterations, totalDuration); + visualizeSlots(slots); } private void visualizeSlots(Slotinfo[] slots) { @@ -141,39 +147,17 @@ public class PerformanceTest { for (int r = 0; r < R; r++) Arrays.fill(display[r], ' '); for (Slotinfo slot : slots) { - int key = slot.key(); - int dir = Masker.Slot.dir(key); - int clueIdx = Masker.Slot.clueIndex(key); + int key = slot.key(); + Clue dir = Clue.from(Masker.Slot.dir(key)); + int clueIdx = Masker.Slot.clueIndex(key); int cr = Masker.IT[clueIdx].r(); int cc = Masker.IT[clueIdx].c(); // User requested: aAAAA for a four letter to RIGHT clue slot. // SwedishGenerator: 1=RIGHT, 0=DOWN, 2=UP, 3=LEFT - char clueChar; - char slotChar; - switch (dir) { - case 1: - clueChar = 'a'; - slotChar = 'A'; - break; // RIGHT - case 0: - clueChar = 'b'; - slotChar = 'B'; - break; // DOWN - case 2: - clueChar = 'c'; - slotChar = 'C'; - break; // UP - case 3: - clueChar = 'd'; - slotChar = 'D'; - break; // LEFT - default: - clueChar = '?'; - slotChar = '?'; - } - + char clueChar = dir.clueChar; + char slotChar = dir.slotChar; display[cr][cc] = clueChar; Gridded.cellWalk((byte) slot.key(), slot.lo(), slot.hi()).forEach(idx -> { diff --git a/src/test/java/puzzle/SwedishGeneratorTest.java b/src/test/java/puzzle/SwedishGeneratorTest.java index 3e98bd7..9e7a00e 100644 --- a/src/test/java/puzzle/SwedishGeneratorTest.java +++ b/src/test/java/puzzle/SwedishGeneratorTest.java @@ -12,6 +12,7 @@ import puzzle.Masker.Clues; import puzzle.Masker.Slot; import static org.junit.jupiter.api.Assertions.*; +import static precomp.Const9x8.*; import static puzzle.SwedishGenerator.*; import static puzzle.SwedishGeneratorTest.Idx.IDX_0_0; @@ -78,31 +79,6 @@ public class SwedishGeneratorTest { static final byte CLUE_UP = 2; static final byte CLUE_LEFT = 3; - static final int OFF_1_0 = Masker.offset(1, 0); - static final int OFF_1_1 = Masker.offset(1, 1); - static final int OFF_1_2 = Masker.offset(1, 2); - static final int OFF_1_3 = Masker.offset(1, 3); - static final int OFF_1_4 = Masker.offset(1, 4); - static final int OFF_1_5 = Masker.offset(1, 5); - static final int OFF_2_1 = Masker.offset(2, 1); - static final int OFF_2_3 = Masker.offset(2, 3); - static final int OFF_2_2 = Masker.offset(2, 2); - static final int OFF_2_4 = Masker.offset(2, 4); - static final int OFF_0_0 = Masker.offset(0, 0); - static final int OFF_0_4 = Masker.offset(0, 4); - static final int OFF_0_5 = Masker.offset(0, 5); - static final int OFF_0_1 = Masker.offset(0, 1); - static final int OFF_0_2 = Masker.offset(0, 2); - static final int OFF_0_3 = Masker.offset(0, 3); - static final int OFF_2_0 = Masker.offset(2, 0); - static final int OFF_2_5 = Masker.offset(2, 5); - static final int OFF_3_5 = Masker.offset(3, 5); - static final int OFF_4_5 = Masker.offset(4, 5); - static final int OFF_3_0 = Masker.offset(3, 0); - static final int OFF_3_1 = Masker.offset(3, 1); - static final int OFF_3_2 = Masker.offset(3, 2); - static final int OFF_3_3 = Masker.offset(3, 3); - static final int OFF_3_4 = Masker.offset(3, 4); static final byte D_BYTE_2 = CLUE_RIGHT; enum Idx {