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 <noreply@anthropic.com>
This commit is contained in:
+41
-8
@@ -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__":
|
||||
|
||||
Reference in New Issue
Block a user