Add RLCD animations and screensavers
This commit is contained in:
@@ -0,0 +1,289 @@
|
||||
# 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()
|
||||
Reference in New Issue
Block a user