# Standalone MicroPython Screensaver: Paddle Bounce # Inspired by paddle-ball 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 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) # Paddle parameters pad_w = 6 pad_h = 36 left_x = 15 right_x = width - 15 - pad_w # Initial states ly = (height - pad_h) // 2 ry = (height - pad_h) // 2 bx, by = width // 2, height // 2 vx, vy = 3.5, 1.8 score_l = 0 score_r = 0 paddle_speed = 3.0 # Simulated tracking error offset target_offset_l = 0 target_offset_r = 0 def reset_ball(serve_to_left): nonlocal bx, by, vx, vy, target_offset_l, target_offset_r bx = width // 2 by = height // 2 vx = -3.5 if serve_to_left else 3.5 vy = random.choice([-2.0, -1.0, 1.0, 2.0]) target_offset_l = random.randint(-12, 12) target_offset_r = random.randint(-12, 12) frame = 0 serve_delay = 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 if serve_delay > 0: serve_delay -= 1 else: # Move ball bx += vx by += vy # Ceiling/floor bounce if by <= 32: by = 32 vy = -vy elif by >= height - 8: by = height - 8 vy = -vy # AI tracking if vx < 0: # Ball heading Left # Track ball center with error offset target_y = by + 3 - (pad_h // 2) + target_offset_l if ly < target_y: ly = min(height - pad_h - 6, ly + paddle_speed) elif ly > target_y: ly = max(32, ly - paddle_speed) else: # Ball heading Right target_y = by + 3 - (pad_h // 2) + target_offset_r if ry < target_y: ry = min(height - pad_h - 6, ry + paddle_speed) elif ry > target_y: ry = max(32, ry - paddle_speed) # Check Left boundary if bx <= left_x + pad_w: if left_x <= bx: # Ball within x-range of paddle, check y-collision if ly - 3 <= by <= ly + pad_h + 3: bx = left_x + pad_w # Bounce & increase speed vx = -vx * 1.05 # Cap horizontal speed to prevent teleporting if abs(vx) > 8.0: vx = 8.0 if vx > 0 else -8.0 vy = vy * 1.05 + random.uniform(-0.6, 0.6) # Assign new tracking error for right paddle target_offset_r = random.randint(-15, 15) else: # Miss! Point for Right score_r += 1 reset_ball(serve_to_left=False) serve_delay = 40 # Check Right boundary if bx + 6 >= right_x: if bx <= right_x + pad_w: # Ball within x-range of paddle, check y-collision if ry - 3 <= by <= ry + pad_h + 3: bx = right_x - 6 # Bounce & increase speed vx = -vx * 1.05 if abs(vx) > 8.0: vx = 8.0 if vx > 0 else -8.0 vy = vy * 1.05 + random.uniform(-0.6, 0.6) # Assign new tracking error for left paddle target_offset_l = random.randint(-15, 15) else: # Miss! Point for Left score_l += 1 reset_ball(serve_to_left=True) serve_delay = 40 # Draw frame display.clear(0) # Header score line display.text(text if text else "PADDLE BOUNCE", 10, 10, 1) display.text(f"{score_l:02d} {score_r:02d}", width // 2 - 24, 10, 1) display.line(0, 26, width, 26, 1) # Draw center dotted line for y in range(30, height, 16): display.fill_rect(width // 2 - 1, y, 2, 8, 1) # Draw paddles display.fill_rect(left_x, int(ly), pad_w, pad_h, 1) display.fill_rect(right_x, int(ry), pad_w, pad_h, 1) # Draw ball (6x6 square) if serve_delay == 0 or (serve_delay // 4) % 2 == 0: display.fill_rect(int(bx), int(by), 6, 6, 1) display.show() frame += 1 sleep_ms(30) except KeyboardInterrupt: print("Screensaver stopped by user interrupt.") if __name__ in ('__main__', '__name__'): run()