Add aspirinV2: fixed-cell 15-puzzle
Cells never move; each NumericSelectLabel shows the number it holds on white and the empty cell takes the board colour via a linked backgroundColor. Adds the V2 generator, build step (with a direct-Java sedonac fallback), offline verifier mode (--v2) and viewer support. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+123
-1
@@ -345,6 +345,128 @@ def main():
|
||||
print("re-shuffle OK")
|
||||
|
||||
|
||||
def astar(s0):
|
||||
"""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])
|
||||
|
||||
def h(s):
|
||||
return sum(abs(i // 4 - (v - 1) // 4) + abs(i % 4 - (v - 1) % 4)
|
||||
for i, v in enumerate(s) if v)
|
||||
|
||||
pq = [(h(s0), 0, s0, [])]
|
||||
seen = {s0: 0}
|
||||
while pq:
|
||||
_, gc, s, p = heapq.heappop(pq)
|
||||
if s == goal:
|
||||
return p
|
||||
z = s.index(0)
|
||||
for dz in (-1, 1, -4, 4):
|
||||
n = z + dz
|
||||
if n < 0 or n > 15 or (dz in (-1, 1) and n // 4 != z // 4):
|
||||
continue
|
||||
l = list(s)
|
||||
l[z], l[n] = l[n], l[z]
|
||||
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]))
|
||||
if len(seen) > 2_000_000:
|
||||
return None
|
||||
|
||||
|
||||
def main_v2():
|
||||
"""aspirinV2.sax: fixed cells, numbers shown via each cell's `in`, empty cell recoloured."""
|
||||
tops, paths = load("aspirinV2.sax")
|
||||
for c in paths.values():
|
||||
c.start()
|
||||
now = 0
|
||||
goal = tuple(list(range(1, 16)) + [0])
|
||||
cell = [paths[f"/ox/Game/Cell{i:02d}"] for i in range(1, 17)]
|
||||
|
||||
def step(n=1):
|
||||
nonlocal now
|
||||
for _ in range(n):
|
||||
now += SCAN_MS + random.randint(-3, 3)
|
||||
run_scan(tops, now)
|
||||
|
||||
def state():
|
||||
return tuple(int(c.g("in")) for c in cell)
|
||||
|
||||
def check():
|
||||
s = state()
|
||||
assert sorted(s) == list(range(16)), s
|
||||
for v, c in zip(s, cell): # empty cell = board colour, numbers on white
|
||||
assert int(c.g("backgroundColor")) == (0x31A6 if v == 0 else 0xFFFF), (s, v)
|
||||
|
||||
def show():
|
||||
s = state()
|
||||
return "\n".join(" ".join(f"{v:3d}" if v else " ." for v in s[r * 4:r * 4 + 4])
|
||||
for r in range(4))
|
||||
|
||||
def solved():
|
||||
return paths["/logic/post/Solved"].g("out", False)
|
||||
|
||||
def moves():
|
||||
return paths["/logic/post/CntF"].g("out")
|
||||
|
||||
def press(k):
|
||||
cell[k].v["touch"] = True
|
||||
step(2)
|
||||
cell[k].v["touch"] = False
|
||||
step(2)
|
||||
|
||||
step(3)
|
||||
assert state() == goal, "boot did not load the solved board: %s" % (state(),)
|
||||
step(120)
|
||||
check()
|
||||
print("after shuffle:\n" + show())
|
||||
assert state() != goal and not solved() and moves() == 0
|
||||
|
||||
z = state().index(0)
|
||||
far = next(k for k in range(16) if abs(k // 4 - z // 4) + abs(k % 4 - z % 4) >= 2)
|
||||
before = state()
|
||||
press(far)
|
||||
assert state() == before, "non-adjacent cell moved"
|
||||
press(z)
|
||||
assert state() == before, "pressing the empty cell changed the board"
|
||||
n = z - 1 if z % 4 else z + 1
|
||||
v = before[n]
|
||||
press(n)
|
||||
s = state()
|
||||
assert s[n] == 0 and s[z] == v and moves() == 1, "valid move failed"
|
||||
cell[z].v["touch"] = True # hold: exactly one move back
|
||||
step(10)
|
||||
cell[z].v["touch"] = False
|
||||
step(2)
|
||||
assert state() == before and moves() == 2, "hold did not move exactly once"
|
||||
check()
|
||||
|
||||
path = astar(state())
|
||||
assert path is not None, "solver gave up"
|
||||
print("solving in", len(path), "presses")
|
||||
for k in path:
|
||||
press(k)
|
||||
step(5)
|
||||
print(show())
|
||||
check()
|
||||
assert state() == goal and solved(), "solved flag not set"
|
||||
print("SOLVED flag OK, moves:", moves())
|
||||
|
||||
paths["/ox/Game/Shuffle"].v["touch"] = True
|
||||
step(2)
|
||||
paths["/ox/Game/Shuffle"].v["touch"] = False
|
||||
step(120)
|
||||
check()
|
||||
assert state() != goal and not solved() and moves() == 0
|
||||
print("re-shuffle OK")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
main_solver() if "--solver" in sys.argv else main()
|
||||
if "--solver" in sys.argv:
|
||||
main_solver()
|
||||
elif "--v2" in sys.argv:
|
||||
main_v2()
|
||||
else:
|
||||
main()
|
||||
|
||||
Reference in New Issue
Block a user