# breakout_game.py - FINAL BUGFREE # # Fixed: Removed invalid "global paddle_x" inside function from steptime import STEPTIME from ws2812 import WS2812 from ssd1306 import SSD1306_I2C import machine from machine import Pin, I2C, freq import utime import urandom # ----- Steptime clock ----- freq(250000000) # ----- OLED ----- i2c = I2C(1, scl=Pin(7), sda=Pin(6), freq=200000) oled = SSD1306_I2C(128, 64, i2c) # ----- NeoPixel ----- power = machine.Pin(11, machine.Pin.OUT) power.value(1) BLACK = (0, 0, 0) RED = (255, 0, 0) ORANGE = (255, 100, 0) YELLOW = (255, 255, 0) GREEN = (0, 255, 0) CYAN = (0, 255, 255) BLUE = (0, 0, 255) PURPLE = (180, 0, 255) WHITE = (255, 255, 255) SCORE_COLORS = [RED, ORANGE, YELLOW, GREEN, CYAN, BLUE, PURPLE] led = WS2812(12, 1, 0.5, 6) def set_led_color(color): led.pixels_fill(color) led.pixels_show() def flash_led(color, times=3): for _ in range(times): set_led_color(color) utime.sleep_ms(50) set_led_color(BLACK) utime.sleep_ms(50) # ----- Touch pads ----- PAD_PINS = [26, 27, 1, 2, 3, 4] PAD_LEFT, PAD_RIGHT, PAD_LAUNCH, PAD_PAUSE, PAD_RESET = 1, 3, 2, 4, 0 for p in PAD_PINS: Pin(p, Pin.IN, Pin.PULL_UP) channels = [] for i, pin in enumerate(PAD_PINS): channels.append([STEPTIME(i, pin), [1e6], [0]]) loop = 200 settle = 20000 thresh = 10000 DEBOUNCE_MS = 150 def read_pads(): deltas = [] for ch in channels: sm, min_val, printable = ch sm.put(loop) sm.put(settle) result = 4294967296 - sm.get() if result < min_val[0]: min_val[0] = result d = result - min_val[0] printable[0] = d deltas.append(d) return deltas def strongest_pressed(deltas): best_i = -1 best_v = thresh for i, v in enumerate(deltas): if v > best_v: best_v = v best_i = i return best_i # ----- Game constants ----- PADDLE_W = 24 PADDLE_H = 6 PADDLE_Y = 56 BALL_SIZE = 4 BRICK_ROWS = 5 BRICK_COLS = 12 BRICK_W = 10 BRICK_H = 6 BRICK_START_Y = 4 PADDLE_SPEED = 6 # ----- Game state ----- paddle_x = 52 ball_x = 64 ball_y = 32 ball_dx = 2 ball_dy = 2 bricks = [[1 for _ in range(BRICK_COLS)] for _ in range(BRICK_ROWS)] score = 0 launched = False game_over = False paused = False def reset_ball(): global ball_x, ball_y, ball_dx, ball_dy, launched ball_x = paddle_x + PADDLE_W//2 ball_y = PADDLE_Y - BALL_SIZE ball_dx = urandom.choice([-2, 2]) ball_dy = -2 launched = False def check_collisions(): global ball_x, ball_y, ball_dx, ball_dy, score # Paddle if (PADDLE_Y - BALL_SIZE <= ball_y <= PADDLE_Y + PADDLE_H and paddle_x - BALL_SIZE <= ball_x <= paddle_x + PADDLE_W + BALL_SIZE): ball_dy = -abs(ball_dy) hit_pos = (ball_x - paddle_x) / PADDLE_W ball_dx = int((hit_pos - 0.5) * 4) flash_led(CYAN) # Bricks brick_col = int(ball_x // BRICK_W) brick_row = int(ball_y // BRICK_H) if (0 <= brick_row < BRICK_ROWS and 0 <= brick_col < BRICK_COLS and bricks[brick_row][brick_col]): bricks[brick_row][brick_col] = 0 score += 10 ball_dy = -ball_dy flash_led(YELLOW) # Walls if ball_x <= 0 or ball_x >= 128 - BALL_SIZE: ball_dx = -ball_dx if ball_y <= 0: ball_dy = -ball_dy if ball_y >= 64: global game_over game_over = True def draw_game(): oled.fill(0) # Bricks for row in range(BRICK_ROWS): for col in range(BRICK_COLS): if bricks[row][col]: x = col * BRICK_W y = BRICK_START_Y + row * BRICK_H oled.rect(x, y, BRICK_W-1, BRICK_H-1, 1) oled.rect(x+1, y+1, BRICK_W-3, BRICK_H-3, 0) # Paddle oled.fill_rect(paddle_x, PADDLE_Y, PADDLE_W, PADDLE_H, 1) # Ball oled.fill_rect(ball_x, ball_y, BALL_SIZE, BALL_SIZE, 1) # UI oled.text("Score:{:3d}".format(score), 0, 0, 1) if not launched: oled.text("PAD2=LAUNCH", 32, 56, 1) elif game_over: oled.text("GAME OVER", 30, 24, 1) oled.text("PAD5=RESET", 28, 36, 1) elif paused: oled.text("PAUSED", 45, 24, 1) oled.text("PAD3=UNPAUSE", 18, 36, 1) oled.show() # ----- Init ----- set_led_color(BLUE) oled.fill(0) oled.text("BREAKOUT v3", 32, 0) oled.text("Hold Pad0=L Pad1=R", 0, 16) oled.text("Pad2=Launch Pad3=Pause", 0, 28) oled.text("Pad5=Reset", 35, 40) oled.show() reset_ball() # ----- Main loop ----- last_pressed = -1 last_press_ms = 0 while True: deltas = read_pads() pressed = strongest_pressed(deltas) now_ms = utime.ticks_ms() # CONTINUOUS paddle movement left_held = deltas[PAD_LEFT] > thresh * 1.5 right_held = deltas[PAD_RIGHT] > thresh * 1.5 if left_held: paddle_x -= PADDLE_SPEED if right_held: paddle_x += PADDLE_SPEED paddle_x = max(0, min(128 - PADDLE_W, paddle_x)) # Edge events ONLY for launch/pause/reset pressed_event = False if pressed != -1 and pressed not in (PAD_LEFT, PAD_RIGHT): if last_pressed == -1 or utime.ticks_diff(now_ms, last_press_ms) > DEBOUNCE_MS: pressed_event = True last_press_ms = now_ms last_pressed = pressed if pressed_event: if pressed == PAD_LAUNCH and not launched: launched = True flash_led(GREEN) elif pressed == PAD_PAUSE: paused = not paused elif pressed == 5: # PAD_RESET (index 5) bricks = [[1 for _ in range(BRICK_COLS)] for _ in range(BRICK_ROWS)] score = 0 game_over = False paused = False reset_ball() set_led_color(BLUE) if launched and not game_over and not paused: ball_x += ball_dx ball_y += ball_dy check_collisions() # LED idx = min(score // 50, len(SCORE_COLORS) - 1) if not game_over: set_led_color(SCORE_COLORS[idx]) else: set_led_color(RED) draw_game() utime.sleep_ms(20)