diff --git a/src/main/java/puzzle/Export.java b/src/main/java/puzzle/Export.java index 395a86c..ee34fa6 100644 --- a/src/main/java/puzzle/Export.java +++ b/src/main/java/puzzle/Export.java @@ -114,7 +114,6 @@ 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]); if (d == 2) { // right -> horizontal @@ -123,10 +122,8 @@ public record Export() { direction = Placed.VERTICAL; } else if (d == 4) { // left -> horizontal (REVERSED) direction = Placed.HORIZONTAL; - isReversed = true; } else if (d == 1) { // up -> vertical (REVERSED) direction = Placed.VERTICAL; - isReversed = true; } else { return null; } @@ -139,7 +136,7 @@ public record Export() { s.clueR(), s.clueC(), cells, - isReversed + !s.increasing() ); } public ExportedPuzzle exportFormatFromFilled(int difficulty, Rewards rewards) { diff --git a/src/main/java/puzzle/SwedishGenerator.java b/src/main/java/puzzle/SwedishGenerator.java index 12b0ef6..4e72d91 100644 --- a/src/main/java/puzzle/SwedishGenerator.java +++ b/src/main/java/puzzle/SwedishGenerator.java @@ -118,7 +118,7 @@ public record SwedishGenerator(Rng rng) { final int[] stack = new int[SIZE]; final Bit seen = new Bit(); long pattern; - final int[] undo = new int[2048]; + final long[] undo = new long[4096]; final long[] bitset = new long[2500]; void setPattern(long p) { this.pattern = p; } @@ -221,6 +221,10 @@ public record SwedishGenerator(Rng rng) { for (var l = lo; l != X; l &= l - 1) processSlot(this, visitor, Long.numberOfTrailingZeros(l)); for (var h = hi; h != X; h &= h - 1) processSlot(this, visitor, 64 | Long.numberOfTrailingZeros(h)); } + void undoPlace(long maskLo, long maskHi) { + for (long b = maskLo; b != 0; b &= b - 1) clearletter(Long.numberOfTrailingZeros(b)); + for (long b = maskHi; b != 0; b &= b - 1) clearletter(64 | Long.numberOfTrailingZeros(b)); + } } static record DictEntry(long[] words, long[][] posBitsets) { } @@ -311,34 +315,18 @@ public record SwedishGenerator(Rng rng) { static final int BIT_FOR_DIR = 3; static Slot from(int key, long lo, long hi) { return new Slot(key, lo, hi); } - void undoPlace(Grid grid, int mask) { - boolean increasing = (dir() == 2 || dir() == 3); - int i = 0; - if (increasing) { - for (long b = lo; b != 0; b &= b - 1, i++) if ((mask & (1 << i)) != 0) grid.clearletter(Long.numberOfTrailingZeros(b)); - for (long b = hi; b != 0; b &= b - 1, i++) if ((mask & (1 << i)) != 0) grid.clearletter(64 | Long.numberOfTrailingZeros(b)); - } else { - for (long b = hi; b != 0; i++) { - int msb = 63 - Long.numberOfLeadingZeros(b); - if ((mask & (1 << i)) != 0) grid.clearletter(64 | msb); - b &= ~(1L << msb); - } - for (long b = lo; b != 0; i++) { - int msb = 63 - Long.numberOfLeadingZeros(b); - if ((mask & (1 << i)) != 0) grid.clearletter(msb); - b &= ~(1L << msb); - } - } - } - public int len() { return Long.bitCount(lo) + Long.bitCount(hi); } - public int clueR() { return Grid.r((key >>> BIT_FOR_DIR)); } - public int clueIndex() { return key >>> BIT_FOR_DIR; } - public int clueC() { return Grid.c((key >>> BIT_FOR_DIR)); } - public int dir() { return key & 7; } - public boolean horiz() { return horiz(dir()); } + + public int len() { return Long.bitCount(lo) + Long.bitCount(hi); } + public int clueR() { return Grid.r((key >>> BIT_FOR_DIR)); } + public int clueIndex() { return key >>> BIT_FOR_DIR; } + public int clueC() { return Grid.c((key >>> BIT_FOR_DIR)); } + public int dir() { return key & 7; } + public boolean horiz() { return horiz(dir()); } + public boolean reversed() { return (key & 2) == 0; } + public boolean increasing() { return (key & 2) != 0; } + public static boolean increasing(int dir) { return (dir & 2) != 0; } public int pos(int i) { - boolean increasing = (dir() == 2 || dir() == 3); - if (increasing) { + if (increasing()) { int bcLo = Long.bitCount(lo); if (i < bcLo) { long temp = lo; @@ -364,8 +352,8 @@ public record SwedishGenerator(Rng rng) { } } } - public static boolean horiz(int d) { return (d & 1) == 0; } - public static int packSlotDir(int idx, int d) { return (idx << BIT_FOR_DIR) | d; } + public static boolean horiz(int d) { return (d & 1) == 0; } + public static int packSlotDir(int idx, int d) { return (idx << BIT_FOR_DIR) | d; } } private static void processSlot(Grid grid, SlotVisitor visitor, int idx) { @@ -382,7 +370,7 @@ public record SwedishGenerator(Rng rng) { // slice ray to stop before first clue, depending on direction monotonicity // right/down => increasing indices; up/left => decreasing indices - boolean increasing = (d == 2 || d == 3); + boolean increasing = Slot.increasing(d); if (increasing) { // first clue is lowest index among hits (lo first, then hi) @@ -426,27 +414,28 @@ public record SwedishGenerator(Rng rng) { var covV = ctx.covV2; covH.clear(); covV.clear(); - /*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)) * 16000L); boolean hasSlots = false; 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); - int d = grid.digitAt(clueIdx); - int di = d - 1; - int key = (clueIdx << 2) | di; - long rLo = PATH_LO[key], rHi = PATH_HI[key]; - long hLo = rLo & lo_cl, hHi = rHi & hi_cl; - if (d == 2 || d == 3) { - if (hLo != 0) { rLo &= ((1L << Long.numberOfTrailingZeros(hLo)) - 1); rHi = 0; } - else if (hHi != 0) { rHi &= ((1L << Long.numberOfTrailingZeros(hHi)) - 1); } + int clueIdx = i | Long.numberOfTrailingZeros(bits); + int d = grid.digitAt(clueIdx); + int di = d - 1; + int key = (clueIdx << 2) | di; + long rLo = PATH_LO[key], rHi = PATH_HI[key]; + long hLo = rLo & lo_cl, hHi = rHi & hi_cl; + if (Slot.increasing(d)) { + if (hLo != 0) { + rLo &= ((1L << Long.numberOfTrailingZeros(hLo)) - 1); + rHi = 0; + } else if (hHi != 0) { rHi &= ((1L << Long.numberOfTrailingZeros(hHi)) - 1); } } else { if (hHi != 0) { int msb = 63 - Long.numberOfLeadingZeros(hHi); - rHi &= ~((1L << msb << 1) - 1); rLo = 0; + rHi &= ~((1L << msb << 1) - 1); + rLo = 0; } else if (hLo != 0) { int msb = 63 - Long.numberOfLeadingZeros(hLo); rLo &= ~((1L << msb << 1) - 1); @@ -454,7 +443,8 @@ public record SwedishGenerator(Rng rng) { } if ((rLo | rHi) != 0) { hasSlots = true; - if (Slot.horiz(d)) covH.or(rLo, rHi); else covV.or(rLo, rHi); + if (Slot.horiz(d)) covH.or(rLo, rHi); + else covV.or(rLo, rHi); if ((Long.bitCount(rLo) + Long.bitCount(rHi)) < MIN_LEN) penalty += 8000; } else { penalty += 25000; @@ -676,77 +666,97 @@ public record SwedishGenerator(Rng rng) { } static long patternForSlot(Grid grid, Slot s) { + if ((s.lo & ~grid.lo) == 0 && (s.hi & ~grid.hi) == 0) return 0; long p = 0; - int i = 0; - boolean increasing = (s.dir() == 2 || s.dir() == 3); - if (increasing) { - for (long b = s.lo; b != 0; b &= b - 1, i++) { - byte ch = grid.byteAt(Long.numberOfTrailingZeros(b)); - if (isLetter(ch)) p |= ((long) (ch & 31)) << (i * 5); + if (s.increasing()) { + for (long b = s.lo & ~grid.lo; b != 0; b &= b - 1) { + int idx = Long.numberOfTrailingZeros(b); + int i = Long.bitCount(s.lo & ((1L << idx) - 1)); + p |= ((long) (grid.g[idx] & 31)) << (i * 5); } - for (long b = s.hi; b != 0; b &= b - 1, i++) { - byte ch = grid.byteAt(64 | Long.numberOfTrailingZeros(b)); - if (isLetter(ch)) p |= ((long) (ch & 31)) << (i * 5); + int offset = Long.bitCount(s.lo); + for (long b = s.hi & ~grid.hi; b != 0; b &= b - 1) { + int idx = Long.numberOfTrailingZeros(b); + int i = offset + Long.bitCount(s.hi & ((1L << idx) - 1)); + p |= ((long) (grid.g[64 | idx] & 31)) << (i * 5); } } else { - for (long b = s.hi; b != 0; i++) { - int msb = 63 - Long.numberOfLeadingZeros(b); - byte ch = grid.byteAt(64 | msb); - if (isLetter(ch)) p |= ((long) (ch & 31)) << (i * 5); - b &= ~(1L << msb); + int offset = Long.bitCount(s.hi); + for (long b = s.hi & ~grid.hi; b != 0; b &= b - 1) { + int idx = Long.numberOfTrailingZeros(b); + int i = Long.bitCount(s.hi & ~((1L << idx) | ((1L << idx) - 1))); + p |= ((long) (grid.g[64 | idx] & 31)) << (i * 5); } - for (long b = s.lo; b != 0; i++) { - int msb = 63 - Long.numberOfLeadingZeros(b); - byte ch = grid.byteAt(msb); - if (isLetter(ch)) p |= ((long) (ch & 31)) << (i * 5); - b &= ~(1L << msb); + for (long b = s.lo & ~grid.lo; b != 0; b &= b - 1) { + int idx = Long.numberOfTrailingZeros(b); + int i = offset + Long.bitCount(s.lo & ~((1L << idx) | ((1L << idx) - 1))); + p |= ((long) (grid.g[idx] & 31)) << (i * 5); } } return p; } - static int slotScore(int[] count, Slot s) { int cross = 0; for (long b = s.lo; b != 0; b &= b - 1) cross += (count[Long.numberOfTrailingZeros(b)] - 1); for (long b = s.hi; b != 0; b &= b - 1) cross += (count[64 | Long.numberOfTrailingZeros(b)] - 1); return cross * 10 + s.len(); } - static boolean placeWord(Grid grid, Slot s, long w, int[] undoBuffer, int offset) { - int mask = 0; - boolean increasing = (s.dir() == 2 || s.dir() == 3); - int i = 0; - if (increasing) { + static boolean placeWord(Grid grid, Slot s, long w, long[] undoBuffer, int offset) { + long maskLo = 0, maskHi = 0; + int i = 0; + if (s.increasing()) { for (long b = s.lo; b != 0; b &= b - 1, i++) { - int idx = Long.numberOfTrailingZeros(b); + int idx = Long.numberOfTrailingZeros(b); byte cur = grid.byteAt(idx), ch = Lemma.byteAt(w, i); - if (cur == DASH) { mask |= (1 << i); grid.setLetter(idx, ch); } - else if (cur != ch) { s.undoPlace(grid, mask); return false; } + if (cur == DASH) { + maskLo |= (1L << idx); + grid.setLetter(idx, ch); + } else if (cur != ch) { + grid.undoPlace(maskLo, maskHi); + return false; + } } for (long b = s.hi; b != 0; b &= b - 1, i++) { - int idx = 64 | Long.numberOfTrailingZeros(b); + int idx = 64 | Long.numberOfTrailingZeros(b); byte cur = grid.byteAt(idx), ch = Lemma.byteAt(w, i); - if (cur == DASH) { mask |= (1 << i); grid.setLetter(idx, ch); } - else if (cur != ch) { s.undoPlace(grid, mask); return false; } + if (cur == DASH) { + maskHi |= (1L << (idx & 63)); + grid.setLetter(idx, ch); + } else if (cur != ch) { + grid.undoPlace(maskLo, maskHi); + return false; + } } } else { for (long b = s.hi; b != 0; i++) { - int msb = 63 - Long.numberOfLeadingZeros(b); - int idx = 64 | msb; + int msb = 63 - Long.numberOfLeadingZeros(b); + int idx = 64 | msb; byte cur = grid.byteAt(idx), ch = Lemma.byteAt(w, i); - if (cur == DASH) { mask |= (1 << i); grid.setLetter(idx, ch); } - else if (cur != ch) { s.undoPlace(grid, mask); return false; } + if (cur == DASH) { + maskHi |= (1L << msb); + grid.setLetter(idx, ch); + } else if (cur != ch) { + grid.undoPlace(maskLo, maskHi); + return false; + } b &= ~(1L << msb); } for (long b = s.lo; b != 0; i++) { - int msb = 63 - Long.numberOfLeadingZeros(b); - int idx = msb; + int msb = 63 - Long.numberOfLeadingZeros(b); + int idx = msb; byte cur = grid.byteAt(idx), ch = Lemma.byteAt(w, i); - if (cur == DASH) { mask |= (1 << i); grid.setLetter(idx, ch); } - else if (cur != ch) { s.undoPlace(grid, mask); return false; } + if (cur == DASH) { + maskLo |= (1L << msb); + grid.setLetter(idx, ch); + } else if (cur != ch) { + grid.undoPlace(maskLo, maskHi); + return false; + } b &= ~(1L << msb); } } - undoBuffer[offset] = mask; + undoBuffer[offset << 1] = maskLo; + undoBuffer[(offset << 1) | 1] = maskHi; return true; } @@ -944,7 +954,7 @@ public record SwedishGenerator(Rng rng) { assigned[k] = X; used.clear(lemIdx); - s.undoPlace(grid, ctx.undo[depth]); + grid.undoPlace(ctx.undo[depth << 1], ctx.undo[(depth << 1) | 1]); } stats.backtracks++; return false; @@ -973,7 +983,7 @@ public record SwedishGenerator(Rng rng) { assigned[k] = X; used.clear(lemIdx); - s.undoPlace(grid, ctx.undo[depth]); + grid.undoPlace(ctx.undo[depth << 1], ctx.undo[(depth << 1) | 1]); } stats.backtracks++; diff --git a/src/test/java/puzzle/SwedishGeneratorTest.java b/src/test/java/puzzle/SwedishGeneratorTest.java index 516b68d..481c4b0 100644 --- a/src/test/java/puzzle/SwedishGeneratorTest.java +++ b/src/test/java/puzzle/SwedishGeneratorTest.java @@ -309,18 +309,18 @@ public class SwedishGeneratorTest { var lo = (1L << Grid.offset(0, 0)) | (1L << Grid.offset(0, 1)) | (1L << Grid.offset(0, 2)); var s = Slot.from(key, lo, 0L); var w1 = Lemma.from("ABC"); - var undoBuffer = new int[10]; + var undoBuffer = new long[10]; // 1. Successful placement in empty grid assertTrue(placeWord(grid, s, w1, undoBuffer, 0)); assertEquals('A', grid.byteAt(0)); assertEquals('B', grid.byteAt(Grid.offset(0, 1))); assertEquals('C', grid.byteAt(Grid.offset(0, 2))); - assertEquals(0b111L, undoBuffer[0]); + assertEquals(lo, undoBuffer[0]); // 2. Successful placement with partial overlap (same characters) assertTrue(placeWord(grid, s, w1, undoBuffer, 1)); - assertEquals(0L, undoBuffer[1]); // 0 new characters placed + assertEquals(0L, undoBuffer[2]); // 0 new characters placed // 3. Conflict: place "ABD" where "ABC" is var w2 = Lemma.from("ABD"); @@ -348,15 +348,15 @@ public class SwedishGeneratorTest { var lo = (1L << Grid.offset(0, 1)) | (1L << Grid.offset(0, 2)); var s = Slot.from(key, lo, 0L); var w = Lemma.from("AZ"); - var undoBuffer = new int[10]; + var undoBuffer = new long[10]; var placed = placeWord(grid, s, w, undoBuffer, 0); assertTrue(placed); assertEquals('A', grid.byteAt(Grid.offset(0, 1))); assertEquals('Z', grid.byteAt(Grid.offset(0, 2))); - assertEquals(0b11L, undoBuffer[0]); + assertEquals(lo, undoBuffer[0]); - s.undoPlace(grid, undoBuffer[0]); + grid.undoPlace( undoBuffer[0], undoBuffer[1]); assertEquals(DASH, grid.byteAt(Grid.offset(0, 1))); assertEquals(DASH, grid.byteAt(Grid.offset(0, 2))); }