From 61d246e551a4622b93031b8ca29ca0716b25eeeb Mon Sep 17 00:00:00 2001 From: mike Date: Mon, 12 Jan 2026 23:25:59 +0100 Subject: [PATCH] introduce bitloops --- src/main/java/puzzle/SwedishGenerator.java | 29 +++--- src/test/java/puzzle/MainTest.java | 6 +- .../java/puzzle/SwedishGeneratorTest.java | 90 +++++++++---------- 3 files changed, 65 insertions(+), 60 deletions(-) diff --git a/src/main/java/puzzle/SwedishGenerator.java b/src/main/java/puzzle/SwedishGenerator.java index efa0c27..62da830 100644 --- a/src/main/java/puzzle/SwedishGenerator.java +++ b/src/main/java/puzzle/SwedishGenerator.java @@ -61,16 +61,21 @@ public record SwedishGenerator(Rng rng) { static int clamp(int x, int a, int b) { return Math.max(a, Math.min(b, x)); } record Pick(Slot slot, CandidateInfo info, boolean done) { } + // 0b11 + //0b00 + // 0b01 + // 0b10 - static final byte B0 = (byte) 0; - static final byte B64 = (byte) 64; - static final long[] OFFSETS_D_IDX = Neighbors9x8.OFFSET_D_IDX; - static final rci[] IT = Neighbors9x8.IT; - static final int[][] MUTATE_RI = new int[SIZE][625]; - static final long[] NBR8_PACKED_LO = Neighbors9x8.NBR8_PACKED_LO; - static final long[] NBR8_PACKED_HI = Neighbors9x8.NBR8_PACKED_HI; - static final long[] PATH_LO = Neighbors9x8.PATH_LO; - static final long[] PATH_HI = Neighbors9x8.PATH_HI; + static final byte B0 = (byte) 0; + static final byte B64 = (byte) 64; + static final long[] OFFSETS_D_IDX = Neighbors9x8.OFFSET_D_IDX; + static final long[] OFFSET_D_IDX_0_BASE = Neighbors9x8.OFFSET_D_IDX_0_BASE; + static final rci[] IT = Neighbors9x8.IT; + static final int[][] MUTATE_RI = new int[SIZE][625]; + static final long[] NBR8_PACKED_LO = Neighbors9x8.NBR8_PACKED_LO; + static final long[] NBR8_PACKED_HI = Neighbors9x8.NBR8_PACKED_HI; + static final long[] PATH_LO = Neighbors9x8.PATH_LO; + static final long[] PATH_HI = Neighbors9x8.PATH_HI; static { for (int i = 0; i < SIZE; i++) { @@ -317,11 +322,11 @@ public record SwedishGenerator(Rng rng) { public static int clueIndex(int key) { return key >>> BIT_FOR_DIR; } public static int dir(int key) { return key & 7; } public boolean horiz() { return horiz(key); } - 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 IntStream walk() { return Gridded.walk((byte) key, lo, hi); } public static boolean horiz(int d) { return (d & 1) == 0; } + public static boolean horizv2(int d) { return (d & 1) == 1; } public static int packSlotDir(int idx, int d) { return (idx << BIT_FOR_DIR) | d; } } @@ -410,7 +415,7 @@ public record SwedishGenerator(Rng rng) { } if ((rLo | rHi) != 0) { hasSlots = true; - if (Slot.horiz(d + 1)) covH.or(rLo, rHi); + if (Slot.horizv2(d)) covH.or(rLo, rHi); else covV.or(rLo, rHi); if ((Long.bitCount(rLo) + Long.bitCount(rHi)) < MIN_LEN) penalty += 8000; } else { @@ -555,7 +560,7 @@ public record SwedishGenerator(Rng rng) { for (var hi = out.hi; hi != X; hi &= hi - 1L) clearClues(out, 64 + Long.numberOfTrailingZeros(hi)); return out; } - public static void clearClues(Grid out, int idx) { if (!out.hasRoomForClue(OFFSETS_D_IDX[(out.digitAt(idx) ) | (idx << 2)])) out.clearClue(idx); } + public static void clearClues(Grid out, int idx) { if (!out.hasRoomForClue(OFFSETS_D_IDX[(out.digitAt(idx)) | (idx << 2)])) out.clearClue(idx); } Grid hillclimb(Grid start, int limit) { var best = start; diff --git a/src/test/java/puzzle/MainTest.java b/src/test/java/puzzle/MainTest.java index 8a3cfc5..0114f52 100644 --- a/src/test/java/puzzle/MainTest.java +++ b/src/test/java/puzzle/MainTest.java @@ -56,9 +56,9 @@ public class MainTest { void testStaticSlotMethods() { // Test static horiz // dir 2 (right) is horizontal - assertTrue(Slot.horiz(2)); + assertTrue(Slot.horizv2(1)); // dir 3 (down) is vertical - assertFalse(Slot.horiz(3)); + assertFalse(Slot.horizv2(2)); } @Test @@ -78,7 +78,7 @@ public class MainTest { @Test public void testHoriz() { assertTrue(Slot.from(2, 0L, 0L).horiz()); - assertTrue(Slot.from(4, 0L, 0L).horiz()); + assertTrue(Slot.from(0, 0L, 0L).horiz()); assertFalse(Slot.from(1, 0L, 0L).horiz()); assertFalse(Slot.from(3, 0L, 0L).horiz()); } diff --git a/src/test/java/puzzle/SwedishGeneratorTest.java b/src/test/java/puzzle/SwedishGeneratorTest.java index b084f17..eb8c6f7 100644 --- a/src/test/java/puzzle/SwedishGeneratorTest.java +++ b/src/test/java/puzzle/SwedishGeneratorTest.java @@ -11,16 +11,16 @@ import static puzzle.SwedishGenerator.*; public class SwedishGeneratorTest { - static final byte LETTER_A = (byte) 'A'; - static final byte LETTER_B = (byte) 'B'; - static final byte LETTER_C = (byte) 'C'; - static final byte LETTER_X = (byte) 'X'; - static final byte LETTER_Z = (byte) 'Z'; - static final byte CLUE_UP = 0; + static final byte LETTER_A = (byte) 'A'; + static final byte LETTER_B = (byte) 'B'; + static final byte LETTER_C = (byte) 'C'; + static final byte LETTER_X = (byte) 'X'; + static final byte LETTER_Z = (byte) 'Z'; + static final byte CLUE_UP = 0; static final byte CLUE_RIGHT = 1; - static final byte CLUE_DOWN = 2; - static final byte CLUE_LEFT = 3; - + static final byte CLUE_DOWN = 2; + static final byte CLUE_LEFT = 3; + static final int OFF_0_0 = Grid.offset(0, 0); static final int OFF_0_1 = Grid.offset(0, 1); static final int OFF_0_2 = Grid.offset(0, 2); @@ -31,11 +31,11 @@ public class SwedishGeneratorTest { static final int OFF_2_5 = Grid.offset(2, 5); static final int OFF_3_5 = Grid.offset(3, 5); static final int OFF_4_5 = Grid.offset(4, 5); - + static final byte D_BYTE_2 = CLUE_RIGHT; @Test void testPatternForSlotAllLetters() { - var grid = new Grid(new byte[]{ LETTER_A, LETTER_B, LETTER_C }); + var grid = new Grid(new byte[]{ LETTER_A, LETTER_B, LETTER_C }); var slot = Slot.from(18 << Slot.BIT_FOR_DIR | (CLUE_RIGHT + 1), 7L, 0L); long pattern = patternForSlot(grid, slot); @@ -44,7 +44,7 @@ public class SwedishGeneratorTest { @Test void testPatternForSlotMixed() { - var grid = new Grid(new byte[]{ LETTER_A, DASH, LETTER_C }); + var grid = new Grid(new byte[]{ LETTER_A, DASH, LETTER_C }); var slot = Slot.from(1 << Slot.BIT_FOR_DIR | (CLUE_RIGHT + 1), 7L, 0L); long pattern = patternForSlot(grid, slot); @@ -62,7 +62,7 @@ public class SwedishGeneratorTest { @Test void testPatternForSlotSingleLetter() { - var grid = new Grid(new byte[]{ LETTER_A, DASH, DASH }); + var grid = new Grid(new byte[]{ LETTER_A, DASH, DASH }); var slot = Slot.from(1 << Slot.BIT_FOR_DIR | (CLUE_RIGHT + 1), 7L, 0L); long pattern = patternForSlot(grid, slot); @@ -90,7 +90,7 @@ public class SwedishGeneratorTest { void testGrid() { var grid = Grid.createEmpty(); grid.setLetter(OFF_0_0, LETTER_A); - grid.setClue(OFF_0_1, CLUE_LEFT); + grid.setClue(OFF_0_1, CLUE_LEFT); assertEquals('A', grid.byteAt(OFF_0_0)); assertEquals(CLUE_LEFT, grid.digitAt(OFF_0_1)); @@ -180,8 +180,8 @@ public class SwedishGeneratorTest { assertEquals(5, Grid.c(cells[1])); assertEquals(5, Grid.c(cells[2])); - assertTrue(Slot.horiz(CLUE_RIGHT + 1)); // right - assertFalse(Slot.horiz(CLUE_DOWN + 1)); // down + assertTrue(Slot.horizv2(CLUE_RIGHT)); // right + assertFalse(Slot.horizv2(CLUE_DOWN)); // down } static int intersectSorted(int[] a, int aLen, int[] b, int bLen, int[] out) { if (aLen == 0 || bLen == 0) return 0; @@ -315,8 +315,8 @@ public class SwedishGeneratorTest { void testPlaceWord() { var grid = Grid.createEmpty(); // Slot at OFF_0_0 length 3, horizontal (right) - var key = (OFF_0_0 << Slot.BIT_FOR_DIR) | (CLUE_RIGHT + 1); - var lo = (1L << OFF_0_0) | (1L << OFF_0_1) | (1L << OFF_0_2); + var key = (OFF_0_0 << Slot.BIT_FOR_DIR) | (CLUE_RIGHT + 1); + var lo = (1L << OFF_0_0) | (1L << OFF_0_1) | (1L << OFF_0_2); var s = Slot.from(key, lo, 0L); var w1 = Lemma.from("ABC"); var undoBuffer = new long[10]; @@ -354,8 +354,8 @@ public class SwedishGeneratorTest { void testBacktrackingHelpers() { var grid = Grid.createEmpty(); // Slot at 0,1 length 2 - var key = (OFF_0_0 << Slot.BIT_FOR_DIR) | (CLUE_RIGHT + 1); - var lo = (1L << OFF_0_1) | (1L << OFF_0_2); + var key = (OFF_0_0 << Slot.BIT_FOR_DIR) | (CLUE_RIGHT + 1); + var lo = (1L << OFF_0_1) | (1L << OFF_0_2); var s = Slot.from(key, lo, 0L); var w = Lemma.from("AZ"); var undoBuffer = new long[10]; @@ -366,11 +366,11 @@ public class SwedishGeneratorTest { assertEquals('Z', grid.byteAt(OFF_0_2)); assertEquals(lo, undoBuffer[0]); - grid.undoPlace( undoBuffer[0], undoBuffer[1]); + grid.undoPlace(undoBuffer[0], undoBuffer[1]); assertEquals(DASH, grid.byteAt(OFF_0_1)); assertEquals(DASH, grid.byteAt(OFF_0_2)); } - + @Test void testInnerWorkings() { // 1. Test Slot.increasing @@ -378,12 +378,12 @@ public class SwedishGeneratorTest { assertTrue(Slot.increasing(CLUE_RIGHT + 1)); // Right assertTrue(Slot.increasing(CLUE_DOWN + 1)); // Down assertFalse(Slot.increasing(CLUE_UP + 1)); // Up - + var sInc = Slot.from((0 << Slot.BIT_FOR_DIR) | CLUE_RIGHT + 1, 1L, 0L); assertTrue(sInc.increasing()); var sDec = Slot.from((0 << Slot.BIT_FOR_DIR) | CLUE_LEFT + 1, 1L, 0L); assertFalse(sDec.increasing()); - + // 2. Test slotScore int[] counts = new int[SIZE]; counts[1] = 2; @@ -392,48 +392,48 @@ public class SwedishGeneratorTest { // cross = (counts[1]-1) + (counts[2]-1) = 1 + 2 = 3 // score = 3 * 10 + len(2) = 32 assertEquals(32, slotScore(counts, sScore)); - + // 3. Test candidateCountForPattern - var words = new long[] { - Lemma.from("AT"), - Lemma.from("CAT"), - Lemma.from("DOGS"), - Lemma.from("APPLE"), - Lemma.from("APPLY"), - Lemma.from("BANAN"), - Lemma.from("BANANA"), - Lemma.from("BANANAS"), - Lemma.from("BANANASS") // length 8 + var words = new long[]{ + Lemma.from("AT"), + Lemma.from("CAT"), + Lemma.from("DOGS"), + Lemma.from("APPLE"), + Lemma.from("APPLY"), + Lemma.from("BANAN"), + Lemma.from("BANANA"), + Lemma.from("BANANAS"), + Lemma.from("BANANASS") // length 8 }; - - var dict = new Dict(words); + + var dict = new Dict(words); var entry5 = dict.index()[5]; - + var ctx = new Context(); ctx.setPattern(Lemma.pack("APP".getBytes(StandardCharsets.US_ASCII))); assertEquals(2, candidateCountForPattern(ctx, entry5)); - + ctx.setPattern(Lemma.pack("BAN".getBytes(StandardCharsets.US_ASCII))); assertEquals(1, candidateCountForPattern(ctx, entry5)); - + ctx.setPattern(Lemma.pack("CAT".getBytes(StandardCharsets.US_ASCII))); assertEquals(0, candidateCountForPattern(ctx, entry5)); } - + @Test void testMaskFitnessDetailed() { - var gen = new SwedishGenerator(new Rng(42)); + var gen = new SwedishGenerator(new Rng(42)); var grid = Grid.createEmpty(); - + // Empty grid: huge penalty long fitEmpty = gen.maskFitness(grid); assertTrue(fitEmpty >= 1_000_000_000L); - + // Grid with one short slot: still high penalty but less than empty grid.setClue(0, D_BYTE_2); // Right from 0,0. Len 2 if 3x3. long fitOne = gen.maskFitness(grid); assertTrue(fitOne < fitEmpty); - + // Test penalty for TARGET_CLUES // TARGET_CLUES = SIZE >>> 2. For 3x3 it is 9 >>> 2 = 2. // If we have 1 clue, |1 - 2| * 16000 = 16000 penalty.