# 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()