Untitled

Anonymous
python
02/14/2026 8:46 PM
34.1 KB
16
Indexable
"""
NEON RUNNER - A Procedurally Generated Platformer
100 unique levels with smooth physics and particle effects
"""

import pygame
import random
import math
import sys
from dataclasses import dataclass
from typing import List, Tuple
from enum import Enum

# Initialize Pygame
pygame.init()

# Constants
WINDOW_WIDTH = 1280
WINDOW_HEIGHT = 720
FPS = 60

# Colors - Neon/Cyberpunk theme
BG_COLOR = (10, 10, 25)
PLAYER_COLOR = (0, 255, 200)
PLATFORM_COLOR = (100, 50, 200)
SPIKE_COLOR = (255, 50, 100)
GOAL_COLOR = (255, 200, 0)
PARTICLE_COLORS = [
    (0, 255, 200),
    (100, 200, 255),
    (255, 100, 200),
    (255, 200, 100)
]

class ParticleType(Enum):
    JUMP = 1
    LAND = 2
    WALL_SLIDE = 3
    DEATH = 4
    GOAL = 5

@dataclass
class Particle:
    x: float
    y: float
    vx: float
    vy: float
    lifetime: int
    max_lifetime: int
    color: Tuple[int, int, int]
    size: float
    particle_type: ParticleType

class Platform:
    def __init__(self, x: float, y: float, width: float, height: float, moving: bool = False):
        self.rect = pygame.Rect(x, y, width, height)
        self.moving = moving
        self.start_x = x
        self.move_range = 200
        self.move_speed = 2
        self.direction = 1
        
    def update(self):
        if self.moving:
            self.rect.x += self.move_speed * self.direction
            if abs(self.rect.x - self.start_x) > self.move_range:
                self.direction *= -1
                
    def draw(self, screen: pygame.Surface, camera_x: float):
        # Draw platform with glow effect
        glow_rect = self.rect.copy()
        glow_rect.x -= camera_x
        
        # Outer glow
        for i in range(3):
            glow = pygame.Surface((glow_rect.width + i*4, glow_rect.height + i*4), pygame.SRCALPHA)
            alpha = 30 - i*10
            color = (*PLATFORM_COLOR, alpha)
            pygame.draw.rect(glow, color, glow.get_rect(), border_radius=8)
            screen.blit(glow, (glow_rect.x - i*2, glow_rect.y - i*2))
        
        # Main platform
        draw_rect = self.rect.copy()
        draw_rect.x -= camera_x
        pygame.draw.rect(screen, PLATFORM_COLOR, draw_rect, border_radius=8)
        pygame.draw.rect(screen, (150, 100, 255), draw_rect, 2, border_radius=8)

class Spike:
    def __init__(self, x: float, y: float, width: float = 30):
        self.rect = pygame.Rect(x, y, width, 20)
        
    def draw(self, screen: pygame.Surface, camera_x: float):
        draw_rect = self.rect.copy()
        draw_rect.x -= camera_x
        
        # Draw spikes as triangles
        num_spikes = self.rect.width // 15
        for i in range(num_spikes):
            x_offset = i * 15
            points = [
                (draw_rect.x + x_offset, draw_rect.bottom),
                (draw_rect.x + x_offset + 7.5, draw_rect.top),
                (draw_rect.x + x_offset + 15, draw_rect.bottom)
            ]
            pygame.draw.polygon(screen, SPIKE_COLOR, points)
            pygame.draw.polygon(screen, (255, 100, 150), points, 2)

class Goal:
    def __init__(self, x: float, y: float):
        self.rect = pygame.Rect(x, y, 40, 60)
        self.animation_offset = 0
        
    def update(self):
        self.animation_offset = (self.animation_offset + 0.1) % (2 * math.pi)
        
    def draw(self, screen: pygame.Surface, camera_x: float):
        draw_rect = self.rect.copy()
        draw_rect.x -= camera_x
        
        # Animated glow
        glow_size = int(20 + math.sin(self.animation_offset) * 5)
        for i in range(glow_size, 0, -2):
            alpha = int(100 * (1 - i / glow_size))
            glow = pygame.Surface((draw_rect.width + i*2, draw_rect.height + i*2), pygame.SRCALPHA)
            pygame.draw.rect(glow, (*GOAL_COLOR, alpha), glow.get_rect(), border_radius=10)
            screen.blit(glow, (draw_rect.x - i, draw_rect.y - i))
        
        # Main goal
        pygame.draw.rect(screen, GOAL_COLOR, draw_rect, border_radius=10)
        pygame.draw.rect(screen, (255, 255, 150), draw_rect, 3, border_radius=10)

