From 19812d81e585ab7677e95de579489928dd9a21f4 Mon Sep 17 00:00:00 2001 From: mike Date: Sat, 17 Jan 2026 21:43:17 +0100 Subject: [PATCH] introduce bitloops --- src/main/java/puzzle/Export.java | 24 ++++++++------------- src/main/java/puzzle/Main.java | 15 ++++++------- src/main/java/puzzle/Masker.java | 4 ++++ src/main/java/puzzle/SwedishGenerator.java | 25 +++++++++++----------- src/test/java/puzzle/MainTest.java | 2 +- 5 files changed, 34 insertions(+), 36 deletions(-) diff --git a/src/main/java/puzzle/Export.java b/src/main/java/puzzle/Export.java index 44690ef..1f10796 100644 --- a/src/main/java/puzzle/Export.java +++ b/src/main/java/puzzle/Export.java @@ -83,6 +83,9 @@ public record Export() { record LetterAt(int index, byte letter) { + public char human() { + return (char) (letter | 64); + } static LetterAt from(int index, byte[] bytes) { return new LetterAt(index, bytes[index]); } } @@ -235,18 +238,14 @@ public record Export() { return simpel; } public ExportedPuzzle exportFormatFromFilled(int difficulty, Rewards rewards) { - var placed = new ArrayList(); - for (var slot : slots) { - placed.add(new Placed(slot.assign().w, slot.key(), Gridded.walk((byte) slot.key(), slot.lo(), slot.hi()).toArray())); - } - // If nothing placed: return full grid mapped to letters/# only - if (placed.isEmpty()) { + if (slots.length == 0) { return new ExportedPuzzle(grid.exportGrid(clues.c, _ -> '#', '#'), new WordOut[0], difficulty, rewards); } - // 2) bounding box around all word cells + arrow cells, with 1-cell margin + var placed = Arrays.stream(slots).map(slot -> new Placed(slot.assign().w, slot.key(), Gridded.walk((byte) slot.key(), slot.lo(), slot.hi()).toArray())).toArray(Placed[]::new); + // 2) bounding box around all word cells + arrow cells, with 1-cell margin int minR = Integer.MAX_VALUE, minC = Integer.MAX_VALUE; int maxR = Integer.MIN_VALUE, maxC = Integer.MIN_VALUE; @@ -265,23 +264,18 @@ public record Export() { } // 3) map of only used letter cells (everything else becomes '#') - var letterAt = new HashMap(); - grid.forEachLetter(clues.c(), (idx, letter) -> { - if (letter == 0) return; - letterAt.put(idx, (char) (64 | letter)); - }); - + var map = grid.stream(clues.c()).collect(Collectors.toMap(LetterAt::index, LetterAt::human)); // 4) render gridv2 over cropped bounds (out-of-bounds become '#') var gridv2 = new String[Math.max(0, maxR - minR + 1)]; for (int r = minR, i = 0; r <= maxR; r++, i++) { var row = new StringBuilder(Math.max(0, maxC - minC + 1)); - for (var c = minC; c <= maxC; c++) row.append(letterAt.getOrDefault(Grid.offset(r, c), '#')); + for (var c = minC; c <= maxC; c++) row.append(map.getOrDefault(Grid.offset(r, c), '#')); gridv2[i] = row.toString(); } // 5) words output with cropped coordinates int MIN_R = minR, MIN_C = minC; - var wordsOut = placed.stream().map(p -> new WordOut( + var wordsOut = Arrays.stream(placed).map(p -> new WordOut( p.lemma, p.startRow() - MIN_R, p.startCol() - MIN_C, diff --git a/src/main/java/puzzle/Main.java b/src/main/java/puzzle/Main.java index c71ea49..0c01119 100644 --- a/src/main/java/puzzle/Main.java +++ b/src/main/java/puzzle/Main.java @@ -402,20 +402,19 @@ public class Main { } static Clues generateNewClues(Rng rng, Opts opts) { var masker = new Masker(rng, new int[STACK_SIZE], Masker.Clues.createEmpty()); - var mask = masker.generateMask(opts.clueSize, opts.pop, opts.gens, opts.offspring); - return mask; + return masker.generateMask(opts.clueSize, opts.pop, opts.gens, opts.offspring); } static PuzzleResult _attempt(Rng rng, Dict dict, Opts opts) { - long t0 = System.currentTimeMillis(); + val multiThreaded = Thread.currentThread().getName().contains("pool"); + long t0 = System.currentTimeMillis(); TOTAL_ATTEMPTS.incrementAndGet(); val mask = generateNewClues(rng, opts); //val mask = generateClues(); if (mask == null) return null; - val multiThreaded = Thread.currentThread().getName().contains("pool"); - var slots = Masker.extractSlots(mask, dict.index()); - val slotInfo = Masker.scoreSlots(slots); - var grid = mask.toGrid(); - var filled = fillMask(rng, slotInfo, grid, multiThreaded); + + val slotInfo = Masker.slots(mask, dict.index()); + var grid = mask.toGrid(); + var filled = fillMask(rng, slotInfo, grid, (!Main.VERBOSE || multiThreaded)); if (!multiThreaded) { System.out.print("\r" + Strings.padRight("", 120) + "\r"); diff --git a/src/main/java/puzzle/Masker.java b/src/main/java/puzzle/Masker.java index ec94766..fd467df 100644 --- a/src/main/java/puzzle/Masker.java +++ b/src/main/java/puzzle/Masker.java @@ -75,6 +75,10 @@ public record Masker(Rng rng, int[] stack, Clues cache) { grid.forEachSlot((key, lo, hi) -> slots[N[0]++] = Slot.from(key, lo, hi, index[Slot.length(lo, hi)])); return slots; } + public static Slotinfo[] slots(Clues mask, DictEntry[] index) { + var slots = Masker.extractSlots(mask, index); + return Masker.scoreSlots(slots); + } public static Slotinfo[] scoreSlots(Slot[] slots) { val count = new byte[SwedishGenerator.SIZE]; Slotinfo[] slotInfo = new Slotinfo[slots.length]; diff --git a/src/main/java/puzzle/SwedishGenerator.java b/src/main/java/puzzle/SwedishGenerator.java index 8a5994b..a25f55a 100644 --- a/src/main/java/puzzle/SwedishGenerator.java +++ b/src/main/java/puzzle/SwedishGenerator.java @@ -134,6 +134,10 @@ public record SwedishGenerator() { public final byte[] g; public long lo, hi; public static int offset(int r, int c) { return r | (c << 3); } + + public Grid copy() { + return new Grid(g.clone(), lo, hi); + } } public static record DictEntry(long[] words, long[][] posBitsets, int length, int numlong) { } @@ -258,8 +262,7 @@ public record SwedishGenerator() { public static FillResult fillMask(final Rng rng, final Slotinfo[] slots, final Grid grid, - final boolean multiThreaded) { - val NO_LOG = (!Main.VERBOSE || multiThreaded); + final boolean NO_LOG) { val used = new long[2048]; val bitset = new long[2500]; val g = grid.g; @@ -388,12 +391,11 @@ public record SwedishGenerator() { var tries = Math.min(MAX_TRIES_PER_SLOT, L); for (var t = 0; t < tries; t++) { - var r = rng.nextFloat(); - //int idxInArray = rng.biasedIndexPow3(L - 1); - var arrIndex = (int) (r * r * r * (L - 1)); - var shardIdx = idxs[arrIndex]; - var w = entry.words[shardIdx]; - var lemIdx = Lemma.unpackIndex(w); + //var r = rng.nextFloat(); + //var idxInArray = (int) (r * r * r * (L - 1)); + int idxInArray = rng.biasedIndexPow3(L - 1); + var w = entry.words[idxs[idxInArray/*(int) (r * r * r * (L - 1))*/]]; + var lemIdx = Lemma.unpackIndex(w); if (Bit1029.get(used, lemIdx)) continue; low = glo; top = ghi; @@ -419,10 +421,9 @@ public record SwedishGenerator() { var tries = Math.min(MAX_TRIES_PER_SLOT, N); for (var t = 0; t < tries; t++) { - double r = rng.nextFloat(); - var shardIdx = (int) (r * r * r * (N - 1)); - var w = entry.words[shardIdx]; - var lemIdx = Lemma.unpackIndex(w); + // double r = rng.nextFloat(); + var w = entry.words[rng.biasedIndexPow3(N - 1)/*(int) (r * r * r * (N - 1))*/]; + var lemIdx = Lemma.unpackIndex(w); if (Bit1029.get(used, lemIdx)) continue; low = glo; top = ghi; diff --git a/src/test/java/puzzle/MainTest.java b/src/test/java/puzzle/MainTest.java index d8f2c2f..a4f055c 100644 --- a/src/test/java/puzzle/MainTest.java +++ b/src/test/java/puzzle/MainTest.java @@ -212,7 +212,7 @@ public class MainTest { var filled = fillMask(rng, slotInfo, grid, false); Assertions.assertTrue(filled.ok(), "Puzzle generation failed (not ok)"); Assertions.assertEquals(17, Slotinfo.wordCount(0, slotInfo), "Number of assigned words changed"); - Assertions.assertEquals("POENIGE", Lemma.asWord(slotInfo[0].assign().w)); + Assertions.assertEquals("VREEMDS", Lemma.asWord(slotInfo[0].assign().w)); Assertions.assertEquals(-1L, grid.lo); Assertions.assertEquals(255L, grid.hi); var g = new Gridded(grid);