← Arcade ๐Ÿ† Players My player card

Game Breakout

โ˜… Game Breakout โ˜…
Game Breakout gameplay screenshot
๐Ÿ“ฆ Game code, download & prompt info
๐Ÿ–ฅ๏ธ Desktop app (python) โ€” play it live above, right in your browser, or download it below
  • Model: openrouter:stealth/union-alpha
  • Test: Game: Breakout (playable, pygame UI)
  • Category: gamedev
  • Kind: code ยท scored as score (higher is better)
  • Run: โ€”
  • Benchmark score: 98.0
  • Benchmark date: 2026-09-16
  • Published: 2026-09-25
Original prompt
Write a complete, runnable Python program using `pygame` that implements a full-featured, playable arcade Breakout game. The player MUST be able to lose. Open a window and implement ALL of the following required mechanics.

1. Ball mechanics & physics: A ball bounces off a movable paddle (LEFT/RIGHT arrow keys), the walls, and bricks. Ball speed gradually accelerates as rallies lengthen. The ball's bounce angle depends on where it strikes the paddle - hitting the outer edges sends it off at sharp, low angles, rewarding precise paddle control.

2. Brick types: The wall includes standard single-hit bricks, multi-hit reinforced bricks, indestructible wall obstacles, and explosive bricks that clear the surrounding bricks when destroyed.

3. Power-ups: Destroyed bricks occasionally drop falling capsules that temporarily modify gameplay: Paddle Expansion (Enlarge) increases paddle width; Laser Vaus equips the paddle with dual cannons to destroy bricks directly; Multi-Ball (Disruption) splits the active ball into multiple simultaneous balls; Catch holds the ball on contact so the player can aim and launch it at will.

4. Enemies & hazards: Flying alien hazards float down the playfield. They deflect the ball unpredictably; hitting them destroys the hazard and awards extra points.

5. Visual theme & boss: Use a cohesive sci-fi visual theme rather than a bare black-and-white screen, and include a final boss fight against a giant floating head named "DOH".

6. Lives & loss system (REQUIRED): The player starts with 3 lives (1 active paddle + 2 in reserve). A life is lost when the active ball falls past the paddle off the bottom of the screen; if the Multi-Ball power-up is active, a life is lost only when the final remaining ball falls. Once all lives are exhausted, show a Game Over screen offering a short timer to insert a coin / continue. Award an extra life at 20,000 points and every 60,000 points thereafter, and allow a rare gray "P" (Player) capsule dropped from destroyed bricks to instantly award an extra life.

7. Scoring & HUD: Display the current score and the remaining lives. Include a game loop that handles the QUIT event. Use only primitive drawing - no external assets. Design note (interpret freely): Breakout's defining trait is turning a single bouncing ball into pure brick-clearing physics, and Arkanoid's later interpretation added the Vaus paddle, multiball, and falling power-up capsules. Keep the classic physics-skill core sharp while the power-up/boss systems above stay playable. SCORING & PERSISTENT HIGH-SCORE BOARD (REQUIRED): The game MUST award points for gameplay actions and maintain a persistent high-score table. When a run finishes, prompt the player to enter their name/initials and record it with the score. Save the top 5 scores (name + score) to a local JSON file in the working directory (e.g. high_scores.json), and also write the latest finished run as {"initials": name, "score": number} to /tmp/alpaca_score.json (overwrite it every run) so the arcade scoreboard can pick it up automatically, load and display them on startup and on the game-over screen, and write a new entry whenever a run finishes. Scores must survive restarting the program. Every new game MUST start with the score reset to 0 - never carry over the previous game's score into the next session. The program must run top-to-bottom and open a visible window. OUTPUT REQUIREMENT: Respond with ONLY the raw, runnable source code in a single fenced code block, with the first line starting at column 0 (no leading indentation). Do not include any thinking, explanation, summary, or prose before or after the code, and keep comments inside the code minimal.
โฌ‡ Download game.py
import json
import math
import os
import random
from dataclasses import dataclass
from pathlib import Path

import pygame

pygame.init()

WIDTH, HEIGHT = 960, 760
FPS = 120
LEFT, RIGHT, TOP = 22, WIDTH - 22, 70
PADDLE_Y = HEIGHT - 67
BALL_RADIUS = 8
BASE_SPEED, MAX_SPEED = 350.0, 840.0
SCORE_FILE = Path.cwd() / "high_scores.json"
ARCADE_FILE = Path("/tmp/alpaca_score.json")

screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("NEON VAUS โ€” Breakout / DOH")
clock = pygame.time.Clock()
Vec = pygame.Vector2

BG = (8, 12, 29)
WHITE = (232, 245, 255)
CYAN = (73, 227, 255)
PINK = (255, 85, 161)
YELLOW = (255, 219, 102)
GRAY = (157, 165, 185)

fonts = {
    "small": pygame.font.Font(None, 22),
    "normal": pygame.font.Font(None, 28),
    "large": pygame.font.Font(None, 49),
    "title": pygame.font.Font(None, 78),
}

POWER_COLORS = {
    "E": (77, 238, 154),
    "L": (255, 95, 117),
    "M": (95, 182, 255),
    "C": (249, 191, 75),
    "P": (177, 182, 195),
}
POWER_NAMES = {"E": "ENLARGE", "L": "LASER", "M": "MULTI", "C": "CATCH"}