class Player:
    def __init__(self, x: float, y: float):
        self.width = 30
        self.height = 40
        self.rect = pygame.Rect(x, y, self.width, self.height)
        self.vel_x = 0
        self.vel_y = 0
        self.on_ground = False
        self.on_wall = False
        self.wall_direction = 0
        self.facing_right = True
        
        # Physics constants
        self.move_speed = 6
        self.jump_power = 15
        self.wall_jump_x = 10
        self.wall_jump_y = 14
        self.gravity = 0.8
        self.max_fall_speed = 20
        self.air_resistance = 0.95
        self.wall_slide_speed = 2
        
        # Animation
        self.squash = 1.0
        self.stretch = 1.0
        self.rotation = 0
        
    def handle_input(self, keys):
        # Horizontal movement
        if keys[pygame.K_LEFT] or keys[pygame.K_a]:
            self.vel_x = -self.move_speed
            self.facing_right = False
        elif keys[pygame.K_RIGHT] or keys[pygame.K_d]:
            self.vel_x = self.move_speed
            self.facing_right = True
        else:
            if self.on_ground:
                self.vel_x *= 0.8
            else:
                self.vel_x *= self.air_resistance
                
    def jump(self, particles: List[Particle]):
        if self.on_ground:
            self.vel_y = -self.jump_power
            self.squash = 0.6
            self.stretch = 1.4
            self._spawn_particles(particles, ParticleType.JUMP, 8)
        elif self.on_wall:
            # Wall jump
            self.vel_y = -self.wall_jump_y
            self.vel_x = self.wall_jump_x * -self.wall_direction
            self.squash = 0.7
            self.stretch = 1.3
            self._spawn_particles(particles, ParticleType.JUMP, 6)
            
    def update(self, platforms: List[Platform], particles: List[Particle]):
        # Apply gravity
        if not (self.on_wall and self.vel_y > 0):
            self.vel_y += self.gravity
            if self.vel_y > self.max_fall_speed:
                self.vel_y = self.max_fall_speed
        else:
            # Wall slide
            self.vel_y = min(self.vel_y, self.wall_slide_speed)
            if random.random() < 0.3:
                self._spawn_particles(particles, ParticleType.WALL_SLIDE, 1)
        
        # Update position
        self.rect.x += self.vel_x
        self._check_horizontal_collisions(platforms)
        
        self.rect.y += self.vel_y
        self._check_vertical_collisions(platforms, particles)
        
        # Update animation
        self.squash += (1.0 - self.squash) * 0.2
        self.stretch += (1.0 - self.stretch) * 0.2
        
        # Rotation based on velocity
        target_rotation = -self.vel_x * 2
        self.rotation += (target_rotation - self.rotation) * 0.1
        
    def _check_horizontal_collisions(self, platforms: List[Platform]):
        self.on_wall = False
        self.wall_direction = 0
        
        for platform in platforms:
            if self.rect.colliderect(platform.rect):
                if self.vel_x > 0:  # Moving right
                    self.rect.right = platform.rect.left
                    self.on_wall = True
                    self.wall_direction = 1
                elif self.vel_x < 0:  # Moving left
                    self.rect.left = platform.rect.right
                    self.on_wall = True
                    self.wall_direction = -1
                self.vel_x = 0
                
    def _check_vertical_collisions(self, platforms: List[Platform], particles: List[Particle]):
        was_on_ground = self.on_ground
        self.on_ground = False
        
        for platform in platforms:
            if self.rect.colliderect(platform.rect):
                if self.vel_y > 0:  # Falling
                    self.rect.bottom = platform.rect.top
                    self.vel_y = 0
                    self.on_ground = True
                    if not was_on_ground:
                        self.squash = 1.3
                        self.stretch = 0.7
                        self._spawn_particles(particles, ParticleType.LAND, 10)
                elif self.vel_y < 0:  # Jumping up
                    self.rect.top = platform.rect.bottom
                    self.vel_y = 0
                    
    def _spawn_particles(self, particles: List[Particle], particle_type: ParticleType, count: int):
        for _ in range(count):
            if particle_type == ParticleType.JUMP:
                vx = random.uniform(-3, 3)
                vy = random.uniform(1, 4)
                lifetime = random.randint(15, 25)
            elif particle_type == ParticleType.LAND:
                vx = random.uniform(-5, 5)
                vy = random.uniform(-2, 0)
                lifetime = random.randint(10, 20)
            elif particle_type == ParticleType.WALL_SLIDE:
                vx = random.uniform(-2, 2) * self.wall_direction
                vy = random.uniform(-1, 1)
                lifetime = random.randint(8, 15)
            elif particle_type == ParticleType.DEATH:
                angle = random.uniform(0, 2 * math.pi)
                speed = random.uniform(2, 8)
                vx = math.cos(angle) * speed
                vy = math.sin(angle) * speed
                lifetime = random.randint(20, 40)
            else:
                vx = vy = 0
                lifetime = 20
                
            particles.append(Particle(
                x=self.rect.centerx,
                y=self.rect.bottom if particle_type != ParticleType.WALL_SLIDE else self.rect.centery,
                vx=vx,
                vy=vy,
                lifetime=lifetime,
                max_lifetime=lifetime,
                color=random.choice(PARTICLE_COLORS),
                size=random.uniform(2, 5),
                particle_type=particle_type
            ))
            
    def draw(self, screen: pygame.Surface, camera_x: float):
        draw_x = self.rect.centerx - camera_x
        draw_y = self.rect.centery
        
        # Apply squash and stretch
        width = self.width * self.squash
        height = self.height * self.stretch
        
        # Create surface for rotation
        surf_size = int(max(width, height) * 1.5)
        player_surf = pygame.Surface((surf_size, surf_size), pygame.SRCALPHA)
        
        # Draw glow
        for i in range(3):
            glow_rect = pygame.Rect(
                surf_size//2 - width//2 - i*2,
                surf_size//2 - height//2 - i*2,
                width + i*4,
                height + i*4
            )
            alpha = 40 - i*10
            pygame.draw.rect(player_surf, (*PLAYER_COLOR, alpha), glow_rect, border_radius=10)
        
        # Draw player body
        player_rect = pygame.Rect(
            surf_size//2 - width//2,
            surf_size//2 - height//2,
            width,
            height
        )
        pygame.draw.rect(player_surf, PLAYER_COLOR, player_rect, border_radius=10)
        pygame.draw.rect(player_surf, (100, 255, 230), player_rect, 2, border_radius=10)
        
        # Draw eyes
        eye_y = surf_size//2 - height//4
        eye_offset = width // 6
        if self.facing_right:
            eye1_x = surf_size//2 + eye_offset
            eye2_x = surf_size//2 + eye_offset + 8
        else:
            eye1_x = surf_size//2 - eye_offset - 8
            eye2_x = surf_size//2 - eye_offset
            
        pygame.draw.circle(player_surf, (255, 255, 255), (int(eye1_x), int(eye_y)), 4)
        pygame.draw.circle(player_surf, (255, 255, 255), (int(eye2_x), int(eye_y)), 4)
        
        # Rotate and blit
        rotated = pygame.transform.rotate(player_surf, self.rotation)
        rotated_rect = rotated.get_rect(center=(draw_x, draw_y))
        screen.blit(rotated, rotated_rect)

