Last active
March 7, 2025 03:28
-
-
Save ThomasRohde/14b27e9b6760d2528578c38b1978bd7b to your computer and use it in GitHub Desktop.
A colorful implementation of the classic Breakout ...
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# /// script | |
# description = 'A colorful Pygame implementation of the classic Breakout game with paddle, ball, and bricks' | |
# authors = [ | |
# 'Script-Magic AI Generator', | |
# ] | |
# date = '2023-11-01' | |
# requires-python = '>=3.9' | |
# dependencies = [ | |
# 'pygame>=2.0.0', | |
# ] | |
# tags = [ | |
# 'generated', | |
# 'script-magic', | |
# 'game', | |
# 'pygame', | |
# 'breakout', | |
# ] | |
# /// | |
# Generated from the prompt: "Create a snazzy game of breakout using Pygame (no sound)" | |
import pygame | |
import sys | |
import random | |
from typing import List, Tuple | |
# Initialize pygame | |
pygame.init() | |
# Constants | |
SCREEN_WIDTH = 800 | |
SCREEN_HEIGHT = 600 | |
PADDLE_WIDTH = 100 | |
PADDLE_HEIGHT = 20 | |
BALL_SIZE = 15 | |
BRICK_WIDTH = 80 | |
BRICK_HEIGHT = 30 | |
BRICK_ROWS = 5 | |
BRICK_COLS = 10 | |
BRICK_GAP = 5 | |
FPS = 60 | |
# Colors | |
BACKGROUND = (0, 0, 20) | |
PADDLE_COLOR = (0, 200, 255) | |
BALL_COLOR = (255, 255, 255) | |
BRICK_COLORS = [ | |
(255, 0, 0), # Red | |
(255, 128, 0), # Orange | |
(255, 255, 0), # Yellow | |
(0, 255, 0), # Green | |
(0, 0, 255), # Blue | |
] | |
TEXT_COLOR = (255, 255, 255) | |
# Game states | |
class GameState: | |
PLAYING = 0 | |
GAME_OVER = 1 | |
WIN = 2 | |
class Paddle: | |
def __init__(self): | |
self.width = PADDLE_WIDTH | |
self.height = PADDLE_HEIGHT | |
self.x = (SCREEN_WIDTH - PADDLE_WIDTH) // 2 | |
self.y = SCREEN_HEIGHT - PADDLE_HEIGHT - 20 | |
self.speed = 10 | |
self.color = PADDLE_COLOR | |
def move(self, direction: int): | |
# Move paddle left or right based on direction | |
self.x += self.speed * direction | |
# Keep paddle within screen bounds | |
if self.x < 0: | |
self.x = 0 | |
elif self.x > SCREEN_WIDTH - self.width: | |
self.x = SCREEN_WIDTH - self.width | |
def draw(self, screen): | |
pygame.draw.rect(screen, self.color, (self.x, self.y, self.width, self.height)) | |
class Ball: | |
def __init__(self): | |
self.radius = BALL_SIZE // 2 | |
self.reset() | |
self.color = BALL_COLOR | |
def reset(self): | |
# Position ball in the middle of the screen | |
self.x = SCREEN_WIDTH // 2 | |
self.y = SCREEN_HEIGHT // 2 | |
# Random starting velocity | |
self.dx = random.choice([-4, 4]) | |
self.dy = -4 | |
def move(self): | |
# Update position | |
self.x += self.dx | |
self.y += self.dy | |
# Handle wall collisions | |
if self.x - self.radius <= 0 or self.x + self.radius >= SCREEN_WIDTH: | |
self.dx *= -1 | |
if self.y - self.radius <= 0: | |
self.dy *= -1 | |
def check_paddle_collision(self, paddle: Paddle): | |
# Check if ball collides with paddle | |
if (self.y + self.radius >= paddle.y and | |
self.x >= paddle.x and | |
self.x <= paddle.x + paddle.width): | |
# Reverse vertical direction | |
self.dy *= -1 | |
# Change horizontal direction based on where ball hits paddle | |
# Hit on left side: ball goes left, right side: ball goes right | |
relative_position = (self.x - paddle.x) / paddle.width | |
self.dx = 8 * (relative_position - 0.5) # -4 to 4 based on position | |
def check_brick_collision(self, bricks: List['Brick']) -> int: | |
score = 0 | |
for brick in bricks[:]: # Use copy of list to safely remove items | |
if brick.active and self.collides_with_brick(brick): | |
brick.active = False | |
self.dy *= -1 | |
score += brick.points | |
# Add random slight variation to ball direction after brick hit | |
self.dx += random.uniform(-0.2, 0.2) | |
# Ensure dx doesn't get too small | |
if abs(self.dx) < 2: | |
self.dx = 2 if self.dx > 0 else -2 | |
return score | |
def collides_with_brick(self, brick: 'Brick') -> bool: | |
# Find the closest point on the brick to the ball center | |
closest_x = max(brick.x, min(self.x, brick.x + BRICK_WIDTH)) | |
closest_y = max(brick.y, min(self.y, brick.y + BRICK_HEIGHT)) | |
# Calculate distance between closest point and ball center | |
distance_x = self.x - closest_x | |
distance_y = self.y - closest_y | |
distance = (distance_x ** 2 + distance_y ** 2) ** 0.5 | |
# If distance is less than ball radius, collision detected | |
return distance <= self.radius | |
def draw(self, screen): | |
pygame.draw.circle(screen, self.color, (int(self.x), int(self.y)), self.radius) | |
class Brick: | |
def __init__(self, x: int, y: int, color: Tuple[int, int, int], row: int): | |
self.x = x | |
self.y = y | |
self.color = color | |
self.active = True | |
self.points = (BRICK_ROWS - row) * 10 # More points for higher bricks | |
def draw(self, screen): | |
if self.active: | |
pygame.draw.rect(screen, self.color, (self.x, self.y, BRICK_WIDTH, BRICK_HEIGHT)) | |
# Add 3D effect with lighter inner rectangle | |
lighter_color = tuple(min(c + 50, 255) for c in self.color) | |
pygame.draw.rect(screen, lighter_color, | |
(self.x + 2, self.y + 2, BRICK_WIDTH - 4, BRICK_HEIGHT - 4)) | |
class Game: | |
def __init__(self): | |
self.screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT)) | |
pygame.display.set_caption("Snazzy Breakout") | |
self.clock = pygame.time.Clock() | |
self.font = pygame.font.SysFont(None, 36) | |
self.paddle = Paddle() | |
self.ball = Ball() | |
self.bricks = self.create_bricks() | |
self.score = 0 | |
self.lives = 3 | |
self.state = GameState.PLAYING | |
def create_bricks(self) -> List[Brick]: | |
bricks = [] | |
top_offset = 50 | |
for row in range(BRICK_ROWS): | |
color = BRICK_COLORS[row % len(BRICK_COLORS)] | |
for col in range(BRICK_COLS): | |
x = col * (BRICK_WIDTH + BRICK_GAP) + BRICK_GAP | |
y = row * (BRICK_HEIGHT + BRICK_GAP) + top_offset | |
bricks.append(Brick(x, y, color, row)) | |
return bricks | |
def handle_events(self): | |
for event in pygame.event.get(): | |
if event.type == pygame.QUIT: | |
pygame.quit() | |
sys.exit() | |
elif event.type == pygame.KEYDOWN: | |
if event.key == pygame.K_r and self.state != GameState.PLAYING: | |
self.reset_game() | |
# Continuous movement | |
keys = pygame.key.get_pressed() | |
if keys[pygame.K_LEFT]: | |
self.paddle.move(-1) | |
if keys[pygame.K_RIGHT]: | |
self.paddle.move(1) | |
def reset_game(self): | |
self.paddle = Paddle() | |
self.ball = Ball() | |
self.bricks = self.create_bricks() | |
self.score = 0 | |
self.lives = 3 | |
self.state = GameState.PLAYING | |
def update(self): | |
if self.state != GameState.PLAYING: | |
return | |
self.ball.move() | |
self.ball.check_paddle_collision(self.paddle) | |
# Check for brick collisions | |
points = self.ball.check_brick_collision(self.bricks) | |
self.score += points | |
# Check if all bricks are destroyed | |
active_bricks = sum(1 for brick in self.bricks if brick.active) | |
if active_bricks == 0: | |
self.state = GameState.WIN | |
# Check if ball falls off bottom | |
if self.ball.y + self.ball.radius > SCREEN_HEIGHT: | |
self.lives -= 1 | |
if self.lives <= 0: | |
self.state = GameState.GAME_OVER | |
else: | |
self.ball.reset() | |
def draw(self): | |
# Fill background | |
self.screen.fill(BACKGROUND) | |
# Draw game elements | |
self.paddle.draw(self.screen) | |
self.ball.draw(self.screen) | |
for brick in self.bricks: | |
brick.draw(self.screen) | |
# Draw score and lives | |
score_text = self.font.render(f"Score: {self.score}", True, TEXT_COLOR) | |
lives_text = self.font.render(f"Lives: {self.lives}", True, TEXT_COLOR) | |
self.screen.blit(score_text, (10, 10)) | |
self.screen.blit(lives_text, (SCREEN_WIDTH - 120, 10)) | |
# Draw game over or win message | |
if self.state == GameState.GAME_OVER: | |
self.draw_centered_text("GAME OVER! Press R to restart", TEXT_COLOR) | |
elif self.state == GameState.WIN: | |
self.draw_centered_text("YOU WIN! Press R to play again", TEXT_COLOR) | |
# Update display | |
pygame.display.flip() | |
def draw_centered_text(self, text: str, color: Tuple[int, int, int]): | |
text_surface = self.font.render(text, True, color) | |
text_rect = text_surface.get_rect(center=(SCREEN_WIDTH//2, SCREEN_HEIGHT//2)) | |
# Add background to make text more visible | |
padding = 10 | |
bg_rect = pygame.Rect(text_rect.x - padding, text_rect.y - padding, | |
text_rect.width + 2*padding, text_rect.height + 2*padding) | |
pygame.draw.rect(self.screen, (0, 0, 50), bg_rect) | |
pygame.draw.rect(self.screen, (100, 100, 255), bg_rect, 3) | |
self.screen.blit(text_surface, text_rect) | |
def run(self): | |
# Main game loop | |
while True: | |
self.handle_events() | |
self.update() | |
self.draw() | |
self.clock.tick(FPS) | |
if __name__ == "__main__": | |
game = Game() | |
game.run() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment