224 lines
8.0 KiB
Python
224 lines
8.0 KiB
Python
# 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()
|