def text(message, x, y, color=WHITE, size="normal", center=False):
    image = fonts[size].render(str(message), True, color)
    rect = image.get_rect()
    if center:
        rect.center = (int(x), int(y))
    else:
        rect.topleft = (int(x), int(y))
    screen.blit(image, rect)
    return rect


def load_scores():
    try:
        with SCORE_FILE.open("r", encoding="utf-8") as file:
            data = json.load(file)
        entries = []
        if isinstance(data, list):
            for row in data:
                if not isinstance(row, dict):
                    continue
                name = row.get("initials", row.get("name", "AAA"))
                score = row.get("score")
                if isinstance(name, str) and isinstance(score, int) and score >= 0:
                    entries.append({"initials": name[:10] or "AAA", "score": score})
        return sorted(entries, key=lambda row: row["score"], reverse=True)[:5]
    except (OSError, ValueError, TypeError):
        return []


def atomic_json(path, data):
    path.parent.mkdir(parents=True, exist_ok=True)
    temporary = path.with_name(path.name + ".tmp")
    with temporary.open("w", encoding="utf-8") as file:
        json.dump(data, file, indent=2)
    os.replace(temporary, path)


def circle_rect_overlap(position, radius, rect):
    closest = Vec(
        max(rect.left, min(position.x, rect.right)),
        max(rect.top, min(position.y, rect.bottom)),
    )
    return (position - closest).length_squared() <= radius * radius


def reflect_rect(ball, rect):
    closest = Vec(
        max(rect.left, min(ball.pos.x, rect.right)),
        max(rect.top, min(ball.pos.y, rect.bottom)),
    )
    delta = ball.pos - closest
    distance_sq = delta.length_squared()
    if distance_sq > BALL_RADIUS * BALL_RADIUS:
        return False
    if distance_sq > 0.00001:
        distance = math.sqrt(distance_sq)
        normal = delta / distance
        ball.pos += normal * (BALL_RADIUS - distance + 0.15)
    else:
        sides = [
            (abs(ball.pos.x - rect.left), Vec(-1, 0), Vec(rect.left - BALL_RADIUS - 0.1, ball.pos.y)),
            (abs(rect.right - ball.pos.x), Vec(1, 0), Vec(rect.right + BALL_RADIUS + 0.1, ball.pos.y)),
            (abs(ball.pos.y - rect.top), Vec(0, -1), Vec(ball.pos.x, rect.top - BALL_RADIUS - 0.1)),
            (abs(rect.bottom - ball.pos.y), Vec(0, 1), Vec(ball.pos.x, rect.bottom + BALL_RADIUS + 0.1)),
        ]
        _, normal, location = min(sides, key=lambda item: item[0])
        ball.pos = location
    dot = ball.vel.dot(normal)
    if dot < 0:
        ball.vel -= 2 * dot * normal
    return True


@dataclass
class Ball:
    pos: Vec
    vel: Vec
    speed: float = BASE_SPEED
    held: bool = False
    offset: float = 0.0
    trail: list = None

    def __post_init__(self):
        self.trail = []


@dataclass
class Brick:
    rect: pygame.Rect
    kind: str
    hp: int = 1
    max_hp: int = 1


@dataclass
class Capsule:
    pos: Vec
    kind: str


@dataclass
class Alien:
    pos: Vec
    origin_x: float
    phase: float
    age: float = 0.0


