diff --git a/src/main/java/puzzle/Main.java b/src/main/java/puzzle/Main.java index 64de30a..e9a509d 100644 --- a/src/main/java/puzzle/Main.java +++ b/src/main/java/puzzle/Main.java @@ -79,7 +79,7 @@ public class Main { } section("Result"); - info(String.format(Locale.ROOT, "simplicity : %.2f", res.filled().simplicity())); + info(String.format(Locale.ROOT, "simplicity : %.2f", res.filled().stats().simplicity)); section("Mask"); System.out.print(indentLines(res.mask().gridToString(), " ")); @@ -347,12 +347,12 @@ public class Main { TOTAL_BACKTRACKS.addAndGet(filled.stats().backtracks); if (filled.ok()) { TOTAL_SUCCESS.incrementAndGet(); - TOTAL_SIMPLICITY.addAndGet((long) (filled.simplicity() * 100)); + TOTAL_SIMPLICITY.addAndGet((long) (filled.stats().simplicity * 100)); } var name = Thread.currentThread().getName(); var status = filled.ok() ? "SUCCESS" : "FAILED"; - var simplicity = String.format(Locale.ROOT, "%.2f", filled.simplicity()); + var simplicity = String.format(Locale.ROOT, "%.2f", filled.stats().simplicity); var nps = (int) (filled.stats().nodes / Math.max(0.001, filled.stats().seconds)); System.out.printf(Locale.ROOT, @@ -360,14 +360,14 @@ public class Main { name, status, filled.stats().nodes, filled.stats().backtracks, nps, simplicity, filled.stats().seconds ); - if (filled.ok() && (opts.minSimplicity <= 0 || filled.simplicity() >= opts.minSimplicity)) { + if (filled.ok() && (opts.minSimplicity <= 0 || filled.stats().simplicity >= opts.minSimplicity)) { return new PuzzleResult(swe, dict, new Gridded(mask), filled); } if (opts.verbose && filled.ok()) { System.err.printf(Locale.ROOT, "simplicity : %.2f (below min %.2f)%n", - filled.simplicity(), opts.minSimplicity + filled.stats().simplicity, opts.minSimplicity ); } return null; diff --git a/src/main/java/puzzle/SwedishGenerator.java b/src/main/java/puzzle/SwedishGenerator.java index d588011..3724822 100644 --- a/src/main/java/puzzle/SwedishGenerator.java +++ b/src/main/java/puzzle/SwedishGenerator.java @@ -3,6 +3,10 @@ package puzzle; import com.google.gson.Gson; import lombok.Getter; import lombok.val; +import precomp.Neighbors9x8; +import precomp.Neighbors9x8.nbrs_16; +import precomp.Neighbors9x8.nbrs_8; +import precomp.Neighbors9x8.rci; import puzzle.Export.Bit; import puzzle.Export.Bit1029; import puzzle.Export.Gridded; @@ -55,71 +59,16 @@ public record SwedishGenerator(Rng rng) { static final ThreadLocal CTX = ThreadLocal.withInitial(Context::new); static boolean isLetter(byte b) { return (b & 64) != 0; } static int clamp(int x, int a, int b) { return Math.max(a, Math.min(b, x)); } - - record nbrs_8(int r, int c) { } - - record nbrs_16(int r, int c, int dr, int dc, int d, byte dbyte) { - - public nbrs_16(int r, int c, int dr, int dc, int d) { this(r, c, dr, dc, d, (byte) (48 + d)); } - } + record Pick(Slot slot, CandidateInfo info, boolean done) { } // Directions for '1'..'6' - static final nbrs_16[] OFFSETS = new nbrs_16[]{ - null, - new nbrs_16(-1, 0, -1, 0, 1), // 1: up - new nbrs_16(0, 1, 0, 1, 2), // 2: right - new nbrs_16(1, 0, 1, 0, 3),// 3: down - new nbrs_16(0, -1, 0, -1, 4),// 4: left - new nbrs_16(0, -1, 1, 0, 5),// 5: vertical down, clue is on the right of the first letter - new nbrs_16(0, 1, 1, 0, 6)// 6: vertical down, clue is on the left of the first letter - }; - final static nbrs_8[] nbrs8 = new nbrs_8[]{ - new nbrs_8(-1, -1), - new nbrs_8(-1, 0), - new nbrs_8(-1, 1), - new nbrs_8(0, -1), - new nbrs_8(0, 1), - new nbrs_8(1, -1), - new nbrs_8(1, 0), - new nbrs_8(1, 1) - }; - static final nbrs_8[] nbrs4 = new nbrs_8[]{ - new nbrs_8(-1, 0), - new nbrs_8(1, 0), - new nbrs_8(0, -1), - new nbrs_8(0, 1) - }; + static final nbrs_16[] OFFSETS = Neighbors9x8.OFFSETS; + static final nbrs_8[] nbrs8 = Neighbors9x8.nbrs8; + static final nbrs_8[] nbrs4 = Neighbors9x8.nbrs4; + static final rci[] IT = Neighbors9x8.IT; - record rci(int r, int c, int i, long n1, long n2, int nbrCount, long n8_1, long n8_2) { } - - static final rci[] IT = new rci[SIZE]; - static { - for (int i = 0; i < SIZE; i++) { - int r = i % R; - int c = i / R; - long n1 = 0, n2 = 0; - for (var d : nbrs4) { - int nr = r + d.r; - int nc = c + d.c; - if (nr >= 0 && nr < R && nc >= 0 && nc < C) { - int nidx = nc * R + nr; - if (nidx < 64) n1 |= (1L << nidx); - else n2 |= (1L << (nidx - 64)); - } - } - long n8_1 = 0, n8_2 = 0; - for (var d : nbrs8) { - int nr = r + d.r; - int nc = c + d.c; - if (nr >= 0 && nr < R && nc >= 0 && nc < C) { - int nidx = nc * R + nr; - if (nidx < 64) n8_1 |= (1L << nidx); - else n8_2 |= (1L << (nidx - 64)); - } - } - IT[i] = new rci(r, c, i, n1, n2, (int) (Long.bitCount(n1) + Long.bitCount(n2)), n8_1, n8_2); - } - } + static final Pick PICK_DONE = new Pick(null, null, true); + static final Pick PICK_NOT_DONE = new Pick(null, null, false); public static final class FillStats { @@ -127,26 +76,19 @@ public record SwedishGenerator(Rng rng) { public long backtracks; public double seconds; public int lastMRV; + public double simplicity; } - record Pick(Slot slot, CandidateInfo info, boolean done) { } - - static final Pick PICK_DONE = new Pick(null, null, true); - static final Pick PICK_NOT_DONE = new Pick(null, null, false); - public static record FillResult(boolean ok, Gridded grid, HashMap clueMap, - FillStats stats, - double simplicity) { + FillStats stats) { - public FillResult(boolean ok, Gridded grid, HashMap assigned, FillStats stats) { - double totalSimplicity = 0; + public FillResult { if (ok) { - for (var w : assigned.values()) totalSimplicity += w.simpel; - totalSimplicity = assigned.isEmpty() ? 0 : totalSimplicity / assigned.size(); + clueMap.forEach((k, v) -> stats.simplicity += v.simpel); + stats.simplicity = clueMap.isEmpty() ? 0 : stats.simplicity / clueMap.size(); } - this(ok, grid, assigned, stats, totalSimplicity); } } @@ -392,7 +334,7 @@ public record SwedishGenerator(Rng rng) { private static void processSlot(Grid grid, SlotVisitor visitor, int idx) { var d = grid.digitAt(idx); var nbrs16 = OFFSETS[d]; - int r = Grid.r(idx), c = Grid.c(idx), rr = r + nbrs16.r, cc = c + nbrs16.c; + int r = Grid.r(idx), c = Grid.c(idx), rr = r + nbrs16.r(), cc = c + nbrs16.c(); if (rr < 0 || rr >= R || cc < 0 || cc >= C || grid.isDigitAt(rr, cc)) return; long packedPos = 0; var n = 0; @@ -400,8 +342,8 @@ public record SwedishGenerator(Rng rng) { while (rr >= 0 && rr < R && cc >= 0 && cc < C && grid.isLetterAt(rr, cc) && n < MAX_WORD_LENGTH) { packedPos |= (long) Grid.offset(rr, cc) << (n * 7); n++; - rr += nbrs16.dr; - cc += nbrs16.dc; + rr += nbrs16.dr(); + cc += nbrs16.dc(); } if (n > 0) { visitor.visit((idx << 4) | d, packedPos, n); @@ -414,12 +356,12 @@ public record SwedishGenerator(Rng rng) { return slots; } boolean hasRoomForClue(Grid grid, int idx, nbrs_16 nbrs16) { - int rr = Grid.r(idx) + nbrs16.r, cc = Grid.c(idx) + nbrs16.c; + int rr = Grid.r(idx) + nbrs16.r(), cc = Grid.c(idx) + nbrs16.c(); var run = 0; while (rr >= 0 && rr < R && cc >= 0 && cc < C && (grid.isLetterAt(rr, cc)) && run < MAX_WORD_LENGTH) { run++; - rr += nbrs16.dr; - cc += nbrs16.dc; + rr += nbrs16.dr(); + cc += nbrs16.dc(); if (run >= MIN_LEN) return true; } return false; @@ -445,7 +387,7 @@ public record SwedishGenerator(Rng rng) { var d = grid.digitAt(clueIdx); var nbrs16 = OFFSETS[d]; - int rr = Grid.r(clueIdx) + nbrs16.r, cc = Grid.c(clueIdx) + nbrs16.c; + int rr = Grid.r(clueIdx) + nbrs16.r(), cc = Grid.c(clueIdx) + nbrs16.c(); if (rr < 0 || rr >= R || cc < 0 || cc >= C || grid.isDigitAt(rr, cc)) continue; long packedPos = 0; @@ -454,8 +396,8 @@ public record SwedishGenerator(Rng rng) { if (grid.isDigitAt(rr, cc)) break; packedPos |= (long) Grid.offset(rr, cc) << (n * 7); n++; - rr += nbrs16.dr; - cc += nbrs16.dc; + rr += nbrs16.dr(); + cc += nbrs16.dc(); } if (n == 0) continue; hasSlots[0] = true; @@ -473,7 +415,7 @@ public record SwedishGenerator(Rng rng) { var d = grid.digitAt(clueIdx); var nbrs16 = OFFSETS[d]; - int rr = Grid.r(clueIdx) + nbrs16.r, cc = Grid.c(clueIdx) + nbrs16.c; + int rr = Grid.r(clueIdx) + nbrs16.r(), cc = Grid.c(clueIdx) + nbrs16.c(); if (rr < 0 || rr >= R || cc < 0 || cc >= C || grid.isDigitAt(rr, cc)) continue; long packedPos = 0; @@ -482,8 +424,8 @@ public record SwedishGenerator(Rng rng) { if (grid.isDigitAt(rr, cc)) break; packedPos |= (long) Grid.offset(rr, cc) << (n * 7); n++; - rr += nbrs16.dr; - cc += nbrs16.dc; + rr += nbrs16.dr(); + cc += nbrs16.dc(); } if (n == 0) continue; hasSlots[0] = true; @@ -526,8 +468,8 @@ public record SwedishGenerator(Rng rng) { size++; for (var d : nbrs8) { - var nr = rr + d.r; - var nc = cc + d.c; + var nr = rr + d.r(); + var nc = cc + d.c(); if (nr < 0 || nr >= R || nc < 0 || nc >= C) continue; var nidx = Grid.offset(nr, nc); if (seen.get(nidx) || grid.isLetterAt(nidx)) continue; @@ -541,8 +483,8 @@ public record SwedishGenerator(Rng rng) { int walls; // dead-end-ish letter cell (3+ walls) for (var rci : IT) { - if (grid.isDigitAt(rci.i)) continue; - walls = (4 - rci.nbrCount) + Long.bitCount(rci.n1 & grid.bo[0]) + Long.bitCount(rci.n2 & grid.bo[1]); + if (grid.isDigitAt(rci.i())) continue; + walls = (4 - rci.nbrCount()) + Long.bitCount(rci.n1() & grid.bo[0]) + Long.bitCount(rci.n2() & grid.bo[1]); if (walls >= 3) penalty[0] += 400; } return penalty[0]; @@ -559,7 +501,7 @@ public record SwedishGenerator(Rng rng) { var d = OFFSETS[rng.randbyte(1, 4)]; if (hasRoomForClue(g, idx, d)) { - g.setClue(idx, d.dbyte); + g.setClue(idx, d.dbyte()); placed++; } } @@ -580,7 +522,7 @@ public record SwedishGenerator(Rng rng) { g.clearClue(ri); } else { var d = OFFSETS[rng.randint(1, 4)]; - if (hasRoomForClue(g, ri, d)) g.setClue(ri, d.dbyte); + if (hasRoomForClue(g, ri, d)) g.setClue(ri, d.dbyte()); } } return g; @@ -591,8 +533,8 @@ public record SwedishGenerator(Rng rng) { var nc = Math.cos(theta); var nr = Math.sin(theta); - for (var rci : IT) out.setAt(rci.i, ((rci.r - CROSS_C) * nc + (rci.c - CROSS_R) * nr >= 0) ? a.byteAt(rci.i) : b.byteAt(rci.i)); - for (var rci : IT) if (out.isDigitAt(rci.i) && !hasRoomForClue(out, rci.i, OFFSETS[out.digitAt(rci.i)])) out.clearClue(rci.i); + for (var rci : IT) out.setAt(rci.i(), ((rci.r() - CROSS_C) * nc + (rci.c() - CROSS_R) * nr >= 0) ? a.byteAt(rci.i()) : b.byteAt(rci.i())); + for (var rci : IT) if (out.isDigitAt(rci.i()) && !hasRoomForClue(out, rci.i(), OFFSETS[out.digitAt(rci.i())])) out.clearClue(rci.i()); return out; } diff --git a/src/test/java/puzzle/ExportFormatTest.java b/src/test/java/puzzle/ExportFormatTest.java index df01f05..8ac1a71 100644 --- a/src/test/java/puzzle/ExportFormatTest.java +++ b/src/test/java/puzzle/ExportFormatTest.java @@ -40,7 +40,7 @@ public class ExportFormatTest { // Terminate thGrid.offset(e slot at) (0,5) with another digit to avoid it extending to MAX_WORD_LENGTH grid.setClue(Grid.offset(0, 5), (byte) '1'); - var fillResult = new FillResult(true, new Gridded(grid), clueMap, null); + var fillResult = new FillResult(true, new Gridded(grid), clueMap, new FillStats()); var puzzleResult = new PuzzleResult(swe, null, null, fillResult); var rewards = new Rewards(10, 5, 1); @@ -80,7 +80,7 @@ public class ExportFormatTest { void testExportFormatEmpty() { var swe = new SwedishGenerator(new Rng(0)); var grid = Grid.createEmpty(); - var fillResult = new FillResult(true, new Gridded(grid), new HashMap<>(), null); + var fillResult = new FillResult(true, new Gridded(grid), new HashMap<>(), new FillStats()); var puzzleResult = new PuzzleResult(swe, null, null, fillResult); var exported = puzzleResult.exportFormatFromFilled(1, new Rewards(0, 0, 0)); diff --git a/src/test/java/puzzle/MainTest.java b/src/test/java/puzzle/MainTest.java index 5f0a3f5..0e87744 100644 --- a/src/test/java/puzzle/MainTest.java +++ b/src/test/java/puzzle/MainTest.java @@ -152,7 +152,7 @@ public class MainTest { if (res != null && res.filled().ok()) { foundSeed = seed; System.out.println("[DEBUG_LOG] Seed found: " + seed); - System.out.println("[DEBUG_LOG] Simplicity: " + res.filled().simplicity()); + System.out.println("[DEBUG_LOG] Simplicity: " + res.filled().stats().simplicity); System.out.println("[DEBUG_LOG] ClueMap Size: " + res.filled().clueMap().size()); System.out.println("[DEBUG_LOG] Grid:"); System.out.println(res.filled().grid().renderHuman()); @@ -167,7 +167,7 @@ public class MainTest { // Regression baseline for seed search starting at 12347, pop 4, gens 20 Assertions.assertEquals(12347, foundSeed, "Found seed changed"); Assertions.assertEquals(20, res.filled().clueMap().size(), "Number of assigned words changed"); - Assertions.assertEquals(758.55, res.filled().simplicity(), 1e-9, "Simplicity value changed"); + Assertions.assertEquals(758.55, res.filled().stats().simplicity, 1e-9, "Simplicity value changed"); Assertions.assertArrayEquals(new byte[]{ 'M', 'A', 'N', 'T', 'A' }, res.filled().clueMap().get(849).word()); } @Test