5/22

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()

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top