Snake Demo

snake_demo.py
import sys
import os
import random
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))

from lunaengine.core import Scene, LunaEngine # Core Items
from lunaengine.ui.elements import * # UI Elements
from lunaengine.graphics.particles import ParticleSystem, ParticleConfig, ExitPoint, PhysicsType  # Adicionar importações de partículas
import pygame

class MainMenuScene(Scene):
    CurrentTheme = ThemeType.EMERALD
    SnakeColor = (0, 255, 0)  # Default green snake
    
    def on_enter(self, previous_scene = None):
        super().on_enter(previous_scene)
        
    def on_exit(self, next_scene = None):
        super().on_exit(next_scene)
    
    def __init__(self, engine: LunaEngine):
        super().__init__(engine)
        
        # Title
        Ui_TitleLabel = TextLabel(512, 80, "Snake Game Demo", 72, root_point=(0.5, 0.5), theme=self.CurrentTheme)
        self.ui_elements.append(Ui_TitleLabel)
        
        # Play button
        Ui_PlayButton = Button(512, 200, 200, 42, "[ Play ]", 40, root_point=(0.5, 0.5), theme=self.CurrentTheme)
        Ui_PlayButton.set_on_click(lambda: engine.set_scene("InGame"))
        self.ui_elements.append(Ui_PlayButton)
        
        # Exit button
        Ui_ExitButton = Button(512, 260, 200, 42, "[ Exit ]", 40, root_point=(0.5, 0.5), theme=self.CurrentTheme)
        Ui_ExitButton.set_on_click(lambda: setattr(engine, 'running', False))
        self.ui_elements.append(Ui_ExitButton)
        
        # Theme dropdown
        Ui_ThemeDropdown = Dropdown(512, 330, 200, 30, engine.get_theme_names(), root_point=(0.5, 0.5), theme=self.CurrentTheme)
        Ui_ThemeDropdown.set_on_selection_changed(lambda i, n: self.Ui_update_theme(n, engine))
        self.ui_elements.append(Ui_ThemeDropdown)
        
        # Snake Color Customization Section
        Ui_ColorLabel = TextLabel(512, 400, "Snake Color Customization", 28, root_point=(0.5, 0.5), theme=self.CurrentTheme)
        self.ui_elements.append(Ui_ColorLabel)
        
        # Red Slider
        Ui_RedLabel = TextLabel(400, 450, "Red:", 20, root_point=(0, 0.5), theme=self.CurrentTheme)
        self.ui_elements.append(Ui_RedLabel)
        
        self.Ui_RedSlider = Slider(450, 450, 150, 20, 0, 255, self.SnakeColor[0], root_point=(0, 0.5), theme=self.CurrentTheme)
        self.Ui_RedSlider.on_value_changed = lambda v: self.update_snake_color(0, int(v))
        self.ui_elements.append(self.Ui_RedSlider)
        
        # Green Slider
        Ui_GreenLabel = TextLabel(400, 490, "Green:", 20, root_point=(0, 0.5), theme=self.CurrentTheme)
        self.ui_elements.append(Ui_GreenLabel)
        
        self.Ui_GreenSlider = Slider(450, 490, 150, 20, 0, 255, self.SnakeColor[1], root_point=(0, 0.5), theme=self.CurrentTheme)
        self.Ui_GreenSlider.on_value_changed = lambda v: self.update_snake_color(1, int(v))
        self.ui_elements.append(self.Ui_GreenSlider)
        
        # Blue Slider
        Ui_BlueLabel = TextLabel(400, 530, "Blue:", 20, root_point=(0, 0.5), theme=self.CurrentTheme)
        self.ui_elements.append(Ui_BlueLabel)
        
        self.Ui_BlueSlider = Slider(450, 530, 150, 20, 0, 255, self.SnakeColor[2], root_point=(0, 0.5), theme=self.CurrentTheme)
        self.Ui_BlueSlider.on_value_changed = lambda v: self.update_snake_color(2, int(v))
        self.ui_elements.append(self.Ui_BlueSlider)
        
        
    def update_snake_color(self, channel: int, value: int):
        """Update snake color when sliders change"""
        new_color = list(self.SnakeColor)
        new_color[channel] = value
        self.SnakeColor = tuple(new_color)
        
    def Ui_update_theme(self, n, engine: LunaEngine):
        self.CurrentTheme = n
        engine.set_global_theme(n)
        
    def update(self, dt):
        pass
            
    def render(self, renderer:Renderer):
        # Draw background
        renderer.draw_rect(0, 0, 1024, 720, ThemeManager.get_theme(ThemeManager.get_current_theme()).background)
        
        # Color preview box
        renderer.draw_rect(750, 450, 100, 100, self.SnakeColor)
        
        # Draw buttons
        for element in self.ui_elements:
            element.render(renderer)
            
