From f078dc99f387caf85cf78393740fb260461d8f55 Mon Sep 17 00:00:00 2001 From: Arda Aydin Date: Sat, 17 Oct 2026 22:01:59 +0300 Subject: [PATCH] aspirinV2: tap to start with a random solvable deal The first tap (or SHUFFLE) loads the solved board and deals it with a Fisher-Yates pass driven by the free-running logistic-map RNG, so tap timing picks the board. A parity latch adds one swap of cells 1 and 2 when needed, so every deal is solvable. Tile moves and deal swaps share one cell-swap mechanism, cutting RAM from 56.4 kB (failed to start on the Orion) to 34.6 kB (runs). Adds deal checks to verify_logic.py --v2 and the hint label to the viewer. Co-Authored-By: Claude Opus 5.5 --- README.md | 14 +- app-ORION-aspirinV2.zip | Bin 273713 -> 271313 bytes aspirinV2.sab | Bin 18741 -> 14953 bytes aspirinV2.sax | 3724 ++++++++++++++++----------------------- gen_aspirinV2.py | 374 ++-- verify_logic.py | 49 +- viewer/OxViewer.java | 2 +- 7 files changed, 1860 insertions(+), 2303 deletions(-) diff --git a/README.md b/README.md index ab0fe8e..cc1a12a 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ |---|---| | `aspirin.sax` / `app-ORION-aspirin.zip` | Playable game: auto-shuffle at start, tap a tile next to the gap to slide it, SHUFFLE button, move counter, SOLVED! | | `aspirin_solver.sax` / `app-ORION-aspirin_solver.zip` | Demo: shuffles 30 moves, then an auto solver plays one move per second until SOLVED! (it records the shuffle and replays the inverse moves) | -| `aspirinV2.sax` / `app-ORION-aspirinV2.zip` | Same game with fixed cells: nothing moves, each cell shows the number it holds on white, the empty cell takes the board colour | +| `aspirinV2.sax` / `app-ORION-aspirinV2.zip` | Fixed cells: nothing moves, each cell shows its number on white, the empty cell takes the board colour. Tap to start deals a random solvable board | ## Run in the simulator @@ -40,6 +40,14 @@ python verify_logic.py --v2 - 16 `NumericSelectLabel` cells; `in` = the number held (0 = empty), text list ` ;1;2;...;15;`. - `backgroundColor` is linked from logic: white for a number, board colour for the empty cell. -- State = 16 cell values in `ADemux2` latches (boot loads the solved board). Blank position is - derived from the values; a move latches 0 into the touched cell and its value into the blank. +- State = 16 cell values in `ADemux2` latches (boot loads the solved board). +- One swap mechanism moves values: each scan at most one pair of cells (A, B) exchanges values. + A tile move swaps the touched cell with the blank (new touch only, and only when adjacent). +- No shuffle moves: the first tap (or SHUFFLE) deals. It loads the solved board, then a + Fisher-Yates pass swaps cell i = 15..2 with a random cell j <= i, one swap per scan + (~1.5 s). The logistic-map value is made ~uniform with a `Linearize` (arcsine CDF). The RNG + runs from boot, so tap timing picks the board. The blank starts in cell 16. +- Solvability: with the blank home, a deal is solvable iff it is an even permutation; a parity + latch counts real swaps and an odd deal gets one extra swap of cells 1 and 2. - Solved = sum of i * value_i over cells 1..15 is 1240 (its unique maximum). +- Keep RAM in mind: the Orion failed to start a 56 kB build of V2; the current one is ~35 kB. diff --git a/app-ORION-aspirinV2.zip b/app-ORION-aspirinV2.zip index 5d4f94e3157830eb20eea9d8d8c16cbf4864fa73..6942c10e8f1c013efed5e1e155097f8287cdfec0 100644 GIT binary patch delta 16013 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

