Files
2026-06-21 21:19:51 -04:00

290 lines
11 KiB
Python

# Standalone MicroPython Screensaver: Maze Chomper
# Inspired by maze-chase arcade classic. Exposes run(text="", frames=None).
import time
import math
import random
try:
import board_config
display = getattr(board_config, 'display_instance', None)
except Exception:
board_config = None
display = None
def sleep_ms(ms):
try:
time.sleep_ms(ms)
except AttributeError:
time.sleep(ms / 1000.0)
def check_touch(d, bc):
"""Robust check for screen touch to stop the screensaver."""
if bc is not None:
touch_inst = getattr(bc, 'touch_instance', None)
if touch_inst is not None:
if hasattr(touch_inst, 'is_touched'):
try:
if touch_inst.is_touched():
return True
except Exception:
pass
for meth in ('get_touch', 'read_touch'):
if hasattr(touch_inst, meth):
try:
res = getattr(touch_inst, meth)()
if res is not None and res is not False and res != 0:
return True
except Exception:
pass
touch_val = getattr(bc, 'touch', None)
if touch_val is not None:
if callable(touch_val):
try:
res = touch_val()
if res is not None and res is not False and res != 0:
return True
except Exception:
pass
else:
if hasattr(touch_val, 'is_touched'):
try:
if touch_val.is_touched():
return True
except Exception:
pass
for meth in ('get_touch', 'read_touch'):
if hasattr(touch_val, meth):
try:
res = getattr(touch_val, meth)()
if res is not None and res is not False and res != 0:
return True
except Exception:
pass
if d is not None:
for meth in ('get_touch', 'touch'):
if hasattr(d, meth):
try:
res = getattr(d, meth)()
if res is not None and res is not False and res != 0:
return True
except Exception:
pass
return False
def random_choice(seq):
if not seq:
return None
return seq[random.randint(0, len(seq) - 1)]
def draw_chomper(d, cx, cy, rad, direction, mouth_open):
# Draw body
for dy in range(-rad, rad + 1):
dx = int(math.sqrt(rad*rad - dy*dy))
d.line(cx - dx, cy + dy, cx + dx, cy + dy, 1)
# Draw open mouth
if mouth_open:
dx_dir, dy_dir = direction
if dx_dir == 0 and dy_dir == 0:
dx_dir = 1
if dx_dir > 0:
for dy in range(-rad//2, rad//2 + 1):
mx = rad - abs(dy) * 2
d.line(cx, cy + dy, cx + mx, cy + dy, 0)
elif dx_dir < 0:
for dy in range(-rad//2, rad//2 + 1):
mx = rad - abs(dy) * 2
d.line(cx - mx, cy + dy, cx, cy + dy, 0)
elif dy_dir > 0:
for dx in range(-rad//2, rad//2 + 1):
my = rad - abs(dx) * 2
d.line(cx + dx, cy, cx + dx, cy + my, 0)
elif dy_dir < 0:
for dx in range(-rad//2, rad//2 + 1):
my = rad - abs(dx) * 2
d.line(cx + dx, cy - my, cx + dx, cy, 0)
def draw_ghost(d, gcx, gcy, rad):
for dy in range(-rad, 0):
dx = int(math.sqrt(rad*rad - dy*dy))
d.line(gcx - dx, gcy + dy, gcx + dx, gcy + dy, 1)
d.fill_rect(gcx - rad, gcy, rad * 2 + 1, rad, 1)
eye_offset = rad // 3
d.fill_rect(gcx - eye_offset - 1, gcy - eye_offset, 2, 2, 0)
d.fill_rect(gcx + eye_offset - 1, gcy - eye_offset, 2, 2, 0)
for i in range(gcx - rad, gcx + rad + 1, 4):
d.fill_rect(i, gcy + rad - 2, 2, 3, 0)
def run(text="", frames=None):
if not display:
print("board_config.display_instance is not available. Skipping screensaver.")
return
width = getattr(display, 'width', 400)
height = getattr(display, 'height', 300)
header_h = 30
grid_y_offset = header_h
cell_size = 40
cols = width // cell_size
rows = (height - header_h) // cell_size
grid = []
for r in range(rows):
row_data = []
for c in range(cols):
is_wall = False
if r == 0 or r == rows - 1 or c == 0 or c == cols - 1:
is_wall = True
elif (r == 2 or r == rows - 3) and (2 <= c <= 3 or cols - 4 <= c <= cols - 3):
is_wall = True
elif r == rows // 2 and (cols // 2 - 1 <= c <= cols // 2 + 1):
is_wall = True
row_data.append(2 if is_wall else 1)
grid.append(row_data)
score = 0
chomper_x, chomper_y = cell_size, grid_y_offset + cell_size
chomper_dx, chomper_dy = 1, 0
ghost_x, ghost_y = (cols - 2) * cell_size, grid_y_offset + (rows - 2) * cell_size
ghost_dx, ghost_dy = -1, 0
frame = 0
try:
while True:
if frames is not None and frames > 0 and frame >= frames:
break
if check_touch(display, board_config):
print("Screensaver stopped by screen touch.")
break
chomper_rel_y = chomper_y - grid_y_offset
if chomper_x % cell_size == 0 and chomper_rel_y % cell_size == 0:
cx, cy = chomper_x // cell_size, chomper_rel_y // cell_size
if grid[cy][cx] == 1:
grid[cy][cx] = 0
score += 10
has_dots = False
for r_data in grid:
if 1 in r_data:
has_dots = True
break
if not has_dots:
for r in range(rows):
for c in range(cols):
if grid[r][c] == 0:
grid[r][c] = 1
valid_dirs = []
dot_dirs = []
for ndx, ndy in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
nx, ny = cx + ndx, cy + ndy
if 0 <= nx < cols and 0 <= ny < rows:
if grid[ny][nx] != 2:
valid_dirs.append((ndx, ndy))
if grid[ny][nx] == 1:
dot_dirs.append((ndx, ndy))
if dot_dirs:
if (chomper_dx, chomper_dy) in dot_dirs:
pass
else:
chomper_dx, chomper_dy = random_choice(dot_dirs)
elif valid_dirs:
if (chomper_dx, chomper_dy) in valid_dirs and random.random() > 0.3:
pass
else:
chomper_dx, chomper_dy = random_choice(valid_dirs)
else:
chomper_dx, chomper_dy = 0, 0
ghost_rel_y = ghost_y - grid_y_offset
if ghost_x % cell_size == 0 and ghost_rel_y % cell_size == 0:
gcx, gcy = ghost_x // cell_size, ghost_rel_y // cell_size
valid_gdirs = []
for ndx, ndy in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
if ndx == -ghost_dx and ndy == -ghost_dy:
continue
nx, ny = gcx + ndx, gcy + ndy
if 0 <= nx < cols and 0 <= ny < rows:
if grid[ny][nx] != 2:
valid_gdirs.append((ndx, ndy))
if not valid_gdirs:
valid_gdirs = [(-ghost_dx, -ghost_dy)]
best_dir = valid_gdirs[0]
min_dist = 999999
ccx, ccy = chomper_x // cell_size, chomper_rel_y // cell_size
for ndx, ndy in valid_gdirs:
nx, ny = gcx + ndx, gcy + ndy
dist = (nx - ccx)**2 + (ny - ccy)**2
if dist < min_dist:
min_dist = dist
best_dir = (ndx, ndy)
if random.random() < 0.25:
ghost_dx, ghost_dy = random_choice(valid_gdirs)
else:
ghost_dx, ghost_dy = best_dir
chomper_x += chomper_dx * 2
chomper_y += chomper_dy * 2
ghost_x += ghost_dx * 2
ghost_y += ghost_dy * 2
dist_sq = (chomper_x - ghost_x)**2 + (chomper_y - ghost_y)**2
if dist_sq < 256:
display.clear(0)
display.text("COLLISION!", width // 2 - 40, height // 2 - 10, 1)
display.show()
sleep_ms(800)
chomper_x, chomper_y = cell_size, grid_y_offset + cell_size
chomper_dx, chomper_dy = 1, 0
ghost_x, ghost_y = (cols - 2) * cell_size, grid_y_offset + (rows - 2) * cell_size
ghost_dx, ghost_dy = -1, 0
frame += 1
continue
display.clear(0)
# Header
display.text(text if text else "MAZE CHOMPER", 10, 10, 1)
display.text(f"SCORE: {score:05d}", width - 120, 10, 1)
display.line(0, 28, width, 28, 1)
# Grid / Walls / Dots
for r in range(rows):
for c in range(cols):
cell_val = grid[r][c]
cx_px = c * cell_size
cy_px = grid_y_offset + r * cell_size
if cell_val == 2:
display.fill_rect(cx_px + 2, cy_px + 2, cell_size - 4, cell_size - 4, 1)
elif cell_val == 1:
display.fill_rect(cx_px + cell_size//2 - 1, cy_px + cell_size//2 - 1, 3, 3, 1)
# Sprites
mouth_open = (frame % 4) < 2
draw_chomper(display, chomper_x + cell_size//2, chomper_y + cell_size//2, 12, (chomper_dx, chomper_dy), mouth_open)
draw_ghost(display, ghost_x + cell_size//2, ghost_y + cell_size//2, 12)
display.show()
frame += 1
sleep_ms(30)
except KeyboardInterrupt:
print("Screensaver stopped by user interrupt.")
if __name__ in ('__main__', '__name__'):
run()