class Level:
    def __init__(self, level_number: int):
        self.level_number = level_number
        self.platforms: List[Platform] = []
        self.spikes: List[Spike] = []
        self.goal: Goal = None
        self.spawn_x = 100
        self.spawn_y = 500
        self.width = 3000 + level_number * 100  # Levels get longer
        
        self._generate()
        
    def _generate(self):
        random.seed(self.level_number)  # Consistent levels
        
        # Starting platform
        self.platforms.append(Platform(0, 550, 200, 40))
        
        # Generate path
        current_x = 250
        current_y = 500
        difficulty = min(self.level_number / 100, 1.0)  # 0 to 1
        
        while current_x < self.width - 300:
            # Decide next platform type and position
            gap_x = random.randint(80 + int(difficulty * 50), 200 + int(difficulty * 100))
            gap_y = random.randint(-150, 100)
            
            next_x = current_x + gap_x
            next_y = max(200, min(600, current_y + gap_y))
            
            # Platform dimensions
            platform_width = random.randint(80, 150 - int(difficulty * 30))
            platform_height = 20
            
            # Add moving platforms occasionally
            moving = random.random() < 0.3 * difficulty
            
            platform = Platform(next_x, next_y, platform_width, platform_height, moving)
            self.platforms.append(platform)
            
            # Add spikes on some platforms
            if random.random() < 0.4 * difficulty and not moving:
                spike_x = next_x + random.randint(20, int(platform_width - 50))
                self.spikes.append(Spike(spike_x, next_y - 20, 30))
            
            # Sometimes add floating spikes
            if random.random() < 0.2 * difficulty:
                spike_x = current_x + gap_x // 2
                spike_y = random.randint(int(min(current_y, next_y)) - 100, int(max(current_y, next_y)))
                self.spikes.append(Spike(spike_x, spike_y, 30))
            
            current_x = next_x
            current_y = next_y
        
        # Goal at the end
        self.goal = Goal(self.width - 200, current_y - 80)
        self.platforms.append(Platform(self.width - 250, current_y, 200, 40))
        
    def update(self):
        for platform in self.platforms:
            platform.update()
        self.goal.update()
        
    def draw(self, screen: pygame.Surface, camera_x: float):
        for platform in self.platforms:
            platform.draw(screen, camera_x)
        for spike in self.spikes:
            spike.draw(screen, camera_x)
        self.goal.draw(screen, camera_x)

