290 lines
11 KiB
Python
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()
|