N^; 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 b{?(DZ_Qon5diUD)Z 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 AssI20 diff --git a/aspirinV2.sax b/aspirinV2.sax index d336b75..a0f5a04 100644 --- a/aspirinV2.sax +++ b/aspirinV2.sax @@ -46,1579 +46,1087 @@ - - - - - - - - - - + - + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + - - - + + + - + - + - + + + + + + + + + + + + + - + - - - - - + - - - - - + - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - + - + - + - + - + - - - - - - - - + - - - - - - - - - - - - - - - - - - - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - + - + - + - + - + + + - - + + + - - - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - + + - - - - - + - + - + - + - - - + - + - + - + - - - - - - - - - - - - + - - - - - - + + - - - - - + - + - + - + - - - + - + - + - + - + - + - - + - - - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + + + - - - + + + - + + + + + - - - + + + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - + + + - + + + + + - - - + + + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - + + + - + + + + + - - - + + + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - + + + - + + + + + - - - + + + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - + + + - + + + + + - - - + + + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - + + + - + + + + + - - - + + + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - + + + - + + + + + - - - + + + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - + + + - + + + + + - - - + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - + - + @@ -1630,7 +1138,7 @@ - + @@ -1642,7 +1150,7 @@ - + @@ -1654,7 +1162,7 @@ - + @@ -1666,7 +1174,7 @@ - + @@ -1678,7 +1186,7 @@ - + @@ -1690,7 +1198,7 @@ - + @@ -1702,7 +1210,7 @@ - + @@ -1714,7 +1222,7 @@ - + @@ -1726,7 +1234,7 @@ - + @@ -1738,7 +1246,7 @@ - + @@ -1750,7 +1258,7 @@ - + @@ -1762,7 +1270,7 @@ - + @@ -1774,7 +1282,7 @@ - + @@ -1786,7 +1294,7 @@ - + @@ -1798,7 +1306,7 @@ - + @@ -1810,7 +1318,7 @@ - + @@ -1822,7 +1330,7 @@ - + @@ -1833,7 +1341,7 @@ - + @@ -1844,7 +1352,7 @@ - + @@ -1856,7 +1364,7 @@ - + @@ -1870,7 +1378,21 @@ - + + + + + + + + + + + + + + + @@ -1891,801 +1413,641 @@ - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -2706,14 +2068,30 @@ - - + + + - + - + + + + + + + + + + + + + + + + diff --git a/gen_aspirinV2.py b/gen_aspirinV2.py index 1c40914..b30b7d5 100644 --- a/gen_aspirinV2.py +++ b/gen_aspirinV2.py @@ -5,21 +5,24 @@ Same game as aspirin.sax, different screen model: nothing moves on screen. (0 = empty) and its text list " ;1;2;...;15;" turns that into the label. - backgroundColor is linked from logic: white for a number, board colour for the empty cell. - Board state = cell values v[1..16] held in ADemux2.out2 (boot loads 1..15, 0 = solved). -- Blank cell: cb = sum_j col_j*(v_j==0), rb likewise (Reset maps v 0..1 -> col..0). -- Cell i is movable when (col-cb)^2 + (row-rb)^2 == 1. A rising edge of (movable AND - (touched OR shuffle-pick)) fires a one-scan OneShot; in that scan cell i latches 0 and the - blank cell latches the moved value MV = sum_i (OS_i ? v_i : 0). -- Shuffle: same logistic-map RNG and direction codes 2*dc+dr as aspirin.sax. +- One swap mechanism: each scan at most one pair of cells (A, B) exchanges values. + Tile move: A = touched cell T = sum(touch_i ? i : 0), B = blank B = sum(v_i == 0 ? i : 0), + only on a new touch and when |T-B| == 4 or (|T-B| == 1, same row). +- Random deal (tap to start, or SHUFFLE): load the solved board, then a Fisher-Yates pass + swaps cell i = 15..2 with cell j = floor(u*i)+1, one swap per scan, u from the logistic-map + RNG made ~uniform. The blank stays in cell 16, so the deal is solvable iff it is an even + permutation; a parity latch adds one swap of cells 1 and 2 when it is odd. + The RNG runs free from boot, so the human's timing picks the board. - Solved: sum_i i*v_i (cells 1..15) == 1240, the unique maximum (rearrangement inequality). """ import base64 +import math OUT = "aspirinV2.sax" N = 4 PITCH = 105 # cell pitch in px CELL = 100 # cell size in px X0, Y0 = 40, 40 # top-left cell -SHUFFLE_SECS = 10.0 SOLVED_SUM = sum(i * i for i in range(1, 16)) # 1240 # RGB565 colours @@ -155,6 +158,11 @@ busy_lbl = page.add("Busy", "ontrolOx::BooleanLabel", x=RX, y=310, width=RW, hei trueText=buf("Shuffling..."), falseText=buf(" "), trueTextColor=C_GREY, falseTextColor=C_PAGE, trueBackgroundColor=C_PAGE, falseBackgroundColor=C_PAGE) +hint_lbl = page.add("Hint", "ontrolOx::BooleanLabel", x=RX, y=350, width=RW, height=36, + fontSize=24, attributes=24, + trueText=buf("Tap to start"), falseText=buf(" "), + trueTextColor=C_WHITE, falseTextColor=C_PAGE, + trueBackgroundColor=C_PAGE, falseBackgroundColor=C_PAGE) solved_lbl = page.add("Solved", "ontrolOx::BooleanLabel", x=RX, y=395, width=RW, height=60, fontSize=36, fontAttributes=1, attributes=24, trueText=buf("SOLVED!"), falseText=buf(" "), @@ -163,29 +171,116 @@ solved_lbl = page.add("Solved", "ontrolOx::BooleanLabel", x=RX, y=395, width=RW, link(page, "pageId", oxbase, "homePageId") # ---------------------------------------------------------------- logic -# folder order = scan order: glob (blank, RNG) -> cells (move detect) -> mid (moved value) -# -> latch (state) -> post (display, win, counter). glob/cells read the previous scan's state. +# One swap mechanism does everything: each scan at most one pair of cells (A, B) swaps values. +# tile move: A = touched cell T, B = blank cell (only when T is next to the blank) +# deal step: A = i, B = j (Fisher-Yates), then an optional parity-fix swap of cells 1 and 2 +# Cells are indexed 1..16; the blank holds value 0. +# Folder order = scan order: glob (blank, touch, RNG, deal control) -> cell (per-cell swap +# taps) -> mid (values at A and B) -> latch (cell values) -> post (display, win, counter) +# -> state (deal step, parity). Everything before latch reads the previous scan's values. logic = root("logic", "sys::Folder") g = logic.add("glob", "sys::Folder") -cf = {i: logic.add(f"c{i:02d}", "sys::Folder") for i in CELLS} +cl = logic.add("cell", "sys::Folder") mid = logic.add("mid", "sys::Folder") lat = logic.add("latch", "sys::Folder") post = logic.add("post", "sys::Folder") +st = logic.add("state", "sys::Folder") + + +def const(parent, name, v): + return parent.add(name, "control::ConstFloat", out=float(v)) + + +# state latches first (created early so glob can link to them; they run in their folders) +V = {i: lat.add(f"V{i:02d}", "control::ADemux2") for i in CELLS} # out2 = cell value +kst = st.add("K", "control::ADemux2") # out2 = deal step (see below) +par = st.add("Par", "control::ADemux2") # out2 = deal swap parity true_c = g.add("TRUE", "control::ConstBool", out=True) boot = g.add("Boot", "control::OneShot", pulseWidth=0.5) # loads the solved board link(true_c, "out", boot, "in") -shreq = g.add("ShReq", "control::Or2") -link(shuf_btn, "touch", shreq, "in1") -link(boot, "out", shreq, "in2") -shuf = g.add("Shuf", "control::OneShot", pulseWidth=SHUFFLE_SECS) -link(shreq, "out", shuf, "in") -link(shuf, "out", busy_lbl, "in") -not_boot = g.add("NotBoot", "control::Not") -link(boot, "out", not_boot, "in") -enable = g.add("En", "control::And2") # shuffle moves allowed -link(shuf, "out", enable, "in1") -link(not_boot, "out", enable, "in2") + +# blank index B = sum_i (v_i == 0 ? i : 0) +bi = {i: reset(g, f"BI{i:02d}", 0, 1, i, 0) for i in CELLS} +for i in CELLS: + link(V[i], "out2", bi[i], "in") +bb = tree(g, "B", "control::Add4", list(bi.values()), "out") + +# touched cell index T = sum_i (touch_i ? i : 0); cell i's constant i doubles as its solved value +init = {i: const(g, f"N{i:02d}", i) for i in CELLS} +tsel = {i: g.add(f"TS{i:02d}", "control::ASW") for i in CELLS} +for i in CELLS: + link(cells[i], "touch", tsel[i], "s1") + link(init[i], "out", tsel[i], "in2") +tt = tree(g, "T", "control::Add4", list(tsel.values()), "out") +anyt = tree(g, "AT", "control::Or4", list(cells.values()), "touch") + +# tap to start: the first touch anywhere (while not started) requests a deal +anyin = g.add("AnyIn", "control::Or2") +link(anyt, "out", anyin, "in1") +link(shuf_btn, "touch", anyin, "in2") +not_st = g.add("NotSt", "control::Not") # in <- Start (prev scan) +link(not_st, "out", hint_lbl, "in") +first = g.add("First", "control::And2") +link(anyin, "out", first, "in1") +link(not_st, "out", first, "in2") +start = g.add("Start", "control::SRLatch") # set once, never reset +link(anyin, "out", start, "s") +link(start, "out", not_st, "in") +dreq = g.add("DReq", "control::Or2") +link(shuf_btn, "touch", dreq, "in1") +link(first, "out", dreq, "in2") +req = g.add("Req", "control::OneShot", pulseWidth=0.05) # one-scan deal request +link(dreq, "out", req, "in") +load = g.add("Load", "control::Or2") # load the solved board +link(boot, "out", load, "in1") +link(req, "out", load, "in2") + +# deal step K: 0..13 = swap i = 15-K with j, 14 = parity fix, >= 15 idle +dealing = g.add("Deal", "ontrolControl::InRange", lowLimit=-0.5, highLimit=14.5) +link(kst, "out2", dealing, "in") +link(dealing, "out", busy_lbl, "in") +not_deal = g.add("NotDl", "control::Not") +link(dealing, "out", not_deal, "in") +swap_st = g.add("SwSt", "ontrolControl::InRange", lowLimit=-0.5, highLimit=13.5) +link(kst, "out2", swap_st, "in") +fix_st = g.add("FixSt", "ontrolControl::InRange", lowLimit=13.5, highLimit=14.5) +link(kst, "out2", fix_st, "in") + +# tile move: T next to B <=> |T-B| == 4, or |T-B| == 1 in the same row +dtb = g.add("DTB", "control::Sub2") +link(tt, "out", dtb, "in1") +link(bb, "out", dtb, "in2") +d2 = g.add("D2", "control::Mul2") +link(dtb, "out", d2, "in1") +link(dtb, "out", d2, "in2") +vert = g.add("Vert", "ontrolControl::InRange", lowLimit=15.5, highLimit=16.5) +link(d2, "out", vert, "in") +horz = g.add("Horz", "ontrolControl::InRange", lowLimit=0.5, highLimit=1.5) +link(d2, "out", horz, "in") +rows = [] +for nm, src in (("RT", tt), ("RB", bb)): # row = floor((x-1)/4) + rq = reset(g, nm + "q", 1, 17, 0, 4) + link(src, "out", rq, "in") + ri = g.add(nm + "i", "control::F2I") + link(rq, "out", ri, "in") + rows.append(ri) +same_row = g.add("SameR", "control::Cmpr") +link(rows[0], "out", same_row, "x") +link(rows[1], "out", same_row, "y") +hz = g.add("HZ", "control::And2") +link(horz, "out", hz, "in1") +link(same_row, "xey", hz, "in2") +adj = g.add("Adj", "control::Or2") +link(vert, "out", adj, "in1") +link(hz, "out", adj, "in2") +tap = g.add("Tap", "control::OneShot", pulseWidth=0.03) # one scan per new touch +link(anyt, "out", tap, "in") +mv = g.add("Move", "control::And4") +link(tap, "out", mv, "in1") +link(adj, "out", mv, "in2") +link(not_deal, "out", mv, "in3") +link(true_c, "out", mv, "in4") # RNG: x' = 0.003 + 0.9945*4*x*(1-x) (logistic map, never hits 0) inv = reset(g, "RInv", 0, 1, 1, 0) @@ -195,108 +290,123 @@ link(rx, "out", inv, "in") link(rx, "out", mul, "in1") link(inv, "out", mul, "in2") link(mul, "out", rx, "in") -# x -> direction code {-2,-1,1,2}; breakpoints at arcsine quartiles for ~uniform picks -code = g.add("Code", "control::Linearize", - x0=0.0, y0=-2.0, x1=0.146, y1=-2.0, x2=0.147, y2=-1.0, x3=0.5, y3=-1.0, - x4=0.501, y4=1.0, x5=0.853, y5=1.0, x6=0.854, y6=2.0, x7=1.0, y7=2.0) -link(rx, "out", code, "in") +# x has an arcsine density; u = (2/pi)*asin(sqrt(x)) is ~uniform. Linearize approximates it +# with 10 points, then Reset stretches the reachable range onto [0, 0.9999]. +U_PTS = [k / 9 for k in range(10)] +X_PTS = [math.sin(math.pi * u / 2) ** 2 for u in U_PTS] +uni = g.add("Uni", "control::Linearize", + **{f"{a}{k}": round(v, 6) for k in range(10) + for a, v in (("x", X_PTS[k]), ("y", U_PTS[k]))}) +link(rx, "out", uni, "in") +u_lo = 2 / math.pi * math.asin(math.sqrt(0.003)) +u_hi = 2 / math.pi * math.asin(math.sqrt(0.9975)) +uu = reset(g, "U", u_lo, u_hi, 0, 0.9999) +link(uni, "out", uu, "in") -# state latches (created now so glob can link to them; they execute in the latch folder) -L = {i: {} for i in CELLS} +# Fisher-Yates over cells 1..15 (blank stays in cell 16): i = 15 - K, j = floor(u*i) + 1 +ii = reset(g, "I", 0, 13, 15, 2) +link(kst, "out2", ii, "in") +ui = g.add("UI", "control::Mul2") +link(uu, "out", ui, "in1") +link(ii, "out", ui, "in2") +jfl = g.add("JFl", "control::F2I") +link(ui, "out", jfl, "in") +jf = g.add("JF", "control::I2F") +link(jfl, "out", jf, "in") +jj = reset(g, "J", 0, 16, 1, 17) # +1 +link(jf, "out", jj, "in") +ieqj = g.add("IeqJ", "control::Cmpr") +link(ii, "out", ieqj, "x") +link(jj, "out", ieqj, "y") +ineq = g.add("INeJ", "control::Not") +link(ieqj, "xey", ineq, "in") +real_sw = g.add("RealSw", "control::And2") # a deal swap that changes cells +link(swap_st, "out", real_sw, "in1") +link(ineq, "out", real_sw, "in2") +# blank in its home cell => solvable iff the permutation is even; fix an odd one with (1 2) +odd = g.add("Odd", "ontrolControl::InRange", lowLimit=0.5, highLimit=1.5) +link(par, "out2", odd, "in") +fix_sw = g.add("FixSw", "control::And2") +link(fix_st, "out", fix_sw, "in1") +link(odd, "out", fix_sw, "in2") +sw_en = g.add("SwEn", "control::Or4") +link(mv, "out", sw_en, "in1") +link(real_sw, "out", sw_en, "in2") +link(fix_sw, "out", sw_en, "in3") + +# swap pair: deal step (i, j) / parity fix (1, 2) / tile move (T, B) +fa = g.add("FA", "control::ASW") +link(ii, "out", fa, "in1") +link(init[1], "out", fa, "in2") +link(fix_st, "out", fa, "s1") +fb = g.add("FB", "control::ASW") +link(jj, "out", fb, "in1") +link(init[2], "out", fb, "in2") +link(fix_st, "out", fb, "s1") +sa = g.add("SA", "control::ASW") +link(tt, "out", sa, "in1") +link(fa, "out", sa, "in2") +link(dealing, "out", sa, "s1") +sb = g.add("SB", "control::ASW") +link(bb, "out", sb, "in1") +link(fb, "out", sb, "in2") +link(dealing, "out", sb, "s1") + +# ---------------------------------------------------------------- cell: swap taps +IA, IB, HIT = {}, {}, {} +va_src, vb_src = [], [] for i in CELLS: - lf = lat.add(f"L{i:02d}", "sys::Folder") - L[i]["init"] = reset(lf, "Init", 0, 1, i % 16, i % 16) # constant: solved value - L[i]["mv"] = lf.add("Mv", "control::ASW") # OS ? 0 : MV - L[i]["in"] = lf.add("In", "control::ASW") # boot ? init : move value - L[i]["zm"] = lf.add("ZM", "control::And2") # blank AND any move - L[i]["s"] = lf.add("S", "control::Or4") - L[i]["v"] = lf.add("V", "control::ADemux2") # out2 = cell value -V = {i: L[i]["v"] for i in CELLS} + ia = cl.add(f"IA{i:02d}", "ontrolControl::InRange", lowLimit=i - 0.5, highLimit=i + 0.5) + ib = cl.add(f"IB{i:02d}", "ontrolControl::InRange", lowLimit=i - 0.5, highLimit=i + 0.5) + link(sa, "out", ia, "in") + link(sb, "out", ib, "in") + a = cl.add(f"VA{i:02d}", "control::ASW") # isA ? v : 0 + link(V[i], "out2", a, "in2") + link(ia, "out", a, "s1") + b = cl.add(f"VB{i:02d}", "control::ASW") # isB ? v : 0 + link(V[i], "out2", b, "in2") + link(ib, "out", b, "s1") + ab = cl.add(f"AB{i:02d}", "control::Or2") + link(ia, "out", ab, "in1") + link(ib, "out", ab, "in2") + hit = cl.add(f"H{i:02d}", "control::And2") # this cell swaps this scan + link(sw_en, "out", hit, "in1") + link(ab, "out", hit, "in2") + IA[i], IB[i], HIT[i] = ia, ib, hit + va_src.append(a) + vb_src.append(b) -# blank position from the cell values -bc = {i: reset(g, f"BC{i:02d}", 0, 1, colrow(i)[0], 0) for i in CELLS} -br = {i: reset(g, f"BR{i:02d}", 0, 1, colrow(i)[1], 0) for i in CELLS} +# ---------------------------------------------------------------- mid: values at A and B +va = tree(mid, "SVA", "control::Add4", va_src, "out") +vb = tree(mid, "SVB", "control::Add4", vb_src, "out") + +# ---------------------------------------------------------------- latch: cell values for i in CELLS: - link(V[i], "out2", bc[i], "in") - link(V[i], "out2", br[i], "in") -cb = tree(g, "CB", "control::Add4", [bc[i] for i in CELLS], "out") -rb = tree(g, "RB", "control::Add4", [br[i] for i in CELLS], "out") - -# ---------------------------------------------------------------- per cell: move detect -P = {} -for i in CELLS: - f = cf[i] - c, r = colrow(i) - p = {} - p["dc"] = reset(f, "Dc", 0, 3, c, c - 3) # col - cb - p["dr"] = reset(f, "Dr", 0, 3, r, r - 3) # row - rb - p["sc"] = f.add("Sc", "control::Mul2") - p["sr"] = f.add("Sr", "control::Mul2") - p["d2"] = f.add("D2", "control::Add2") - p["val"] = f.add("Val", "ontrolControl::InRange", lowLimit=0.5, highLimit=1.5) - p["k"] = f.add("K", "control::Add4") # 2*dc + dr - p["m"] = f.add("M", "control::Cmpr") - p["h"] = f.add("H", "control::And2") - p["p"] = f.add("P", "control::Or2") - p["g"] = f.add("G", "control::And2") - p["os"] = f.add("OS", "control::OneShot", pulseWidth=0.03) - p["z"] = f.add("Z", "ontrolControl::InRange", lowLimit=-0.5, highLimit=0.5) # empty - p["out"] = f.add("Out", "control::ASW") # OS ? v : 0 - P[i] = p - link(cb, "out", p["dc"], "in") - link(rb, "out", p["dr"], "in") - link(p["dc"], "out", p["sc"], "in1") - link(p["dc"], "out", p["sc"], "in2") - link(p["dr"], "out", p["sr"], "in1") - link(p["dr"], "out", p["sr"], "in2") - link(p["sc"], "out", p["d2"], "in1") - link(p["sr"], "out", p["d2"], "in2") - link(p["d2"], "out", p["val"], "in") - link(p["dc"], "out", p["k"], "in1") - link(p["dc"], "out", p["k"], "in2") - link(p["dr"], "out", p["k"], "in3") - link(p["k"], "out", p["m"], "x") - link(code, "out", p["m"], "y") - link(p["m"], "xey", p["h"], "in1") - link(enable, "out", p["h"], "in2") - link(cells[i], "touch", p["p"], "in1") - link(p["h"], "out", p["p"], "in2") - link(p["val"], "out", p["g"], "in1") - link(p["p"], "out", p["g"], "in2") - link(p["g"], "out", p["os"], "in") - link(V[i], "out2", p["z"], "in") - link(V[i], "out2", p["out"], "in2") - link(p["os"], "out", p["out"], "s1") - -# ---------------------------------------------------------------- mid: moved value -mv = tree(mid, "MV", "control::Add4", [P[i]["out"] for i in CELLS], "out") -anym = tree(mid, "AM", "control::Or4", [P[i]["os"] for i in CELLS], "out") - -# ---------------------------------------------------------------- latch wiring -for i in CELLS: - l, p = L[i], P[i] - link(mv, "out", l["mv"], "in1") # in2 unlinked = 0 - link(p["os"], "out", l["mv"], "s1") - link(l["mv"], "out", l["in"], "in1") - link(l["init"], "out", l["in"], "in2") - link(boot, "out", l["in"], "s1") - link(p["z"], "out", l["zm"], "in1") - link(anym, "out", l["zm"], "in2") - link(boot, "out", l["s"], "in1") - link(p["os"], "out", l["s"], "in2") - link(l["zm"], "out", l["s"], "in3") - link(l["in"], "out", l["v"], "in") - link(l["s"], "out", l["v"], "s1") + sv = lat.add(f"S{i:02d}", "control::ASW") # A takes B's value, B takes A's + link(va, "out", sv, "in1") + link(vb, "out", sv, "in2") + link(IA[i], "out", sv, "s1") + li = lat.add(f"L{i:02d}", "control::ASW") # load ? solved value : swap value + link(sv, "out", li, "in1") + if i < 16: # cell 16 loads 0 (in2 unlinked) + link(init[i], "out", li, "in2") + link(load, "out", li, "s1") + le = lat.add(f"E{i:02d}", "control::Or2") + link(load, "out", le, "in1") + link(HIT[i], "out", le, "in2") + link(li, "out", V[i], "in") + link(le, "out", V[i], "s1") + lat.kids.remove(V[i]) # latch runs after its inputs + lat.kids.append(V[i]) # ---------------------------------------------------------------- post: display, win, counter for i in CELLS: - df = post.add(f"D{i:02d}", "sys::Folder") - num = df.add("Num", "control::F2I") + num = post.add(f"Num{i:02d}", "control::F2I") link(V[i], "out2", num, "in") link(num, "out", cells[i], "in") - bgf = reset(df, "BgF", 0, 1, C_BOARD, C_TILE) # 0 -> board, 1..15 -> white + bgf = reset(post, f"BgF{i:02d}", 0, 1, C_BOARD, C_TILE) # 0 -> board, 1..15 -> white link(V[i], "out2", bgf, "in") - bg = df.add("Bg", "control::F2I") + bg = post.add(f"Bg{i:02d}", "control::F2I") link(bgf, "out", bg, "in") link(bg, "out", cells[i], "backgroundColor") @@ -307,21 +417,49 @@ w = tree(post, "WS", "control::Add4", list(wt.values()), "out") w_ok = post.add("WOk", "ontrolControl::InRange", lowLimit=SOLVED_SUM - 0.5, highLimit=SOLVED_SUM + 0.5) link(w, "out", w_ok, "in") -not_sh = post.add("NotSh", "control::Not") -link(shuf, "out", not_sh, "in") -solved = post.add("Solved", "control::And2") +solved = post.add("Solved", "control::And4") link(w_ok, "out", solved, "in1") -link(not_sh, "out", solved, "in2") +link(not_deal, "out", solved, "in2") +link(start, "out", solved, "in3") # no SOLVED! before the first game +link(true_c, "out", solved, "in4") link(solved, "out", solved_lbl, "in") cnt = post.add("Cnt", "control::Count") -link(anym, "out", cnt, "in") +link(mv, "out", cnt, "in") link(true_c, "out", cnt, "enable") -link(shuf, "out", cnt, "r") +link(dealing, "out", cnt, "r") cntf = post.add("CntF", "control::I2F") link(cnt, "out", cntf, "in") link(cntf, "out", moves_lbl, "in") +# ---------------------------------------------------------------- state: deal step, parity +# their inputs are computed here first, then the latches (moved to the end) take them +knext = reset(st, "KNext", 0, 16, 1, 17) # K + 1, stops at 17 (idle) +link(kst, "out2", knext, "in") +k1 = st.add("K1", "control::ASW") # request -> 0 +link(knext, "out", k1, "in1") +link(req, "out", k1, "s1") +c16 = const(st, "C16", 16) +k2 = st.add("K2", "control::ASW") # boot -> 16 (idle) +link(k1, "out", k2, "in1") +link(c16, "out", k2, "in2") +link(boot, "out", k2, "s1") +link(k2, "out", kst, "in") +link(true_c, "out", kst, "s1") +pinv = reset(st, "PInv", 0, 1, 1, 0) # 1 - Par +link(par, "out2", pinv, "in") +p1 = st.add("P1", "control::ASW") # request -> 0 (in2 unlinked) +link(pinv, "out", p1, "in1") +link(req, "out", p1, "s1") +link(p1, "out", par, "in") +ps = st.add("PS", "control::Or2") +link(req, "out", ps, "in1") +link(real_sw, "out", ps, "in2") +link(ps, "out", par, "s1") +st.kids.remove(kst) +st.kids.remove(par) +st.kids += [kst, par] + # ---------------------------------------------------------------- write body = [] for rt in (service, driver, logic, ox): diff --git a/verify_logic.py b/verify_logic.py index 6028472..9228cf0 100644 --- a/verify_logic.py +++ b/verify_logic.py @@ -345,7 +345,7 @@ def main(): print("re-shuffle OK") -def astar(s0): +def astar(s0, weight=1.5): """Weighted A* on a 16-tuple (0 = blank); returns the list of cell indices to press.""" import heapq goal = tuple(list(range(1, 16)) + [0]) @@ -370,11 +370,19 @@ def astar(s0): ns = tuple(l) if seen.get(ns, 1e9) > gc + 1: seen[ns] = gc + 1 - heapq.heappush(pq, (gc + 1 + 1.5 * h(ns), gc + 1, ns, p + [n])) + heapq.heappush(pq, (gc + 1 + weight * h(ns), gc + 1, ns, p + [n])) if len(seen) > 2_000_000: return None +def solvable(s): + """4x4: permutation parity (blank as 16) == parity of the blank's distance from home.""" + perm = [v if v else 16 for v in s] + inv = sum(1 for a in range(16) for b in range(a + 1, 16) if perm[a] > perm[b]) + z = s.index(0) + return inv % 2 == (3 - z // 4 + 3 - z % 4) % 2 + + def main_v2(): """aspirinV2.sax: fixed cells, numbers shown via each cell's `in`, empty cell recoloured.""" tops, paths = load("aspirinV2.sax") @@ -416,11 +424,18 @@ def main_v2(): cell[k].v["touch"] = False step(2) + hint = paths["/ox/Game/Hint"] step(3) assert state() == goal, "boot did not load the solved board: %s" % (state(),) - step(120) + step(random.randint(20, 80)) # idle: no shuffle until the first tap check() - print("after shuffle:\n" + show()) + assert state() == goal and hint.g("in", False) and not solved() and moves() == 0 + press(0) # first tap (cell 1, not next to the gap) deals + assert not hint.g("in", False) + step(30) + check() + print("after deal:\n" + show()) + assert solvable(state()), "dealt an unsolvable board" assert state() != goal and not solved() and moves() == 0 z = state().index(0) @@ -442,7 +457,7 @@ def main_v2(): assert state() == before and moves() == 2, "hold did not move exactly once" check() - path = astar(state()) + path = astar(state(), weight=4) # random deals are deep; any solution will do assert path is not None, "solver gave up" print("solving in", len(path), "presses") for k in path: @@ -456,10 +471,28 @@ def main_v2(): paths["/ox/Game/Shuffle"].v["touch"] = True step(2) paths["/ox/Game/Shuffle"].v["touch"] = False - step(120) + step(30) check() - assert state() != goal and not solved() and moves() == 0 - print("re-shuffle OK") + assert state() != goal and not solved() and moves() == 0 and solvable(state()) + print("re-deal OK") + + # many deals: always solvable, all different, every number reaches every cell + seen, where = set(), [set() for _ in range(16)] + for _ in range(300): + step(random.randint(1, 40)) + paths["/ox/Game/Shuffle"].v["touch"] = True + step(1) + paths["/ox/Game/Shuffle"].v["touch"] = False + step(20) + s = state() + check() + assert solvable(s), s + seen.add(s) + assert s[15] == 0, "deal moved the blank out of cell 16" + for k, v in enumerate(s): + where[k].add(v) + assert all(len(w) == 15 for w in where[:15]), [len(w) for w in where] + print(f"300 deals: {len(seen)} distinct, all solvable, every number seen in cells 1-15") if __name__ == "__main__": diff --git a/viewer/OxViewer.java b/viewer/OxViewer.java index 660223f..3a0bbe2 100644 --- a/viewer/OxViewer.java +++ b/viewer/OxViewer.java @@ -11,7 +11,7 @@ import sedona.sox.*; * Usage: java -cp ".;sedona.jar" OxViewer [host] [port] [user] [password] */ public class OxViewer extends JPanel { - static final String[] LABELS = {"Title", "Sub", "Moves", "Shuffle", "Busy", "Solving", "Solved"}; + static final String[] LABELS = {"Title", "Sub", "Moves", "Shuffle", "Busy", "Solving", "Hint", "Solved"}; SoxClient client; SoxComponent page; SoxComponent[] tiles = new SoxComponent[16];