class Game:
    def __init__(self):
        self.screen = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT))
        pygame.display.set_caption("NEON RUNNER")
        self.clock = pygame.time.Clock()
        self.running = True
        
        self.current_level = 1
        self.total_levels = 100
        self.level: Level = None
        self.player: Player = None
        self.particles: List[Particle] = []
        self.camera_x = 0
        
        self.state = "menu"  # menu, playing, level_complete, game_over
        self.transition_timer = 0
        
        # Font
        self.font_large = pygame.font.Font(None, 72)
        self.font_medium = pygame.font.Font(None, 48)
        self.font_small = pygame.font.Font(None, 32)
        
        self._start_level()
        
    def _start_level(self):
        self.level = Level(self.current_level)
        self.player = Player(self.level.spawn_x, self.level.spawn_y)
        self.particles.clear()
        self.camera_x = 0
        self.state = "playing"
        
    def _next_level(self):
        self.current_level += 1
        if self.current_level > self.total_levels:
            self.state = "game_won"
        else:
            self._start_level()
            
    def _reset_level(self):
        self.player = Player(self.level.spawn_x, self.level.spawn_y)
        self.particles.clear()
        self.camera_x = 0
        self.state = "playing"
        
    def handle_events(self):
        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 or event.key == pygame.K_w or event.key == pygame.K_UP:
                    if self.state == "menu":
                        self.state = "playing"
                    elif self.state == "playing":
                        self.player.jump(self.particles)
                    elif self.state == "level_complete":
                        self._next_level()
                    elif self.state == "game_over":
                        self._reset_level()
                    elif self.state == "game_won":
                        self.current_level = 1
                        self._start_level()
                elif event.key == pygame.K_r:
                    if self.state == "playing" or self.state == "game_over":
                        self._reset_level()
                        
    def update(self):
        if self.state == "menu":
            # Spawn some ambient particles
            if random.random() < 0.1:
                self.particles.append(Particle(
                    x=random.randint(0, WINDOW_WIDTH),
                    y=random.randint(0, WINDOW_HEIGHT),
                    vx=random.uniform(-1, 1),
                    vy=random.uniform(-1, 1),
                    lifetime=random.randint(60, 120),
                    max_lifetime=120,
                    color=random.choice(PARTICLE_COLORS),
                    size=random.uniform(2, 4),
                    particle_type=ParticleType.GOAL
                ))
        elif self.state == "playing":
            keys = pygame.key.get_pressed()
            self.player.handle_input(keys)
            self.player.update(self.level.platforms, self.particles)
            self.level.update()
            
            # Camera follow player
            target_camera_x = self.player.rect.centerx - WINDOW_WIDTH // 3
            target_camera_x = max(0, min(target_camera_x, self.level.width - WINDOW_WIDTH))
            self.camera_x += (target_camera_x - self.camera_x) * 0.1
            
            # Check death
            if self.player.rect.top > WINDOW_HEIGHT + 50:
                self.state = "game_over"
                self.player._spawn_particles(self.particles, ParticleType.DEATH, 30)
                
            # Check spike collision
            for spike in self.level.spikes:
                if self.player.rect.colliderect(spike.rect):
                    self.state = "game_over"
                    self.player._spawn_particles(self.particles, ParticleType.DEATH, 30)
                    
            # Check goal
            if self.player.rect.colliderect(self.level.goal.rect):
                self.state = "level_complete"
                self.transition_timer = 60
                
        elif self.state == "level_complete":
            self.transition_timer -= 1
            if self.transition_timer <= 0:
                self._next_level()
            # Spawn celebration particles
            if random.random() < 0.3:
                self.particles.append(Particle(
                    x=self.level.goal.rect.centerx,
                    y=self.level.goal.rect.centery,
                    vx=random.uniform(-5, 5),
                    vy=random.uniform(-8, -2),
                    lifetime=random.randint(30, 60),
                    max_lifetime=60,
                    color=random.choice(PARTICLE_COLORS),
                    size=random.uniform(3, 6),
                    particle_type=ParticleType.GOAL
                ))
                
        # Update particles
        for particle in self.particles[:]:
            particle.x += particle.vx
            particle.y += particle.vy
            particle.vy += 0.2  # Gravity
            particle.lifetime -= 1
            if particle.lifetime <= 0:
                self.particles.remove(particle)
                
    def draw(self):
        self.screen.fill(BG_COLOR)
        
        if self.state == "menu":
            self._draw_menu()
        else:
            # Draw level
            self.level.draw(self.screen, self.camera_x)
            
            # Draw player
            if self.state != "game_over":
                self.player.draw(self.screen, self.camera_x)
            
            # Draw particles
            for particle in self.particles:
                alpha = int(255 * (particle.lifetime / particle.max_lifetime))
                color = (*particle.color, alpha)
                size = int(particle.size * (particle.lifetime / particle.max_lifetime))
                if size > 0:
                    surf = pygame.Surface((size*2, size*2), pygame.SRCALPHA)
                    pygame.draw.circle(surf, color, (size, size), size)
                    draw_x = particle.x - self.camera_x
                    self.screen.blit(surf, (draw_x - size, particle.y - size))
            
            # Draw UI
            self._draw_ui()
            
            if self.state == "level_complete":
                self._draw_level_complete()
            elif self.state == "game_over":
                self._draw_game_over()
            elif self.state == "game_won":
                self._draw_game_won()
                
        pygame.display.flip()
        
    def _draw_menu(self):
        # Draw particles
        for particle in self.particles:
            alpha = int(255 * (particle.lifetime / particle.max_lifetime))
            color = (*particle.color, alpha)
            size = int(particle.size)
            if size > 0:
                pygame.draw.circle(self.screen, color, (int(particle.x), int(particle.y)), size)
        
        title = self.font_large.render("NEON RUNNER", True, PLAYER_COLOR)
        title_rect = title.get_rect(center=(WINDOW_WIDTH//2, 200))
        self.screen.blit(title, title_rect)
        
        subtitle = self.font_medium.render("100 Procedurally Generated Levels", True, (150, 150, 255))
        subtitle_rect = subtitle.get_rect(center=(WINDOW_WIDTH//2, 280))
        self.screen.blit(subtitle, subtitle_rect)
        
        instructions = [
            "ARROW KEYS or WASD - Move",
            "SPACE or W or UP - Jump",
            "Hold against wall to WALL SLIDE",
            "Jump while on wall for WALL JUMP",
            "R - Restart Level",
            "ESC - Quit",
            "",
            "Press SPACE to Start"
        ]
        
        y = 380
        for line in instructions:
            text = self.font_small.render(line, True, (200, 200, 255))
            text_rect = text.get_rect(center=(WINDOW_WIDTH//2, y))
            self.screen.blit(text, text_rect)
            y += 40
            
    def _draw_ui(self):
        level_text = self.font_small.render(f"Level {self.current_level}/{self.total_levels}", True, PLAYER_COLOR)
        self.screen.blit(level_text, (20, 20))
        
        hint = self.font_small.render("R - Restart", True, (150, 150, 200))
        self.screen.blit(hint, (20, 60))
        
    def _draw_level_complete(self):
        overlay = pygame.Surface((WINDOW_WIDTH, WINDOW_HEIGHT), pygame.SRCALPHA)
        overlay.fill((0, 0, 0, 128))
        self.screen.blit(overlay, (0, 0))
        
        text = self.font_large.render("LEVEL COMPLETE!", True, GOAL_COLOR)
        text_rect = text.get_rect(center=(WINDOW_WIDTH//2, WINDOW_HEIGHT//2 - 50))
        self.screen.blit(text, text_rect)
        
        next_text = self.font_medium.render("Press SPACE for next level", True, (200, 200, 255))
        next_rect = next_text.get_rect(center=(WINDOW_WIDTH//2, WINDOW_HEIGHT//2 + 50))
        self.screen.blit(next_text, next_rect)
        
    def _draw_game_over(self):
        overlay = pygame.Surface((WINDOW_WIDTH, WINDOW_HEIGHT), pygame.SRCALPHA)
        overlay.fill((0, 0, 0, 128))
        self.screen.blit(overlay, (0, 0))
        
        text = self.font_large.render("GAME OVER", True, SPIKE_COLOR)
        text_rect = text.get_rect(center=(WINDOW_WIDTH//2, WINDOW_HEIGHT//2 - 50))
        self.screen.blit(text, text_rect)
        
        retry_text = self.font_medium.render("Press SPACE to retry", True, (200, 200, 255))
        retry_rect = retry_text.get_rect(center=(WINDOW_WIDTH//2, WINDOW_HEIGHT//2 + 50))
        self.screen.blit(retry_text, retry_rect)
        
    def _draw_game_won(self):
        overlay = pygame.Surface((WINDOW_WIDTH, WINDOW_HEIGHT), pygame.SRCALPHA)
        overlay.fill((0, 0, 0, 200))
        self.screen.blit(overlay, (0, 0))
        
        # Spawn celebration particles
        if random.random() < 0.5:
            self.particles.append(Particle(
                x=random.randint(0, WINDOW_WIDTH),
                y=-10,
                vx=random.uniform(-2, 2),
                vy=random.uniform(2, 5),
                lifetime=random.randint(60, 120),
                max_lifetime=120,
                color=random.choice(PARTICLE_COLORS),
                size=random.uniform(4, 8),
                particle_type=ParticleType.GOAL
            ))
        
        text = self.font_large.render("CONGRATULATIONS!", True, GOAL_COLOR)
        text_rect = text.get_rect(center=(WINDOW_WIDTH//2, WINDOW_HEIGHT//2 - 100))
        self.screen.blit(text, text_rect)
        
        complete = self.font_medium.render("You completed all 100 levels!", True, PLAYER_COLOR)
        complete_rect = complete.get_rect(center=(WINDOW_WIDTH//2, WINDOW_HEIGHT//2))
        self.screen.blit(complete, complete_rect)
        
        again = self.font_small.render("Press SPACE to play again", True, (200, 200, 255))
        again_rect = again.get_rect(center=(WINDOW_WIDTH//2, WINDOW_HEIGHT//2 + 100))
        self.screen.blit(again, again_rect)
        
    def run(self):
        while self.running:
            self.handle_events()
            self.update()
            self.draw()
            self.clock.tick(FPS)
            
        pygame.quit()
        sys.exit()

if __name__ == "__main__":
    game = Game()
    game.run()
Editor is loading...
Leave a Comment