#!/usr/bin/env python3 """ Pathfinding Showcase Demo ========================= Demonstrates various pathfinding scenarios with multiple entities. Features: - Multiple entities pathfinding simultaneously - Chase mode: entities pursue targets - Flee mode: entities avoid threats - Patrol mode: entities follow waypoints - Visual debugging: show Dijkstra distance field """ import mcrfpy import sys import random # Colors WALL_COLOR = mcrfpy.Color(40, 40, 40) FLOOR_COLOR = mcrfpy.Color(220, 220, 240) PATH_COLOR = mcrfpy.Color(180, 250, 180) THREAT_COLOR = mcrfpy.Color(255, 100, 100) GOAL_COLOR = mcrfpy.Color(100, 255, 100) DIJKSTRA_COLORS = [ mcrfpy.Color(50, 50, 100), # Far mcrfpy.Color(70, 70, 150), mcrfpy.Color(90, 90, 200), mcrfpy.Color(110, 110, 250), mcrfpy.Color(150, 150, 255), mcrfpy.Color(200, 200, 255), # Near ] # Entity types PLAYER = 64 # @ ENEMY = 69 # E TREASURE = 36 # $ PATROL = 80 # P # Global state grid = None player = None enemies = [] treasures = [] patrol_entities = [] mode = "CHASE" show_dijkstra = False animation_speed = 3.0 def create_dungeon(): """Create a dungeon-like map""" global grid mcrfpy.createScene("pathfinding_showcase") # Create larger grid for showcase grid = mcrfpy.Grid(grid_x=30, grid_y=20) grid.fill_color = mcrfpy.Color(0, 0, 0) # Initialize all as floor for y in range(20): for x in range(30): grid.at(x, y).walkable = True grid.at(x, y).transparent = True grid.at(x, y).color = FLOOR_COLOR # Create rooms and corridors rooms = [ (2, 2, 8, 6), # Top-left room (20, 2, 8, 6), # Top-right room (11, 8, 8, 6), # Center room (2, 14, 8, 5), # Bottom-left room (20, 14, 8, 5), # Bottom-right room ] # Create room walls for rx, ry, rw, rh in rooms: # Top and bottom walls for x in range(rx, rx + rw): if 0 <= x < 30: grid.at(x, ry).walkable = False grid.at(x, ry).color = WALL_COLOR grid.at(x, ry + rh - 1).walkable = False grid.at(x, ry + rh - 1).color = WALL_COLOR # Left and right walls for y in range(ry, ry + rh): if 0 <= y < 20: grid.at(rx, y).walkable = False grid.at(rx, y).color = WALL_COLOR grid.at(rx + rw - 1, y).walkable = False grid.at(rx + rw - 1, y).color = WALL_COLOR # Create doorways doorways = [ (6, 2), (24, 2), # Top room doors (6, 7), (24, 7), # Top room doors bottom (15, 8), (15, 13), # Center room doors (6, 14), (24, 14), # Bottom room doors (11, 11), (18, 11), # Center room side doors ] for x, y in doorways: if 0 <= x < 30 and 0 <= y < 20: grid.at(x, y).walkable = True grid.at(x, y).color = FLOOR_COLOR # Add some corridors # Horizontal corridors for x in range(10, 20): grid.at(x, 5).walkable = True grid.at(x, 5).color = FLOOR_COLOR grid.at(x, 16).walkable = True grid.at(x, 16).color = FLOOR_COLOR # Vertical corridors for y in range(5, 17): grid.at(10, y).walkable = True grid.at(10, y).color = FLOOR_COLOR grid.at(19, y).walkable = True grid.at(19, y).color = FLOOR_COLOR def spawn_entities(): """Spawn various entity types""" global player, enemies, treasures, patrol_entities # Clear existing entities grid.entities.clear() enemies = [] treasures = [] patrol_entities = [] # Spawn player in center room player = mcrfpy.Entity(15, 11) player.sprite_index = PLAYER grid.entities.append(player) # Spawn enemies in corners enemy_positions = [(4, 4), (24, 4), (4, 16), (24, 16)] for x, y in enemy_positions: enemy = mcrfpy.Entity(x, y) enemy.sprite_index = ENEMY grid.entities.append(enemy) enemies.append(enemy) # Spawn treasures treasure_positions = [(6, 5), (24, 5), (15, 10)] for x, y in treasure_positions: treasure = mcrfpy.Entity(x, y) treasure.sprite_index = TREASURE grid.entities.append(treasure) treasures.append(treasure) # Spawn patrol entities patrol = mcrfpy.Entity(10, 10) patrol.sprite_index = PATROL patrol.waypoints = [(10, 10), (19, 10), (19, 16), (10, 16)] # Square patrol patrol.waypoint_index = 0 grid.entities.append(patrol) patrol_entities.append(patrol) def visualize_dijkstra(target_x, target_y): """Visualize Dijkstra distance field""" if not show_dijkstra: return # Compute Dijkstra from target grid.compute_dijkstra(target_x, target_y) # Color tiles based on distance max_dist = 30.0 for y in range(20): for x in range(30): if grid.at(x, y).walkable: dist = grid.get_dijkstra_distance(x, y) if dist is not None and dist < max_dist: # Map distance to color index color_idx = int((dist / max_dist) * len(DIJKSTRA_COLORS)) color_idx = min(color_idx, len(DIJKSTRA_COLORS) - 1) grid.at(x, y).color = DIJKSTRA_COLORS[color_idx] def move_enemies(dt): """Move enemies based on current mode""" if mode == "CHASE": # Enemies chase player for enemy in enemies: path = enemy.path_to(int(player.x), int(player.y)) if path and len(path) > 1: # Don't move onto player # Move towards player next_x, next_y = path[1] # Smooth movement dx = next_x - enemy.x dy = next_y - enemy.y enemy.x += dx * dt * animation_speed enemy.y += dy * dt * animation_speed elif mode == "FLEE": # Enemies flee from player for enemy in enemies: # Compute opposite direction dx = enemy.x - player.x dy = enemy.y - player.y # Find safe spot in that direction target_x = int(enemy.x + dx * 2) target_y = int(enemy.y + dy * 2) # Clamp to grid target_x = max(0, min(29, target_x)) target_y = max(0, min(19, target_y)) path = enemy.path_to(target_x, target_y) if path and len(path) > 0: next_x, next_y = path[0] # Move away from player dx = next_x - enemy.x dy = next_y - enemy.y enemy.x += dx * dt * animation_speed enemy.y += dy * dt * animation_speed def move_patrols(dt): """Move patrol entities along waypoints""" for patrol in patrol_entities: if not hasattr(patrol, 'waypoints'): continue # Get current waypoint target_x, target_y = patrol.waypoints[patrol.waypoint_index] # Check if reached waypoint dist = abs(patrol.x - target_x) + abs(patrol.y - target_y) if dist < 0.5: # Move to next waypoint patrol.waypoint_index = (patrol.waypoint_index + 1) % len(patrol.waypoints) target_x, target_y = patrol.waypoints[patrol.waypoint_index] # Path to waypoint path = patrol.path_to(target_x, target_y) if path and len(path) > 0: next_x, next_y = path[0] dx = next_x - patrol.x dy = next_y - patrol.y patrol.x += dx * dt * animation_speed * 0.5 # Slower patrol speed patrol.y += dy * dt * animation_speed * 0.5 def update_entities(dt): """Update all entity movements""" move_enemies(dt / 1000.0) # Convert to seconds move_patrols(dt / 1000.0) # Update Dijkstra visualization if show_dijkstra and player: visualize_dijkstra(int(player.x), int(player.y)) def handle_keypress(scene_name, keycode): """Handle keyboard input""" global mode, show_dijkstra, player # Mode switching if keycode == 49: # '1' mode = "CHASE" mode_text.text = "Mode: CHASE - Enemies pursue player" clear_colors() elif keycode == 50: # '2' mode = "FLEE" mode_text.text = "Mode: FLEE - Enemies avoid player" clear_colors() elif keycode == 51: # '3' mode = "PATROL" mode_text.text = "Mode: PATROL - Entities follow waypoints" clear_colors() # Toggle Dijkstra visualization elif keycode == 68 or keycode == 100: # 'D' or 'd' show_dijkstra = not show_dijkstra debug_text.text = f"Dijkstra Debug: {'ON' if show_dijkstra else 'OFF'}" if not show_dijkstra: clear_colors() # Move player with arrow keys or WASD elif keycode in [87, 119]: # W/w - Up if player.y > 0: path = player.path_to(int(player.x), int(player.y) - 1) if path: player.y -= 1 elif keycode in [83, 115]: # S/s - Down if player.y < 19: path = player.path_to(int(player.x), int(player.y) + 1) if path: player.y += 1 elif keycode in [65, 97]: # A/a - Left if player.x > 0: path = player.path_to(int(player.x) - 1, int(player.y)) if path: player.x -= 1 elif keycode in [68, 100]: # D/d - Right if player.x < 29: path = player.path_to(int(player.x) + 1, int(player.y)) if path: player.x += 1 # Reset elif keycode == 82 or keycode == 114: # 'R' or 'r' spawn_entities() clear_colors() # Quit elif keycode == 81 or keycode == 113 or keycode == 256: # Q/q/ESC print("\nExiting pathfinding showcase...") sys.exit(0) def clear_colors(): """Reset floor colors""" for y in range(20): for x in range(30): if grid.at(x, y).walkable: grid.at(x, y).color = FLOOR_COLOR # Create the showcase print("Pathfinding Showcase Demo") print("=========================") print("Controls:") print(" WASD - Move player") print(" 1 - Chase mode (enemies pursue)") print(" 2 - Flee mode (enemies avoid)") print(" 3 - Patrol mode") print(" D - Toggle Dijkstra visualization") print(" R - Reset entities") print(" Q/ESC - Quit") # Create dungeon create_dungeon() spawn_entities() # Set up UI ui = mcrfpy.sceneUI("pathfinding_showcase") ui.append(grid) # Scale and position grid.size = (750, 500) # 30*25, 20*25 grid.position = (25, 60) # Add title title = mcrfpy.Caption("Pathfinding Showcase", 300, 10) title.fill_color = mcrfpy.Color(255, 255, 255) ui.append(title) # Add mode text mode_text = mcrfpy.Caption("Mode: CHASE - Enemies pursue player", 25, 580) mode_text.fill_color = mcrfpy.Color(255, 255, 200) ui.append(mode_text) # Add debug text debug_text = mcrfpy.Caption("Dijkstra Debug: OFF", 25, 600) debug_text.fill_color = mcrfpy.Color(200, 200, 255) ui.append(debug_text) # Add legend legend = mcrfpy.Caption("@ Player E Enemy $ Treasure P Patrol", 25, 620) legend.fill_color = mcrfpy.Color(150, 150, 150) ui.append(legend) # Set up input handling mcrfpy.keypressScene(handle_keypress) # Set up animation timer mcrfpy.setTimer("entities", update_entities, 16) # 60 FPS # Show scene mcrfpy.setScene("pathfinding_showcase") print("\nShowcase ready! Move with WASD and watch entities react.")