Skip to content

Instantly share code, notes, and snippets.

@CypherpunkSamurai
Created February 19, 2025 16:26
Show Gist options
  • Save CypherpunkSamurai/1c3c8e6e1d120f51d05a608a0366f991 to your computer and use it in GitHub Desktop.
Save CypherpunkSamurai/1c3c8e6e1d120f51d05a608a0366f991 to your computer and use it in GitHub Desktop.
Pygame Breaker
import pygame
import sys
import random
import abc
import math
from typing import List, Tuple
# Initialize Pygame
pygame.init()
# Constants
WINDOW_WIDTH = 800
WINDOW_HEIGHT = 600
PADDLE_WIDTH = 100
PADDLE_HEIGHT = 20
BALL_SIZE = 8
BRICK_WIDTH = 80
BRICK_HEIGHT = 30
BRICK_ROWS = 5
BRICK_COLS = 10
PADDLE_SPEED = 8
FPS = 60
# Colors
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
RED = (255, 0, 0)
BLUE = (0, 0, 255)
GREEN = (0, 255, 0)
YELLOW = (255, 255, 0)
ORANGE = (255, 165, 0)
COLORS = [RED, ORANGE, YELLOW, GREEN, BLUE]
class Particle:
def __init__(self, x: float, y: float, color: Tuple[int, int, int]):
self.x = x
self.y = y
self.color = color
self.size = random.randint(2, 4)
angle = random.uniform(0, 2 * math.pi)
speed = random.uniform(2, 5)
self.dx = math.cos(angle) * speed
self.dy = math.sin(angle) * speed
self.life = 1.0 # Life from 1 to 0
def update(self):
self.x += self.dx
self.y += self.dy
self.dy += 0.1 # Gravity
self.life -= 0.02
self.size = max(0, self.size - 0.1)
return self.life > 0
def draw(self, screen):
alpha = int(self.life * 255)
particle_surface = pygame.Surface((self.size * 2, self.size * 2), pygame.SRCALPHA)
pygame.draw.circle(particle_surface, (*self.color, alpha),
(self.size, self.size), self.size)
screen.blit(particle_surface, (int(self.x - self.size), int(self.y - self.size)))
class Brick:
def __init__(self, x: int, y: int, color: Tuple[int, int, int], strength: int = 1):
self.rect = pygame.Rect(x, y, BRICK_WIDTH, BRICK_HEIGHT)
self.color = color
self.strength = strength
self.hit_animation = 0
def draw(self, screen):
# Draw brick with hit animation
if self.hit_animation > 0:
color = tuple(min(255, c + 100) for c in self.color)
self.hit_animation -= 1
else:
color = self.color
pygame.draw.rect(screen, color, self.rect)
pygame.draw.rect(screen, tuple(max(0, c - 50) for c in self.color),
self.rect, 2) # Border
class Player(abc.ABC):
def __init__(self, paddle_x: float, paddle_y: float):
self.paddle_x = paddle_x
self.paddle_y = paddle_y
self.target_x = paddle_x # For smooth movement
self.paddle_rect = pygame.Rect(paddle_x, paddle_y, PADDLE_WIDTH, PADDLE_HEIGHT)
self.trail: List[Tuple[float, float]] = []
@abc.abstractmethod
def update(self, ball_x: float, ball_y: float):
"""Update paddle position based on implementation"""
pass
def draw(self, screen):
# Draw trail effect
for i, (x, y) in enumerate(self.trail):
alpha = int(255 * (i / len(self.trail)))
trail_surface = pygame.Surface((PADDLE_WIDTH, PADDLE_HEIGHT), pygame.SRCALPHA)
pygame.draw.rect(trail_surface, (*BLUE[:3], alpha),
(0, 0, PADDLE_WIDTH, PADDLE_HEIGHT))
screen.blit(trail_surface, (int(x), int(y)))
# Draw main paddle
self.paddle_rect = pygame.Rect(self.paddle_x, self.paddle_y, PADDLE_WIDTH, PADDLE_HEIGHT)
pygame.draw.rect(screen, BLUE, self.paddle_rect)
# Add a gradient effect
for i in range(3):
pygame.draw.rect(screen, tuple(max(0, c - 20 * i) for c in BLUE),
self.paddle_rect, 2)
# Update trail
self.trail.append((self.paddle_x, self.paddle_y))
if len(self.trail) > 3:
self.trail.pop(0)
class PlayerHuman(Player):
def __init__(self, paddle_x: float, paddle_y: float):
super().__init__(paddle_x, paddle_y)
def update(self, ball_x: float, ball_y: float):
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
self.target_x = max(0, self.paddle_x - PADDLE_SPEED)
if keys[pygame.K_RIGHT]:
self.target_x = min(WINDOW_WIDTH - PADDLE_WIDTH, self.paddle_x + PADDLE_SPEED)
# Smooth movement
self.paddle_x += (self.target_x - self.paddle_x) * 0.3
class PlayerCPU(Player):
def __init__(self, paddle_x: float, paddle_y: float, difficulty: int = 3):
super().__init__(paddle_x, paddle_y)
self.difficulty = max(1, min(5, difficulty))
self.speed = PADDLE_SPEED * (self.difficulty / 3)
self.prediction_error = (6 - self.difficulty) * 20
self.reaction_delay = max(1, (6 - self.difficulty) * 5)
self.frame_counter = 0
def update(self, ball_x: float, ball_y: float):
self.frame_counter += 1
if self.frame_counter % self.reaction_delay == 0:
# Predict ball position with error based on difficulty
target_x = ball_x + random.randint(-self.prediction_error, self.prediction_error)
target_x = max(PADDLE_WIDTH/2, min(WINDOW_WIDTH - PADDLE_WIDTH/2, target_x))
self.target_x = target_x - PADDLE_WIDTH/2
# Smooth movement
self.paddle_x += (self.target_x - self.paddle_x) * (self.difficulty * 0.1)
class Game:
def __init__(self, player_type: str = "human", difficulty: int = 3):
self.screen = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT))
pygame.display.set_caption("Brick Breaker")
self.clock = pygame.time.Clock()
# Player initialization
paddle_x = WINDOW_WIDTH // 2 - PADDLE_WIDTH // 2
paddle_y = WINDOW_HEIGHT - 40
if player_type.lower() == "human":
self.player = PlayerHuman(paddle_x, paddle_y)
else:
self.player = PlayerCPU(paddle_x, paddle_y, difficulty)
# Ball initialization
self.ball_x = WINDOW_WIDTH // 2
self.ball_y = WINDOW_HEIGHT - 60
self.ball_dx = 5
self.ball_dy = -5
self.ball_trail: List[Tuple[float, float]] = []
# Game state
self.particles: List[Particle] = []
self.bricks: List[Brick] = []
self.score = 0
self.combo = 1
self.combo_timer = 0
self.game_paused = False
# UI
self.font = pygame.font.Font(None, 36)
self.small_font = pygame.font.Font(None, 24)
self.init_bricks()
def init_bricks(self):
for row in range(BRICK_ROWS):
for col in range(BRICK_COLS):
brick = Brick(
col * (BRICK_WIDTH + 2) + 1,
row * (BRICK_HEIGHT + 2) + 1,
COLORS[row % len(COLORS)],
strength=BRICK_ROWS - row
)
self.bricks.append(brick)
def create_particles(self, x: float, y: float, color: Tuple[int, int, int], count: int = 10):
for _ in range(count):
self.particles.append(Particle(x, y, color))
def handle_events(self):
for event in pygame.event.get():
if event.type == pygame.QUIT:
return False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_r:
self.reset_game()
elif event.key == pygame.K_p:
self.game_paused = not self.game_paused
return True
def update(self):
if self.game_paused:
return
# Update player
self.player.update(self.ball_x, self.ball_y)
# Update ball position
self.ball_x += self.ball_dx
self.ball_y += self.ball_dy
# Update ball trail
self.ball_trail.append((self.ball_x, self.ball_y))
if len(self.ball_trail) > 5:
self.ball_trail.pop(0)
# Ball collision with walls
if self.ball_x <= 0 or self.ball_x >= WINDOW_WIDTH - BALL_SIZE:
self.ball_dx *= -1
self.create_particles(self.ball_x, self.ball_y, WHITE)
if self.ball_y <= 0:
self.ball_dy *= -1
self.create_particles(self.ball_x, self.ball_y, WHITE)
# Ball collision with paddle
ball_rect = pygame.Rect(self.ball_x - BALL_SIZE/2,
self.ball_y - BALL_SIZE/2,
BALL_SIZE, BALL_SIZE)
if ball_rect.colliderect(self.player.paddle_rect):
self.ball_dy = -abs(self.ball_dy)
hit_pos = (self.ball_x - self.player.paddle_x) / PADDLE_WIDTH
self.ball_dx = 7 * (hit_pos - 0.5) * 2
self.create_particles(self.ball_x, self.ball_y, BLUE)
# Ball collision with bricks
for brick in self.bricks[:]:
if ball_rect.colliderect(brick.rect):
brick.strength -= 1
if brick.strength <= 0:
self.bricks.remove(brick)
self.score += 10 * self.combo
self.combo += 1
self.combo_timer = 2 * FPS # 2 seconds
self.create_particles(self.ball_x, self.ball_y, brick.color, 15)
else:
brick.hit_animation = 10
self.create_particles(self.ball_x, self.ball_y, brick.color, 5)
# Determine bounce direction based on collision side
brick_center_x = brick.rect.centerx
brick_center_y = brick.rect.centery
angle = math.atan2(self.ball_y - brick_center_y,
self.ball_x - brick_center_x)
if abs(math.sin(angle)) > 0.7: # Top/bottom collision
self.ball_dy *= -1
else: # Side collision
self.ball_dx *= -1
# Update particles
self.particles = [p for p in self.particles if p.update()]
# Update combo timer
if self.combo_timer > 0:
self.combo_timer -= 1
else:
self.combo = 1
# Check if ball is below paddle (game over)
if self.ball_y >= WINDOW_HEIGHT:
self.reset_game()
def reset_game(self):
self.ball_x = WINDOW_WIDTH // 2
self.ball_y = WINDOW_HEIGHT - 60
self.ball_dx = random.choice([-5, 5])
self.ball_dy = -5
self.score = 0
self.combo = 1
self.combo_timer = 0
self.bricks.clear()
self.particles.clear()
self.init_bricks()
self.player.paddle_x = WINDOW_WIDTH // 2 - PADDLE_WIDTH // 2
self.game_paused = False
def draw(self):
self.screen.fill(BLACK)
# Draw ball trail
for i, (x, y) in enumerate(self.ball_trail):
alpha = int(255 * (i / len(self.ball_trail)))
trail_surface = pygame.Surface((BALL_SIZE, BALL_SIZE), pygame.SRCALPHA)
pygame.draw.circle(trail_surface, (WHITE[0], WHITE[1], WHITE[2], alpha),
(BALL_SIZE//2, BALL_SIZE//2), BALL_SIZE//2)
self.screen.blit(trail_surface, (int(x - BALL_SIZE/2), int(y - BALL_SIZE/2)))
# Draw player
self.player.draw(self.screen)
# Draw particles
for particle in self.particles:
particle.draw(self.screen)
# Draw ball
pygame.draw.circle(self.screen, WHITE,
(int(self.ball_x), int(self.ball_y)), BALL_SIZE)
# Draw bricks
for brick in self.bricks:
brick.draw(self.screen)
# Draw score and combo
score_text = self.font.render(f"Score: {self.score}", True, WHITE)
self.screen.blit(score_text, (10, 10))
if self.combo > 1:
combo_text = self.font.render(f"Combo: x{self.combo}", True,
(255, 255, 0))
self.screen.blit(combo_text, (10, 50))
# Draw player type and difficulty if CPU
player_info = "Human Player" if isinstance(self.player, PlayerHuman) else f"CPU Player (Difficulty: {self.player.difficulty})"
info_text = self.font.render(player_info, True, GREEN)
self.screen.blit(info_text, (WINDOW_WIDTH - 250, 10))
# Draw controls info
controls_text = self.small_font.render("P: Pause | R: Reset", True, WHITE)
self.screen.blit(controls_text, (WINDOW_WIDTH - 150, WINDOW_HEIGHT - 30))
# Draw pause screen
if self.game_paused:
pause_surface = pygame.Surface((WINDOW_WIDTH, WINDOW_HEIGHT), pygame.SRCALPHA)
pause_surface.fill((0, 0, 0, 128))
self.screen.blit(pause_surface, (0, 0))
pause_text = self.font.render("PAUSED", True, WHITE)
text_rect = pause_text.get_rect(center=(WINDOW_WIDTH/2, WINDOW_HEIGHT/2))
self.screen.blit(pause_text, text_rect)
pygame.display.flip()
def run(self):
running = True
while running:
running = self.handle_events()
self.update()
self.draw()
self.clock.tick(FPS)
if __name__ == "__main__":
# Create game with either human or CPU player
# game = Game(player_type="human") # For human player
game = Game(player_type="cpu", difficulty=3) # For CPU player, difficulty 1-5
game.run()
pygame.quit()
sys.exit()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment