diff --git a/src/main/java/puzzle/Export.java b/src/main/java/puzzle/Export.java index 3e82c3b..395a86c 100644 --- a/src/main/java/puzzle/Export.java +++ b/src/main/java/puzzle/Export.java @@ -67,6 +67,10 @@ public record Export() { if ((bitIndex & 64) == 0) this.l1 |= 1L << bitIndex; else this.l2 |= 1L << (bitIndex & 63); } + public void or(long lo, long hi) { + this.l1 |= lo; + this.l2 |= hi; + } public void clear() { l1 = 0L; l2 = 0L; @@ -142,10 +146,10 @@ public record Export() { var g = filled().grid(); var placed = new ArrayList(); var clueMap = filled().clueMap(); - g.grid().forEachSlot((int key, long packedPos, int len) -> { + g.grid().forEachSlot((int key, long lo, long hi) -> { var word = clueMap.get(key); if (word != null) { - var p = extractPlacedFromSlot(Slot.from(key, packedPos, len), word); + var p = extractPlacedFromSlot(Slot.from(key, lo, hi), word); if (p != null) placed.add(p); } }); diff --git a/src/main/java/puzzle/SwedishGenerator.java b/src/main/java/puzzle/SwedishGenerator.java index 7e1827c..12b0ef6 100644 --- a/src/main/java/puzzle/SwedishGenerator.java +++ b/src/main/java/puzzle/SwedishGenerator.java @@ -36,7 +36,7 @@ public record SwedishGenerator(Rng rng) { // 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); } + @FunctionalInterface interface SlotVisitor { void visit(int key, long lo, long hi); } //@formatter:on static final long GT_1_OFFSET_53_BIT = 0x3E00000000000000L; static final long X = 0L; @@ -307,28 +307,68 @@ public record SwedishGenerator(Rng rng) { } } - static record Slot(int key, long packedPos, long packedPoslo, long packedPoshi) { + static record Slot(int key, long lo, long hi) { static final int BIT_FOR_DIR = 3; - static Slot from(int key, long packedPos, int len) { return new Slot(key, packedPos | ((long) len << 56), 0L, 0L); } - void undoPlace(Grid grid, int mask) { for (int i = 0, len = len(); i < len; i++) if ((mask & (1L << i)) != 0) grid.clearletter(pos(i)); } - public int len() { return (int) (packedPos >>> 56); } + 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(key); } - public int pos(int i) { return offset(packedPos, i); } - public static boolean horiz(int key) { return (key & 1) == 0; } - public static int offset(long packedPos, int i) { return (int) ((packedPos >>> (i * 7)) & 127); } + public boolean horiz() { return horiz(dir()); } + public int pos(int i) { + boolean increasing = (dir() == 2 || dir() == 3); + if (increasing) { + int bcLo = Long.bitCount(lo); + if (i < bcLo) { + long temp = lo; + for (int k = 0; k < i; k++) temp &= temp - 1; + return Long.numberOfTrailingZeros(temp); + } else { + i -= bcLo; + long temp = hi; + for (int k = 0; k < i; k++) temp &= temp - 1; + return 64 | Long.numberOfTrailingZeros(temp); + } + } else { + int bcHi = Long.bitCount(hi); + if (i < bcHi) { + long temp = hi; + for (int k = 0; k < i; k++) temp &= ~(1L << (63 - Long.numberOfLeadingZeros(temp))); + return 64 | (63 - Long.numberOfLeadingZeros(temp)); + } else { + i -= bcHi; + long temp = lo; + for (int k = 0; k < i; k++) temp &= ~(1L << (63 - Long.numberOfLeadingZeros(temp))); + return 63 - Long.numberOfLeadingZeros(temp); + } + } + } + public static boolean horiz(int d) { return (d & 1) == 0; } public static int packSlotDir(int idx, int d) { return (idx << BIT_FOR_DIR) | d; } } - interface SlotVisitor2 { - - void visitMasks(int slotDir, long loMask, long hiMask); - } - private static void processSlot(Grid grid, SlotVisitor2 visitor, int idx) { + private static void processSlot(Grid grid, SlotVisitor visitor, int idx) { int d = grid.digitAt(idx); // 1..4 int di = d - 1; // 0..3 int key = (idx << 2) | di; @@ -370,28 +410,13 @@ public record SwedishGenerator(Rng rng) { } if ((rayLo | rayHi) != 0) { - visitor.visitMasks(Slot.packSlotDir(idx, d), rayLo, rayHi); - } - } - - private static void processSlot(Grid grid, SlotVisitor visitor, int idx) { - var d = grid.digitAt(idx); - var packed = OFFSETS_D_IDX[(d - 1) | (idx << 2)]; - long packedPos = 0L; - int k = 0; - for (long n = (packed >>> 56), offset = 0L, iidx; k < n; k++, offset += 7L) { - iidx = ((packed >>> offset) & 0x7FL); - if (grid.isClue(iidx)) break; - packedPos |= iidx << offset; - } - if (k > 0) { - visitor.visit(Slot.packSlotDir(idx, d), packedPos, k); + visitor.visit(Slot.packSlotDir(idx, d), rayLo, rayHi); } } static ArrayList extractSlots(Grid grid) { var slots = new ArrayList(32); - grid.forEachSlot((key, packedPos, len) -> slots.add(Slot.from(key, packedPos, len))); + grid.forEachSlot((key, lo, hi) -> slots.add(Slot.from(key, lo, hi))); return slots; } @@ -409,20 +434,28 @@ public record SwedishGenerator(Rng rng) { 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); - long packed = OFFSETS_D_IDX[(d - 1) | (clueIdx << 2)]; - - int n = (int) (packed >>> 56) * 7, k, idx; - var horiz = Slot.horiz(d) ? covH : covV; - for (k = 0; k < n; k += 7) { - idx = (int) ((packed >>> (k)) & 0x7F); - if (grid.isClue(idx)) break; - horiz.set(idx); + 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); } + } else { + if (hHi != 0) { + int msb = 63 - Long.numberOfLeadingZeros(hHi); + rHi &= ~((1L << msb << 1) - 1); rLo = 0; + } else if (hLo != 0) { + int msb = 63 - Long.numberOfLeadingZeros(hLo); + rLo &= ~((1L << msb << 1) - 1); + } } - if (k > 0) { + if ((rLo | rHi) != 0) { hasSlots = true; - if (k < MIN_LEN7) penalty += 8000; + 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; } @@ -644,37 +677,73 @@ public record SwedishGenerator(Rng rng) { static long patternForSlot(Grid grid, Slot s) { long p = 0; - for (int i = 0, len = s.len(); i < len; i++) { - byte ch = grid.byteAt(s.pos(i)); - if (isLetter(ch)) { - p |= ((long) (ch & 31)) << (i * 5); + 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); + } + 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); + } + } 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); + } + 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); } } return p; } static int slotScore(int[] count, Slot s) { - int cross = 0, len = s.len(); - for (int i = 0; i < len; i++) cross += (count[s.pos(i)] - 1); - return cross * 10 + len; + 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; - byte cur, ch; - for (int i = 0, leng = s.len(), idx; i < leng; i++) { - idx = s.pos(i); - cur = grid.byteAt(idx); - ch = Lemma.byteAt(w, i); - if (cur == DASH) { - mask |= (1 << i); - grid.setLetter(idx, ch); - } else if (cur != ch) { - for (var j = 0; j < i; j++) { - if ((mask & (1 << j)) != 0) { - grid.clearletter(s.pos(j)); - } - } - return false; + int mask = 0; + boolean increasing = (s.dir() == 2 || s.dir() == 3); + int i = 0; + if (increasing) { + for (long b = s.lo; b != 0; b &= b - 1, i++) { + 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; } + } + for (long b = s.hi; b != 0; b &= b - 1, i++) { + 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; } + } + } else { + for (long b = s.hi; b != 0; i++) { + 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; } + b &= ~(1L << msb); + } + for (long b = s.lo; b != 0; i++) { + 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; } + b &= ~(1L << msb); } } undoBuffer[offset] = mask; @@ -764,7 +833,10 @@ public record SwedishGenerator(Rng rng) { Arrays.fill(count, 0, SIZE, 0); val slots = extractSlots(grid); - for (var s : slots) for (int i = 0, len = s.len(); i < len; i++) count[s.pos(i)]++; + for (var s : slots) { + for (long b = s.lo; b != 0; b &= b - 1) count[Long.numberOfTrailingZeros(b)]++; + for (long b = s.hi; b != 0; b &= b - 1) count[64 | Long.numberOfTrailingZeros(b)]++; + } val slotScores = new int[slots.size()]; for (int i = 0; i < slots.size(); i++) slotScores[i] = slotScore(count, slots.get(i)); diff --git a/src/test/java/puzzle/MainTest.java b/src/test/java/puzzle/MainTest.java index 0f12126..7b20618 100644 --- a/src/test/java/puzzle/MainTest.java +++ b/src/test/java/puzzle/MainTest.java @@ -37,12 +37,6 @@ public class MainTest { @Test void testStaticSlotMethods() { - // Test static offset extraction - // packedPos: offset(1, 1) at index 0, offset(2, 2) at index 1 - var packedPos = ((long) Grid.offset(1, 1)) | (((long) Grid.offset(2, 2)) << 7); - assertEquals(Grid.offset(1, 1), Slot.offset(packedPos, 0)); - assertEquals(Grid.offset(2, 2), Slot.offset(packedPos, 1)); - // Test static horiz // dir 2 (right) is horizontal assertTrue(Slot.horiz(2)); @@ -56,21 +50,20 @@ public class MainTest { grid.setClue(0, (byte) '2'); // right var count = new AtomicInteger(0); - grid.forEachSlot((key, packedPos, len) -> { + grid.forEachSlot((key, lo, hi) -> { count.incrementAndGet(); - assertEquals(8, len); - assertEquals(0, Grid.r(Slot.offset(packedPos, 0))); - assertEquals(1, Grid.c(Slot.offset(packedPos, 0))); + assertEquals(8, Long.bitCount(lo) + Long.bitCount(hi)); + assertEquals(0, Grid.r(Long.numberOfTrailingZeros(lo))); + assertEquals(1, Grid.c(Long.numberOfTrailingZeros(lo))); }); assertEquals(1, count.get()); } @Test public void testHoriz() { - assertTrue(Slot.from(2, 0L, 1).horiz()); - assertTrue(Slot.from(4, 0L, 1).horiz()); - assertFalse(Slot.from(1, 0L, 1).horiz()); - assertFalse(Slot.from(3, 0L, 1).horiz()); - assertFalse(Slot.from(5, 0L, 1).horiz()); + assertTrue(Slot.from(2, 0L, 0L).horiz()); + assertTrue(Slot.from(4, 0L, 0L).horiz()); + assertFalse(Slot.from(1, 0L, 0L).horiz()); + assertFalse(Slot.from(3, 0L, 0L).horiz()); } @Test public void testGridBasics() { diff --git a/src/test/java/puzzle/SwedishGeneratorTest.java b/src/test/java/puzzle/SwedishGeneratorTest.java index 563dfb1..516b68d 100644 --- a/src/test/java/puzzle/SwedishGeneratorTest.java +++ b/src/test/java/puzzle/SwedishGeneratorTest.java @@ -15,7 +15,7 @@ public class SwedishGeneratorTest { @Test void testPatternForSlotAllLetters() { var grid = new Grid(new byte[]{ 65, 66, 67 }); // A B C - var slot = Slot.from(18, ((long) 0) | ((long) 1 << 7) | ((long) 2 << 14), 3); + var slot = Slot.from(18, 7L, 0L); long pattern = patternForSlot(grid, slot); assertEquals(1 | (2 << 5) | (3 << 10), pattern); @@ -24,7 +24,7 @@ public class SwedishGeneratorTest { @Test void testPatternForSlotMixed() { var grid = new Grid(new byte[]{ 65, DASH, 67 }); // A - C - var slot = Slot.from(1 << 4 | 2, ((long) 0) | ((long) 1 << 7) | ((long) 2 << 14), 3); + var slot = Slot.from(1 << 3 | 2, 7L, 0L); long pattern = patternForSlot(grid, slot); assertEquals(1 | (0 << 5) | (3 << 10), pattern); @@ -33,7 +33,7 @@ public class SwedishGeneratorTest { @Test void testPatternForSlotAllDashes() { var grid = new Grid(new byte[]{ DASH, DASH, DASH }); // - - - - var slot = Slot.from(1 << 4 | 2, ((long) 0) | ((long) 1 << 7) | ((long) 2 << 14), 3); + var slot = Slot.from(1 << 3 | 2, 7L, 0L); long pattern = patternForSlot(grid, slot); assertEquals(0, pattern); @@ -42,7 +42,7 @@ public class SwedishGeneratorTest { @Test void testPatternForSlotSingleLetter() { var grid = new Grid(new byte[]{ 65, DASH, DASH }); // A - - - var slot = Slot.from(1 << 4 | 2, ((long) 0) | ((long) 1 << 7) | ((long) 2 << 14), 3); + var slot = Slot.from(1 << 3 | 2, 7L, 0L); long pattern = patternForSlot(grid, slot); assertEquals(1, pattern); @@ -136,21 +136,21 @@ public class SwedishGeneratorTest { // key = (r << 8) | (c << 4) | d var offset = Grid.offset(2, 3); System.out.println("[DEBUG_LOG] Grid.offset(2, 3) = " + offset); - var key = (offset << Slot.BIT_FOR_DIR) | 5; + var key = (offset << Slot.BIT_FOR_DIR) | 3; System.out.println("[DEBUG_LOG] key = " + key); - long packedPos = 0; + long lo = 0; // pos 0: (2, 5) - packedPos |= Grid.offset(2, 5); + lo |= 1L << Grid.offset(2, 5); // pos 1: (3, 5) - packedPos |= (long) Grid.offset(3, 5) << 7; + lo |= 1L << Grid.offset(3, 5); // pos 2: (4, 5) - packedPos |= (long) Grid.offset(4, 5) << 14; + lo |= 1L << Grid.offset(4, 5); - var s = Slot.from(key, packedPos, 3); + var s = Slot.from(key, lo, 0L); System.out.println("[DEBUG_LOG] s.dir() = " + s.dir()); assertEquals(2, s.clueR()); assertEquals(3, s.clueC()); - assertEquals(5, s.dir()); + assertEquals(3, s.dir()); assertFalse(s.horiz()); assertEquals(2, Grid.r(s.pos(0))); assertEquals(3, Grid.r(s.pos(1))); @@ -305,10 +305,9 @@ public class SwedishGeneratorTest { void testPlaceWord() { var grid = Grid.createEmpty(); // Slot at (0,0) length 3, horizontal (right) - // key = (r << 8) | (c << 4) | d. Here we just need a valid slot for placeWord. - // r(i) and c(i) are used by placeWord. - var packedPos = ((long) Grid.offset(0, 0)) | (((long) Grid.offset(0, 1)) << 7) | (((long) Grid.offset(0, 2)) << 14); - var s = Slot.from(0, packedPos, 3); + var key = (Grid.offset(0,0) << Slot.BIT_FOR_DIR) | 2; + 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]; @@ -345,8 +344,9 @@ public class SwedishGeneratorTest { void testBacktrackingHelpers() { var grid = Grid.createEmpty(); // 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 key = (Grid.offset(0,0) << Slot.BIT_FOR_DIR) | 2; + 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];