class InGameScene(Scene):
    
    def on_enter(self, previous_scene = None):
        return super().on_enter(previous_scene)
    
    def on_exit(self, next_scene = None):
        return super().on_exit(next_scene)
    
    def __init__(self, engine: LunaEngine):
        super().__init__(engine)
        
        # Get snake color from main menu
        self.snake_color = engine.scenes["MainMenu"].SnakeColor if "MainMenu" in engine.scenes else (0, 255, 0)
        
        # Game state
        self.reset_game()
        
        # UI elements
        self.Ui_ScoreLabel = TextLabel(10, 10, f"Score: {self.score}", 24, root_point=(0, 0))
        self.Ui_InfoLabel = TextLabel(10, 40, "WASD/Arrows to move | ESC to menu", 18, root_point=(0, 0))
        self.ui_elements.append(self.Ui_ScoreLabel)
        self.ui_elements.append(self.Ui_InfoLabel)
        
        # Particle system for apple flare effect
        self.particle_system = ParticleSystem(max_particles=500)
        self.setup_apple_particles()
        
        # Register key events
        @engine.on_event(pygame.KEYDOWN)
        def on_key_press(event):
            self.handle_key_press(event.key)
    
    def setup_apple_particles(self):
        """Setup custom particle effects for the apple"""
        # Apple glow/flare effect
        apple_glow_config = ParticleConfig(
            color_start=(255, 50, 50),    # Bright red
            color_end=(255, 200, 50),     # Orange/yellow
            size_start=2.0,
            size_end=2.0,
            lifetime=1.0,
            speed=15.0,
            gravity=0.0,
            spread=360.0,  # Full circle
            fade_out=True,
            grow=True
        )
        self.particle_system.register_custom_particle("apple_glow", apple_glow_config)
        
        # Apple sparkle effect
        apple_sparkle_config = ParticleConfig(
            color_start=(255, 255, 200),  # Bright yellow
            color_end=(255, 100, 100),    # Red
            size_start=1.0,
            size_end=3.0,
            lifetime=0.8,
            speed=40.0,
            gravity=0.0,
            spread=180.0,  # Half circle
            fade_out=True
        )
        self.particle_system.register_custom_particle("apple_sparkle", apple_sparkle_config)
    
    def reset_game(self):
        """Reset the game to initial state"""
        # Snake properties
        self.cell_size = 20
        self.grid_width = 1024 // self.cell_size
        self.grid_height = 720 // self.cell_size
        
        # Snake starts in the middle
        self.snake = [
            (self.grid_width // 2, self.grid_height // 2),
            (self.grid_width // 2 - 1, self.grid_height // 2),
            (self.grid_width // 2 - 2, self.grid_height // 2)
        ]
        self.direction = (1, 0)  # Start moving right
        self.next_direction = self.direction
        
        # Game state
        self.score = 0
        self.game_over = False
        self.base_speed = 6  # Reduced base speed (moves per second)
        self.speed = self.base_speed
        self.move_timer = 0
        
        # Spawn first apple
        self.spawn_apple()
    
    def spawn_apple(self):
        """Spawn an apple at a random position not occupied by the snake"""
        while True:
            apple_pos = (
                random.randint(0, self.grid_width - 1),
                random.randint(0, self.grid_height - 1)
            )
            if apple_pos not in self.snake:
                self.apple = apple_pos
                break
    
    def emit_apple_particles(self):
        """Emit flare particles around the apple"""
        if hasattr(self, 'apple'):
            apple_x = self.apple[0] * self.cell_size
            apple_y = self.apple[1] * self.cell_size
            
            # Continuous glow effect
            self.particle_system.emit(
                x=apple_x, y=apple_y,
                particle_type="apple_glow",
                count=2,
                exit_point=ExitPoint("circular"),
                physics_type=PhysicsType("topdown"),
                spread=360.0
            )
            
            # Occasional sparkles (less frequent)
            if random.random() < 0.3:  # 30% chance each frame
                self.particle_system.emit(
                    x=apple_x, y=apple_y,
                    particle_type="apple_sparkle",
                    count=1,
                    exit_point=ExitPoint("circular"),
                    physics_type=PhysicsType("topdown"),
                    spread=180.0
                )
    
    def handle_key_press(self, key):
        """Handle keyboard input"""
        if key == pygame.K_ESCAPE:
            self.engine.set_scene("MainMenu")
            return
        
        if self.game_over:
            if key == pygame.K_SPACE:
                self.reset_game()
            return
        
        # Movement controls (WASD and Arrows)
        if key in [pygame.K_RIGHT, pygame.K_d] and self.direction != (-1, 0):
            self.next_direction = (1, 0)
        elif key in [pygame.K_LEFT, pygame.K_a] and self.direction != (1, 0):
            self.next_direction = (-1, 0)
        elif key in [pygame.K_DOWN, pygame.K_s] and self.direction != (0, -1):
            self.next_direction = (0, 1)
        elif key in [pygame.K_UP, pygame.K_w] and self.direction != (0, 1):
            self.next_direction = (0, -1)
    
    def update_snake(self):
        """Update snake position and check collisions"""
        # Update direction
        self.direction = self.next_direction
        
        # Calculate new head position with wrap-around
        head_x, head_y = self.snake[0]
        new_head_x = (head_x + self.direction[0]) % self.grid_width
        new_head_y = (head_y + self.direction[1]) % self.grid_height
        new_head = (new_head_x, new_head_y)
        
        # Check collision with self
        if new_head in self.snake:
            self.game_over = True
            return
        
        # Move snake
        self.snake.insert(0, new_head)
        
        # Check if snake ate apple
        if new_head == self.apple:
            self.score += 10
            # Emit burst of particles when apple is eaten
            self.emit_apple_eaten_particles()
            self.spawn_apple()
            # Increase speed slightly every 3 apples (slower progression)
            if self.score % 30 == 0:
                self.speed = min(12, self.speed + 1)  # Lower max speed
        else:
            # Remove tail if no apple eaten
            self.snake.pop()
    
    def emit_apple_eaten_particles(self):
        """Emit a special particle burst when apple is eaten"""
        if hasattr(self, 'apple'):
            apple_x = self.apple[0] * self.cell_size
            apple_y = self.apple[1] * self.cell_size
            
            # Big burst when apple is eaten
            self.particle_system.emit(
                x=apple_x, y=apple_y,
                particle_type="apple_glow",
                count=15,
                exit_point=ExitPoint("circular"),
                physics_type=PhysicsType("topdown"),
                spread=360.0
            )
            
            self.particle_system.emit(
                x=apple_x, y=apple_y,
                particle_type="apple_sparkle",
                count=10,
                exit_point=ExitPoint("circular"),
                physics_type=PhysicsType("topdown"),
                spread=360.0
            )
    
    def update(self, dt):
        if self.game_over:
            return
            
        # Update movement timer with delay
        self.move_timer += dt
        move_interval = 0.65 / self.speed  # This creates the delay
        
        if self.move_timer >= move_interval:
            self.update_snake()
            self.move_timer = 0
        
        # Update particle system
        self.particle_system.update(dt)
        
        # Emit continuous apple particles (only if game is running)
        if not self.game_over:
            self.emit_apple_particles()
        
        # Update UI
        self.Ui_ScoreLabel.set_text(f"Score: {self.score}")
        
        # Update snake color from main menu
        if "MainMenu" in self.engine.scenes:
            self.snake_color = self.engine.scenes["MainMenu"].SnakeColor
    
    def render(self, renderer: Renderer):
        # Draw background
        
        current_theme = ThemeManager.get_theme(ThemeManager.get_current_theme())
        renderer.draw_rect(0, 0, 1024, 720, current_theme.background)
        
        # Draw grid (optional, for visual reference)
        grid_color = tuple(max(0, c - 20) for c in current_theme.background2)
        for x in range(0, 1024, self.cell_size):
            renderer.draw_line(x, 0, x, 720, grid_color)
        for y in range(0, 720, self.cell_size):
            renderer.draw_line(0, y, 1024, y, grid_color)
        
        # Draw apple
        apple_x = self.apple[0] * self.cell_size
        apple_y = self.apple[1] * self.cell_size
        renderer.draw_rect(apple_x, apple_y, self.cell_size, self.cell_size, (255, 0, 0))
        
        # Draw snake with custom color
        for i, (x, y) in enumerate(self.snake):
            segment_x = x * self.cell_size
            segment_y = y * self.cell_size
            
            # Head is brighter, body is darker
            if i == 0:
                # Bright head
                head_color = tuple(min(255, c + 30) for c in self.snake_color)
                color = head_color
            else:
                # Gradient body color (darker towards tail)
                darken_factor = min(100, i * 8)
                color = tuple(max(0, c - darken_factor) for c in self.snake_color)
            
            renderer.draw_rect(segment_x, segment_y, self.cell_size, self.cell_size, color)
            
            # Draw border around segments
            border_color = tuple(max(0, c - 40) for c in color)
            renderer.draw_rect(segment_x, segment_y, self.cell_size, self.cell_size, border_color, fill=False)
        
        # Draw game over screen
        if self.game_over:
            # Semi-transparent overlay
            overlay = pygame.Surface((1024, 720), pygame.SRCALPHA)
            overlay.fill((0, 0, 0, 180))
            renderer.draw_surface(overlay, 0, 0)
            
            # Game over text
            font = pygame.font.Font(None, 72)
            game_over_text = font.render("GAME OVER", True, (255, 255, 255))
            score_text = font.render(f"Final Score: {self.score}", True, (255, 255, 255))
            restart_text = font.render("Press SPACE to restart", True, (255, 255, 255))
            
            renderer.draw_surface(game_over_text, 512 - game_over_text.get_width() // 2, 300)
            renderer.draw_surface(score_text, 512 - score_text.get_width() // 2, 380)
            renderer.draw_surface(restart_text, 512 - restart_text.get_width() // 2, 460)
        
        # Draw UI elements
        for element in self.ui_elements:
            element.render(renderer)
            
def main():
    engine = LunaEngine("LunaEngine - Snake Demo", 1024, 720, False)
    engine.fps = 120
    
    engine.add_scene("MainMenu", MainMenuScene)
    engine.add_scene("InGame", InGameScene)
    engine.set_scene("MainMenu")
    engine.run()
    
if __name__ == "__main__":
    main()
About This Example