From aceaa0fc18e800ed56b9e16f564c49fa86dbe195 Mon Sep 17 00:00:00 2001 From: mike Date: Fri, 16 Jan 2026 23:42:50 +0100 Subject: [PATCH] introduce bitloops --- src/main/java/puzzle/Export.java | 31 ++++++++++--- src/main/java/puzzle/Main.java | 4 +- src/main/java/puzzle/SwedishGenerator.java | 37 +++++++++------- src/test/java/puzzle/MainTest.java | 43 +++++++++++-------- .../java/puzzle/SwedishGeneratorTest.java | 13 +++--- 5 files changed, 78 insertions(+), 50 deletions(-) diff --git a/src/main/java/puzzle/Export.java b/src/main/java/puzzle/Export.java index 548b875..7ed493f 100644 --- a/src/main/java/puzzle/Export.java +++ b/src/main/java/puzzle/Export.java @@ -21,6 +21,7 @@ import static puzzle.SwedishGenerator.R; import static puzzle.SwedishGenerator.Lemma; import static puzzle.SwedishGenerator.Slot; import static puzzle.SwedishGenerator.C; +import static puzzle.SwedishGenerator.X; /** * ExportFormat.java @@ -43,8 +44,11 @@ public record Export() { static final String INIT = IntStream.range(0, R).mapToObj(l_ -> " ").collect(Collectors.joining("\n")); - public record Clued(@Delegate Clues mask) { + public record ClueAt(int index, int clue) { } + + public record Clued(@Delegate Clues c) { + public Clued deepCopyGrid() { return new Clued(new Clues(c.lo, c.hi, c.vlo, c.vhi, c.rlo, c.rhi)); } String gridToString() { var sb = new StringBuilder(INIT); forEachSlot((s, l, a) -> { @@ -56,6 +60,19 @@ public record Export() { }); return sb.toString(); } + public Stream stream() { + val stream = Stream.builder(); + for (var l = c.lo & ~c.rlo & c.vlo ; l != X; l &= l - 1) stream.accept(new ClueAt(Long.numberOfTrailingZeros(l), 1)); + for (var l = c.lo & ~c.rlo & ~c.vlo ; l != X; l &= l - 1) stream.accept(new ClueAt(Long.numberOfTrailingZeros(l), 0)); + for (var l = c.lo & c.rlo & ~c.vlo ; l != X; l &= l - 1) stream.accept(new ClueAt(Long.numberOfTrailingZeros(l), 2)); + for (var l = c.lo & c.rlo & c.vlo ; l != X; l &= l - 1) stream.accept(new ClueAt(Long.numberOfTrailingZeros(l), 3)); + for (var h = c.hi & ~c.rhi & c.vhi ; h != X; h &= h - 1) stream.accept(new ClueAt(64 | Long.numberOfTrailingZeros(h), 1)); + for (var h = c.hi & ~c.rhi & ~c.vhi ; h != X; h &= h - 1) stream.accept(new ClueAt(64 | Long.numberOfTrailingZeros(h), 0)); + for (var h = c.hi & c.rhi & ~c.vhi ; h != X; h &= h - 1) stream.accept(new ClueAt((64 | Long.numberOfTrailingZeros(h)), 2)); + for (var h = c.hi & c.rhi & c.vhi ; h != X; h &= h - 1) stream.accept(new ClueAt((64 | Long.numberOfTrailingZeros(h)), 3)); + + return stream.build(); + } } @FunctionalInterface @@ -74,13 +91,13 @@ public record Export() { public Stream stream(Clues clues) { val stream = Stream.builder(); - for (var l = grid.lo & ~clues.lo; l != SwedishGenerator.X; l &= l - 1) stream.accept(LetterAt.from(Long.numberOfTrailingZeros(l), grid.g)); - for (var h = grid.hi & ~clues.hi; h != SwedishGenerator.X; h &= h - 1) stream.accept(LetterAt.from(64 | Long.numberOfTrailingZeros(h), grid.g)); + for (var l = grid.lo & ~clues.lo; l != X; l &= l - 1) stream.accept(LetterAt.from(Long.numberOfTrailingZeros(l), grid.g)); + for (var h = grid.hi & ~clues.hi; h != X; h &= h - 1) stream.accept(LetterAt.from(64 | Long.numberOfTrailingZeros(h), grid.g)); return stream.build(); } public void forEachLetter(Clues clues, LetterVisit visitor) { - for (var l = grid.lo & ~clues.lo; l != SwedishGenerator.X; l &= l - 1) visitor.visit(Long.numberOfTrailingZeros(l), grid.g); - for (var h = grid.hi & ~clues.hi; h != SwedishGenerator.X; h &= h - 1) visitor.visit(64 | Long.numberOfTrailingZeros(h), grid.g); + for (var l = grid.lo & ~clues.lo; l != X; l &= l - 1) visitor.visit(Long.numberOfTrailingZeros(l), grid.g); + for (var h = grid.hi & ~clues.hi; h != X; h &= h - 1) visitor.visit(64 | Long.numberOfTrailingZeros(h), grid.g); } public static IntStream walk(byte base, long lo, long hi) { if (Slot.increasing(base)) { @@ -227,7 +244,7 @@ public record Export() { // If nothing placed: return full grid mapped to letters/# only if (placed.isEmpty()) { - return new ExportedPuzzle(g.exportGrid(clues.mask, _ -> '#', '#'), new WordOut[0], difficulty, rewards); + return new ExportedPuzzle(g.exportGrid(clues.c, _ -> '#', '#'), new WordOut[0], difficulty, rewards); } // 2) bounding box around all word cells + arrow cells, with 1-cell margin @@ -251,7 +268,7 @@ public record Export() { // 3) map of only used letter cells (everything else becomes '#') var letterAt = new HashMap(); - g.forEachLetter(clues.mask(), (idx, letter) -> { + g.forEachLetter(clues.c(), (idx, letter) -> { if (letter == 0) return; letterAt.put(idx, (char) (64 | letter)); }); diff --git a/src/main/java/puzzle/Main.java b/src/main/java/puzzle/Main.java index 094bb13..d78b8bf 100644 --- a/src/main/java/puzzle/Main.java +++ b/src/main/java/puzzle/Main.java @@ -104,10 +104,10 @@ public class Main { System.out.print(indentLines(res.clues().gridToString(), " ")); section("Grid (raw)"); - System.out.print(indentLines(res.filled().grid().gridToString(res.clues().mask()), " ")); + System.out.print(indentLines(res.filled().grid().gridToString(res.clues().c()), " ")); section("Grid (human)"); - System.out.print(indentLines(res.filled().grid().renderHuman(res.clues().mask()), " ")); + System.out.print(indentLines(res.filled().grid().renderHuman(res.clues().c()), " ")); var exported = res.exportFormatFromFilled(1, new Rewards(50, 2, 1)); diff --git a/src/main/java/puzzle/SwedishGenerator.java b/src/main/java/puzzle/SwedishGenerator.java index 79845d5..5a44e12 100644 --- a/src/main/java/puzzle/SwedishGenerator.java +++ b/src/main/java/puzzle/SwedishGenerator.java @@ -9,6 +9,7 @@ import lombok.val; import precomp.Neighbors9x8; import precomp.Neighbors9x8.rci; import puzzle.Export.Bit1029; +import puzzle.Export.ClueAt; import puzzle.Export.DictEntryDTO; import puzzle.Export.Gridded; import puzzle.Export.Strings; @@ -178,7 +179,6 @@ public record SwedishGenerator(Rng rng, int[] stack, Clues cache) { long lo, hi, vlo, vhi, rlo, rhi; public static Clues createEmpty() { return new Clues(0, 0, 0, 0, 0, 0); } - public Clues deepCopyGrid() { return new Clues(lo, hi, vlo, vhi, rlo, rhi); } boolean cluelessLo(int idx) { if (!isClueLo(idx)) return false; clearClueLo(~(1L << idx)); @@ -205,18 +205,13 @@ public record SwedishGenerator(Rng rng, int[] stack, Clues cache) { if ((idx & 2) != 0) rhi |= mask; else rhi &= ~mask; } - public byte digitAtRevLo(int idx) { return (byte) (2 | ((vlo >>> idx) & 1L)); } - public byte digitAtRevHi(int idx) { return (byte) (2 | ((vhi >>> (idx & 63)) & 1L)); } - public byte digitAtDevLo(int idx) { return (byte) ((vlo >>> idx) & 1L); } - public byte digitAtDevHi(int idx) { return (byte) ((vhi >>> (idx & 63)) & 1L); } - public int digitAtHi(int idx) { return (int) ((((rhi >>> idx) & 1L) << 1) | ((vhi >>> idx) & 1L)); } - public int digitAtLo(int idx) { return (int) ((((rlo >>> idx) & 1L) << 1) | ((vlo >>> idx) & 1L)); } - public void clearClueLo(long mask) { + int digitAtLo(int idx) { return (int) ((((rlo >>> idx) & 1L) << 1) | ((vlo >>> idx) & 1L)); } + void clearClueLo(long mask) { lo &= mask; vlo &= mask; rlo &= mask; } - public void clearClueHi(long mask) { + void clearClueHi(long mask) { hi &= mask; vhi &= mask; rhi &= mask; @@ -239,8 +234,8 @@ public record SwedishGenerator(Rng rng, int[] stack, Clues cache) { for (var l = lo & ~rlo & ~vlo; l != X; l &= l - 1) processSlot(this, visitor, Slot.packSlotKey(Long.numberOfTrailingZeros(l), 0)); for (var l = lo & rlo & ~vlo; l != X; l &= l - 1) processSlotRev(this, visitor, Slot.packSlotKey(Long.numberOfTrailingZeros(l), 2)); for (var l = lo & rlo & vlo; l != X; l &= l - 1) processSlotRev(this, visitor, Slot.packSlotKey(Long.numberOfTrailingZeros(l), 3)); - for (var h = hi & ~rhi & vlo; h != X; h &= h - 1) processSlot(this, visitor, Slot.packSlotKey(64 | Long.numberOfTrailingZeros(h), 1)); - for (var h = hi & ~rhi & ~vlo; h != X; h &= h - 1) processSlot(this, visitor, Slot.packSlotKey(64 | Long.numberOfTrailingZeros(h), 0)); + for (var h = hi & ~rhi & vhi; h != X; h &= h - 1) processSlot(this, visitor, Slot.packSlotKey(64 | Long.numberOfTrailingZeros(h), 1)); + for (var h = hi & ~rhi & ~vhi; h != X; h &= h - 1) processSlot(this, visitor, Slot.packSlotKey(64 | Long.numberOfTrailingZeros(h), 0)); for (var h = hi & rhi & ~vhi; h != X; h &= h - 1) processSlotRev(this, visitor, Slot.packSlotKey((64 | Long.numberOfTrailingZeros(h)), 2)); for (var h = hi & rhi & vhi; h != X; h &= h - 1) processSlotRev(this, visitor, Slot.packSlotKey((64 | Long.numberOfTrailingZeros(h)), 3)); } @@ -628,7 +623,7 @@ public record SwedishGenerator(Rng rng, int[] stack, Clues cache) { else maskHi |= (1L << (i - 64)); } } - var out = new Clues( + var c = new Clues( (a.lo & ~maskLo) | (other.lo & maskLo), (a.hi & ~maskHi) | (other.hi & maskHi), (a.vlo & ~maskLo) | (other.vlo & maskLo), @@ -636,12 +631,22 @@ public record SwedishGenerator(Rng rng, int[] stack, Clues cache) { (a.rlo & ~maskLo) | (other.rlo & maskLo), (a.rhi & ~maskHi) | (other.rhi & maskHi)); - for (var lo = out.lo; lo != X; lo &= lo - 1L) clearCluesLo(out, Long.numberOfTrailingZeros(lo)); - for (var hi = out.hi; hi != X; hi &= hi - 1L) clearCluesHi(out, Long.numberOfTrailingZeros(hi)); - return out; + // for (var lo = c.lo; lo != X; lo &= lo - 1L) clearCluesLo(c, Long.numberOfTrailingZeros(lo)); + // for (var hi = c.hi; hi != X; hi &= hi - 1L) clearCluesHi(c, Long.numberOfTrailingZeros(hi)); + + for (var l = c.lo & ~c.rlo & ~c.vlo ; l != X; l &= l - 1) clearCluesLo(c,Long.numberOfTrailingZeros(l), 0); + for (var l = c.lo & ~c.rlo & c.vlo ; l != X; l &= l - 1) clearCluesLo(c,Long.numberOfTrailingZeros(l), 1); + for (var l = c.lo & c.rlo & ~c.vlo ; l != X; l &= l - 1) clearCluesLo(c,Long.numberOfTrailingZeros(l), 2); + for (var l = c.lo & c.rlo & c.vlo ; l != X; l &= l - 1) clearCluesLo(c,Long.numberOfTrailingZeros(l), 3); + for (var h = c.hi & ~c.rhi & ~c.vhi ; h != X; h &= h - 1) clearCluesHi(c, Long.numberOfTrailingZeros(h), 0); + for (var h = c.hi & ~c.rhi & c.vhi ; h != X; h &= h - 1) clearCluesHi(c, Long.numberOfTrailingZeros(h), 1); + for (var h = c.hi & c.rhi & ~c.vhi ; h != X; h &= h - 1) clearCluesHi(c,( Long.numberOfTrailingZeros(h)), 2); + for (var h = c.hi & c.rhi & ~c.vhi ; h != X; h &= h - 1) clearCluesHi(c,( Long.numberOfTrailingZeros(h)), 3); + return c; } public static void clearCluesLo(Clues out, int idx) { if (!out.hasRoomForClue(OFFSETS_D_IDX[Slot.packSlotKey(idx, out.digitAtLo(idx))])) out.clearClueLo(~(1L << idx)); } - public static void clearCluesHi(Clues out, int idx) { if (!out.hasRoomForClue(OFFSETS_D_IDX[Slot.packSlotKey(64 | idx, out.digitAtHi(idx))])) out.clearClueHi(~(1L << idx)); } + public static void clearCluesLo(Clues out, int idx,int d) { if (!out.hasRoomForClue(OFFSETS_D_IDX[Slot.packSlotKey(idx, d)])) out.clearClueLo(~(1L << idx)); } + public static void clearCluesHi(Clues out, int idx,int d) { if (!out.hasRoomForClue(OFFSETS_D_IDX[Slot.packSlotKey(64 | idx, d)])) out.clearClueHi(~(1L << idx)); } Clues hillclimb(Clues start, int clue_size, int limit) { var best = start; diff --git a/src/test/java/puzzle/MainTest.java b/src/test/java/puzzle/MainTest.java index 1d1a482..e43b7d5 100644 --- a/src/test/java/puzzle/MainTest.java +++ b/src/test/java/puzzle/MainTest.java @@ -3,12 +3,12 @@ package puzzle; import lombok.val; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import puzzle.Export.ClueAt; import puzzle.Export.Clued; import puzzle.Export.Gridded; import puzzle.Export.LetterVisit.LetterAt; import puzzle.Export.PuzzleResult; import puzzle.Export.Rewards; -import puzzle.ExportFormatTest.Clue; import puzzle.Main.Opts; import puzzle.SwedishGenerator.Rng; import puzzle.SwedishGenerator.Slot; @@ -95,17 +95,19 @@ public class MainTest { } @Test public void testGridBasics() { - var clues = Clues.createEmpty(); + var clues = new Clued(Clues.createEmpty()); val key = Slot.packSlotKey(OFF_2_1, CLUE_UP); clues.setClueLo(IDX_2_1.lo, CLUE_UP); var grid = new Gridded(clues.toGrid()); // Test set/get placeWord(grid.grid(), grid.grid().g, key, (1L << OFF_1_1) | (1L << OFF_0_1), 0, AZ); - val arr = grid.stream(clues).collect(Collectors.toMap(LetterAt::index, LetterAt::letter)); - Assertions.assertEquals(LETTER_A, arr.get(OFF_1_1)); - Assertions.assertEquals(LETTER_Z, arr.get(OFF_0_1)); - Assertions.assertEquals(CLUE_UP, (byte) clues.digitAtLo(OFF_2_1)); + val map = grid.stream(clues.c()).collect(Collectors.toMap(LetterAt::index, LetterAt::letter)); + Assertions.assertEquals(LETTER_A, map.get(OFF_1_1)); + Assertions.assertEquals(LETTER_Z, map.get(OFF_0_1)); + var clueMap = clues.stream().collect(Collectors.toMap(ClueAt::index, ClueAt::clue)); + Assertions.assertEquals(1, clueMap.size()); + Assertions.assertEquals(CLUE_UP, clueMap.get(OFF_2_1)); // Test isLetterAt Assertions.assertTrue(clues.notClue(OFF_0_0)); @@ -116,7 +118,8 @@ public class MainTest { // Test isDigitAt Assertions.assertFalse(clues.isClueLo(OFF_0_0)); Assertions.assertTrue(clues.isClueLo(OFF_2_1)); - Assertions.assertEquals(CLUE_UP, (byte) clues.digitAtLo(OFF_2_1)); + clueMap = clues.stream().collect(Collectors.toMap(ClueAt::index, ClueAt::clue)); + Assertions.assertEquals(CLUE_UP, clueMap.get(OFF_2_1)); Assertions.assertFalse(clues.isClueLo(OFF_2_3)); Assertions.assertFalse(clues.isClueLo(OFF_1_1)); @@ -127,18 +130,20 @@ public class MainTest { } @Test public void testCluesDeepCopy() { - var grid = Clues.createEmpty(); - grid.setClueLo(IDX_0_0.lo, RIGHT.dir); - grid.setClueLo(IDX_0_1.lo, UP.dir); - grid.setClueLo(IDX_1_0.lo, LEFT.dir); - grid.setClueLo(IDX_1_1.lo, DOWN.dir); + var clues = new Clued(Clues.createEmpty()); + clues.setClueLo(IDX_0_0.lo, RIGHT.dir); + clues.setClueLo(IDX_0_1.lo, UP.dir); + clues.setClueLo(IDX_1_0.lo, LEFT.dir); + clues.setClueLo(IDX_1_1.lo, DOWN.dir); - var copy = grid.deepCopyGrid(); - Assertions.assertEquals(1, copy.digitAtLo(0)); + var copy = clues.deepCopyGrid(); + var clueMap = clues.stream().collect(Collectors.toMap(ClueAt::index, ClueAt::clue)); + Assertions.assertEquals(CLUE_RIGHT, clueMap.get(OFF_0_0)); copy.setClueLo(IDX_0_0.lo, DOWN.dir); - Assertions.assertEquals(DOWN.dir, copy.digitAtLo(0)); - Assertions.assertEquals(1, grid.digitAtLo(0)); // Original should be unchanged + var copied = copy.stream().collect(Collectors.toMap(ClueAt::index, ClueAt::clue)); + Assertions.assertEquals(DOWN.dir, copied.get(OFF_0_0)); + Assertions.assertEquals(RIGHT.dir, clueMap.get(OFF_0_0)); } @Test public void testMini() { @@ -198,9 +203,9 @@ public class MainTest { System.out.println("[DEBUG_LOG] Simplicity: " + res.filled().stats().simplicity); System.out.println("[DEBUG_LOG] ClueMap Size: " + res.filled().wordCount()); System.out.println("[DEBUG_LOG] Grid:"); - System.out.println(res.filled().grid().renderHuman(res.clues().mask())); - System.out.println(res.filled().grid().gridToString(res.clues().mask())); - System.out.println(res.filled().grid().renderHuman(res.clues().mask())); + System.out.println(res.filled().grid().renderHuman(res.clues().c())); + System.out.println(res.filled().grid().gridToString(res.clues().c())); + System.out.println(res.filled().grid().renderHuman(res.clues().c())); break; } } diff --git a/src/test/java/puzzle/SwedishGeneratorTest.java b/src/test/java/puzzle/SwedishGeneratorTest.java index 455eaaf..342bdf9 100644 --- a/src/test/java/puzzle/SwedishGeneratorTest.java +++ b/src/test/java/puzzle/SwedishGeneratorTest.java @@ -3,6 +3,7 @@ package puzzle; import lombok.val; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import puzzle.Export.Clued; import puzzle.Export.Gridded; import puzzle.Export.IntListDTO; import puzzle.Export.LetterVisit.LetterAt; @@ -317,16 +318,16 @@ public class SwedishGeneratorTest { var rng = new Rng(42); var gen = new SwedishGenerator(rng, new int[STACK_SIZE], Clues.createEmpty()); - var g1 = gen.randomMask(18); - assertNotNull(g1); + var c1 = new Clued(gen.randomMask(18)); + assertNotNull(c1); - var g2 = gen.mutate(g1.deepCopyGrid()); + var g2 = new Clued(gen.mutate(c1.deepCopyGrid().c())); assertNotNull(g2); - assertNotSame(g1, g2); + assertNotSame(c1.c(), g2.c()); - assertNotNull(gen.crossover(g1, g2)); + assertNotNull(gen.crossover(c1.c(), g2.c())); - var g4 = gen.hillclimb(g1, 18, 10); + var g4 = gen.hillclimb(c1.c(), 18, 10); assertNotNull(g4); }