class Game:
    def __init__(self):
        self.scores = load_scores()
        self.state = "title"
        self.paused = False
        self.time = 0.0
        self.score = 0
        self.lives = 3
        self.level = 1
        self.next_extra_life = 20000
        self.paddle_x = WIDTH / 2
        self.paddle_width = 112
        self.aim = 0.0
        self.balls = []
        self.bricks = []
        self.capsules = []
        self.aliens = []
        self.lasers = []
        self.particles = []
        self.effects = {}
        self.alien_timer = 6.0
        self.laser_cooldown = 0.0
        self.banner = ""
        self.banner_timer = 0.0
        self.continue_timer = 10.0
        self.name = ""
        self.result_message = ""
        self.run_recorded = True
        self.save_error = ""
        self.boss_hp = 0
        self.boss_max_hp = 36
        self.boss_phase = 0.0
        self.boss_flash = 0.0
        self.boss_rect = pygame.Rect(0, 0, 220, 154)
        star_rng = random.Random(8)
        self.stars = [
            (star_rng.randrange(WIDTH), star_rng.randrange(TOP, HEIGHT),
             star_rng.choice([1, 1, 2]), star_rng.uniform(5, 24))
            for _ in range(100)
        ]

    @property
    def paddle(self):
        return pygame.Rect(
            round(self.paddle_x - self.paddle_width / 2),
            PADDLE_Y, round(self.paddle_width), 18
        )

    def announce(self, message, duration=2.2):
        self.banner = message
        self.banner_timer = duration

    def add_score(self, amount):
        self.score += amount
        while self.score >= self.next_extra_life:
            self.lives += 1
            self.next_extra_life += 60000
            self.announce("SCORE BONUS: EXTRA PLAYER!", 3.0)

    def burst(self, position, color, count=14):
        for _ in range(count):
            angle = random.uniform(0, math.tau)
            velocity = Vec(math.cos(angle), math.sin(angle)) * random.uniform(40, 200)
            duration = random.uniform(0.25, 0.65)
            self.particles.append([Vec(position), velocity, duration, duration, color])
        if len(self.particles) > 900:
            self.particles = self.particles[-900:]

    def clear_powers(self):
        self.effects.clear()
        self.paddle_width = 112
        self.lasers.clear()
        self.capsules.clear()
        self.laser_cooldown = 0

    def serve(self):
        self.paddle_x = WIDTH / 2
        self.aim = 0
        self.balls = [Ball(
            Vec(self.paddle_x, PADDLE_Y - BALL_RADIUS - 1),
            Vec(0, -BASE_SPEED), held=True
        )]

    def new_game(self):
        self.score = 0
        self.lives = 3
        self.next_extra_life = 20000
        self.level = 1
        self.name = ""
        self.run_recorded = False
        self.save_error = ""
        self.paused = False
        self.particles.clear()
        self.state = "playing"
        self.setup_level()

    def setup_level(self):
        self.clear_powers()
        self.bricks.clear()
        self.aliens.clear()
        self.boss_phase = 0.0
        self.alien_timer = 5.0
        if self.level <= 2:
            columns = 12
            gap = 5
            brick_width = 69
            total = columns * brick_width + (columns - 1) * gap
            start_x = (WIDTH - total) // 2
            for row in range(7):
                for col in range(columns):
                    if row == 6 and col % 3 != 1:
                        continue
                    kind = "standard"
                    hp = 1
                    if row in (2, 4) and col in (2, 9):
                        kind = "wall"
                    elif (row + col * 2 + self.level) % 9 == 0:
                        kind = "explosive"
                    elif (row + col + self.level) % 4 == 0 or (
                        self.level == 2 and row == 0
                    ):
                        kind = "reinforced"
                        hp = 2 if self.level == 1 else 3
                    rect = pygame.Rect(start_x + col * (brick_width + gap),
                                       126 + row * 31, brick_width, 25)
                    self.bricks.append(Brick(rect, kind, hp, hp))
            self.boss_hp = 0
            self.announce(f"SECTOR {self.level} โ€” CLEAR ALL BREAKABLE BRICKS", 3.0)
        else:
            self.boss_hp = self.boss_max_hp
            self.position_boss()
            self.announce("FINAL SECTOR โ€” DESTROY DOH", 3.5)
        self.serve()

    def position_boss(self):
        self.boss_rect.center = (
            round(WIDTH / 2 + math.sin(self.boss_phase * 0.77) * 260),
            round(178 + math.sin(self.boss_phase * 1.31) * 35)
        )

    def launch(self):
        if self.state != "playing" or self.paused:
            return
        for ball in self.balls:
            if ball.held:
                angle = math.radians(self.aim)
                ball.vel = Vec(math.sin(angle), -math.cos(angle)) * ball.speed
                ball.held = False
                ball.trail.clear()

    def drop_capsule(self, position):
        roll = random.random()
        if roll < 0.012:
            kind = "P"
        elif roll < 0.235:
            kind = random.choice(["E", "L", "M", "C"])
        else:
            return
        self.capsules.append(Capsule(Vec(position), kind))

    def damage_brick(self, brick):
        if brick not in self.bricks or brick.kind == "wall":
            return
        brick.hp -= 1
        if brick.hp > 0:
            self.add_score(30)
            self.burst(brick.rect.center, CYAN, 5)
            return
        queue = [brick]
        while queue:
            current = queue.pop()
            if current not in self.bricks or current.kind == "wall":
                continue
            self.bricks.remove(current)
            value = {"standard": 150, "reinforced": 350, "explosive": 250}[current.kind]
            self.add_score(value)
            color = PINK if current.kind == "explosive" else CYAN
            self.burst(current.rect.center, color, 18)
            self.drop_capsule(current.rect.center)
            if current.kind == "explosive":
                blast = current.rect.inflate(160, 74)
                self.burst(current.rect.center, YELLOW, 35)
                for neighbor in list(self.bricks):
                    if neighbor.kind != "wall" and blast.colliderect(neighbor.rect):
                        queue.append(neighbor)

    def hit_boss(self):
        if self.boss_hp <= 0:
            return
        self.boss_hp -= 1
        self.boss_flash = 0.13
        self.add_score(250)
        self.burst(self.boss_rect.center, PINK, 12)
        if self.boss_hp == 0:
            self.add_score(10000)
            self.burst(self.boss_rect.center, YELLOW, 100)

    def apply_power(self, kind):
        if kind == "P":
            self.lives += 1
            self.announce("PLAYER CAPSULE: EXTRA LIFE!")
            return
        self.effects[kind] = 18.0 if kind != "M" else 22.0
        if kind == "E":
            self.paddle_width = 180
        elif kind == "M":
            moving = [ball for ball in self.balls if not ball.held]
            if not moving:
                self.launch()
                moving = list(self.balls)
            additions = []
            for original in moving:
                for angle in (-25, 25):
                    if len(self.balls) + len(additions) >= 6:
                        break
                    direction = original.vel.rotate(angle)
                    if abs(direction.y) < 100:
                        direction.y = -140
                    direction.scale_to_length(original.speed)
                    additions.append(Ball(Vec(original.pos), direction, original.speed))
            self.balls.extend(additions)
        self.announce(POWER_NAMES[kind] + " ONLINE")

    def expire_power(self, kind):
        if kind == "E":
            self.paddle_width = 112
        elif kind == "C":
            self.launch()
        elif kind == "M" and len(self.balls) > 1:
            moving = [ball for ball in self.balls if not ball.held]
            self.balls = [(moving or self.balls)[0]]

    def lose_life(self):
        self.lives -= 1
        self.clear_powers()
        self.aliens.clear()
        if self.lives <= 0:
            self.state = "gameover"
            self.continue_timer = 10.0
            self.announce("")
        else:
            self.serve()
            self.announce("PLAYER LOST โ€” SPACE TO LAUNCH", 2.5)

    def continue_game(self):
        self.lives = 3
        self.state = "playing"
        self.paused = False
        self.clear_powers()
        self.aliens.clear()
        self.serve()
        self.announce("COIN ACCEPTED โ€” CONTINUING RUN", 2.5)

    def finish_run(self, victory=False):
        self.state = "name"
        self.paused = False
        self.name = ""
        self.result_message = "DOH DEFEATED โ€” SECTOR SECURED!" if victory else "GAME OVER"
        pygame.key.start_text_input()

    def record_run(self):
        if self.run_recorded:
            return
        name = self.name.strip().upper()[:10] or "AAA"
        entry = {"initials": name, "score": int(self.score)}
        self.scores = sorted(
            self.scores + [entry], key=lambda row: row["score"], reverse=True
        )[:5]
        errors = []
        try:
            atomic_json(SCORE_FILE, self.scores)
        except OSError as error:
            errors.append("high_scores.json: " + str(error))
        try:
            atomic_json(ARCADE_FILE, entry)
        except OSError as error:
            errors.append("/tmp/alpaca_score.json: " + str(error))
        self.save_error = " | ".join(errors)
        if self.save_error:
            print("Score save error:", self.save_error)
        self.run_recorded = True

    def handle_event(self, event):
        if event.type == pygame.TEXTINPUT and self.state == "name":
            for char in event.text.upper():
                if char.isascii() and (char.isalnum() or char in " -_"):
                    if len(self.name) < 10:
                        self.name += char
        if event.type != pygame.KEYDOWN:
            return
        key = event.key
        if self.state in ("title", "results"):
            if key in (pygame.K_RETURN, pygame.K_SPACE):
                self.new_game()
        elif self.state == "playing":
            if key in (pygame.K_ESCAPE, pygame.K_p):
                self.paused = not self.paused
            elif key == pygame.K_SPACE:
                self.launch()
        elif self.state == "gameover":
            if key == pygame.K_c:
                self.continue_game()
            elif key == pygame.K_RETURN:
                self.finish_run()
        elif self.state == "name":
            if key == pygame.K_BACKSPACE:
                self.name = self.name[:-1]
            elif key == pygame.K_RETURN:
                self.record_run()
                pygame.key.stop_text_input()
                self.state = "results"

    def update_particles(self, dt):
        for particle in self.particles:
            particle[0] += particle[1] * dt
            particle[1] *= max(0, 1 - dt * 1.6)
            particle[2] -= dt
        self.particles = [particle for particle in self.particles if particle[2] > 0]

    def update(self, dt):
        self.time += dt
        if self.state == "gameover":
            self.update_particles(dt)
            self.continue_timer -= dt
            if self.continue_timer <= 0:
                self.finish_run()
            return
        if self.state != "playing":
            self.update_particles(dt)
            return
        if self.paused:
            return

        self.update_particles(dt)
        self.banner_timer = max(0, self.banner_timer - dt)
        self.boss_flash = max(0, self.boss_flash - dt)
        keys = pygame.key.get_pressed()
        movement = int(keys[pygame.K_RIGHT]) - int(keys[pygame.K_LEFT])
        self.paddle_x += movement * 610 * dt
        self.paddle_x = max(LEFT + self.paddle_width / 2,
                            min(RIGHT - self.paddle_width / 2, self.paddle_x))
        self.aim += (int(keys[pygame.K_d]) - int(keys[pygame.K_a])) * 100 * dt
        self.aim = max(-72, min(72, self.aim))

        for kind in list(self.effects):
            self.effects[kind] -= dt
            if self.effects[kind] <= 0:
                del self.effects[kind]
                self.expire_power(kind)

        if self.level == 3:
            self.boss_phase += dt
            self.position_boss()

        self.update_aliens(dt)
        self.update_capsules(dt)
        self.update_lasers(dt, keys)

        steps = max(1, math.ceil(dt / (1 / 240)))
        sub_dt = dt / steps
        for ball in list(self.balls):
            if ball not in self.balls:
                continue
            if ball.held:
                limit = self.paddle_width / 2 - BALL_RADIUS
                ball.offset = max(-limit, min(limit, ball.offset))
                ball.pos = Vec(self.paddle_x + ball.offset, PADDLE_Y - BALL_RADIUS - 1)
                continue
            ball.speed = min(MAX_SPEED, ball.speed + 4.2 * dt)
            if ball.vel.length_squared() < 1:
                ball.vel = Vec(0, -ball.speed)
            ball.vel.scale_to_length(ball.speed)
            ball.trail.append(Vec(ball.pos))
            ball.trail = ball.trail[-10:]
            for _ in range(steps):
                self.update_ball(ball, sub_dt)
                if ball.held or ball.pos.y > HEIGHT + 30:
                    break
            if ball.pos.y > HEIGHT + 30 and ball in self.balls:
                self.balls.remove(ball)

        if self.level == 3 and self.boss_hp <= 0:
            self.finish_run(victory=True)
            return
        if self.level < 3 and not any(brick.kind != "wall" for brick in self.bricks):
            self.add_score(2000)
            self.level += 1
            self.setup_level()
            return
        if not self.balls:
            self.lose_life()

    def update_capsules(self, dt):
        for capsule in list(self.capsules):
            capsule.pos.y += 130 * dt
            rect = pygame.Rect(round(capsule.pos.x - 15), round(capsule.pos.y - 10), 30, 20)
            if rect.colliderect(self.paddle):
                self.capsules.remove(capsule)
                self.add_score(100)
                self.apply_power(capsule.kind)
            elif capsule.pos.y > HEIGHT + 20:
                self.capsules.remove(capsule)

    def update_aliens(self, dt):
        self.alien_timer -= dt
        if self.alien_timer <= 0:
            self.alien_timer = random.uniform(4.0, 7.0) if self.level < 3 else 3.2
            if len(self.aliens) < 5:
                x = random.uniform(100, WIDTH - 100)
                self.aliens.append(Alien(Vec(x, TOP + 28), x, random.uniform(0, math.tau)))
        for alien in list(self.aliens):
            alien.age += dt
            alien.pos.y += (44 if self.level < 3 else 65) * dt
            alien.pos.x = max(LEFT + 25, min(
                RIGHT - 25, alien.origin_x + math.sin(alien.phase + alien.age * 2.3) * 65
            ))
            if alien.pos.y > PADDLE_Y - 25:
                self.aliens.remove(alien)

    def destroy_alien(self, alien):
        if alien in self.aliens:
            self.aliens.remove(alien)
            self.add_score(500)
            self.burst(alien.pos, (131, 255, 134), 22)

    def update_lasers(self, dt, keys):
        self.laser_cooldown -= dt
        if "L" in self.effects and keys[pygame.K_SPACE] and self.laser_cooldown <= 0:
            self.laser_cooldown = 0.19
            for side in (-1, 1):
                self.lasers.append(Vec(
                    self.paddle_x + side * (self.paddle_width / 2 - 10), PADDLE_Y - 10
                ))
        for laser in list(self.lasers):
            old_y = laser.y
            laser.y -= 900 * dt
            sweep = pygame.Rect(round(laser.x - 2), round(laser.y - 10),
                                4, max(12, math.ceil(old_y - laser.y + 12)))
            hit = False
            for alien in list(self.aliens):
                if sweep.colliderect(pygame.Rect(
                    round(alien.pos.x - 19), round(alien.pos.y - 14), 38, 28
                )):
                    self.destroy_alien(alien)
                    hit = True
                    break
            if not hit and self.level == 3 and self.boss_hp > 0:
                if sweep.colliderect(self.boss_rect):
                    self.hit_boss()
                    hit = True
            if not hit:
                candidates = [brick for brick in self.bricks if sweep.colliderect(brick.rect)]
                if candidates:
                    brick = max(candidates, key=lambda item: item.rect.bottom)
                    self.damage_brick(brick)
                    hit = True
            if hit or laser.y < TOP:
                if laser in self.lasers:
                    self.lasers.remove(laser)

    def update_ball(self, ball, dt):
        previous = Vec(ball.pos)
        ball.pos += ball.vel * dt
        if ball.pos.x - BALL_RADIUS < LEFT:
            ball.pos.x = LEFT + BALL_RADIUS
            ball.vel.x = abs(ball.vel.x)
        elif ball.pos.x + BALL_RADIUS > RIGHT:
            ball.pos.x = RIGHT - BALL_RADIUS
            ball.vel.x = -abs(ball.vel.x)
        if ball.pos.y - BALL_RADIUS < TOP:
            ball.pos.y = TOP + BALL_RADIUS
            ball.vel.y = abs(ball.vel.y)

        paddle = self.paddle
        if (ball.vel.y > 0
                and previous.y + BALL_RADIUS <= paddle.top + 5
                and circle_rect_overlap(ball.pos, BALL_RADIUS, paddle)):
            offset = max(-1.0, min(1.0, (ball.pos.x - self.paddle_x) / (self.paddle_width / 2)))
            ball.pos.y = PADDLE_Y - BALL_RADIUS - 0.2
            ball.speed = min(MAX_SPEED, ball.speed + 7)
            if "C" in self.effects:
                ball.held = True
                ball.offset = offset * (self.paddle_width / 2 - BALL_RADIUS)
                ball.trail.clear()
                return
            angle = math.radians(offset * 74)
            ball.vel = Vec(math.sin(angle), -math.cos(angle)) * ball.speed
            self.burst(ball.pos, CYAN, 4)

        for alien in list(self.aliens):
            if (ball.pos - alien.pos).length_squared() < (BALL_RADIUS + 19) ** 2:
                angle = random.uniform(-math.pi, math.pi)
                direction = Vec(math.cos(angle), math.sin(angle))
                if abs(direction.y) < 0.25:
                    direction.y = -0.35
                ball.vel = direction.normalize() * ball.speed
                self.destroy_alien(alien)
                break

        if self.level == 3 and self.boss_hp > 0:
            if reflect_rect(ball, self.boss_rect):
                self.hit_boss()

        for brick in list(self.bricks):
            if reflect_rect(ball, brick.rect):
                self.damage_brick(brick)
                break

        if abs(ball.vel.y) < 65:
            ball.vel.y = -65 if ball.vel.y <= 0 else 65
            ball.vel.scale_to_length(ball.speed)

    def draw_background(self):
        screen.fill(BG)
        for x, y, radius, speed in self.stars:
            yy = TOP + (y - TOP + self.time * speed) % (HEIGHT - TOP)
            pygame.draw.circle(screen, (48, 72, 103), (int(x), int(yy)), radius)
        for x in range(LEFT, RIGHT, 48):
            pygame.draw.line(screen, (13, 25, 45), (x, TOP), (x, HEIGHT))
        for y in range(TOP, HEIGHT, 48):
            pygame.draw.line(screen, (13, 25, 45), (LEFT, y), (RIGHT, y))
        pygame.draw.line(screen, CYAN, (LEFT - 3, TOP), (LEFT - 3, HEIGHT - 37), 3)
        pygame.draw.line(screen, CYAN, (RIGHT + 3, TOP), (RIGHT + 3, HEIGHT - 37), 3)
        pygame.draw.line(screen, (62, 122, 159), (LEFT, TOP), (RIGHT, TOP), 2)
        pygame.draw.line(screen, (128, 37, 69), (LEFT, HEIGHT - 35), (RIGHT, HEIGHT - 35), 2)

    def draw_bricks(self):
        palette = [(49, 135, 212), (124, 81, 205), (40, 174, 189), (189, 71, 151)]
        for brick in self.bricks:
            rect = brick.rect
            if brick.kind == "wall":
                color = (77, 85, 109)
            elif brick.kind == "reinforced":
                color = (184, 155, 83)
            elif brick.kind == "explosive":
                color = (217, 72, 91)
            else:
                color = palette[(rect.y // 31) % len(palette)]
            pygame.draw.rect(screen, (4, 7, 17), rect.move(3, 3), border_radius=4)
            pygame.draw.rect(screen, color, rect, border_radius=4)
            lighter = tuple(min(255, channel + 50) for channel in color)
            pygame.draw.line(screen, lighter, (rect.left + 4, rect.top + 3),
                             (rect.right - 5, rect.top + 3), 2)
            if brick.kind == "wall":
                pygame.draw.rect(screen, GRAY, rect.inflate(-7, -7), 1, border_radius=2)
                for x in (rect.left + 10, rect.right - 10):
                    pygame.draw.circle(screen, (210, 220, 235), (x, rect.centery), 2)
            elif brick.kind == "reinforced":
                for i in range(brick.hp):
                    cx = rect.centerx + (i - (brick.hp - 1) / 2) * 13
                    pygame.draw.rect(screen, YELLOW, (int(cx - 3), rect.centery - 3, 6, 7))
                if brick.hp < brick.max_hp:
                    pygame.draw.lines(screen, (60, 48, 33), False, [
                        (rect.left + 17, rect.top + 6),
                        (rect.left + 25, rect.centery),
                        (rect.left + 20, rect.bottom - 3)
                    ], 2)
            elif brick.kind == "explosive":
                text("X", rect.centerx, rect.centery + 1, WHITE, "small", True)

    def draw_boss(self):
        if self.level != 3 or self.boss_hp <= 0:
            return
        rect = self.boss_rect
        color = (245, 182, 228) if self.boss_flash > 0 else (137, 108, 171)
        for side in (-1, 1):
            x = rect.centerx + side * (rect.width // 2 + 10)
            pygame.draw.rect(screen, (71, 63, 110), (x - 12, rect.top + 37, 24, 80), border_radius=8)
            pygame.draw.line(screen, CYAN, (x, rect.top + 49), (x, rect.top + 98), 3)
        pygame.draw.rect(screen, (54, 39, 86), rect.inflate(10, 10), border_radius=30)
        pygame.draw.rect(screen, color, rect, border_radius=26)
        pygame.draw.rect(screen, (174, 148, 204), (rect.x + 24, rect.y + 8, rect.w - 48, 25), border_radius=8)
        for side in (-1, 1):
            ex = rect.centerx + side * 53
            pygame.draw.rect(screen, (26, 16, 43), (ex - 27, rect.y + 48, 54, 25), border_radius=5)
            pygame.draw.rect(screen, PINK, (ex - 19, rect.y + 55, 38, 10), border_radius=3)
            pygame.draw.circle(screen, WHITE, (ex, rect.y + 59), 4)
        pygame.draw.polygon(screen, (97, 75, 135), [
            (rect.centerx, rect.y + 57),
            (rect.centerx - 19, rect.y + 106),
            (rect.centerx + 21, rect.y + 106)
        ])
        pygame.draw.rect(screen, (28, 20, 43), (rect.centerx - 52, rect.bottom - 33, 104, 17), border_radius=4)
        for x in range(rect.centerx - 43, rect.centerx + 48, 18):
            pygame.draw.line(screen, (186, 167, 207), (x, rect.bottom - 31), (x, rect.bottom - 18), 3)
        text("D O H", rect.centerx, rect.y - 17, PINK, "normal", True)
        bar = pygame.Rect(WIDTH // 2 - 180, 84, 360, 12)
        pygame.draw.rect(screen, (60, 29, 57), bar, border_radius=6)
        pygame.draw.rect(screen, PINK, (
            bar.x, bar.y, max(0, int(bar.width * self.boss_hp / self.boss_max_hp)), bar.height
        ), border_radius=6)
        pygame.draw.rect(screen, WHITE, bar, 1, border_radius=6)

    def draw_playfield(self):
        self.draw_bricks()
        self.draw_boss()
        for alien in self.aliens:
            x, y = round(alien.pos.x), round(alien.pos.y)
            pygame.draw.ellipse(screen, (45, 126, 105), (x - 23, y - 7, 46, 17))
            pygame.draw.ellipse(screen, (121, 250, 157), (x - 12, y - 17, 24, 23))
            pygame.draw.line(screen, (12, 38, 42), (x - 7, y - 7), (x + 7, y - 7), 5)
            for dx in (-15, 0, 15):
                pygame.draw.circle(screen, YELLOW, (x + dx, y + 5), 2)
        for capsule in self.capsules:
            rect = pygame.Rect(round(capsule.pos.x - 16), round(capsule.pos.y - 11), 32, 22)
            pygame.draw.rect(screen, POWER_COLORS[capsule.kind], rect, border_radius=10)
            pygame.draw.rect(screen, WHITE, rect, 1, border_radius=10)
            text(capsule.kind, rect.centerx, rect.centery + 1, BG, "small", True)
        for laser in self.lasers:
            pygame.draw.line(screen, PINK, (int(laser.x), int(laser.y)),
                             (int(laser.x), int(laser.y - 15)), 5)
            pygame.draw.line(screen, WHITE, (int(laser.x), int(laser.y)),
                             (int(laser.x), int(laser.y - 15)), 2)

        paddle = self.paddle
        if self.state == "playing" or self.lives > 0:
            pygame.draw.rect(screen, (26, 67, 103), paddle.inflate(0, 7), border_radius=8)
            pygame.draw.rect(screen, (116, 175, 209), paddle, border_radius=7)
            pygame.draw.rect(screen, CYAN if "C" not in self.effects else YELLOW,
                             paddle.inflate(-30, -8), border_radius=4)
            for side in (-1, 1):
                x = self.paddle_x + side * (self.paddle_width / 2 - 9)
                pygame.draw.rect(screen, PINK, (round(x - 8), PADDLE_Y - 2, 16, 22), border_radius=4)
                if "L" in self.effects:
                    pygame.draw.rect(screen, WHITE, (round(x - 3), PADDLE_Y - 13, 6, 15), border_radius=2)

        for ball in self.balls:
            for i, point in enumerate(ball.trail):
                radius = max(1, int(BALL_RADIUS * (i + 1) / 12))
                pygame.draw.circle(screen, (29, 76 + i * 8, 107 + i * 9),
                                   (round(point.x), round(point.y)), radius)
            pygame.draw.circle(screen, CYAN, (round(ball.pos.x), round(ball.pos.y)), BALL_RADIUS + 2)
            pygame.draw.circle(screen, WHITE, (round(ball.pos.x), round(ball.pos.y)), BALL_RADIUS - 1)
            if ball.held and self.state == "playing":
                direction = Vec(math.sin(math.radians(self.aim)), -math.cos(math.radians(self.aim)))
                for distance in range(20, 100, 13):
                    point = ball.pos + direction * distance
                    pygame.draw.circle(screen, YELLOW, (round(point.x), round(point.y)), 2)

        for position, velocity, life, total, color in self.particles:
            faded = tuple(int(channel * life / total) for channel in color)
            pygame.draw.circle(screen, faded, (round(position.x),
round(position.y)), 3)

    def draw_hud(self):
        pygame.draw.rect(screen, (10, 16, 34), (0, 0, WIDTH, TOP - 4))
        pygame.draw.line(screen, (62, 122, 159), (0, TOP - 4), (WIDTH, TOP - 4), 2)
        text("SCORE", 30, 12, CYAN, "small")
        text(f"{self.score:07d}", 30, 32, WHITE, "large")
        text("HIGH", WIDTH // 2 - 60, 12, PINK, "small")
        best = self.scores[0]["score"] if self.scores else 0
        text(f"{best:07d}", WIDTH // 2 - 60, 32, WHITE, "normal")
        text("LIVES", WIDTH - 200, 12, YELLOW, "small")
        for i in range(min(self.lives, 6)):
            x = WIDTH - 200 + i * 30
            pygame.draw.circle(screen, CYAN, (x + 10, 42), 9)
            pygame.draw.circle(screen, WHITE, (x + 10, 42), 4)
        if self.lives > 6:
            text(f"+{self.lives - 6}", WIDTH - 20, 32, WHITE, "small")
        if self.level == 3:
            text("DOH", WIDTH - 90, 12, PINK, "small")
        else:
            text(f"SECTOR {self.level}", WIDTH - 110, 12, CYAN, "small")
        y = 60
        for kind, remaining in self.effects.items():
            label = POWER_NAMES.get(kind, kind)
            text(f"{label} {remaining:0.1f}", 30, y, POWER_COLORS.get(kind, WHITE), "small")
            y += 20
        if self.banner_timer > 0 and self.banner:
            text(self.banner, WIDTH // 2, HEIGHT // 2 - 120, YELLOW, "normal", True)

    def draw_title(self):
        self.draw_background()
        text("NEON VAUS", WIDTH // 2, 190, CYAN, "title", True)
        text("BREAKOUT  //  DOH PROTOCOL", WIDTH // 2, 250, PINK, "normal", True)
        text("LEFT / RIGHT  MOVE PADDLE", WIDTH // 2, 330, WHITE, "normal", True)
        text("SPACE  LAUNCH  /  FIRE LASER", WIDTH // 2, 360, WHITE, "normal", True)
        text("A / D  AIM HELD BALL", WIDTH // 2, 390, WHITE, "normal", True)
        text("P / ESC  PAUSE", WIDTH // 2, 420, WHITE, "normal", True)
        text("PRESS ENTER OR SPACE TO START", WIDTH // 2, 500, YELLOW, "large", True)
        self.draw_scoreboard(WIDTH // 2, 580)

    def draw_scoreboard(self, cx, top):
        text("HIGH SCORES", cx, top, PINK, "normal", True)
        if not self.scores:
            text("NO RECORDS YET", cx, top + 34, GRAY, "small", True)
            return
        for i, row in enumerate(self.scores):
            y = top + 34 + i * 24
            text(f"{i + 1}. {row['initials']}", cx - 90, y, WHITE, "small")
            text(f"{row['score']:07d}", cx + 90, y, CYAN, "small")

    def draw_gameover(self):
        self.draw_playfield()
        overlay = pygame.Surface((WIDTH, HEIGHT), pygame.SRCALPHA)
        overlay.fill((4, 6, 18, 205))
        screen.blit(overlay, (0, 0))
        text("GAME OVER", WIDTH // 2, 200, PINK, "title", True)
        text(f"FINAL SCORE  {self.score:07d}", WIDTH // 2, 280, WHITE, "large", True)
        text(f"CONTINUE IN {max(0, self.continue_timer):0.1f}s", WIDTH // 2, 340, YELLOW, "normal", True)
        text("PRESS C TO INSERT COIN", WIDTH // 2, 380, CYAN, "normal", True)
        text("PRESS ENTER TO END RUN", WIDTH // 2, 410, GRAY, "small", True)
        self.draw_scoreboard(WIDTH // 2, 470)

    def draw_name(self):
        self.draw_playfield()
        overlay = pygame.Surface((WIDTH, HEIGHT), pygame.SRCALPHA)
        overlay.fill((4, 6, 18, 215))
        screen.blit(overlay, (0, 0))
        text(self.result_message, WIDTH // 2, 200, PINK, "large", True)
        text(f"FINAL SCORE  {self.score:07d}", WIDTH // 2, 260, WHITE, "normal", True)
        text("ENTER YOUR INITIALS", WIDTH // 2, 330, CYAN, "normal", True)
        box = pygame.Rect(WIDTH // 2 - 160, 360, 320, 60)
        pygame.draw.rect(screen, (12, 22, 44), box, border_radius=8)
        pygame.draw.rect(screen, CYAN, box, 2, border_radius=8)
        text(self.name + "_", box.centerx, box.centery, WHITE, "large", True)
        text("PRESS ENTER TO SAVE", WIDTH // 2, 450, YELLOW, "small", True)

    def draw_results(self):
        self.draw_background()
        text("RUN RECORDED", WIDTH // 2, 150, CYAN, "large", True)
        text(f"{self.name or 'AAA'}  โ€”  {self.score:07d}", WIDTH // 2, 210, WHITE, "normal", True)
        if self.save_error:
            text("SAVE WARNING: " + self.save_error[:70], WIDTH // 2, 250, PINK, "small", True)
        self.draw_scoreboard(WIDTH // 2, 300)
        text("PRESS ENTER OR SPACE FOR NEW GAME", WIDTH // 2, 560, YELLOW, "normal", True)

    def draw(self):
        if self.state == "title":
            self.draw_title()
        elif self.state == "playing":
            self.draw_background()
            self.draw_playfield()
            self.draw_hud()
            if self.paused:
                overlay = pygame.Surface((WIDTH, HEIGHT), pygame.SRCALPHA)
                overlay.fill((4, 6, 18, 170))
                screen.blit(overlay, (0, 0))
                text("PAUSED", WIDTH // 2, HEIGHT // 2, CYAN, "title", True)
        elif self.state == "gameover":
            self.draw_gameover()
        elif self.state == "name":
            self.draw_name()
        elif self.state == "results":
            self.draw_results()
        pygame.display.flip()


def main():
    game = Game()
    running = True
    while running:
        dt = min(clock.tick(FPS) / 1000.0, 0.05)
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False
            else:
                game.handle_event(event)
        game.update(dt)
        game.draw()
    pygame.quit()


if __name__ == "__main__":
    main()

๐ŸŽฎ Press โ–ถ Play above to run it live in your browser, or download it below and run locally (pip install pygame), then enter your score by hand