Skip to content

Instantly share code, notes, and snippets.

@ThomasRohde
Last active March 7, 2025 03:28
Show Gist options
  • Save ThomasRohde/14b27e9b6760d2528578c38b1978bd7b to your computer and use it in GitHub Desktop.
Save ThomasRohde/14b27e9b6760d2528578c38b1978bd7b to your computer and use it in GitHub Desktop.
A colorful implementation of the classic Breakout ...
# /// 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