import pygame
import math
import random
import json
import os
from typing import List, Tuple, Set
from enum import Enum
from dataclasses import dataclass, field
# Constants
W, H = 800, 400
GROUND = 320
PLAYER_SIZE = 32
PLAYER_X = 120
GRAVITY = 0.72
JUMP_V = -15
COLORS = ["#e74c3c", "#f1c40f", "#2ecc71", "#3498db", "#9b59b6", "#e67e22", "#00ffcc", "#ff69b4"]
FPS = 60
class Status(Enum):
START = "start"
PLAYING = "playing"
DEAD = "dead"
LEVEL_COMPLETE = "level_complete"
class ObstacleType(Enum):
SPIKE = "spike"
BLOCK = "block"
TALL = "tall"
GAP = "gap"
ROTATING_SAW = "rotating_saw"
DOUBLE_SPIKE = "double_spike"
MOVING_BLOCK = "moving_block"
CEILING = "ceiling"
NARROW_GAP = "narrow_gap"
def hex_to_rgb(hex_color: str) -> Tuple[int, int, int]:
"""Convert hex color to RGB tuple."""
if hex_color == "transparent":
return (0, 0, 0)
hex_color = hex_color.lstrip("#")
if len(hex_color) != 6:
return (255, 255, 255)
try:
return tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4))
except ValueError:
return (255, 255, 255)
@dataclass
class Obstacle:
x: float
type: ObstacleType
w: int
h: int
color: str
rotation: float = 0
moving_y: float = 0
moving_direction: int = 1
@dataclass
class Particle:
x: float
y: float
vx: float
vy: float
life: float
color: str
@dataclass
class Star:
x: float
y: float
speed: float
@dataclass
class GameState:
y: float
vy: float
on_ground: bool
rotation: float
obstacles: List[Obstacle] = field(default_factory=list)
particles: List[Particle] = field(default_factory=list)
stars: List[Star] = field(default_factory=list)
score: int = 0
best: int = 0
attempts: int = 0
speed: float = 5
spawn_timer: int = 0
tick: int = 0
status: Status = Status.START
flash_timer: int = 0
color_idx: int = 0
level: int = 1
level_progress: int = 0
level_complete_timer: int = 0
def init_state(best: int = 0, attempts: int = 0, level: int = 1) -> GameState:
"""Initialize game state."""
stars = [
Star(x=random.random() * W, y=random.random() * (GROUND - 20), speed=1 + random.random() * 3)
for _ in range(80)
]
return GameState(
y=GROUND - PLAYER_SIZE,
vy=0,
on_ground=True,
rotation=0,
obstacles=[],
particles=[],
stars=stars,
score=0,
best=best,
attempts=attempts,
speed=5,
spawn_timer=0,
tick=0,
status=Status.START,
flash_timer=0,
color_idx=0,
level=level,
level_progress=0,
level_complete_timer=0,
)
def get_level_config(level: int):
"""Get difficulty configuration for a level."""
configs = {
1: {"max_speed": 5.5, "spawn_interval": 80, "speed_increase": 0.002, "obstacle_types": ["spike", "block"]},
2: {"max_speed": 6.5, "spawn_interval": 70, "speed_increase": 0.003, "obstacle_types": ["spike", "block", "tall"]},
3: {"max_speed": 7.5, "spawn_interval": 60, "speed_increase": 0.0035, "obstacle_types": ["spike", "block", "tall", "gap"]},
4: {"max_speed": 8.5, "spawn_interval": 50, "speed_increase": 0.004, "obstacle_types": ["spike", "block", "tall", "gap", "double_spike"]},
5: {"max_speed": 9.5, "spawn_interval": 45, "speed_increase": 0.0045, "obstacle_types": ["spike", "block", "tall", "gap", "double_spike", "rotating_saw"]},
6: {"max_speed": 10.5, "spawn_interval": 40, "speed_increase": 0.005, "obstacle_types": ["spike", "block", "tall", "gap", "double_spike", "rotating_saw", "moving_block"]},
}
return configs.get(level, configs[6])
def make_obstacle(score: int, color_idx: int, level: int) -> Obstacle:
"""Create a random obstacle based on level."""
config = get_level_config(level)
obstacle_types = config["obstacle_types"]
r = random.random()
color = COLORS[(color_idx + 1) % len(COLORS)]
# Determine which type to spawn
if "rotating_saw" in obstacle_types and r < 0.08:
return Obstacle(x=W + 40, type=ObstacleType.ROTATING_SAW, w=32, h=32, color=color)
elif "moving_block" in obstacle_types and r < 0.12:
return Obstacle(x=W + 40, type=ObstacleType.MOVING_BLOCK, w=28, h=28, color=color, moving_y=0, moving_direction=1)
elif "double_spike" in obstacle_types and r < 0.15:
return Obstacle(x=W + 40, type=ObstacleType.DOUBLE_SPIKE, w=28, h=56, color=color)
elif "gap" in obstacle_types and r < 0.35:
return Obstacle(x=W + 40, type=ObstacleType.GAP, w=60, h=0, color="transparent")
elif "tall" in obstacle_types and r < 0.60:
return Obstacle(x=W + 40, type=ObstacleType.TALL, w=28, h=60, color=color)
elif "block" in obstacle_types and r < 0.85:
return Obstacle(x=W + 40, type=ObstacleType.BLOCK, w=32, h=32, color=color)
else:
return Obstacle(x=W + 40, type=ObstacleType.SPIKE, w=28, h=28, color=color)
def draw_rounded_rect(surface: pygame.Surface, rect: pygame.Rect, color: Tuple[int, int, int], radius: int):
"""Draw a rounded rectangle."""
pygame.draw.rect(surface, color, rect, border_radius=radius)
def render(surface: pygame.Surface, state: GameState):
"""Render the game."""
# Background gradient
if state.status == Status.DEAD:
bg_color_top = hex_to_rgb("#300")
bg_color_bottom = hex_to_rgb("#100")
else:
bg_color_top = hex_to_rgb("#050518")
bg_color_bottom = hex_to_rgb("#0a0a20")
# Simple gradient approximation
for y in range(H):
ratio = y / H
r = int(bg_color_top[0] * (1 - ratio) + bg_color_bottom[0] * ratio)
g = int(bg_color_top[1] * (1 - ratio) + bg_color_bottom[1] * ratio)
b = int(bg_color_top[2] * (1 - ratio) + bg_color_bottom[2] * ratio)
pygame.draw.line(surface, (r, g, b), (0, y), (W, y))
# Draw stars
for star in state.stars:
alpha = 0.3 + 0.3 * math.sin(state.tick * 0.02 + star.y)
star_surface = pygame.Surface((2, 2), pygame.SRCALPHA)
star_surface.fill((255, 255, 255, int(alpha * 255)))
surface.blit(star_surface, (int(star.x), int(star.y)))
# Draw ground
pygame.draw.rect(surface, hex_to_rgb("#1a1a3e"), (0, GROUND, W, H - GROUND))
# Ground line
ground_color = hex_to_rgb(COLORS[state.color_idx])
for x in range(0, W, 40):
pygame.draw.line(surface, ground_color, (x, GROUND), (x, H), 1)
for x in range(0, W, 60):
pygame.draw.line(surface, (255, 255, 255, 8), (x, 0), (x, GROUND), 1)
# Draw obstacles
for obs in state.obstacles:
if obs.color == "transparent":
color = (0, 0, 0)
else:
color = hex_to_rgb(obs.color)
if obs.type == ObstacleType.SPIKE:
points = [
(obs.x, GROUND),
(obs.x + obs.w / 2, GROUND - obs.h),
(obs.x + obs.w, GROUND),
]
pygame.draw.polygon(surface, color, points)
elif obs.type == ObstacleType.DOUBLE_SPIKE:
# Top spike
points_top = [
(obs.x, GROUND - obs.h // 2),
(obs.x + obs.w / 2, GROUND - obs.h),
(obs.x + obs.w, GROUND - obs.h // 2),
]
pygame.draw.polygon(surface, color, points_top)
# Bottom spike (ceiling)
points_bottom = [
(obs.x, GROUND - obs.h // 2),
(obs.x + obs.w / 2, GROUND - obs.h // 4),
(obs.x + obs.w, GROUND - obs.h // 2),
]
pygame.draw.polygon(surface, color, points_bottom)
elif obs.type == ObstacleType.ROTATING_SAW:
# Draw rotating saw
saw_surface = pygame.Surface((obs.w, obs.h), pygame.SRCALPHA)
pygame.draw.circle(saw_surface, color, (obs.w // 2, obs.h // 2), obs.w // 2)
# Draw saw teeth
for i in range(8):
angle = (obs.rotation + i * math.pi / 4)
x = obs.w // 2 + math.cos(angle) * (obs.w // 2 - 2)
y = obs.h // 2 + math.sin(angle) * (obs.h // 2 - 2)
pygame.draw.circle(saw_surface, (255, 100, 100), (int(x), int(y)), 2)
surface.blit(saw_surface, (int(obs.x), int(GROUND - obs.h)))
elif obs.type == ObstacleType.MOVING_BLOCK:
rect = pygame.Rect(obs.x, GROUND - obs.h + obs.moving_y, obs.w, obs.h)
draw_rounded_rect(surface, rect, color, 3)
elif obs.type == ObstacleType.GAP:
pygame.draw.rect(surface, (0, 0, 0), (obs.x, GROUND, obs.w, H - GROUND))
pygame.draw.line(surface, color, (obs.x, GROUND), (obs.x + obs.w, GROUND), 2)
else:
rect = pygame.Rect(obs.x, GROUND - obs.h, obs.w, obs.h)
draw_rounded_rect(surface, rect, color, 3)
# Draw particles
for particle in state.particles:
if particle.life > 0:
size = int(8 * particle.life)
if size > 0:
particle_surface = pygame.Surface((size * 2, size * 2), pygame.SRCALPHA)
color = hex_to_rgb(particle.color)
pygame.draw.circle(particle_surface, (*color, int(particle.life * 255)),
(size, size), size)
surface.blit(particle_surface, (int(particle.x - size), int(particle.y - size)))
# Flash effect
if state.flash_timer > 0:
flash_surface = pygame.Surface((W, H), pygame.SRCALPHA)
flash_surface.fill((255, 50, 50, int((state.flash_timer / 20) * 0.5 * 255)))
surface.blit(flash_surface, (0, 0))
# Draw player
player_surface = pygame.Surface((PLAYER_SIZE, PLAYER_SIZE), pygame.SRCALPHA)
color1 = hex_to_rgb(COLORS[state.color_idx])
draw_rounded_rect(player_surface, pygame.Rect(0, 0, PLAYER_SIZE, PLAYER_SIZE), color1, 5)
pygame.draw.line(player_surface, (255, 255, 255, 85), (8, 8), (24, 24), 2)
pygame.draw.line(player_surface, (255, 255, 255, 85), (24, 8), (8, 24), 2)
pygame.draw.circle(player_surface, (255, 255, 255), (PLAYER_SIZE // 2, PLAYER_SIZE // 2), 4)
if not state.on_ground:
for i in range(1, 4):
pygame.draw.circle(player_surface, (*color1, int(68 / i)), (-i * 8, 0), int(4 / i))
rotated = pygame.transform.rotate(player_surface, math.degrees(state.rotation))
rotated_rect = rotated.get_rect(center=(PLAYER_X + PLAYER_SIZE // 2, int(state.y + PLAYER_SIZE // 2)))
surface.blit(rotated, rotated_rect)
# Draw HUD
hud_surface = pygame.Surface((W, 36), pygame.SRCALPHA)
hud_surface.fill((0, 0, 0, 140))
surface.blit(hud_surface, (0, 0))
font_small = pygame.font.Font(None, 16)
font_tiny = pygame.font.Font(None, 11)
# Level and Score
score_text = font_small.render(f"LEVEL {state.level} | SCORE: {state.score}", True, (255, 255, 255))
surface.blit(score_text, (12, 11))
# Progress bar for level
level_target = 500 * state.level
pct = min(100, (state.score % level_target) // (level_target // 100))
progress_text = font_small.render(f"{pct}%", True, color1)
surface.blit(progress_text, (W // 2 - 20, 11))
pygame.draw.rect(surface, (255, 255, 255, 51), (W // 2 - 80, 26, 160, 3))
pygame.draw.rect(surface, color1, (W // 2 - 80, 26, int(pct * 1.6), 3))
# Best score
best_text = font_tiny.render(f"BEST:{state.best} ATTEMPTS:{state.attempts}", True, (255, 255, 255, 85))
surface.blit(best_text, (W - 180, 11))
# Game states
font_large = pygame.font.Font(None, 38)
font_medium = pygame.font.Font(None, 14)
if state.status == Status.START:
overlay = pygame.Surface((W, H), pygame.SRCALPHA)
overlay.fill((0, 0, 0, 153))
surface.blit(overlay, (0, 0))
title = font_large.render("GEOMETRY DASH", True, hex_to_rgb(COLORS[0]))
surface.blit(title, (W // 2 - title.get_width() // 2, H // 2 - 70))
level_text = font_medium.render(f"Starting Level {state.level}", True, (200, 200, 255))
surface.blit(level_text, (W // 2 - level_text.get_width() // 2, H // 2 - 20))
inst = font_medium.render("Tap / SPACE to jump over obstacles!", True, (255, 255, 255))
surface.blit(inst, (W // 2 - inst.get_width() // 2, H // 2 + 4))
blink = 0.5 + 0.5 * math.sin(state.tick * 0.1)
press_text = font_medium.render("PRESS SPACE OR TAP", True, (74, 222, 128))
press_surface = pygame.Surface(press_text.get_size(), pygame.SRCALPHA)
press_surface.fill((0, 0, 0, 0))
press_surface.blit(press_text, (0, 0))
press_surface.set_alpha(int(blink * 255))
surface.blit(press_surface, (W // 2 - press_text.get_width() // 2, H // 2 + 50))
elif state.status == Status.LEVEL_COMPLETE:
overlay = pygame.Surface((W, H), pygame.SRCALPHA)
overlay.fill((0, 0, 0, 153))
surface.blit(overlay, (0, 0))
complete = font_large.render(f"LEVEL {state.level} COMPLETE!", True, hex_to_rgb("#2ecc71"))
surface.blit(complete, (W // 2 - complete.get_width() // 2, H // 2 - 70))
score_msg = font_medium.render(f"Score: {state.score}", True, (255, 255, 255))
surface.blit(score_msg, (W // 2 - score_msg.get_width() // 2, H // 2 - 4))
next_msg = font_medium.render(f"Next: Level {state.level + 1}", True, hex_to_rgb("#f1c40f"))
surface.blit(next_msg, (W // 2 - next_msg.get_width() // 2, H // 2 + 22))
blink = 0.5 + 0.5 * math.sin(state.tick * 0.1)
cont = font_medium.render("SPACE/Tap to continue", True, (74, 222, 128))
cont_surface = pygame.Surface(cont.get_size(), pygame.SRCALPHA)
cont_surface.blit(cont, (0, 0))
cont_surface.set_alpha(int(blink * 255))
surface.blit(cont_surface, (W // 2 - cont.get_width() // 2, H // 2 + 60))
elif state.status == Status.DEAD:
overlay = pygame.Surface((W, H), pygame.SRCALPHA)
overlay.fill((0, 0, 0, 153))
surface.blit(overlay, (0, 0))
crashed = font_large.render("CRASHED!", True, hex_to_rgb("#e74c3c"))
surface.blit(crashed, (W // 2 - crashed.get_width() // 2, H // 2 - 70))
score_msg = font_medium.render(f"Level {state.level} | Score: {state.score}", True, (255, 255, 255))
surface.blit(score_msg, (W // 2 - score_msg.get_width() // 2, H // 2 - 4))
best_msg = font_medium.render("Best: " + str(state.best), True, hex_to_rgb("#f1c40f"))
surface.blit(best_msg, (W // 2 - best_msg.get_width() // 2, H // 2 + 22))
blink = 0.5 + 0.5 * math.sin(state.tick * 0.1)
retry = font_medium.render("SPACE/Tap to retry", True, (74, 222, 128))
retry_surface = pygame.Surface(retry.get_size(), pygame.SRCALPHA)
retry_surface.fill((0, 0, 0, 0))
retry_surface.blit(retry, (0, 0))
retry_surface.set_alpha(int(blink * 255))
surface.blit(retry_surface, (W // 2 - retry.get_width() // 2, H // 2 + 60))
class GeometryDashGame:
def __init__(self):
pygame.init()
self.screen = pygame.display.set_mode((W, H))
pygame.display.set_caption("Geometry Dash")
self.clock = pygame.time.Clock()
self.running = True
self.state = None
self.best = self.load_best_score()
self.start_new_game()
def load_best_score(self) -> int:
"""Load best score from file."""
try:
if os.path.exists("geodash_best.txt"):
with open("geodash_best.txt", "r") as f:
return int(f.read().strip())
except:
pass
return 0
def save_best_score(self):
"""Save best score to file."""
with open("geodash_best.txt", "w") as f:
f.write(str(self.state.best))
def start_new_game(self, level: int = 1):
"""Start a new game."""
attempts = self.state.attempts + 1 if self.state else 0
self.state = init_state(self.best, attempts, level)
def jump(self):
"""Handle jump action."""
if self.state.status == Status.START:
self.state.status = Status.PLAYING
self.state.vy = JUMP_V
self.state.on_ground = False
elif self.state.status == Status.DEAD:
self.start_new_game(self.state.level)
self.state.status = Status.PLAYING
self.state.vy = JUMP_V
self.state.on_ground = False
elif self.state.status == Status.LEVEL_COMPLETE:
self.start_new_game(self.state.level + 1)
self.state.status = Status.PLAYING
self.state.vy = JUMP_V
self.state.on_ground = False
elif self.state.on_ground:
self.state.vy = JUMP_V
self.state.on_ground = False
def update(self):
"""Update game state."""
state = self.state
state.tick += 1
if state.flash_timer > 0:
state.flash_timer -= 1
# Update particles
for p in state.particles:
p.x += p.vx
p.y += p.vy
p.vy += 0.1
p.life -= 0.04
state.particles = [p for p in state.particles if p.life > 0]
# Update stars
for star in state.stars:
star.x -= star.speed
if star.x < 0:
star.x = W
if state.status == Status.PLAYING:
state.score += 1
state.color_idx = (state.score // 200) % len(COLORS)
config = get_level_config(state.level)
state.speed = min(config["max_speed"], 3 + state.score * config["speed_increase"])
# Physics
state.vy += GRAVITY
state.y += state.vy
if state.on_ground:
state.rotation += 0
else:
state.rotation += 0.1 * (1 if state.vy > 0 else -1)
# Ground collision
if state.y >= GROUND - PLAYER_SIZE:
state.y = GROUND - PLAYER_SIZE
state.vy = 0
state.on_ground = True
state.rotation = round(state.rotation / (math.pi / 2)) * (math.pi / 2)
else:
state.on_ground = False
# Spawn obstacles
state.spawn_timer += 1
config = get_level_config(state.level)
si = max(config["spawn_interval"] - (state.score // 500) * 3, 20)
if state.spawn_timer > si:
state.spawn_timer = 0
if random.random() > 0.08:
state.obstacles.append(make_obstacle(state.score, state.color_idx, state.level))
# Move obstacles and update rotating/moving obstacles
for obs in state.obstacles:
obs.x -= state.speed
if obs.type == ObstacleType.ROTATING_SAW:
obs.rotation += 0.2
elif obs.type == ObstacleType.MOVING_BLOCK:
obs.moving_y += obs.moving_direction * 2
if obs.moving_y > 20 or obs.moving_y < -20:
obs.moving_direction *= -1
state.obstacles = [obs for obs in state.obstacles if obs.x > -80]
# Collision detection
px = PLAYER_X + 4
py = state.y + 4
pw = PLAYER_SIZE - 8
ph = PLAYER_SIZE - 8
for obs in state.obstacles:
hit = False
if obs.type == ObstacleType.SPIKE:
mid_x = obs.x + obs.w / 2
if px < obs.x + obs.w and px + pw > obs.x:
rel_x = (PLAYER_X + PLAYER_SIZE / 2 - mid_x) / (obs.w / 2)
spike_top = GROUND - obs.h * (1 - abs(rel_x))
if py + ph > spike_top:
hit = True
elif obs.type == ObstacleType.DOUBLE_SPIKE:
if px < obs.x + obs.w and px + pw > obs.x:
mid_x = obs.x + obs.w / 2
rel_x = (PLAYER_X + PLAYER_SIZE / 2 - mid_x) / (obs.w / 2)
spike_top = GROUND - obs.h * (1 - abs(rel_x))
spike_bottom = GROUND - obs.h // 4
if (py + ph > spike_top) or (py < spike_bottom):
hit = True
elif obs.type == ObstacleType.ROTATING_SAW:
saw_center_x = obs.x + obs.w / 2
saw_center_y = GROUND - obs.h / 2
player_center_x = PLAYER_X + PLAYER_SIZE / 2
player_center_y = state.y + PLAYER_SIZE / 2
dist = math.sqrt((player_center_x - saw_center_x) ** 2 + (player_center_y - saw_center_y) ** 2)
if dist < (obs.w / 2 + PLAYER_SIZE / 2):
hit = True
elif obs.type == ObstacleType.MOVING_BLOCK:
block_y = GROUND - obs.h + obs.moving_y
if px < obs.x + obs.w and px + pw > obs.x and py < GROUND and py + ph > block_y:
hit = True
elif obs.type == ObstacleType.BLOCK or obs.type == ObstacleType.TALL:
if px < obs.x + obs.w and px + pw > obs.x and py < GROUND and py + ph > GROUND - obs.h:
hit = True
elif obs.type == ObstacleType.GAP:
if px + pw > obs.x and px < obs.x + obs.w and state.on_ground:
hit = True
if hit:
# Particle burst
for _ in range(16):
angle = random.random() * math.pi * 2
state.particles.append(Particle(
x=PLAYER_X + PLAYER_SIZE / 2,
y=state.y + PLAYER_SIZE / 2,
vx=math.cos(angle) * 4,
vy=math.sin(angle) * 4,
life=1,
color=COLORS[state.color_idx]
))
# Update best score
if state.score > state.best:
state.best = state.score
self.save_best_score()
self.best = state.best
state.flash_timer = 20
state.status = Status.DEAD
break
# Check level completion
level_target = 500 * state.level
if state.score >= level_target:
state.status = Status.LEVEL_COMPLETE
state.level_complete_timer = 0
def handle_events(self):
"""Handle input events."""
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.running = False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
self.running = False
elif event.key in (pygame.K_SPACE, pygame.K_UP):
self.jump()
elif event.type == pygame.MOUSEBUTTONDOWN:
self.jump()
def run(self):
"""Main game loop."""
while self.running:
self.handle_events()
self.update()
render(self.screen, self.state)
pygame.display.flip()
self.clock.tick(FPS)
pygame.quit()
if __name__ == "__main__":
game = GeometryDashGame()
game.run()
i made it like the famous game but it sucks and i started it awhile ago and forgot about it
so i used to be a big fan of bomber 2D utra so i made this with the help of ai and google
import pygame
import math
import random
from typing import List, Tuple, Set
from enum import Enum
from dataclasses import dataclass, field
pygame.init()
# Constants
TILE = 44
COLS, ROWS = 16, 14
W = COLS * TILE
H = ROWS * TILE + 50
FPS = 60
class CellType(Enum):
WALL = "wall"
BLOCK = "block"
EMPTY = "empty"
class Status(Enum):
START = "start"
PLAYING = "playing"
DEAD = "dead"
WIN = "win"
@dataclass
class Bomb:
x: int
y: int
timer: int
range: int
owner: str = "player" # "player" or "enemy"
@dataclass
class Explosion:
x: int
y: int
life: int
dir: str
tick: int = 0
@dataclass
class Enemy:
x: int
y: int
dir: int
alive: bool
moveTimer: int
type: int
bombTimer: int = 0
canDropBomb: bool = True
@dataclass
class Particle:
x: float
y: float
vx: float
vy: float
life: float
color: Tuple[int, int, int]
size: int
@dataclass
class PowerUp:
x: int
y: int
type: str # "bomb", "range", "speed", "shield", "slow", "freeze"
collected: bool
@dataclass
class GameState:
grid: List[List[CellType]]
player: dict
bombs: List[Bomb]
explosions: List[Explosion]
enemies: List[Enemy]
particles: List[Particle]
powerups: List[PowerUp]
score: int
lives: int
level: int
status: Status
tick: int
keys: Set[str]
invTimer: int
bombCooldown: int
maxBombs: int
range: int
moveTimer: int
currentBombs: int
shield: bool
shieldTimer: int
slowEffect: float
DIRS = [
{"dx": 0, "dy": -1},
{"dx": 1, "dy": 0},
{"dx": 0, "dy": 1},
{"dx": -1, "dy": 0}
]
ENEMY_COLORS = [
(231, 76, 60), # Red
(155, 89, 182), # Purple
(241, 196, 15), # Yellow
(230, 126, 34) # Orange
]
POWERUP_COLORS = {
"bomb": (230, 126, 34),
"range": (52, 152, 219),
"speed": (46, 204, 113),
"shield": (241, 196, 15),
"slow": (155, 89, 182),
"freeze": (52, 211, 213)
}
def make_grid(level: int) -> List[List[CellType]]:
"""Create game grid with walls and blocks."""
grid = [[CellType.EMPTY for _ in range(COLS)] for _ in range(ROWS)]
for r in range(ROWS):
for c in range(COLS):
if r == 0 or r == ROWS - 1 or c == 0 or c == COLS - 1:
grid[r][c] = CellType.WALL
elif r % 2 == 0 and c % 2 == 0:
grid[r][c] = CellType.WALL
elif (r > 2 or c > 2) and random.random() < 0.45 + level * 0.02:
grid[r][c] = CellType.BLOCK
# Clear starting area
for r in range(1, 3):
for c in range(1, 3):
grid[r][c] = CellType.EMPTY
return grid
def make_enemies(level: int) -> List[Enemy]:
"""Create enemies for the level."""
count = 4 + level * 2
enemies = []
for _ in range(count):
while True:
x = 2 + random.randint(0, COLS - 5)
y = 2 + random.randint(0, ROWS - 5)
if not (x < 4 and y < 4):
break
enemies.append(Enemy(
x=x, y=y, dir=random.randint(0, 3),
alive=True, moveTimer=0, type=random.randint(0, min(3, level) - 1),
bombTimer=0, canDropBomb=True
))
return enemies
def init_state(level: int = 1) -> GameState:
"""Initialize game state."""
return GameState(
grid=make_grid(level),
player={"x": 1, "y": 1},
bombs=[],
explosions=[],
enemies=make_enemies(level),
particles=[],
powerups=[],
score=0,
lives=3,
level=level,
status=Status.START,
tick=0,
keys=set(),
invTimer=0,
bombCooldown=0,
maxBombs=2,
range=2,
moveTimer=0,
currentBombs=0,
shield=False,
shieldTimer=0,
slowEffect=1.0
)
def draw_particle_effect(surface: pygame.Surface, x: float, y: float, color: Tuple[int, int, int], size: int, alpha: int):
"""Draw a particle with glow effect."""
if size <= 0:
return
# Glow
glow_surface = pygame.Surface((size * 4, size * 4), pygame.SRCALPHA)
pygame.draw.circle(glow_surface, (*color, alpha // 3), (size * 2, size * 2), size * 2)
surface.blit(glow_surface, (int(x - size * 2), int(y - size * 2)))
# Core
pygame.draw.circle(surface, (*color, alpha), (int(x), int(y)), max(1, size))
def render(surface: pygame.Surface, state: GameState):
"""Render the game."""
# Background
surface.fill((17, 17, 17))
# Draw grid
for r in range(ROWS):
for c in range(COLS):
cx = c * TILE
cy = r * TILE + 50
cell = state.grid[r][c]
if cell == CellType.WALL:
# Stone wall with texture
pygame.draw.rect(surface, (44, 62, 80), (cx, cy, TILE, TILE))
pygame.draw.rect(surface, (52, 73, 94), (cx + 2, cy + 2, TILE - 4, TILE // 3))
pygame.draw.rect(surface, (26, 37, 47), (cx, cy, TILE, TILE), 2)
# Wall pattern
for i in range(2):
pygame.draw.circle(surface, (34, 49, 63), (cx + 8 + i * 15, cy + 8), 3)
elif cell == CellType.BLOCK:
# Wood block with texture
pygame.draw.rect(surface, (139, 69, 19), (cx, cy, TILE, TILE))
pygame.draw.rect(surface, (160, 82, 45, 136), (cx + 3, cy + 3, TILE - 6, TILE - 6))
pygame.draw.rect(surface, (107, 52, 16), (cx, cy, TILE, TILE), 2)
# Wood grain
pygame.draw.rect(surface, (255, 255, 255, 17), (cx + 4, cy + 4, TILE // 3, 4))
pygame.draw.line(surface, (107, 52, 16), (cx, cy + TILE // 2), (cx + TILE, cy + TILE // 2), 1)
else:
# Empty tile with checkerboard
color = (26, 26, 46) if (r + c) % 2 == 0 else (22, 22, 38)
pygame.draw.rect(surface, color, (cx, cy, TILE, TILE))
pygame.draw.line(surface, (50, 50, 80), (cx, cy), (cx + TILE, cy), 1)
# Draw explosions with particles
for ex in state.explosions:
alpha = max(0, int((ex.life / 20) * 200))
# Main explosion glow
glow_radius = int(TILE * 0.7 * (ex.life / 20))
if glow_radius > 0:
glow_surface = pygame.Surface((glow_radius * 2, glow_radius * 2), pygame.SRCALPHA)
pygame.draw.circle(glow_surface, (241, 196, 15, alpha // 2), (glow_radius, glow_radius), glow_radius)
center_x = ex.x * TILE + TILE // 2
center_y = ex.y * TILE + TILE // 2 + 50
surface.blit(glow_surface, (int(center_x - glow_radius), int(center_y - glow_radius)))
# Explosion core
explosion_surface = pygame.Surface((TILE, TILE), pygame.SRCALPHA)
color_gradient = int(255 * (ex.life / 20))
pygame.draw.circle(explosion_surface, (255, 200, 0, alpha), (TILE // 2, TILE // 2), int(TILE * 0.6))
pygame.draw.circle(explosion_surface, (255, 100, 0, alpha), (TILE // 2, TILE // 2), int(TILE * 0.4))
surface.blit(explosion_surface, (ex.x * TILE, ex.y * TILE + 50))
# Draw particles
for p in state.particles:
if p.life > 0:
alpha = int(p.life * 255)
draw_particle_effect(surface, p.x, p.y, p.color, p.size, alpha)
# Draw bombs
for b in state.bombs:
bx = b.x * TILE + TILE // 2
by = b.y * TILE + TILE // 2 + 50
pulse = 0.9 + 0.15 * math.sin(state.tick * 0.3 * (30 / b.timer))
# Bomb color based on owner
bomb_color = (51, 51, 51) if b.owner == "player" else (180, 50, 50)
bomb_surface = pygame.Surface((TILE, TILE), pygame.SRCALPHA)
# Body
pygame.draw.circle(bomb_surface, bomb_color, (TILE // 2, TILE // 2), int(TILE * 0.38))
pygame.draw.circle(bomb_surface, (85, 85, 85), (TILE // 2 - 4, TILE // 2 - 4), int(TILE * 0.15))
# Fuse
fuse_alpha = 128 if b.timer < 60 else 255
fuse_color = (241, 196, 15) if b.owner == "player" else (255, 100, 100)
fuse_surf = pygame.Surface((TILE, TILE), pygame.SRCALPHA)
pygame.draw.line(fuse_surf, (*fuse_color, fuse_alpha), (TILE // 2 + 4, TILE // 2 - int(TILE * 0.38)),
(TILE // 2 + 6, TILE // 2 - int(TILE * 0.65)), 2)
pygame.draw.circle(fuse_surf, (*fuse_color, fuse_alpha), (TILE // 2 + 6, TILE // 2 - int(TILE * 0.65)), 3)
bomb_surface.blit(fuse_surf, (0, 0))
# Scale and draw
scaled = pygame.transform.scale(bomb_surface, (int(TILE * pulse), int(TILE * pulse)))
surface.blit(scaled, (int(bx - TILE * pulse // 2), int(by - TILE * pulse // 2)))
# Glow
glow_color = (241, 196, 15) if b.owner == "player" else (255, 100, 100)
glow = pygame.Surface((TILE * 2, TILE * 2), pygame.SRCALPHA)
pygame.draw.circle(glow, (*glow_color, 50), (TILE, TILE), int(TILE * 0.7))
surface.blit(glow, (int(bx - TILE), int(by - TILE)))
# Draw powerups
for pu in state.powerups:
if not pu.collected:
px = pu.x * TILE + TILE // 2
py = pu.y * TILE + TILE // 2 + 50
# Rotating powerup with glow
color = POWERUP_COLORS[pu.type]
glow = pygame.Surface((TILE * 1.2, TILE * 1.2), pygame.SRCALPHA)
pygame.draw.circle(glow, (*color, 60), (int(TILE * 0.6), int(TILE * 0.6)), int(TILE * 0.5))
surface.blit(glow, (int(px - TILE * 0.6), int(py - TILE * 0.6)))
# Powerup sprite
pu_surf = pygame.Surface((TILE - 10, TILE - 10), pygame.SRCALPHA)
if pu.type == "bomb":
pygame.draw.circle(pu_surf, color, (TILE // 2 - 5, TILE // 2 - 5), 8)
pygame.draw.line(pu_surf, (255, 255, 0), (TILE // 2 - 1, TILE // 2 - 13), (TILE // 2 - 1, TILE // 2 - 15), 2)
elif pu.type == "range":
pygame.draw.circle(pu_surf, color, (TILE // 2 - 5, TILE // 2 - 5), 8)
pygame.draw.line(pu_surf, (255, 255, 255), (TILE // 2 - 12, TILE // 2 - 5), (TILE // 2 + 2, TILE // 2 - 5), 2)
pygame.draw.line(pu_surf, (255, 255, 255), (TILE // 2 - 5, TILE // 2 - 12), (TILE // 2 - 5, TILE // 2 + 2), 2)
elif pu.type == "speed":
pygame.draw.circle(pu_surf, color, (TILE // 2 - 5, TILE // 2 - 5), 8)
pygame.draw.polygon(pu_surf, (255, 255, 255), [(TILE // 2 - 12, TILE // 2 - 5),
(TILE // 2 - 5, TILE // 2 - 11),
(TILE // 2 - 5, TILE // 2 + 1)])
elif pu.type == "shield":
pygame.draw.circle(pu_surf, color, (TILE // 2 - 5, TILE // 2 - 5), 8)
pygame.draw.circle(pu_surf, (255, 255, 255), (TILE // 2 - 5, TILE // 2 - 5), 12, 2)
elif pu.type == "slow":
pygame.draw.circle(pu_surf, color, (TILE // 2 - 5, TILE // 2 - 5), 8)
pygame.draw.rect(pu_surf, (255, 255, 255), (TILE // 2 - 10, TILE // 2 - 3, 10, 6))
elif pu.type == "freeze":
pygame.draw.circle(pu_surf, color, (TILE // 2 - 5, TILE // 2 - 5), 8)
pygame.draw.rect(pu_surf, (200, 220, 255), (TILE // 2 - 10, TILE // 2 - 3, 5, 6))
pygame.draw.rect(pu_surf, (200, 220, 255), (TILE // 2 - 2, TILE // 2 - 3, 5, 6))
angle = state.tick * 5
rotated = pygame.transform.rotate(pu_surf, angle)
surface.blit(rotated, (int(px - rotated.get_width() // 2), int(py - rotated.get_height() // 2)))
# Draw enemies
for e in state.enemies:
if not e.alive:
continue
ex = e.x * TILE + TILE // 2
ey = e.y * TILE + TILE // 2 + 50
color = ENEMY_COLORS[e.type % len(ENEMY_COLORS)]
# Glow
glow = pygame.Surface((TILE * 1.5, TILE * 1.5), pygame.SRCALPHA)
pygame.draw.circle(glow, (*color, 80), (int(TILE * 0.75), int(TILE * 0.75)), int(TILE * 0.6))
surface.blit(glow, (int(ex - TILE * 0.75), int(ey - TILE * 0.75)))
# Body
pygame.draw.circle(surface, color, (ex, ey), int(TILE * 0.42))
# Eyes
pygame.draw.circle(surface, (0, 0, 0), (ex - 6, ey - 5), 5)
pygame.draw.circle(surface, (0, 0, 0), (ex + 6, ey - 5), 5)
pygame.draw.circle(surface, (255, 255, 255), (ex - 5, ey - 6), 2)
pygame.draw.circle(surface, (255, 255, 255), (ex + 7, ey - 6), 2)
# Pupils
pupil_offset = int(2 * math.sin(state.tick * 0.05))
pygame.draw.circle(surface, (0, 0, 0), (ex - 5 + pupil_offset, ey - 6 + pupil_offset), 1)
pygame.draw.circle(surface, (0, 0, 0), (ex + 7 + pupil_offset, ey - 6 + pupil_offset), 1)
# Mouth
pygame.draw.arc(surface, (0, 0, 0), (ex - 8, ey + 1, 16, 10), 0.2, math.pi - 0.2, 2)
# Draw player
px = state.player["x"] * TILE + TILE // 2
py = state.player["y"] * TILE + TILE // 2 + 50
inv_flashing = state.invTimer > 0 and (state.tick // 4) % 2 == 0
if not inv_flashing:
# Shield effect
if state.shield:
shield_glow = pygame.Surface((TILE * 2, TILE * 2), pygame.SRCALPHA)
pygame.draw.circle(shield_glow, (241, 196, 15, 100), (TILE, TILE), int(TILE * 0.9))
surface.blit(shield_glow, (int(px - TILE), int(py - TILE)))
pygame.draw.circle(surface, (241, 196, 15), (px, py), int(TILE * 0.55), 2)
# Glow
glow = pygame.Surface((TILE * 1.5, TILE * 1.5), pygame.SRCALPHA)
pygame.draw.circle(glow, (52, 211, 153, 100), (int(TILE * 0.75), int(TILE * 0.75)), int(TILE * 0.7))
surface.blit(glow, (int(px - TILE * 0.75), int(py - TILE * 0.75)))
# Body
pygame.draw.circle(surface, (52, 211, 153), (px, py), int(TILE * 0.44))
pygame.draw.circle(surface, (0, 153, 170), (px, py), int(TILE * 0.3))
# Eyes (animated)
eye_offset = int(2 * math.sin(state.tick * 0.1))
pygame.draw.circle(surface, (255, 255, 255), (px - 6, py - 5), 5)
pygame.draw.circle(surface, (255, 255, 255), (px + 6, py - 5), 5)
pygame.draw.circle(surface, (0, 0, 0), (px - 5 + eye_offset, py - 6), 2)
pygame.draw.circle(surface, (0, 0, 0), (px + 7 + eye_offset, py - 6), 2)
# Smile
pygame.draw.arc(surface, (0, 0, 0), (px - 8, py + 1, 16, 10), 0.2, math.pi - 0.2, 2)
# HUD background
pygame.draw.rect(surface, (0, 0, 0), (0, 0, W, 50))
pygame.draw.line(surface, (100, 100, 100), (0, 50), (W, 50), 1)
# HUD text
font_large = pygame.font.Font(None, 18)
font_small = pygame.font.Font(None, 14)
font_tiny = pygame.font.Font(None, 12)
score_text = font_large.render(f"SCORE: {state.score}", True, (52, 211, 153))
surface.blit(score_text, (10, 10))
lives_text = font_large.render("❤️ " * state.lives, True, (231, 76, 60))
surface.blit(lives_text, (10, 30))
level_text = font_large.render(f"LVL {state.level} ENEMIES: {sum(1 for e in state.enemies if e.alive)}", True, (241, 196, 15))
level_rect = level_text.get_rect(center=(W // 2, 25))
surface.blit(level_text, level_rect)
# Powerup status
status_y = 10
pygame.draw.circle(surface, (230, 126, 34), (W - 180, status_y + 5), 4)
bomb_text = font_tiny.render(f"x{state.currentBombs}/{state.maxBombs}", True, (200, 200, 200))
surface.blit(bomb_text, (W - 170, status_y))
pygame.draw.line(surface, (52, 152, 219), (W - 130, status_y), (W - 110, status_y), 3)
range_text = font_tiny.render(f"x{state.range}", True, (200, 200, 200))
surface.blit(range_text, (W - 100, status_y))
if state.shield:
pygame.draw.circle(surface, (241, 196, 15), (W - 50, status_y + 5), 4)
pygame.draw.circle(surface, (241, 196, 15), (W - 50, status_y + 5), 6, 1)
shield_text = font_tiny.render("ON", True, (241, 196, 15))
surface.blit(shield_text, (W - 40, status_y))
controls_text = font_small.render("WASD/Arrows=Move SPACE=Bomb", True, (170, 170, 170))
surface.blit(controls_text, (W - 320, 30))
# Game state overlays
if state.status == Status.START:
overlay = pygame.Surface((W, H), pygame.SRCALPHA)
overlay.fill((0, 0, 0, 166))
surface.blit(overlay, (0, 0))
font_title = pygame.font.Font(None, 60)
font_desc = pygame.font.Font(None, 16)
title = font_title.render("💣 BOMBERMAN", True, (241, 196, 15))
surface.blit(title, (W // 2 - title.get_width() // 2, H // 2 - 100))
desc1 = font_desc.render("WASD/Arrows to move · SPACE to plant bomb", True, (255, 255, 255))
surface.blit(desc1, (W // 2 - desc1.get_width() // 2, H // 2))
desc2 = font_desc.render("Destroy blocks, defeat enemies, collect power-ups!", True, (255, 255, 255))
surface.blit(desc2, (W // 2 - desc2.get_width() // 2, H // 2 + 20))
desc3 = font_desc.render("Enemies drop bombs! Watch out!", True, (255, 100, 100))
surface.blit(desc3, (W // 2 - desc3.get_width() // 2, H // 2 + 40))
blink = 0.5 + 0.5 * math.sin(state.tick * 0.1)
start_text = font_desc.render("PRESS SPACE", True, (74, 222, 128))
start_surf = pygame.Surface(start_text.get_size(), pygame.SRCALPHA)
start_surf.blit(start_text, (0, 0))
start_surf.set_alpha(int(blink * 255))
surface.blit(start_surf, (W // 2 - start_text.get_width() // 2, H // 2 + 80))
elif state.status == Status.WIN:
overlay = pygame.Surface((W, H), pygame.SRCALPHA)
overlay.fill((0, 0, 0, 166))
surface.blit(overlay, (0, 0))
font_title = pygame.font.Font(None, 60)
font_desc = pygame.font.Font(None, 16)
title = font_title.render("LEVEL CLEAR! 🎉", True, (241, 196, 15))
surface.blit(title, (W // 2 - title.get_width() // 2, H // 2 - 100))
score_text = font_desc.render(f"Score: {state.score}", True, (255, 255, 255))
surface.blit(score_text, (W // 2 - score_text.get_width() // 2, H // 2))
blink = 0.5 + 0.5 * math.sin(state.tick * 0.1)
next_text = font_desc.render("SPACE for next level", True, (74, 222, 128))
next_surf = pygame.Surface(next_text.get_size(), pygame.SRCALPHA)
next_surf.blit(next_text, (0, 0))
next_surf.set_alpha(int(blink * 255))
surface.blit(next_surf, (W // 2 - next_text.get_width() // 2, H // 2 + 60))
elif state.status == Status.DEAD:
overlay = pygame.Surface((W, H), pygame.SRCALPHA)
overlay.fill((0, 0, 0, 166))
surface.blit(overlay, (0, 0))
font_title = pygame.font.Font(None, 60)
font_desc = pygame.font.Font(None, 16)
title = font_title.render("GAME OVER", True, (231, 76, 60))
surface.blit(title, (W // 2 - title.get_width() // 2, H // 2 - 100))
score_text = font_desc.render(f"Score: {state.score}", True, (255, 255, 255))
surface.blit(score_text, (W // 2 - score_text.get_width() // 2, H // 2))
blink = 0.5 + 0.5 * math.sin(state.tick * 0.1)
retry_text = font_desc.render("SPACE to retry", True, (74, 222, 128))
retry_surf = pygame.Surface(retry_text.get_size(), pygame.SRCALPHA)
retry_surf.blit(retry_text, (0, 0))
retry_surf.set_alpha(int(blink * 255))
surface.blit(retry_surf, (W // 2 - retry_text.get_width() // 2, H // 2 + 60))
class BombermanGame:
def __init__(self):
pygame.init()
self.screen = pygame.display.set_mode((W, H))
pygame.display.set_caption("Bomberman - Extended Edition")
self.clock = pygame.time.Clock()
self.running = True
self.level = 1
self.state = init_state(self.level)
def plant_bomb(self, x: int, y: int, owner: str = "player"):
"""Plant a bomb at position."""
if any(b.x == x and b.y == y for b in self.state.bombs):
return
if owner == "player":
if self.state.currentBombs >= self.state.maxBombs:
return
if self.state.bombCooldown > 0:
return
self.state.bombCooldown = 20
self.state.currentBombs += 1
self.state.bombs.append(Bomb(
x=x,
y=y,
timer=150,
range=self.state.range,
owner=owner
))
def try_move(self, dx: int, dy: int):
"""Try to move player."""
nx = self.state.player["x"] + dx
ny = self.state.player["y"] + dy
if nx < 0 or nx >= COLS or ny < 0 or ny >= ROWS:
return
if self.state.grid[ny][nx] != CellType.EMPTY:
return
if any(b.x == nx and b.y == ny for b in self.state.bombs):
return
self.state.player["x"] = nx
self.state.player["y"] = ny
def update(self):
"""Update game state."""
state = self.state
state.tick += 1
if state.invTimer > 0:
state.invTimer -= 1
if state.shieldTimer > 0:
state.shieldTimer -= 1
if state.shieldTimer <= 0:
state.shield = False
if state.bombCooldown > 0:
state.bombCooldown -= 1
if state.status == Status.PLAYING:
state.moveTimer += 1
# Handle movement
if state.moveTimer >= 8:
state.moveTimer = 0
if "KeyA" in state.keys or "ArrowLeft" in state.keys:
self.try_move(-1, 0)
elif "KeyD" in state.keys or "ArrowRight" in state.keys:
self.try_move(1, 0)
elif "KeyW" in state.keys or "ArrowUp" in state.keys:
self.try_move(0, -1)
elif "KeyS" in state.keys or "ArrowDown" in state.keys:
self.try_move(0, 1)
# Update bombs
for i in range(len(state.bombs) - 1, -1, -1):
state.bombs[i].timer -= 1
if state.bombs[i].timer <= 0:
bomb = state.bombs.pop(i)
if bomb.owner == "player":
state.currentBombs -= 1
# Explosion at bomb center
state.explosions.append(Explosion(x=bomb.x, y=bomb.y, life=20, dir="center"))
# Particle burst at bomb
for _ in range(8):
angle = random.random() * 2 * math.pi
speed = 2 + random.random() * 2
state.particles.append(Particle(
x=bomb.x * TILE + TILE // 2,
y=bomb.y * TILE + TILE // 2 + 50,
vx=math.cos(angle) * speed,
vy=math.sin(angle) * speed,
life=1.0,
color=(241, 196, 15),
size=4
))
# Directional explosions
for d in DIRS:
for r in range(1, bomb.range + 1):
ex = bomb.x + d["dx"] * r
ey = bomb.y + d["dy"] * r
if ex < 0 or ex >= COLS or ey < 0 or ey >= ROWS:
break
if state.grid[ey][ex] == CellType.WALL:
break
state.explosions.append(Explosion(x=ex, y=ey, life=20, dir=f"{d['dx']},{d['dy']}"))
if state.grid[ey][ex] == CellType.BLOCK:
state.grid[ey][ex] = CellType.EMPTY
state.score += 10
# Power-up chance
if random.random() < 0.2 and r == 1:
pu_type = random.choice(["bomb", "range", "speed", "shield", "slow", "freeze"])
state.powerups.append(PowerUp(x=ex, y=ey, type=pu_type, collected=False))
break
# Update explosions and particles
for i in range(len(state.explosions) - 1, -1, -1):
state.explosions[i].life -= 1
state.explosions[i].tick += 1
if state.explosions[i].life <= 0:
state.explosions.pop(i)
continue
ex = state.explosions[i]
# Check enemy collision
for enemy in state.enemies:
if enemy.alive and enemy.x == ex.x and enemy.y == ex.y:
enemy.alive = False
state.score += 50 + state.level * 10
# Particle burst
for _ in range(12):
angle = random.random() * 2 * math.pi
speed = 3 + random.random() * 2
color = ENEMY_COLORS[enemy.type % len(ENEMY_COLORS)]
state.particles.append(Particle(
x=enemy.x * TILE + TILE // 2,
y=enemy.y * TILE + TILE // 2 + 50,
vx=math.cos(angle) * speed,
vy=math.sin(angle) * speed,
life=1.0,
color=color,
size=3
))
# Check player collision
if state.player["x"] == ex.x and state.player["y"] == ex.y and state.invTimer <= 0:
if state.shield:
state.shield = False
state.shieldTimer = 0
else:
state.lives -= 1
state.invTimer = 120
if state.lives <= 0:
state.status = Status.DEAD
else:
# Particle burst
for _ in range(16):
angle = random.random() * 2 * math.pi
speed = 4 + random.random() * 2
state.particles.append(Particle(
x=state.player["x"] * TILE + TILE // 2,
y=state.player["y"] * TILE + TILE // 2 + 50,
vx=math.cos(angle) * speed,
vy=math.sin(angle) * speed,
life=1.0,
color=(52, 211, 153),
size=4
))
# Update particles
for p in state.particles:
p.x += p.vx
p.y += p.vy
p.vy += 0.2
p.life -= 0.04
state.particles = [p for p in state.particles if p.life > 0]
# Check powerup collection
for pu in state.powerups:
if not pu.collected and pu.x == state.player["x"] and pu.y == state.player["y"]:
pu.collected = True
if pu.type == "bomb":
state.maxBombs = min(8, state.maxBombs + 1)
elif pu.type == "range":
state.range = min(8, state.range + 1)
elif pu.type == "speed":
pass # Speed bonus
elif pu.type == "shield":
state.shield = True
state.shieldTimer = 300
elif pu.type == "slow":
state.slowEffect = 0.5
elif pu.type == "freeze":
state.slowEffect = 0.2
state.score += 25
# Update enemies
for enemy in state.enemies:
if not enemy.alive:
continue
enemy.moveTimer += 1
move_every = max(5, 20 - min(12, state.level * 2))
move_every = int(move_every / state.slowEffect)
if enemy.moveTimer >= move_every:
enemy.moveTimer = 0
if random.random() < 0.3:
enemy.dir = random.randint(0, 3)
d = DIRS[enemy.dir]
nx = enemy.x + d["dx"]
ny = enemy.y + d["dy"]
if (0 <= nx < COLS and 0 <= ny < ROWS and
state.grid[ny][nx] == CellType.EMPTY and
not any(b.x == nx and b.y == ny for b in state.bombs)):
enemy.x = nx
enemy.y = ny
else:
enemy.dir = random.randint(0, 3)
# Enemy bomb dropping
enemy.bombTimer += 1
bomb_drop_chance = 0.005 + state.level * 0.001
if enemy.bombTimer > 30 and random.random() < bomb_drop_chance and enemy.canDropBomb:
self.plant_bomb(enemy.x, enemy.y, owner="enemy")
enemy.bombTimer = 0
enemy.canDropBomb = False
elif enemy.bombTimer > 200:
enemy.canDropBomb = True
# Check player collision
if enemy.x == state.player["x"] and enemy.y == state.player["y"] and state.invTimer <= 0:
if state.shield:
state.shield = False
state.shieldTimer = 0
else:
state.lives -= 1
state.invTimer = 120
if state.lives <= 0:
state.status = Status.DEAD
else:
for _ in range(16):
angle = random.random() * 2 * math.pi
speed = 4 + random.random() * 2
state.particles.append(Particle(
x=state.player["x"] * TILE + TILE // 2,
y=state.player["y"] * TILE + TILE // 2 + 50,
vx=math.cos(angle) * speed,
vy=math.sin(angle) * speed,
life=1.0,
color=(52, 211, 153),
size=4
))
# Check level completion
if all(not e.alive for e in state.enemies) and state.status == Status.PLAYING:
state.score += state.level * 200
state.status = Status.WIN
def handle_events(self):
"""Handle input events."""
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.running = False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
self.running = False
elif event.key == pygame.K_SPACE:
if self.state.status == Status.START:
self.state.status = Status.PLAYING
elif self.state.status == Status.DEAD:
self.level = 1
self.state = init_state(self.level)
self.state.status = Status.PLAYING
elif self.state.status == Status.WIN:
self.level += 1
self.state = init_state(self.level)
self.state.status = Status.PLAYING
elif self.state.status == Status.PLAYING:
self.plant_bomb(self.state.player["x"], self.state.player["y"])
elif event.key in (pygame.K_a, pygame.K_d, pygame.K_w, pygame.K_s,
pygame.K_LEFT, pygame.K_RIGHT, pygame.K_UP, pygame.K_DOWN):
key_map = {
pygame.K_a: "KeyA",
pygame.K_d: "KeyD",
pygame.K_w: "KeyW",
pygame.K_s: "KeyS",
pygame.K_LEFT: "ArrowLeft",
pygame.K_RIGHT: "ArrowRight",
pygame.K_UP: "ArrowUp",
pygame.K_DOWN: "ArrowDown"
}
self.state.keys.add(key_map[event.key])
elif event.type == pygame.KEYUP:
key_map = {
pygame.K_a: "KeyA",
pygame.K_d: "KeyD",
pygame.K_w: "KeyW",
pygame.K_s: "KeyS",
pygame.K_LEFT: "ArrowLeft",
pygame.K_RIGHT: "ArrowRight",
pygame.K_UP: "ArrowUp",
pygame.K_DOWN: "ArrowDown"
}
if event.key in key_map:
self.state.keys.discard(key_map[event.key])
def run(self):
"""Main game loop."""
while self.running:
self.handle_events()
self.update()
render(self.screen, self.state)
pygame.display.flip()
self.clock.tick(FPS)
pygame.quit()
if __name__ == "__main__":
game = BombermanGame()
game.run()