feat(engine): implement perspective FOV, pathfinding, and GUI text widgets
Major Engine Enhancements: - Complete FOV (Field of View) system with perspective rendering - UIGrid.perspective property for entity-based visibility - Three-layer overlay colors (unexplored, explored, visible) - Per-entity visibility state tracking - Perfect knowledge updates only for explored areas - Advanced Pathfinding Integration - A* pathfinding implementation in UIGrid - Entity.path_to() method for direct pathfinding - Dijkstra maps for multi-target pathfinding - Path caching for performance optimization - GUI Text Input Widgets - TextInputWidget class with cursor, selection, scrolling - Improved widget with proper text rendering and input handling - Example showcase of multiple text input fields - Foundation for in-game console and chat systems - Performance & Architecture Improvements - PyTexture copy operations optimized - GameEngine update cycle refined - UIEntity property handling enhanced - UITestScene modernized Test Suite: - Interactive visibility demos showing FOV in action - Pathfinding comparison (A* vs Dijkstra) - Debug utilities for visibility and empty path handling - Sizzle reel demo combining pathfinding and vision - Multiple text input test scenarios This commit brings McRogueFace closer to a complete roguelike engine with essential features like line-of-sight, intelligent pathfinding, and interactive text input capabilities. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
051a2ca951
commit
d13153ddb4
25 changed files with 3317 additions and 225 deletions
|
|
@ -1,165 +1,208 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Animation System Demo - Shows all animation capabilities"""
|
||||
"""
|
||||
Animation Demo: Grid Center & Entity Movement
|
||||
=============================================
|
||||
|
||||
Demonstrates:
|
||||
- Animated grid centering following entity
|
||||
- Smooth entity movement along paths
|
||||
- Perspective shifts with zoom transitions
|
||||
- Field of view updates
|
||||
"""
|
||||
|
||||
import mcrfpy
|
||||
import math
|
||||
import sys
|
||||
|
||||
# Create main scene
|
||||
mcrfpy.createScene("animation_demo")
|
||||
ui = mcrfpy.sceneUI("animation_demo")
|
||||
mcrfpy.setScene("animation_demo")
|
||||
# Setup scene
|
||||
mcrfpy.createScene("anim_demo")
|
||||
|
||||
# Title
|
||||
title = mcrfpy.Caption((400, 30), "McRogueFace Animation System Demo", mcrfpy.default_font)
|
||||
title.size = 24
|
||||
title.fill_color = (255, 255, 255)
|
||||
# Note: centered property doesn't exist for Caption
|
||||
# Create grid
|
||||
grid = mcrfpy.Grid(grid_x=30, grid_y=20)
|
||||
grid.fill_color = mcrfpy.Color(20, 20, 30)
|
||||
|
||||
# Simple map
|
||||
for y in range(20):
|
||||
for x in range(30):
|
||||
cell = grid.at(x, y)
|
||||
# Create walls around edges and some obstacles
|
||||
if x == 0 or x == 29 or y == 0 or y == 19:
|
||||
cell.walkable = False
|
||||
cell.transparent = False
|
||||
cell.color = mcrfpy.Color(40, 30, 30)
|
||||
elif (x == 10 and 5 <= y <= 15) or (y == 10 and 5 <= x <= 25):
|
||||
cell.walkable = False
|
||||
cell.transparent = False
|
||||
cell.color = mcrfpy.Color(60, 40, 40)
|
||||
else:
|
||||
cell.walkable = True
|
||||
cell.transparent = True
|
||||
cell.color = mcrfpy.Color(80, 80, 100)
|
||||
|
||||
# Create entities
|
||||
player = mcrfpy.Entity(5, 5, grid=grid)
|
||||
player.sprite_index = 64 # @
|
||||
|
||||
enemy = mcrfpy.Entity(25, 15, grid=grid)
|
||||
enemy.sprite_index = 69 # E
|
||||
|
||||
# Update visibility
|
||||
player.update_visibility()
|
||||
enemy.update_visibility()
|
||||
|
||||
# UI setup
|
||||
ui = mcrfpy.sceneUI("anim_demo")
|
||||
ui.append(grid)
|
||||
grid.position = (100, 100)
|
||||
grid.size = (600, 400)
|
||||
|
||||
title = mcrfpy.Caption("Animation Demo - Grid Center & Entity Movement", 200, 20)
|
||||
title.fill_color = mcrfpy.Color(255, 255, 255)
|
||||
ui.append(title)
|
||||
|
||||
# 1. Position Animation Demo
|
||||
pos_frame = mcrfpy.Frame(50, 100, 80, 80)
|
||||
pos_frame.fill_color = (255, 100, 100)
|
||||
pos_frame.outline = 2
|
||||
ui.append(pos_frame)
|
||||
status = mcrfpy.Caption("Press 1: Move Player | 2: Move Enemy | 3: Perspective Shift | Q: Quit", 100, 50)
|
||||
status.fill_color = mcrfpy.Color(200, 200, 200)
|
||||
ui.append(status)
|
||||
|
||||
pos_label = mcrfpy.Caption((50, 80), "Position Animation", mcrfpy.default_font)
|
||||
pos_label.fill_color = (200, 200, 200)
|
||||
ui.append(pos_label)
|
||||
|
||||
# 2. Size Animation Demo
|
||||
size_frame = mcrfpy.Frame(200, 100, 50, 50)
|
||||
size_frame.fill_color = (100, 255, 100)
|
||||
size_frame.outline = 2
|
||||
ui.append(size_frame)
|
||||
|
||||
size_label = mcrfpy.Caption((200, 80), "Size Animation", mcrfpy.default_font)
|
||||
size_label.fill_color = (200, 200, 200)
|
||||
ui.append(size_label)
|
||||
|
||||
# 3. Color Animation Demo
|
||||
color_frame = mcrfpy.Frame(350, 100, 80, 80)
|
||||
color_frame.fill_color = (255, 0, 0)
|
||||
ui.append(color_frame)
|
||||
|
||||
color_label = mcrfpy.Caption((350, 80), "Color Animation", mcrfpy.default_font)
|
||||
color_label.fill_color = (200, 200, 200)
|
||||
ui.append(color_label)
|
||||
|
||||
# 4. Easing Functions Demo
|
||||
easing_y = 250
|
||||
easing_frames = []
|
||||
easings = ["linear", "easeIn", "easeOut", "easeInOut", "easeInElastic", "easeOutBounce"]
|
||||
|
||||
for i, easing in enumerate(easings):
|
||||
x = 50 + i * 120
|
||||
|
||||
frame = mcrfpy.Frame(x, easing_y, 20, 20)
|
||||
frame.fill_color = (100, 150, 255)
|
||||
ui.append(frame)
|
||||
easing_frames.append((frame, easing))
|
||||
|
||||
label = mcrfpy.Caption((x, easing_y - 20), easing, mcrfpy.default_font)
|
||||
label.size = 12
|
||||
label.fill_color = (200, 200, 200)
|
||||
ui.append(label)
|
||||
|
||||
# 5. Complex Animation Demo
|
||||
complex_frame = mcrfpy.Frame(300, 350, 100, 100)
|
||||
complex_frame.fill_color = (128, 128, 255)
|
||||
complex_frame.outline = 3
|
||||
ui.append(complex_frame)
|
||||
|
||||
complex_label = mcrfpy.Caption((300, 330), "Complex Multi-Property", mcrfpy.default_font)
|
||||
complex_label.fill_color = (200, 200, 200)
|
||||
ui.append(complex_label)
|
||||
|
||||
# Start animations
|
||||
def start_animations(runtime):
|
||||
# 1. Position animation - back and forth
|
||||
x_anim = mcrfpy.Animation("x", 500.0, 3.0, "easeInOut")
|
||||
x_anim.start(pos_frame)
|
||||
|
||||
# 2. Size animation - pulsing
|
||||
w_anim = mcrfpy.Animation("w", 150.0, 2.0, "easeInOut")
|
||||
h_anim = mcrfpy.Animation("h", 150.0, 2.0, "easeInOut")
|
||||
w_anim.start(size_frame)
|
||||
h_anim.start(size_frame)
|
||||
|
||||
# 3. Color animation - rainbow cycle
|
||||
color_anim = mcrfpy.Animation("fill_color", (0, 255, 255, 255), 2.0, "linear")
|
||||
color_anim.start(color_frame)
|
||||
|
||||
# 4. Easing demos - all move up with different easings
|
||||
for frame, easing in easing_frames:
|
||||
y_anim = mcrfpy.Animation("y", 150.0, 2.0, easing)
|
||||
y_anim.start(frame)
|
||||
|
||||
# 5. Complex animation - multiple properties
|
||||
cx_anim = mcrfpy.Animation("x", 500.0, 4.0, "easeInOut")
|
||||
cy_anim = mcrfpy.Animation("y", 400.0, 4.0, "easeOut")
|
||||
cw_anim = mcrfpy.Animation("w", 150.0, 4.0, "easeInElastic")
|
||||
ch_anim = mcrfpy.Animation("h", 150.0, 4.0, "easeInElastic")
|
||||
outline_anim = mcrfpy.Animation("outline", 10.0, 4.0, "linear")
|
||||
|
||||
cx_anim.start(complex_frame)
|
||||
cy_anim.start(complex_frame)
|
||||
cw_anim.start(complex_frame)
|
||||
ch_anim.start(complex_frame)
|
||||
outline_anim.start(complex_frame)
|
||||
|
||||
# Individual color component animations
|
||||
r_anim = mcrfpy.Animation("fill_color.r", 255.0, 4.0, "easeInOut")
|
||||
g_anim = mcrfpy.Animation("fill_color.g", 100.0, 4.0, "easeInOut")
|
||||
b_anim = mcrfpy.Animation("fill_color.b", 50.0, 4.0, "easeInOut")
|
||||
|
||||
r_anim.start(complex_frame)
|
||||
g_anim.start(complex_frame)
|
||||
b_anim.start(complex_frame)
|
||||
|
||||
print("All animations started!")
|
||||
|
||||
# Reverse some animations
|
||||
def reverse_animations(runtime):
|
||||
# Position back
|
||||
x_anim = mcrfpy.Animation("x", 50.0, 3.0, "easeInOut")
|
||||
x_anim.start(pos_frame)
|
||||
|
||||
# Size back
|
||||
w_anim = mcrfpy.Animation("w", 50.0, 2.0, "easeInOut")
|
||||
h_anim = mcrfpy.Animation("h", 50.0, 2.0, "easeInOut")
|
||||
w_anim.start(size_frame)
|
||||
h_anim.start(size_frame)
|
||||
|
||||
# Color cycle continues
|
||||
color_anim = mcrfpy.Animation("fill_color", (255, 0, 255, 255), 2.0, "linear")
|
||||
color_anim.start(color_frame)
|
||||
|
||||
# Easing frames back down
|
||||
for frame, easing in easing_frames:
|
||||
y_anim = mcrfpy.Animation("y", 250.0, 2.0, easing)
|
||||
y_anim.start(frame)
|
||||
|
||||
# Continue color cycle
|
||||
def cycle_colors(runtime):
|
||||
color_anim = mcrfpy.Animation("fill_color", (255, 255, 0, 255), 2.0, "linear")
|
||||
color_anim.start(color_frame)
|
||||
|
||||
# Info text
|
||||
info = mcrfpy.Caption((400, 550), "Watch as different properties animate with various easing functions!", mcrfpy.default_font)
|
||||
info.fill_color = (255, 255, 200)
|
||||
# Note: centered property doesn't exist for Caption
|
||||
info = mcrfpy.Caption("Perspective: Player", 500, 70)
|
||||
info.fill_color = mcrfpy.Color(100, 255, 100)
|
||||
ui.append(info)
|
||||
|
||||
# Schedule animations
|
||||
mcrfpy.setTimer("start", start_animations, 500)
|
||||
mcrfpy.setTimer("reverse", reverse_animations, 4000)
|
||||
mcrfpy.setTimer("cycle", cycle_colors, 2500)
|
||||
# Movement functions
|
||||
def move_player_demo():
|
||||
"""Demo player movement with camera follow"""
|
||||
# Calculate path to a destination
|
||||
path = player.path_to(20, 10)
|
||||
if not path:
|
||||
status.text = "No path available!"
|
||||
return
|
||||
|
||||
status.text = f"Moving player along {len(path)} steps..."
|
||||
|
||||
# Animate along path
|
||||
for i, (x, y) in enumerate(path[:5]): # First 5 steps
|
||||
delay = i * 500 # 500ms between steps
|
||||
|
||||
# Schedule movement
|
||||
def move_step(dt, px=x, py=y):
|
||||
# Animate entity position
|
||||
anim_x = mcrfpy.Animation("x", float(px), 0.4, "easeInOut")
|
||||
anim_y = mcrfpy.Animation("y", float(py), 0.4, "easeInOut")
|
||||
anim_x.start(player)
|
||||
anim_y.start(player)
|
||||
|
||||
# Update visibility
|
||||
player.update_visibility()
|
||||
|
||||
# Animate camera to follow
|
||||
center_x = px * 16 # Assuming 16x16 tiles
|
||||
center_y = py * 16
|
||||
cam_anim = mcrfpy.Animation("center", (center_x, center_y), 0.4, "easeOut")
|
||||
cam_anim.start(grid)
|
||||
|
||||
mcrfpy.setTimer(f"player_move_{i}", move_step, delay)
|
||||
|
||||
# Exit handler
|
||||
def on_key(key):
|
||||
if key == "Escape":
|
||||
mcrfpy.exit()
|
||||
def move_enemy_demo():
|
||||
"""Demo enemy movement"""
|
||||
# Calculate path
|
||||
path = enemy.path_to(10, 5)
|
||||
if not path:
|
||||
status.text = "Enemy has no path!"
|
||||
return
|
||||
|
||||
status.text = f"Moving enemy along {len(path)} steps..."
|
||||
|
||||
# Animate along path
|
||||
for i, (x, y) in enumerate(path[:5]): # First 5 steps
|
||||
delay = i * 500
|
||||
|
||||
def move_step(dt, ex=x, ey=y):
|
||||
anim_x = mcrfpy.Animation("x", float(ex), 0.4, "easeInOut")
|
||||
anim_y = mcrfpy.Animation("y", float(ey), 0.4, "easeInOut")
|
||||
anim_x.start(enemy)
|
||||
anim_y.start(enemy)
|
||||
enemy.update_visibility()
|
||||
|
||||
# If following enemy, update camera
|
||||
if grid.perspective == 1:
|
||||
center_x = ex * 16
|
||||
center_y = ey * 16
|
||||
cam_anim = mcrfpy.Animation("center", (center_x, center_y), 0.4, "easeOut")
|
||||
cam_anim.start(grid)
|
||||
|
||||
mcrfpy.setTimer(f"enemy_move_{i}", move_step, delay)
|
||||
|
||||
mcrfpy.keypressScene(on_key)
|
||||
def perspective_shift_demo():
|
||||
"""Demo dramatic perspective shift"""
|
||||
status.text = "Perspective shift in progress..."
|
||||
|
||||
# Phase 1: Zoom out
|
||||
zoom_out = mcrfpy.Animation("zoom", 0.5, 1.5, "easeInExpo")
|
||||
zoom_out.start(grid)
|
||||
|
||||
# Phase 2: Switch perspective at peak
|
||||
def switch_perspective(dt):
|
||||
if grid.perspective == 0:
|
||||
grid.perspective = 1
|
||||
info.text = "Perspective: Enemy"
|
||||
info.fill_color = mcrfpy.Color(255, 100, 100)
|
||||
target = enemy
|
||||
else:
|
||||
grid.perspective = 0
|
||||
info.text = "Perspective: Player"
|
||||
info.fill_color = mcrfpy.Color(100, 255, 100)
|
||||
target = player
|
||||
|
||||
# Update camera to new target
|
||||
center_x = target.x * 16
|
||||
center_y = target.y * 16
|
||||
cam_anim = mcrfpy.Animation("center", (center_x, center_y), 0.5, "linear")
|
||||
cam_anim.start(grid)
|
||||
|
||||
mcrfpy.setTimer("switch_persp", switch_perspective, 1600)
|
||||
|
||||
# Phase 3: Zoom back in
|
||||
def zoom_in(dt):
|
||||
zoom_in_anim = mcrfpy.Animation("zoom", 1.0, 1.5, "easeOutExpo")
|
||||
zoom_in_anim.start(grid)
|
||||
status.text = "Perspective shift complete!"
|
||||
|
||||
mcrfpy.setTimer("zoom_in", zoom_in, 2100)
|
||||
|
||||
print("Animation demo started! Press Escape to exit.")
|
||||
# Input handler
|
||||
def handle_input(key, state):
|
||||
if state != "start":
|
||||
return
|
||||
|
||||
if key == "q":
|
||||
print("Exiting demo...")
|
||||
sys.exit(0)
|
||||
elif key == "1":
|
||||
move_player_demo()
|
||||
elif key == "2":
|
||||
move_enemy_demo()
|
||||
elif key == "3":
|
||||
perspective_shift_demo()
|
||||
|
||||
# Set scene
|
||||
mcrfpy.setScene("anim_demo")
|
||||
mcrfpy.keypressScene(handle_input)
|
||||
|
||||
# Initial setup
|
||||
grid.perspective = 0
|
||||
grid.zoom = 1.0
|
||||
|
||||
# Center on player initially
|
||||
center_x = player.x * 16
|
||||
center_y = player.y * 16
|
||||
initial_cam = mcrfpy.Animation("center", (center_x, center_y), 0.5, "easeOut")
|
||||
initial_cam.start(grid)
|
||||
|
||||
print("Animation Demo Started!")
|
||||
print("======================")
|
||||
print("Press 1: Animate player movement with camera follow")
|
||||
print("Press 2: Animate enemy movement")
|
||||
print("Press 3: Dramatic perspective shift with zoom")
|
||||
print("Press Q: Quit")
|
||||
print()
|
||||
print("Watch how the grid center smoothly follows entities")
|
||||
print("and how perspective shifts create cinematic effects!")
|
||||
235
tests/astar_vs_dijkstra.py
Normal file
235
tests/astar_vs_dijkstra.py
Normal file
|
|
@ -0,0 +1,235 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
A* vs Dijkstra Visual Comparison
|
||||
=================================
|
||||
|
||||
Shows the difference between A* (single target) and Dijkstra (multi-target).
|
||||
"""
|
||||
|
||||
import mcrfpy
|
||||
import sys
|
||||
|
||||
# Colors
|
||||
WALL_COLOR = mcrfpy.Color(40, 20, 20)
|
||||
FLOOR_COLOR = mcrfpy.Color(60, 60, 80)
|
||||
ASTAR_COLOR = mcrfpy.Color(0, 255, 0) # Green for A*
|
||||
DIJKSTRA_COLOR = mcrfpy.Color(0, 150, 255) # Blue for Dijkstra
|
||||
START_COLOR = mcrfpy.Color(255, 100, 100) # Red for start
|
||||
END_COLOR = mcrfpy.Color(255, 255, 100) # Yellow for end
|
||||
|
||||
# Global state
|
||||
grid = None
|
||||
mode = "ASTAR"
|
||||
start_pos = (5, 10)
|
||||
end_pos = (27, 10) # Changed from 25 to 27 to avoid the wall
|
||||
|
||||
def create_map():
|
||||
"""Create a map with obstacles to show pathfinding differences"""
|
||||
global grid
|
||||
|
||||
mcrfpy.createScene("pathfinding_comparison")
|
||||
|
||||
# Create grid
|
||||
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).color = FLOOR_COLOR
|
||||
|
||||
# Create obstacles that make A* and Dijkstra differ
|
||||
obstacles = [
|
||||
# Vertical wall with gaps
|
||||
[(15, y) for y in range(3, 17) if y not in [8, 12]],
|
||||
# Horizontal walls
|
||||
[(x, 5) for x in range(10, 20)],
|
||||
[(x, 15) for x in range(10, 20)],
|
||||
# Maze-like structure
|
||||
[(x, 10) for x in range(20, 25)],
|
||||
[(25, y) for y in range(5, 15)],
|
||||
]
|
||||
|
||||
for obstacle_group in obstacles:
|
||||
for x, y in obstacle_group:
|
||||
grid.at(x, y).walkable = False
|
||||
grid.at(x, y).color = WALL_COLOR
|
||||
|
||||
# Mark start and end
|
||||
grid.at(start_pos[0], start_pos[1]).color = START_COLOR
|
||||
grid.at(end_pos[0], end_pos[1]).color = END_COLOR
|
||||
|
||||
def clear_paths():
|
||||
"""Clear path highlighting"""
|
||||
for y in range(20):
|
||||
for x in range(30):
|
||||
cell = grid.at(x, y)
|
||||
if cell.walkable:
|
||||
cell.color = FLOOR_COLOR
|
||||
|
||||
# Restore start and end colors
|
||||
grid.at(start_pos[0], start_pos[1]).color = START_COLOR
|
||||
grid.at(end_pos[0], end_pos[1]).color = END_COLOR
|
||||
|
||||
def show_astar():
|
||||
"""Show A* path"""
|
||||
clear_paths()
|
||||
|
||||
# Compute A* path
|
||||
path = grid.compute_astar_path(start_pos[0], start_pos[1], end_pos[0], end_pos[1])
|
||||
|
||||
# Color the path
|
||||
for i, (x, y) in enumerate(path):
|
||||
if (x, y) != start_pos and (x, y) != end_pos:
|
||||
grid.at(x, y).color = ASTAR_COLOR
|
||||
|
||||
status_text.text = f"A* Path: {len(path)} steps (optimized for single target)"
|
||||
status_text.fill_color = ASTAR_COLOR
|
||||
|
||||
def show_dijkstra():
|
||||
"""Show Dijkstra exploration"""
|
||||
clear_paths()
|
||||
|
||||
# Compute Dijkstra from start
|
||||
grid.compute_dijkstra(start_pos[0], start_pos[1])
|
||||
|
||||
# Color cells by distance (showing exploration)
|
||||
max_dist = 40.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:
|
||||
# Color based on distance
|
||||
intensity = int(255 * (1 - dist / max_dist))
|
||||
grid.at(x, y).color = mcrfpy.Color(0, intensity // 2, intensity)
|
||||
|
||||
# Get the actual path
|
||||
path = grid.get_dijkstra_path(end_pos[0], end_pos[1])
|
||||
|
||||
# Highlight the actual path more brightly
|
||||
for x, y in path:
|
||||
if (x, y) != start_pos and (x, y) != end_pos:
|
||||
grid.at(x, y).color = DIJKSTRA_COLOR
|
||||
|
||||
# Restore start and end
|
||||
grid.at(start_pos[0], start_pos[1]).color = START_COLOR
|
||||
grid.at(end_pos[0], end_pos[1]).color = END_COLOR
|
||||
|
||||
status_text.text = f"Dijkstra: {len(path)} steps (explores all directions)"
|
||||
status_text.fill_color = DIJKSTRA_COLOR
|
||||
|
||||
def show_both():
|
||||
"""Show both paths overlaid"""
|
||||
clear_paths()
|
||||
|
||||
# Get both paths
|
||||
astar_path = grid.compute_astar_path(start_pos[0], start_pos[1], end_pos[0], end_pos[1])
|
||||
grid.compute_dijkstra(start_pos[0], start_pos[1])
|
||||
dijkstra_path = grid.get_dijkstra_path(end_pos[0], end_pos[1])
|
||||
|
||||
print(astar_path, dijkstra_path)
|
||||
|
||||
# Color Dijkstra path first (blue)
|
||||
for x, y in dijkstra_path:
|
||||
if (x, y) != start_pos and (x, y) != end_pos:
|
||||
grid.at(x, y).color = DIJKSTRA_COLOR
|
||||
|
||||
# Then A* path (green) - will overwrite shared cells
|
||||
for x, y in astar_path:
|
||||
if (x, y) != start_pos and (x, y) != end_pos:
|
||||
grid.at(x, y).color = ASTAR_COLOR
|
||||
|
||||
# Mark differences
|
||||
different_cells = []
|
||||
for cell in dijkstra_path:
|
||||
if cell not in astar_path:
|
||||
different_cells.append(cell)
|
||||
|
||||
status_text.text = f"Both paths: A*={len(astar_path)} steps, Dijkstra={len(dijkstra_path)} steps"
|
||||
if different_cells:
|
||||
info_text.text = f"Paths differ at {len(different_cells)} cells"
|
||||
else:
|
||||
info_text.text = "Paths are identical"
|
||||
|
||||
def handle_keypress(key_str, state):
|
||||
"""Handle keyboard input"""
|
||||
global mode
|
||||
if state == "end": return
|
||||
print(key_str)
|
||||
if key_str == "Esc" or key_str == "Q":
|
||||
print("\nExiting...")
|
||||
sys.exit(0)
|
||||
elif key_str == "A" or key_str == "1":
|
||||
mode = "ASTAR"
|
||||
show_astar()
|
||||
elif key_str == "D" or key_str == "2":
|
||||
mode = "DIJKSTRA"
|
||||
show_dijkstra()
|
||||
elif key_str == "B" or key_str == "3":
|
||||
mode = "BOTH"
|
||||
show_both()
|
||||
elif key_str == "Space":
|
||||
# Refresh current mode
|
||||
if mode == "ASTAR":
|
||||
show_astar()
|
||||
elif mode == "DIJKSTRA":
|
||||
show_dijkstra()
|
||||
else:
|
||||
show_both()
|
||||
|
||||
# Create the demo
|
||||
print("A* vs Dijkstra Pathfinding Comparison")
|
||||
print("=====================================")
|
||||
print("Controls:")
|
||||
print(" A or 1 - Show A* path (green)")
|
||||
print(" D or 2 - Show Dijkstra (blue gradient)")
|
||||
print(" B or 3 - Show both paths")
|
||||
print(" Q/ESC - Quit")
|
||||
print()
|
||||
print("A* is optimized for single-target pathfinding")
|
||||
print("Dijkstra explores in all directions (good for multiple targets)")
|
||||
|
||||
create_map()
|
||||
|
||||
# Set up UI
|
||||
ui = mcrfpy.sceneUI("pathfinding_comparison")
|
||||
ui.append(grid)
|
||||
|
||||
# Scale and position
|
||||
grid.size = (600, 400) # 30*20, 20*20
|
||||
grid.position = (100, 100)
|
||||
|
||||
# Add title
|
||||
title = mcrfpy.Caption("A* vs Dijkstra Pathfinding", 250, 20)
|
||||
title.fill_color = mcrfpy.Color(255, 255, 255)
|
||||
ui.append(title)
|
||||
|
||||
# Add status
|
||||
status_text = mcrfpy.Caption("Press A for A*, D for Dijkstra, B for Both", 100, 60)
|
||||
status_text.fill_color = mcrfpy.Color(200, 200, 200)
|
||||
ui.append(status_text)
|
||||
|
||||
# Add info
|
||||
info_text = mcrfpy.Caption("", 100, 520)
|
||||
info_text.fill_color = mcrfpy.Color(200, 200, 200)
|
||||
ui.append(info_text)
|
||||
|
||||
# Add legend
|
||||
legend1 = mcrfpy.Caption("Red=Start, Yellow=End, Green=A*, Blue=Dijkstra", 100, 540)
|
||||
legend1.fill_color = mcrfpy.Color(150, 150, 150)
|
||||
ui.append(legend1)
|
||||
|
||||
legend2 = mcrfpy.Caption("Dark=Walls, Light=Floor", 100, 560)
|
||||
legend2.fill_color = mcrfpy.Color(150, 150, 150)
|
||||
ui.append(legend2)
|
||||
|
||||
# Set scene and input
|
||||
mcrfpy.setScene("pathfinding_comparison")
|
||||
mcrfpy.keypressScene(handle_keypress)
|
||||
|
||||
# Show initial A* path
|
||||
show_astar()
|
||||
|
||||
print("\nDemo ready!")
|
||||
99
tests/debug_astar_demo.py
Normal file
99
tests/debug_astar_demo.py
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Debug the astar_vs_dijkstra demo issue"""
|
||||
|
||||
import mcrfpy
|
||||
import sys
|
||||
|
||||
# Same setup as the demo
|
||||
start_pos = (5, 10)
|
||||
end_pos = (25, 10)
|
||||
|
||||
print("Debugging A* vs Dijkstra demo...")
|
||||
print(f"Start: {start_pos}, End: {end_pos}")
|
||||
|
||||
# Create scene and grid
|
||||
mcrfpy.createScene("debug")
|
||||
grid = mcrfpy.Grid(grid_x=30, grid_y=20)
|
||||
|
||||
# Initialize all as floor
|
||||
print("\nInitializing 30x20 grid...")
|
||||
for y in range(20):
|
||||
for x in range(30):
|
||||
grid.at(x, y).walkable = True
|
||||
|
||||
# Test path before obstacles
|
||||
print("\nTest 1: Path with no obstacles")
|
||||
path1 = grid.compute_astar_path(start_pos[0], start_pos[1], end_pos[0], end_pos[1])
|
||||
print(f" Path: {path1[:5]}...{path1[-3:] if len(path1) > 5 else ''}")
|
||||
print(f" Length: {len(path1)}")
|
||||
|
||||
# Add obstacles from the demo
|
||||
obstacles = [
|
||||
# Vertical wall with gaps
|
||||
[(15, y) for y in range(3, 17) if y not in [8, 12]],
|
||||
# Horizontal walls
|
||||
[(x, 5) for x in range(10, 20)],
|
||||
[(x, 15) for x in range(10, 20)],
|
||||
# Maze-like structure
|
||||
[(x, 10) for x in range(20, 25)],
|
||||
[(25, y) for y in range(5, 15)],
|
||||
]
|
||||
|
||||
print("\nAdding obstacles...")
|
||||
wall_count = 0
|
||||
for obstacle_group in obstacles:
|
||||
for x, y in obstacle_group:
|
||||
grid.at(x, y).walkable = False
|
||||
wall_count += 1
|
||||
if wall_count <= 5:
|
||||
print(f" Wall at ({x}, {y})")
|
||||
|
||||
print(f" Total walls added: {wall_count}")
|
||||
|
||||
# Check specific cells
|
||||
print(f"\nChecking key positions:")
|
||||
print(f" Start ({start_pos[0]}, {start_pos[1]}): walkable={grid.at(start_pos[0], start_pos[1]).walkable}")
|
||||
print(f" End ({end_pos[0]}, {end_pos[1]}): walkable={grid.at(end_pos[0], end_pos[1]).walkable}")
|
||||
|
||||
# Check if path is blocked
|
||||
print(f"\nChecking horizontal line at y=10:")
|
||||
blocked_x = []
|
||||
for x in range(30):
|
||||
if not grid.at(x, 10).walkable:
|
||||
blocked_x.append(x)
|
||||
|
||||
print(f" Blocked x positions: {blocked_x}")
|
||||
|
||||
# Test path with obstacles
|
||||
print("\nTest 2: Path with obstacles")
|
||||
path2 = grid.compute_astar_path(start_pos[0], start_pos[1], end_pos[0], end_pos[1])
|
||||
print(f" Path: {path2}")
|
||||
print(f" Length: {len(path2)}")
|
||||
|
||||
# Check if there's any path at all
|
||||
if not path2:
|
||||
print("\n No path found! Checking why...")
|
||||
|
||||
# Check if we can reach the vertical wall gap
|
||||
print("\n Testing path to wall gap at (15, 8):")
|
||||
path_to_gap = grid.compute_astar_path(start_pos[0], start_pos[1], 15, 8)
|
||||
print(f" Path to gap: {path_to_gap}")
|
||||
|
||||
# Check from gap to end
|
||||
print("\n Testing path from gap (15, 8) to end:")
|
||||
path_from_gap = grid.compute_astar_path(15, 8, end_pos[0], end_pos[1])
|
||||
print(f" Path from gap: {path_from_gap}")
|
||||
|
||||
# Check walls more carefully
|
||||
print("\nDetailed wall analysis:")
|
||||
print(" Walls at x=25 (blocking end?):")
|
||||
for y in range(5, 15):
|
||||
print(f" ({25}, {y}): walkable={grid.at(25, y).walkable}")
|
||||
|
||||
def timer_cb(dt):
|
||||
sys.exit(0)
|
||||
|
||||
ui = mcrfpy.sceneUI("debug")
|
||||
ui.append(grid)
|
||||
mcrfpy.setScene("debug")
|
||||
mcrfpy.setTimer("exit", timer_cb, 100)
|
||||
80
tests/debug_empty_paths.py
Normal file
80
tests/debug_empty_paths.py
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Debug empty paths issue"""
|
||||
|
||||
import mcrfpy
|
||||
import sys
|
||||
|
||||
print("Debugging empty paths...")
|
||||
|
||||
# Create scene and grid
|
||||
mcrfpy.createScene("debug")
|
||||
grid = mcrfpy.Grid(grid_x=10, grid_y=10)
|
||||
|
||||
# Initialize grid - all walkable
|
||||
print("\nInitializing grid...")
|
||||
for y in range(10):
|
||||
for x in range(10):
|
||||
grid.at(x, y).walkable = True
|
||||
|
||||
# Test simple path
|
||||
print("\nTest 1: Simple path from (0,0) to (5,5)")
|
||||
path = grid.compute_astar_path(0, 0, 5, 5)
|
||||
print(f" A* path: {path}")
|
||||
print(f" Path length: {len(path)}")
|
||||
|
||||
# Test with Dijkstra
|
||||
print("\nTest 2: Same path with Dijkstra")
|
||||
grid.compute_dijkstra(0, 0)
|
||||
dpath = grid.get_dijkstra_path(5, 5)
|
||||
print(f" Dijkstra path: {dpath}")
|
||||
print(f" Path length: {len(dpath)}")
|
||||
|
||||
# Check if grid is properly initialized
|
||||
print("\nTest 3: Checking grid cells")
|
||||
for y in range(3):
|
||||
for x in range(3):
|
||||
cell = grid.at(x, y)
|
||||
print(f" Cell ({x},{y}): walkable={cell.walkable}")
|
||||
|
||||
# Test with walls
|
||||
print("\nTest 4: Path with wall")
|
||||
grid.at(2, 2).walkable = False
|
||||
grid.at(3, 2).walkable = False
|
||||
grid.at(4, 2).walkable = False
|
||||
print(" Added wall at y=2, x=2,3,4")
|
||||
|
||||
path2 = grid.compute_astar_path(0, 0, 5, 5)
|
||||
print(f" A* path with wall: {path2}")
|
||||
print(f" Path length: {len(path2)}")
|
||||
|
||||
# Test invalid paths
|
||||
print("\nTest 5: Path to blocked cell")
|
||||
grid.at(9, 9).walkable = False
|
||||
path3 = grid.compute_astar_path(0, 0, 9, 9)
|
||||
print(f" Path to blocked cell: {path3}")
|
||||
|
||||
# Check TCOD map sync
|
||||
print("\nTest 6: Verify TCOD map is synced")
|
||||
# Try to force a sync
|
||||
print(" Checking if syncTCODMap exists...")
|
||||
if hasattr(grid, 'sync_tcod_map'):
|
||||
print(" Calling sync_tcod_map()")
|
||||
grid.sync_tcod_map()
|
||||
else:
|
||||
print(" No sync_tcod_map method found")
|
||||
|
||||
# Try path again
|
||||
print("\nTest 7: Path after potential sync")
|
||||
path4 = grid.compute_astar_path(0, 0, 5, 5)
|
||||
print(f" A* path: {path4}")
|
||||
|
||||
def timer_cb(dt):
|
||||
sys.exit(0)
|
||||
|
||||
# Quick UI setup
|
||||
ui = mcrfpy.sceneUI("debug")
|
||||
ui.append(grid)
|
||||
mcrfpy.setScene("debug")
|
||||
mcrfpy.setTimer("exit", timer_cb, 100)
|
||||
|
||||
print("\nStarting timer...")
|
||||
59
tests/debug_visibility.py
Normal file
59
tests/debug_visibility.py
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Debug visibility crash"""
|
||||
|
||||
import mcrfpy
|
||||
import sys
|
||||
|
||||
print("Debug visibility...")
|
||||
|
||||
# Create scene and grid
|
||||
mcrfpy.createScene("debug")
|
||||
grid = mcrfpy.Grid(grid_x=5, grid_y=5)
|
||||
|
||||
# Initialize grid
|
||||
print("Initializing grid...")
|
||||
for y in range(5):
|
||||
for x in range(5):
|
||||
cell = grid.at(x, y)
|
||||
cell.walkable = True
|
||||
cell.transparent = True
|
||||
|
||||
# Create entity
|
||||
print("Creating entity...")
|
||||
entity = mcrfpy.Entity(2, 2)
|
||||
entity.sprite_index = 64
|
||||
grid.entities.append(entity)
|
||||
print(f"Entity at ({entity.x}, {entity.y})")
|
||||
|
||||
# Check gridstate
|
||||
print(f"\nGridstate length: {len(entity.gridstate)}")
|
||||
print(f"Expected: {5 * 5}")
|
||||
|
||||
# Try to access gridstate
|
||||
print("\nChecking gridstate access...")
|
||||
try:
|
||||
if len(entity.gridstate) > 0:
|
||||
state = entity.gridstate[0]
|
||||
print(f"First state: visible={state.visible}, discovered={state.discovered}")
|
||||
except Exception as e:
|
||||
print(f"Error accessing gridstate: {e}")
|
||||
|
||||
# Try update_visibility
|
||||
print("\nTrying update_visibility...")
|
||||
try:
|
||||
entity.update_visibility()
|
||||
print("update_visibility succeeded")
|
||||
except Exception as e:
|
||||
print(f"Error in update_visibility: {e}")
|
||||
|
||||
# Try perspective
|
||||
print("\nTesting perspective...")
|
||||
print(f"Initial perspective: {grid.perspective}")
|
||||
try:
|
||||
grid.perspective = 0
|
||||
print(f"Set perspective to 0: {grid.perspective}")
|
||||
except Exception as e:
|
||||
print(f"Error setting perspective: {e}")
|
||||
|
||||
print("\nTest complete")
|
||||
sys.exit(0)
|
||||
234
tests/dijkstra_all_paths.py
Normal file
234
tests/dijkstra_all_paths.py
Normal file
|
|
@ -0,0 +1,234 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Dijkstra Demo - Shows ALL Path Combinations (Including Invalid)
|
||||
===============================================================
|
||||
|
||||
Cycles through every possible entity pair to demonstrate both
|
||||
valid paths and properly handled invalid paths (empty lists).
|
||||
"""
|
||||
|
||||
import mcrfpy
|
||||
import sys
|
||||
|
||||
# High contrast colors
|
||||
WALL_COLOR = mcrfpy.Color(40, 20, 20) # Very dark red/brown
|
||||
FLOOR_COLOR = mcrfpy.Color(60, 60, 80) # Dark blue-gray
|
||||
PATH_COLOR = mcrfpy.Color(0, 255, 0) # Bright green
|
||||
START_COLOR = mcrfpy.Color(255, 100, 100) # Light red
|
||||
END_COLOR = mcrfpy.Color(100, 100, 255) # Light blue
|
||||
NO_PATH_COLOR = mcrfpy.Color(255, 0, 0) # Pure red for unreachable
|
||||
|
||||
# Global state
|
||||
grid = None
|
||||
entities = []
|
||||
current_combo_index = 0
|
||||
all_combinations = [] # All possible pairs
|
||||
current_path = []
|
||||
|
||||
def create_map():
|
||||
"""Create the map with entities"""
|
||||
global grid, entities, all_combinations
|
||||
|
||||
mcrfpy.createScene("dijkstra_all")
|
||||
|
||||
# Create grid
|
||||
grid = mcrfpy.Grid(grid_x=14, grid_y=10)
|
||||
grid.fill_color = mcrfpy.Color(0, 0, 0)
|
||||
|
||||
# Map layout - Entity 1 is intentionally trapped!
|
||||
map_layout = [
|
||||
"..............", # Row 0
|
||||
"..W.....WWWW..", # Row 1
|
||||
"..W.W...W.EW..", # Row 2 - Entity 1 TRAPPED at (10,2)
|
||||
"..W.....W..W..", # Row 3
|
||||
"..W...E.WWWW..", # Row 4 - Entity 2 at (6,4)
|
||||
"E.W...........", # Row 5 - Entity 3 at (0,5)
|
||||
"..W...........", # Row 6
|
||||
"..W...........", # Row 7
|
||||
"..W.WWW.......", # Row 8
|
||||
"..............", # Row 9
|
||||
]
|
||||
|
||||
# Create the map
|
||||
entity_positions = []
|
||||
for y, row in enumerate(map_layout):
|
||||
for x, char in enumerate(row):
|
||||
cell = grid.at(x, y)
|
||||
|
||||
if char == 'W':
|
||||
cell.walkable = False
|
||||
cell.color = WALL_COLOR
|
||||
else:
|
||||
cell.walkable = True
|
||||
cell.color = FLOOR_COLOR
|
||||
|
||||
if char == 'E':
|
||||
entity_positions.append((x, y))
|
||||
|
||||
# Create entities
|
||||
entities = []
|
||||
for i, (x, y) in enumerate(entity_positions):
|
||||
entity = mcrfpy.Entity(x, y)
|
||||
entity.sprite_index = 49 + i # '1', '2', '3'
|
||||
grid.entities.append(entity)
|
||||
entities.append(entity)
|
||||
|
||||
print("Map Analysis:")
|
||||
print("=============")
|
||||
for i, (x, y) in enumerate(entity_positions):
|
||||
print(f"Entity {i+1} at ({x}, {y})")
|
||||
|
||||
# Generate ALL combinations (including invalid ones)
|
||||
all_combinations = []
|
||||
for i in range(len(entities)):
|
||||
for j in range(len(entities)):
|
||||
if i != j: # Skip self-paths
|
||||
all_combinations.append((i, j))
|
||||
|
||||
print(f"\nTotal path combinations to test: {len(all_combinations)}")
|
||||
|
||||
def clear_path_colors():
|
||||
"""Reset all floor tiles to original color"""
|
||||
global current_path
|
||||
|
||||
for y in range(grid.grid_y):
|
||||
for x in range(grid.grid_x):
|
||||
cell = grid.at(x, y)
|
||||
if cell.walkable:
|
||||
cell.color = FLOOR_COLOR
|
||||
|
||||
current_path = []
|
||||
|
||||
def show_combination(index):
|
||||
"""Show a specific path combination (valid or invalid)"""
|
||||
global current_combo_index, current_path
|
||||
|
||||
current_combo_index = index % len(all_combinations)
|
||||
from_idx, to_idx = all_combinations[current_combo_index]
|
||||
|
||||
# Clear previous path
|
||||
clear_path_colors()
|
||||
|
||||
# Get entities
|
||||
e_from = entities[from_idx]
|
||||
e_to = entities[to_idx]
|
||||
|
||||
# Calculate path
|
||||
path = e_from.path_to(int(e_to.x), int(e_to.y))
|
||||
current_path = path if path else []
|
||||
|
||||
# Always color start and end positions
|
||||
grid.at(int(e_from.x), int(e_from.y)).color = START_COLOR
|
||||
grid.at(int(e_to.x), int(e_to.y)).color = NO_PATH_COLOR if not path else END_COLOR
|
||||
|
||||
# Color the path if it exists
|
||||
if path:
|
||||
# Color intermediate steps
|
||||
for i, (x, y) in enumerate(path):
|
||||
if i > 0 and i < len(path) - 1:
|
||||
grid.at(x, y).color = PATH_COLOR
|
||||
|
||||
status_text.text = f"Path {current_combo_index + 1}/{len(all_combinations)}: Entity {from_idx+1} → Entity {to_idx+1} = {len(path)} steps"
|
||||
status_text.fill_color = mcrfpy.Color(100, 255, 100) # Green for valid
|
||||
|
||||
# Show path steps
|
||||
path_display = []
|
||||
for i, (x, y) in enumerate(path[:5]):
|
||||
path_display.append(f"({x},{y})")
|
||||
if len(path) > 5:
|
||||
path_display.append("...")
|
||||
path_text.text = "Path: " + " → ".join(path_display)
|
||||
else:
|
||||
status_text.text = f"Path {current_combo_index + 1}/{len(all_combinations)}: Entity {from_idx+1} → Entity {to_idx+1} = NO PATH!"
|
||||
status_text.fill_color = mcrfpy.Color(255, 100, 100) # Red for invalid
|
||||
path_text.text = "Path: [] (No valid path exists)"
|
||||
|
||||
# Update info
|
||||
info_text.text = f"From: Entity {from_idx+1} at ({int(e_from.x)}, {int(e_from.y)}) | To: Entity {to_idx+1} at ({int(e_to.x)}, {int(e_to.y)})"
|
||||
|
||||
def handle_keypress(key_str, state):
|
||||
"""Handle keyboard input"""
|
||||
global current_combo_index
|
||||
if state == "end": return
|
||||
|
||||
if key_str == "Esc" or key_str == "Q":
|
||||
print("\nExiting...")
|
||||
sys.exit(0)
|
||||
elif key_str == "Space" or key_str == "N":
|
||||
show_combination(current_combo_index + 1)
|
||||
elif key_str == "P":
|
||||
show_combination(current_combo_index - 1)
|
||||
elif key_str == "R":
|
||||
show_combination(current_combo_index)
|
||||
elif key_str in "123456":
|
||||
combo_num = int(key_str) - 1 # 0-based index
|
||||
if combo_num < len(all_combinations):
|
||||
show_combination(combo_num)
|
||||
|
||||
# Create the demo
|
||||
print("Dijkstra All Paths Demo")
|
||||
print("=======================")
|
||||
print("Shows ALL path combinations including invalid ones")
|
||||
print("Entity 1 is trapped - paths to/from it will be empty!")
|
||||
print()
|
||||
|
||||
create_map()
|
||||
|
||||
# Set up UI
|
||||
ui = mcrfpy.sceneUI("dijkstra_all")
|
||||
ui.append(grid)
|
||||
|
||||
# Scale and position
|
||||
grid.size = (560, 400)
|
||||
grid.position = (120, 100)
|
||||
|
||||
# Add title
|
||||
title = mcrfpy.Caption("Dijkstra - All Paths (Valid & Invalid)", 200, 20)
|
||||
title.fill_color = mcrfpy.Color(255, 255, 255)
|
||||
ui.append(title)
|
||||
|
||||
# Add status (will change color based on validity)
|
||||
status_text = mcrfpy.Caption("Ready", 120, 60)
|
||||
status_text.fill_color = mcrfpy.Color(255, 255, 100)
|
||||
ui.append(status_text)
|
||||
|
||||
# Add info
|
||||
info_text = mcrfpy.Caption("", 120, 80)
|
||||
info_text.fill_color = mcrfpy.Color(200, 200, 200)
|
||||
ui.append(info_text)
|
||||
|
||||
# Add path display
|
||||
path_text = mcrfpy.Caption("Path: None", 120, 520)
|
||||
path_text.fill_color = mcrfpy.Color(200, 200, 200)
|
||||
ui.append(path_text)
|
||||
|
||||
# Add controls
|
||||
controls = mcrfpy.Caption("SPACE/N=Next, P=Previous, 1-6=Jump to path, Q=Quit", 120, 540)
|
||||
controls.fill_color = mcrfpy.Color(150, 150, 150)
|
||||
ui.append(controls)
|
||||
|
||||
# Add legend
|
||||
legend = mcrfpy.Caption("Red Start→Blue End (valid) | Red Start→Red End (invalid)", 120, 560)
|
||||
legend.fill_color = mcrfpy.Color(150, 150, 150)
|
||||
ui.append(legend)
|
||||
|
||||
# Expected results info
|
||||
expected = mcrfpy.Caption("Entity 1 is trapped: paths 1→2, 1→3, 2→1, 3→1 will fail", 120, 580)
|
||||
expected.fill_color = mcrfpy.Color(255, 150, 150)
|
||||
ui.append(expected)
|
||||
|
||||
# Set scene first, then set up input handler
|
||||
mcrfpy.setScene("dijkstra_all")
|
||||
mcrfpy.keypressScene(handle_keypress)
|
||||
|
||||
# Show first combination
|
||||
show_combination(0)
|
||||
|
||||
print("\nDemo ready!")
|
||||
print("Expected results:")
|
||||
print(" Path 1: Entity 1→2 = NO PATH (Entity 1 is trapped)")
|
||||
print(" Path 2: Entity 1→3 = NO PATH (Entity 1 is trapped)")
|
||||
print(" Path 3: Entity 2→1 = NO PATH (Entity 1 is trapped)")
|
||||
print(" Path 4: Entity 2→3 = Valid path")
|
||||
print(" Path 5: Entity 3→1 = NO PATH (Entity 1 is trapped)")
|
||||
print(" Path 6: Entity 3→2 = Valid path")
|
||||
236
tests/dijkstra_cycle_paths.py
Normal file
236
tests/dijkstra_cycle_paths.py
Normal file
|
|
@ -0,0 +1,236 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Dijkstra Demo - Cycles Through Different Path Combinations
|
||||
==========================================================
|
||||
|
||||
Shows paths between different entity pairs, skipping impossible paths.
|
||||
"""
|
||||
|
||||
import mcrfpy
|
||||
import sys
|
||||
|
||||
# High contrast colors
|
||||
WALL_COLOR = mcrfpy.Color(40, 20, 20) # Very dark red/brown
|
||||
FLOOR_COLOR = mcrfpy.Color(60, 60, 80) # Dark blue-gray
|
||||
PATH_COLOR = mcrfpy.Color(0, 255, 0) # Bright green
|
||||
START_COLOR = mcrfpy.Color(255, 100, 100) # Light red
|
||||
END_COLOR = mcrfpy.Color(100, 100, 255) # Light blue
|
||||
|
||||
# Global state
|
||||
grid = None
|
||||
entities = []
|
||||
current_path_index = 0
|
||||
path_combinations = []
|
||||
current_path = []
|
||||
|
||||
def create_map():
|
||||
"""Create the map with entities"""
|
||||
global grid, entities
|
||||
|
||||
mcrfpy.createScene("dijkstra_cycle")
|
||||
|
||||
# Create grid
|
||||
grid = mcrfpy.Grid(grid_x=14, grid_y=10)
|
||||
grid.fill_color = mcrfpy.Color(0, 0, 0)
|
||||
|
||||
# Map layout
|
||||
map_layout = [
|
||||
"..............", # Row 0
|
||||
"..W.....WWWW..", # Row 1
|
||||
"..W.W...W.EW..", # Row 2 - Entity 1 at (10,2) is TRAPPED!
|
||||
"..W.....W..W..", # Row 3
|
||||
"..W...E.WWWW..", # Row 4 - Entity 2 at (6,4)
|
||||
"E.W...........", # Row 5 - Entity 3 at (0,5)
|
||||
"..W...........", # Row 6
|
||||
"..W...........", # Row 7
|
||||
"..W.WWW.......", # Row 8
|
||||
"..............", # Row 9
|
||||
]
|
||||
|
||||
# Create the map
|
||||
entity_positions = []
|
||||
for y, row in enumerate(map_layout):
|
||||
for x, char in enumerate(row):
|
||||
cell = grid.at(x, y)
|
||||
|
||||
if char == 'W':
|
||||
cell.walkable = False
|
||||
cell.color = WALL_COLOR
|
||||
else:
|
||||
cell.walkable = True
|
||||
cell.color = FLOOR_COLOR
|
||||
|
||||
if char == 'E':
|
||||
entity_positions.append((x, y))
|
||||
|
||||
# Create entities
|
||||
entities = []
|
||||
for i, (x, y) in enumerate(entity_positions):
|
||||
entity = mcrfpy.Entity(x, y)
|
||||
entity.sprite_index = 49 + i # '1', '2', '3'
|
||||
grid.entities.append(entity)
|
||||
entities.append(entity)
|
||||
|
||||
print("Entities created:")
|
||||
for i, (x, y) in enumerate(entity_positions):
|
||||
print(f" Entity {i+1} at ({x}, {y})")
|
||||
|
||||
# Check which entity is trapped
|
||||
print("\nChecking accessibility:")
|
||||
for i, e in enumerate(entities):
|
||||
# Try to path to each other entity
|
||||
can_reach = []
|
||||
for j, other in enumerate(entities):
|
||||
if i != j:
|
||||
path = e.path_to(int(other.x), int(other.y))
|
||||
if path:
|
||||
can_reach.append(j+1)
|
||||
|
||||
if not can_reach:
|
||||
print(f" Entity {i+1} at ({int(e.x)}, {int(e.y)}) is TRAPPED!")
|
||||
else:
|
||||
print(f" Entity {i+1} can reach entities: {can_reach}")
|
||||
|
||||
# Generate valid path combinations (excluding trapped entity)
|
||||
global path_combinations
|
||||
path_combinations = []
|
||||
|
||||
# Only paths between entities 2 and 3 (indices 1 and 2) will work
|
||||
# since entity 1 (index 0) is trapped
|
||||
if len(entities) >= 3:
|
||||
# Entity 2 to Entity 3
|
||||
path = entities[1].path_to(int(entities[2].x), int(entities[2].y))
|
||||
if path:
|
||||
path_combinations.append((1, 2, path))
|
||||
|
||||
# Entity 3 to Entity 2
|
||||
path = entities[2].path_to(int(entities[1].x), int(entities[1].y))
|
||||
if path:
|
||||
path_combinations.append((2, 1, path))
|
||||
|
||||
print(f"\nFound {len(path_combinations)} valid paths")
|
||||
|
||||
def clear_path_colors():
|
||||
"""Reset all floor tiles to original color"""
|
||||
global current_path
|
||||
|
||||
for y in range(grid.grid_y):
|
||||
for x in range(grid.grid_x):
|
||||
cell = grid.at(x, y)
|
||||
if cell.walkable:
|
||||
cell.color = FLOOR_COLOR
|
||||
|
||||
current_path = []
|
||||
|
||||
def show_path(index):
|
||||
"""Show a specific path combination"""
|
||||
global current_path_index, current_path
|
||||
|
||||
if not path_combinations:
|
||||
status_text.text = "No valid paths available (Entity 1 is trapped!)"
|
||||
return
|
||||
|
||||
current_path_index = index % len(path_combinations)
|
||||
from_idx, to_idx, path = path_combinations[current_path_index]
|
||||
|
||||
# Clear previous path
|
||||
clear_path_colors()
|
||||
|
||||
# Get entities
|
||||
e_from = entities[from_idx]
|
||||
e_to = entities[to_idx]
|
||||
|
||||
# Color the path
|
||||
current_path = path
|
||||
if path:
|
||||
# Color start and end
|
||||
grid.at(int(e_from.x), int(e_from.y)).color = START_COLOR
|
||||
grid.at(int(e_to.x), int(e_to.y)).color = END_COLOR
|
||||
|
||||
# Color intermediate steps
|
||||
for i, (x, y) in enumerate(path):
|
||||
if i > 0 and i < len(path) - 1:
|
||||
grid.at(x, y).color = PATH_COLOR
|
||||
|
||||
# Update status
|
||||
status_text.text = f"Path {current_path_index + 1}/{len(path_combinations)}: Entity {from_idx+1} → Entity {to_idx+1} ({len(path)} steps)"
|
||||
|
||||
# Update path display
|
||||
path_display = []
|
||||
for i, (x, y) in enumerate(path[:5]): # Show first 5 steps
|
||||
path_display.append(f"({x},{y})")
|
||||
if len(path) > 5:
|
||||
path_display.append("...")
|
||||
path_text.text = "Path: " + " → ".join(path_display) if path_display else "Path: None"
|
||||
|
||||
def handle_keypress(key_str, state):
|
||||
"""Handle keyboard input"""
|
||||
global current_path_index
|
||||
if state == "end": return
|
||||
if key_str == "Esc":
|
||||
print("\nExiting...")
|
||||
sys.exit(0)
|
||||
elif key_str == "N" or key_str == "Space":
|
||||
show_path(current_path_index + 1)
|
||||
elif key_str == "P":
|
||||
show_path(current_path_index - 1)
|
||||
elif key_str == "R":
|
||||
show_path(current_path_index)
|
||||
|
||||
# Create the demo
|
||||
print("Dijkstra Path Cycling Demo")
|
||||
print("==========================")
|
||||
print("Note: Entity 1 is trapped by walls!")
|
||||
print()
|
||||
|
||||
create_map()
|
||||
|
||||
# Set up UI
|
||||
ui = mcrfpy.sceneUI("dijkstra_cycle")
|
||||
ui.append(grid)
|
||||
|
||||
# Scale and position
|
||||
grid.size = (560, 400)
|
||||
grid.position = (120, 100)
|
||||
|
||||
# Add title
|
||||
title = mcrfpy.Caption("Dijkstra Pathfinding - Cycle Paths", 200, 20)
|
||||
title.fill_color = mcrfpy.Color(255, 255, 255)
|
||||
ui.append(title)
|
||||
|
||||
# Add status
|
||||
status_text = mcrfpy.Caption("Press SPACE to cycle paths", 120, 60)
|
||||
status_text.fill_color = mcrfpy.Color(255, 255, 100)
|
||||
ui.append(status_text)
|
||||
|
||||
# Add path display
|
||||
path_text = mcrfpy.Caption("Path: None", 120, 520)
|
||||
path_text.fill_color = mcrfpy.Color(200, 200, 200)
|
||||
ui.append(path_text)
|
||||
|
||||
# Add controls
|
||||
controls = mcrfpy.Caption("SPACE/N=Next, P=Previous, R=Refresh, Q=Quit", 120, 540)
|
||||
controls.fill_color = mcrfpy.Color(150, 150, 150)
|
||||
ui.append(controls)
|
||||
|
||||
# Add legend
|
||||
legend = mcrfpy.Caption("Red=Start, Blue=End, Green=Path, Dark=Wall", 120, 560)
|
||||
legend.fill_color = mcrfpy.Color(150, 150, 150)
|
||||
ui.append(legend)
|
||||
|
||||
# Show first valid path
|
||||
mcrfpy.setScene("dijkstra_cycle")
|
||||
mcrfpy.keypressScene(handle_keypress)
|
||||
|
||||
# Display initial path
|
||||
if path_combinations:
|
||||
show_path(0)
|
||||
else:
|
||||
status_text.text = "No valid paths! Entity 1 is trapped!"
|
||||
|
||||
print("\nDemo ready!")
|
||||
print("Controls:")
|
||||
print(" SPACE or N - Next path")
|
||||
print(" P - Previous path")
|
||||
print(" R - Refresh current path")
|
||||
print(" Q - Quit")
|
||||
161
tests/dijkstra_debug.py
Normal file
161
tests/dijkstra_debug.py
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Debug version of Dijkstra pathfinding to diagnose visualization issues
|
||||
"""
|
||||
|
||||
import mcrfpy
|
||||
import sys
|
||||
|
||||
# Colors
|
||||
WALL_COLOR = mcrfpy.Color(60, 30, 30)
|
||||
FLOOR_COLOR = mcrfpy.Color(200, 200, 220)
|
||||
PATH_COLOR = mcrfpy.Color(200, 250, 220)
|
||||
ENTITY_COLORS = [
|
||||
mcrfpy.Color(255, 100, 100), # Entity 1 - Red
|
||||
mcrfpy.Color(100, 255, 100), # Entity 2 - Green
|
||||
mcrfpy.Color(100, 100, 255), # Entity 3 - Blue
|
||||
]
|
||||
|
||||
# Global state
|
||||
grid = None
|
||||
entities = []
|
||||
first_point = None
|
||||
second_point = None
|
||||
|
||||
def create_simple_map():
|
||||
"""Create a simple test map"""
|
||||
global grid, entities
|
||||
|
||||
mcrfpy.createScene("dijkstra_debug")
|
||||
|
||||
# Small grid for easy debugging
|
||||
grid = mcrfpy.Grid(grid_x=10, grid_y=10)
|
||||
grid.fill_color = mcrfpy.Color(0, 0, 0)
|
||||
|
||||
print("Initializing 10x10 grid...")
|
||||
|
||||
# Initialize all as floor
|
||||
for y in range(10):
|
||||
for x in range(10):
|
||||
grid.at(x, y).walkable = True
|
||||
grid.at(x, y).transparent = True
|
||||
grid.at(x, y).color = FLOOR_COLOR
|
||||
|
||||
# Add a simple wall
|
||||
print("Adding walls at:")
|
||||
walls = [(5, 2), (5, 3), (5, 4), (5, 5), (5, 6)]
|
||||
for x, y in walls:
|
||||
print(f" Wall at ({x}, {y})")
|
||||
grid.at(x, y).walkable = False
|
||||
grid.at(x, y).color = WALL_COLOR
|
||||
|
||||
# Create 3 entities
|
||||
entity_positions = [(2, 5), (8, 5), (5, 8)]
|
||||
entities = []
|
||||
|
||||
print("\nCreating entities at:")
|
||||
for i, (x, y) in enumerate(entity_positions):
|
||||
print(f" Entity {i+1} at ({x}, {y})")
|
||||
entity = mcrfpy.Entity(x, y)
|
||||
entity.sprite_index = 49 + i # '1', '2', '3'
|
||||
grid.entities.append(entity)
|
||||
entities.append(entity)
|
||||
|
||||
return grid
|
||||
|
||||
def test_path_highlighting():
|
||||
"""Test path highlighting with debug output"""
|
||||
print("\n" + "="*50)
|
||||
print("Testing path highlighting...")
|
||||
|
||||
# Select first two entities
|
||||
e1 = entities[0]
|
||||
e2 = entities[1]
|
||||
|
||||
print(f"\nEntity 1 position: ({e1.x}, {e1.y})")
|
||||
print(f"Entity 2 position: ({e2.x}, {e2.y})")
|
||||
|
||||
# Use entity.path_to()
|
||||
print("\nCalling entity.path_to()...")
|
||||
path = e1.path_to(int(e2.x), int(e2.y))
|
||||
|
||||
print(f"Path returned: {path}")
|
||||
print(f"Path length: {len(path)} steps")
|
||||
|
||||
if path:
|
||||
print("\nHighlighting path cells:")
|
||||
for i, (x, y) in enumerate(path):
|
||||
print(f" Step {i}: ({x}, {y})")
|
||||
# Get current color for debugging
|
||||
cell = grid.at(x, y)
|
||||
old_color = (cell.color.r, cell.color.g, cell.color.b)
|
||||
|
||||
# Set new color
|
||||
cell.color = PATH_COLOR
|
||||
new_color = (cell.color.r, cell.color.g, cell.color.b)
|
||||
|
||||
print(f" Color changed from {old_color} to {new_color}")
|
||||
print(f" Walkable: {cell.walkable}")
|
||||
|
||||
# Also test grid's Dijkstra methods
|
||||
print("\n" + "-"*30)
|
||||
print("Testing grid Dijkstra methods...")
|
||||
|
||||
grid.compute_dijkstra(int(e1.x), int(e1.y))
|
||||
grid_path = grid.get_dijkstra_path(int(e2.x), int(e2.y))
|
||||
distance = grid.get_dijkstra_distance(int(e2.x), int(e2.y))
|
||||
|
||||
print(f"Grid path: {grid_path}")
|
||||
print(f"Grid distance: {distance}")
|
||||
|
||||
# Verify colors were set
|
||||
print("\nVerifying cell colors after highlighting:")
|
||||
for x, y in path[:3]: # Check first 3 cells
|
||||
cell = grid.at(x, y)
|
||||
color = (cell.color.r, cell.color.g, cell.color.b)
|
||||
expected = (PATH_COLOR.r, PATH_COLOR.g, PATH_COLOR.b)
|
||||
match = color == expected
|
||||
print(f" Cell ({x}, {y}): color={color}, expected={expected}, match={match}")
|
||||
|
||||
def handle_keypress(scene_name, keycode):
|
||||
"""Simple keypress handler"""
|
||||
if keycode == 81 or keycode == 113 or keycode == 256: # Q/q/ESC
|
||||
print("\nExiting debug...")
|
||||
sys.exit(0)
|
||||
elif keycode == 32: # Space
|
||||
print("\nSpace pressed - retesting path highlighting...")
|
||||
test_path_highlighting()
|
||||
|
||||
# Create the map
|
||||
print("Dijkstra Debug Test")
|
||||
print("===================")
|
||||
grid = create_simple_map()
|
||||
|
||||
# Initial path test
|
||||
test_path_highlighting()
|
||||
|
||||
# Set up UI
|
||||
ui = mcrfpy.sceneUI("dijkstra_debug")
|
||||
ui.append(grid)
|
||||
|
||||
# Position and scale
|
||||
grid.position = (50, 50)
|
||||
grid.size = (400, 400) # 10*40
|
||||
|
||||
# Add title
|
||||
title = mcrfpy.Caption("Dijkstra Debug - Press SPACE to retest, Q to quit", 50, 10)
|
||||
title.fill_color = mcrfpy.Color(255, 255, 255)
|
||||
ui.append(title)
|
||||
|
||||
# Add debug info
|
||||
info = mcrfpy.Caption("Check console for debug output", 50, 470)
|
||||
info.fill_color = mcrfpy.Color(200, 200, 200)
|
||||
ui.append(info)
|
||||
|
||||
# Set up scene
|
||||
mcrfpy.keypressScene(handle_keypress)
|
||||
mcrfpy.setScene("dijkstra_debug")
|
||||
|
||||
print("\nScene ready. The path should be highlighted in cyan.")
|
||||
print("If you don't see the path, there may be a rendering issue.")
|
||||
print("Press SPACE to retest, Q to quit.")
|
||||
137
tests/dijkstra_demo_working.py
Normal file
137
tests/dijkstra_demo_working.py
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Working Dijkstra Demo with Clear Visual Feedback
|
||||
================================================
|
||||
|
||||
This demo shows pathfinding with high-contrast colors.
|
||||
"""
|
||||
|
||||
import mcrfpy
|
||||
import sys
|
||||
|
||||
# High contrast colors
|
||||
WALL_COLOR = mcrfpy.Color(40, 20, 20) # Very dark red/brown for walls
|
||||
FLOOR_COLOR = mcrfpy.Color(60, 60, 80) # Dark blue-gray for floors
|
||||
PATH_COLOR = mcrfpy.Color(0, 255, 0) # Pure green for paths
|
||||
START_COLOR = mcrfpy.Color(255, 0, 0) # Red for start
|
||||
END_COLOR = mcrfpy.Color(0, 0, 255) # Blue for end
|
||||
|
||||
print("Dijkstra Demo - High Contrast")
|
||||
print("==============================")
|
||||
|
||||
# Create scene
|
||||
mcrfpy.createScene("dijkstra_demo")
|
||||
|
||||
# Create grid with exact layout from user
|
||||
grid = mcrfpy.Grid(grid_x=14, grid_y=10)
|
||||
grid.fill_color = mcrfpy.Color(0, 0, 0)
|
||||
|
||||
# Map layout
|
||||
map_layout = [
|
||||
"..............", # Row 0
|
||||
"..W.....WWWW..", # Row 1
|
||||
"..W.W...W.EW..", # Row 2
|
||||
"..W.....W..W..", # Row 3
|
||||
"..W...E.WWWW..", # Row 4
|
||||
"E.W...........", # Row 5
|
||||
"..W...........", # Row 6
|
||||
"..W...........", # Row 7
|
||||
"..W.WWW.......", # Row 8
|
||||
"..............", # Row 9
|
||||
]
|
||||
|
||||
# Create the map
|
||||
entity_positions = []
|
||||
for y, row in enumerate(map_layout):
|
||||
for x, char in enumerate(row):
|
||||
cell = grid.at(x, y)
|
||||
|
||||
if char == 'W':
|
||||
cell.walkable = False
|
||||
cell.color = WALL_COLOR
|
||||
else:
|
||||
cell.walkable = True
|
||||
cell.color = FLOOR_COLOR
|
||||
|
||||
if char == 'E':
|
||||
entity_positions.append((x, y))
|
||||
|
||||
print(f"Map created: {grid.grid_x}x{grid.grid_y}")
|
||||
print(f"Entity positions: {entity_positions}")
|
||||
|
||||
# Create entities
|
||||
entities = []
|
||||
for i, (x, y) in enumerate(entity_positions):
|
||||
entity = mcrfpy.Entity(x, y)
|
||||
entity.sprite_index = 49 + i # '1', '2', '3'
|
||||
grid.entities.append(entity)
|
||||
entities.append(entity)
|
||||
print(f"Entity {i+1} at ({x}, {y})")
|
||||
|
||||
# Highlight a path immediately
|
||||
if len(entities) >= 2:
|
||||
e1, e2 = entities[0], entities[1]
|
||||
print(f"\nCalculating path from Entity 1 ({e1.x}, {e1.y}) to Entity 2 ({e2.x}, {e2.y})...")
|
||||
|
||||
path = e1.path_to(int(e2.x), int(e2.y))
|
||||
print(f"Path found: {path}")
|
||||
print(f"Path length: {len(path)} steps")
|
||||
|
||||
if path:
|
||||
print("\nHighlighting path in bright green...")
|
||||
# Color start and end specially
|
||||
grid.at(int(e1.x), int(e1.y)).color = START_COLOR
|
||||
grid.at(int(e2.x), int(e2.y)).color = END_COLOR
|
||||
|
||||
# Color the path
|
||||
for i, (x, y) in enumerate(path):
|
||||
if i > 0 and i < len(path) - 1: # Skip start and end
|
||||
grid.at(x, y).color = PATH_COLOR
|
||||
print(f" Colored ({x}, {y}) green")
|
||||
|
||||
# Keypress handler
|
||||
def handle_keypress(scene_name, keycode):
|
||||
if keycode == 81 or keycode == 113 or keycode == 256: # Q/q/ESC
|
||||
print("\nExiting...")
|
||||
sys.exit(0)
|
||||
elif keycode == 32: # Space
|
||||
print("\nRefreshing path colors...")
|
||||
# Re-color the path to ensure it's visible
|
||||
if len(entities) >= 2 and path:
|
||||
for x, y in path[1:-1]:
|
||||
grid.at(x, y).color = PATH_COLOR
|
||||
|
||||
# Set up UI
|
||||
ui = mcrfpy.sceneUI("dijkstra_demo")
|
||||
ui.append(grid)
|
||||
|
||||
# Scale grid
|
||||
grid.size = (560, 400) # 14*40, 10*40
|
||||
grid.position = (120, 100)
|
||||
|
||||
# Add title
|
||||
title = mcrfpy.Caption("Dijkstra Pathfinding - High Contrast", 200, 20)
|
||||
title.fill_color = mcrfpy.Color(255, 255, 255)
|
||||
ui.append(title)
|
||||
|
||||
# Add legend
|
||||
legend1 = mcrfpy.Caption("Red=Start, Blue=End, Green=Path", 120, 520)
|
||||
legend1.fill_color = mcrfpy.Color(200, 200, 200)
|
||||
ui.append(legend1)
|
||||
|
||||
legend2 = mcrfpy.Caption("Press Q to quit, SPACE to refresh", 120, 540)
|
||||
legend2.fill_color = mcrfpy.Color(150, 150, 150)
|
||||
ui.append(legend2)
|
||||
|
||||
# Entity info
|
||||
info = mcrfpy.Caption(f"Path: Entity 1 to 2 = {len(path) if 'path' in locals() else 0} steps", 120, 60)
|
||||
info.fill_color = mcrfpy.Color(255, 255, 100)
|
||||
ui.append(info)
|
||||
|
||||
# Set up input
|
||||
mcrfpy.keypressScene(handle_keypress)
|
||||
mcrfpy.setScene("dijkstra_demo")
|
||||
|
||||
print("\nDemo ready! The path should be clearly visible in bright green.")
|
||||
print("Red = Start, Blue = End, Green = Path")
|
||||
print("Press SPACE to refresh colors if needed.")
|
||||
|
|
@ -17,10 +17,10 @@ The path between selected entities is automatically highlighted.
|
|||
import mcrfpy
|
||||
import sys
|
||||
|
||||
# Colors
|
||||
# Colors - using more distinct values
|
||||
WALL_COLOR = mcrfpy.Color(60, 30, 30)
|
||||
FLOOR_COLOR = mcrfpy.Color(200, 200, 220)
|
||||
PATH_COLOR = mcrfpy.Color(200, 250, 220)
|
||||
FLOOR_COLOR = mcrfpy.Color(100, 100, 120) # Darker floor for better contrast
|
||||
PATH_COLOR = mcrfpy.Color(50, 255, 50) # Bright green for path
|
||||
ENTITY_COLORS = [
|
||||
mcrfpy.Color(255, 100, 100), # Entity 1 - Red
|
||||
mcrfpy.Color(100, 255, 100), # Entity 2 - Green
|
||||
|
|
|
|||
201
tests/interactive_visibility.py
Normal file
201
tests/interactive_visibility.py
Normal file
|
|
@ -0,0 +1,201 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Interactive Visibility Demo
|
||||
==========================
|
||||
|
||||
Controls:
|
||||
- WASD: Move the player (green @)
|
||||
- Arrow keys: Move enemy (red E)
|
||||
- Tab: Cycle perspective (Omniscient → Player → Enemy → Omniscient)
|
||||
- Space: Update visibility for current entity
|
||||
- R: Reset positions
|
||||
"""
|
||||
|
||||
import mcrfpy
|
||||
import sys
|
||||
|
||||
# Create scene and grid
|
||||
mcrfpy.createScene("visibility_demo")
|
||||
grid = mcrfpy.Grid(grid_x=30, grid_y=20)
|
||||
grid.fill_color = mcrfpy.Color(20, 20, 30) # Dark background
|
||||
|
||||
# Initialize grid - all walkable and transparent
|
||||
for y in range(20):
|
||||
for x in range(30):
|
||||
cell = grid.at(x, y)
|
||||
cell.walkable = True
|
||||
cell.transparent = True
|
||||
cell.color = mcrfpy.Color(100, 100, 120) # Floor color
|
||||
|
||||
# Create walls
|
||||
walls = [
|
||||
# Central cross
|
||||
[(15, y) for y in range(8, 12)],
|
||||
[(x, 10) for x in range(13, 18)],
|
||||
|
||||
# Rooms
|
||||
# Top-left room
|
||||
[(x, 5) for x in range(2, 8)] + [(8, y) for y in range(2, 6)],
|
||||
[(2, y) for y in range(2, 6)] + [(x, 2) for x in range(2, 8)],
|
||||
|
||||
# Top-right room
|
||||
[(x, 5) for x in range(22, 28)] + [(22, y) for y in range(2, 6)],
|
||||
[(28, y) for y in range(2, 6)] + [(x, 2) for x in range(22, 28)],
|
||||
|
||||
# Bottom-left room
|
||||
[(x, 15) for x in range(2, 8)] + [(8, y) for y in range(15, 18)],
|
||||
[(2, y) for y in range(15, 18)] + [(x, 18) for x in range(2, 8)],
|
||||
|
||||
# Bottom-right room
|
||||
[(x, 15) for x in range(22, 28)] + [(22, y) for y in range(15, 18)],
|
||||
[(28, y) for y in range(15, 18)] + [(x, 18) for x in range(22, 28)],
|
||||
]
|
||||
|
||||
for wall_group in walls:
|
||||
for x, y in wall_group:
|
||||
if 0 <= x < 30 and 0 <= y < 20:
|
||||
cell = grid.at(x, y)
|
||||
cell.walkable = False
|
||||
cell.transparent = False
|
||||
cell.color = mcrfpy.Color(40, 20, 20) # Wall color
|
||||
|
||||
# Create entities
|
||||
player = mcrfpy.Entity(5, 10, grid=grid)
|
||||
player.sprite_index = 64 # @
|
||||
enemy = mcrfpy.Entity(25, 10, grid=grid)
|
||||
enemy.sprite_index = 69 # E
|
||||
|
||||
# Update initial visibility
|
||||
player.update_visibility()
|
||||
enemy.update_visibility()
|
||||
|
||||
# Global state
|
||||
current_perspective = -1
|
||||
perspective_names = ["Omniscient", "Player", "Enemy"]
|
||||
|
||||
# UI Setup
|
||||
ui = mcrfpy.sceneUI("visibility_demo")
|
||||
ui.append(grid)
|
||||
grid.position = (50, 100)
|
||||
grid.size = (900, 600) # 30*30, 20*30
|
||||
|
||||
# Title
|
||||
title = mcrfpy.Caption("Interactive Visibility Demo", 350, 20)
|
||||
title.fill_color = mcrfpy.Color(255, 255, 255)
|
||||
ui.append(title)
|
||||
|
||||
# Info displays
|
||||
perspective_label = mcrfpy.Caption("Perspective: Omniscient", 50, 50)
|
||||
perspective_label.fill_color = mcrfpy.Color(200, 200, 200)
|
||||
ui.append(perspective_label)
|
||||
|
||||
controls = mcrfpy.Caption("WASD: Move player | Arrows: Move enemy | Tab: Cycle perspective | Space: Update visibility | R: Reset", 50, 730)
|
||||
controls.fill_color = mcrfpy.Color(150, 150, 150)
|
||||
ui.append(controls)
|
||||
|
||||
player_info = mcrfpy.Caption("Player: (5, 10)", 700, 50)
|
||||
player_info.fill_color = mcrfpy.Color(100, 255, 100)
|
||||
ui.append(player_info)
|
||||
|
||||
enemy_info = mcrfpy.Caption("Enemy: (25, 10)", 700, 70)
|
||||
enemy_info.fill_color = mcrfpy.Color(255, 100, 100)
|
||||
ui.append(enemy_info)
|
||||
|
||||
# Helper functions
|
||||
def move_entity(entity, dx, dy):
|
||||
"""Move entity if target is walkable"""
|
||||
new_x = int(entity.x + dx)
|
||||
new_y = int(entity.y + dy)
|
||||
|
||||
if 0 <= new_x < 30 and 0 <= new_y < 20:
|
||||
cell = grid.at(new_x, new_y)
|
||||
if cell.walkable:
|
||||
entity.x = new_x
|
||||
entity.y = new_y
|
||||
entity.update_visibility()
|
||||
return True
|
||||
return False
|
||||
|
||||
def update_info():
|
||||
"""Update info displays"""
|
||||
player_info.text = f"Player: ({int(player.x)}, {int(player.y)})"
|
||||
enemy_info.text = f"Enemy: ({int(enemy.x)}, {int(enemy.y)})"
|
||||
|
||||
def cycle_perspective():
|
||||
"""Cycle through perspectives"""
|
||||
global current_perspective
|
||||
|
||||
# Cycle: -1 → 0 → 1 → -1
|
||||
current_perspective = (current_perspective + 2) % 3 - 1
|
||||
|
||||
grid.perspective = current_perspective
|
||||
name = perspective_names[current_perspective + 1]
|
||||
perspective_label.text = f"Perspective: {name}"
|
||||
|
||||
# Key handlers
|
||||
def handle_keys(key, state):
|
||||
"""Handle keyboard input"""
|
||||
if state == "end": return
|
||||
key = key.lower()
|
||||
# Player movement (WASD)
|
||||
if key == "w":
|
||||
move_entity(player, 0, -1)
|
||||
elif key == "s":
|
||||
move_entity(player, 0, 1)
|
||||
elif key == "a":
|
||||
move_entity(player, -1, 0)
|
||||
elif key == "d":
|
||||
move_entity(player, 1, 0)
|
||||
|
||||
# Enemy movement (Arrows)
|
||||
elif key == "up":
|
||||
move_entity(enemy, 0, -1)
|
||||
elif key == "down":
|
||||
move_entity(enemy, 0, 1)
|
||||
elif key == "left":
|
||||
move_entity(enemy, -1, 0)
|
||||
elif key == "right":
|
||||
move_entity(enemy, 1, 0)
|
||||
|
||||
# Tab to cycle perspective
|
||||
elif key == "tab":
|
||||
cycle_perspective()
|
||||
|
||||
# Space to update visibility
|
||||
elif key == "space":
|
||||
player.update_visibility()
|
||||
enemy.update_visibility()
|
||||
print("Updated visibility for both entities")
|
||||
|
||||
# R to reset
|
||||
elif key == "r":
|
||||
player.x, player.y = 5, 10
|
||||
enemy.x, enemy.y = 25, 10
|
||||
player.update_visibility()
|
||||
enemy.update_visibility()
|
||||
update_info()
|
||||
print("Reset positions")
|
||||
|
||||
# Q to quit
|
||||
elif key == "q":
|
||||
print("Exiting...")
|
||||
sys.exit(0)
|
||||
|
||||
update_info()
|
||||
|
||||
# Set scene first
|
||||
mcrfpy.setScene("visibility_demo")
|
||||
|
||||
# Register key handler (operates on current scene)
|
||||
mcrfpy.keypressScene(handle_keys)
|
||||
|
||||
print("Interactive Visibility Demo")
|
||||
print("===========================")
|
||||
print("WASD: Move player (green @)")
|
||||
print("Arrows: Move enemy (red E)")
|
||||
print("Tab: Cycle perspective")
|
||||
print("Space: Update visibility")
|
||||
print("R: Reset positions")
|
||||
print("Q: Quit")
|
||||
print("\nCurrent perspective: Omniscient (shows all)")
|
||||
print("Try moving entities and switching perspectives!")
|
||||
375
tests/path_vision_fixed.py
Normal file
375
tests/path_vision_fixed.py
Normal file
|
|
@ -0,0 +1,375 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Path & Vision Sizzle Reel (Fixed)
|
||||
=================================
|
||||
|
||||
Fixed version with proper animation chaining to prevent glitches.
|
||||
"""
|
||||
|
||||
import mcrfpy
|
||||
import sys
|
||||
|
||||
class PathAnimator:
|
||||
"""Handles step-by-step animation with proper completion tracking"""
|
||||
|
||||
def __init__(self, entity, name="animator"):
|
||||
self.entity = entity
|
||||
self.name = name
|
||||
self.path = []
|
||||
self.current_index = 0
|
||||
self.step_duration = 0.4
|
||||
self.animating = False
|
||||
self.on_step = None
|
||||
self.on_complete = None
|
||||
|
||||
def set_path(self, path):
|
||||
"""Set the path to animate along"""
|
||||
self.path = path
|
||||
self.current_index = 0
|
||||
|
||||
def start(self):
|
||||
"""Start animating"""
|
||||
if not self.path:
|
||||
return
|
||||
|
||||
self.animating = True
|
||||
self.current_index = 0
|
||||
self._move_to_next()
|
||||
|
||||
def stop(self):
|
||||
"""Stop animating"""
|
||||
self.animating = False
|
||||
mcrfpy.delTimer(f"{self.name}_check")
|
||||
|
||||
def _move_to_next(self):
|
||||
"""Move to next position in path"""
|
||||
if not self.animating or self.current_index >= len(self.path):
|
||||
self.animating = False
|
||||
if self.on_complete:
|
||||
self.on_complete()
|
||||
return
|
||||
|
||||
# Get next position
|
||||
x, y = self.path[self.current_index]
|
||||
|
||||
# Create animations
|
||||
anim_x = mcrfpy.Animation("x", float(x), self.step_duration, "easeInOut")
|
||||
anim_y = mcrfpy.Animation("y", float(y), self.step_duration, "easeInOut")
|
||||
|
||||
anim_x.start(self.entity)
|
||||
anim_y.start(self.entity)
|
||||
|
||||
# Update visibility
|
||||
self.entity.update_visibility()
|
||||
|
||||
# Callback for each step
|
||||
if self.on_step:
|
||||
self.on_step(self.current_index, x, y)
|
||||
|
||||
# Schedule next move
|
||||
delay = int(self.step_duration * 1000) + 50 # Add small buffer
|
||||
mcrfpy.setTimer(f"{self.name}_next", self._handle_next, delay)
|
||||
|
||||
def _handle_next(self, dt):
|
||||
"""Timer callback to move to next position"""
|
||||
self.current_index += 1
|
||||
mcrfpy.delTimer(f"{self.name}_next")
|
||||
self._move_to_next()
|
||||
|
||||
# Global state
|
||||
grid = None
|
||||
player = None
|
||||
enemy = None
|
||||
player_animator = None
|
||||
enemy_animator = None
|
||||
demo_phase = 0
|
||||
|
||||
def create_scene():
|
||||
"""Create the demo environment"""
|
||||
global grid, player, enemy
|
||||
|
||||
mcrfpy.createScene("fixed_demo")
|
||||
|
||||
# Create grid
|
||||
grid = mcrfpy.Grid(grid_x=30, grid_y=20)
|
||||
grid.fill_color = mcrfpy.Color(20, 20, 30)
|
||||
|
||||
# Simple dungeon layout
|
||||
map_layout = [
|
||||
"##############################",
|
||||
"#......#########.....#########",
|
||||
"#......#########.....#########",
|
||||
"#......#.........#...#########",
|
||||
"#......#.........#...#########",
|
||||
"####.###.........#.###########",
|
||||
"####.............#.###########",
|
||||
"####.............#.###########",
|
||||
"####.###.........#.###########",
|
||||
"#......#.........#...#########",
|
||||
"#......#.........#...#########",
|
||||
"#......#########.#...........#",
|
||||
"#......#########.#...........#",
|
||||
"#......#########.#...........#",
|
||||
"#......#########.#############",
|
||||
"####.###########.............#",
|
||||
"####.........................#",
|
||||
"####.###########.............#",
|
||||
"#......#########.............#",
|
||||
"##############################",
|
||||
]
|
||||
|
||||
# Build map
|
||||
for y, row in enumerate(map_layout):
|
||||
for x, char in enumerate(row):
|
||||
cell = grid.at(x, y)
|
||||
if char == '#':
|
||||
cell.walkable = False
|
||||
cell.transparent = False
|
||||
cell.color = mcrfpy.Color(40, 30, 30)
|
||||
else:
|
||||
cell.walkable = True
|
||||
cell.transparent = True
|
||||
cell.color = mcrfpy.Color(80, 80, 100)
|
||||
|
||||
# Create entities
|
||||
player = mcrfpy.Entity(3, 3, grid=grid)
|
||||
player.sprite_index = 64 # @
|
||||
|
||||
enemy = mcrfpy.Entity(26, 16, grid=grid)
|
||||
enemy.sprite_index = 69 # E
|
||||
|
||||
# Initial visibility
|
||||
player.update_visibility()
|
||||
enemy.update_visibility()
|
||||
|
||||
# Set initial perspective
|
||||
grid.perspective = 0
|
||||
|
||||
def setup_ui():
|
||||
"""Create UI elements"""
|
||||
ui = mcrfpy.sceneUI("fixed_demo")
|
||||
ui.append(grid)
|
||||
|
||||
grid.position = (50, 80)
|
||||
grid.size = (700, 500)
|
||||
|
||||
title = mcrfpy.Caption("Path & Vision Demo (Fixed)", 300, 20)
|
||||
title.fill_color = mcrfpy.Color(255, 255, 255)
|
||||
ui.append(title)
|
||||
|
||||
global status_text, perspective_text
|
||||
status_text = mcrfpy.Caption("Initializing...", 50, 50)
|
||||
status_text.fill_color = mcrfpy.Color(200, 200, 200)
|
||||
ui.append(status_text)
|
||||
|
||||
perspective_text = mcrfpy.Caption("Perspective: Player", 550, 50)
|
||||
perspective_text.fill_color = mcrfpy.Color(100, 255, 100)
|
||||
ui.append(perspective_text)
|
||||
|
||||
controls = mcrfpy.Caption("Space: Start/Pause | R: Restart | Q: Quit", 250, 600)
|
||||
controls.fill_color = mcrfpy.Color(150, 150, 150)
|
||||
ui.append(controls)
|
||||
|
||||
def update_camera_smooth(target, duration=0.3):
|
||||
"""Smoothly move camera to entity"""
|
||||
center_x = target.x * 23 # Approximate pixel size
|
||||
center_y = target.y * 23
|
||||
|
||||
cam_anim = mcrfpy.Animation("center", (center_x, center_y), duration, "easeOut")
|
||||
cam_anim.start(grid)
|
||||
|
||||
def start_demo():
|
||||
"""Start the demo sequence"""
|
||||
global demo_phase, player_animator, enemy_animator
|
||||
|
||||
demo_phase = 1
|
||||
status_text.text = "Phase 1: Player movement with camera follow"
|
||||
|
||||
# Player path
|
||||
player_path = [
|
||||
(3, 3), (3, 6), (4, 6), (7, 6), (7, 8),
|
||||
(10, 8), (13, 8), (16, 8), (16, 10),
|
||||
(16, 13), (16, 16), (20, 16), (24, 16)
|
||||
]
|
||||
|
||||
# Setup player animator
|
||||
player_animator = PathAnimator(player, "player")
|
||||
player_animator.set_path(player_path)
|
||||
player_animator.step_duration = 0.5
|
||||
|
||||
def on_player_step(index, x, y):
|
||||
"""Called for each player step"""
|
||||
status_text.text = f"Player step {index+1}/{len(player_path)}"
|
||||
if grid.perspective == 0:
|
||||
update_camera_smooth(player, 0.4)
|
||||
|
||||
def on_player_complete():
|
||||
"""Called when player path is complete"""
|
||||
start_phase_2()
|
||||
|
||||
player_animator.on_step = on_player_step
|
||||
player_animator.on_complete = on_player_complete
|
||||
player_animator.start()
|
||||
|
||||
def start_phase_2():
|
||||
"""Start enemy movement phase"""
|
||||
global demo_phase
|
||||
|
||||
demo_phase = 2
|
||||
status_text.text = "Phase 2: Enemy movement (may enter player's view)"
|
||||
|
||||
# Enemy path
|
||||
enemy_path = [
|
||||
(26, 16), (22, 16), (18, 16), (16, 16),
|
||||
(16, 13), (16, 10), (16, 8), (13, 8),
|
||||
(10, 8), (7, 8), (7, 6), (4, 6)
|
||||
]
|
||||
|
||||
# Setup enemy animator
|
||||
enemy_animator.set_path(enemy_path)
|
||||
enemy_animator.step_duration = 0.4
|
||||
|
||||
def on_enemy_step(index, x, y):
|
||||
"""Check if enemy is visible to player"""
|
||||
if grid.perspective == 0:
|
||||
# Check if enemy is in player's view
|
||||
enemy_idx = int(y) * grid.grid_x + int(x)
|
||||
if enemy_idx < len(player.gridstate) and player.gridstate[enemy_idx].visible:
|
||||
status_text.text = "Enemy spotted in player's view!"
|
||||
|
||||
def on_enemy_complete():
|
||||
"""Start perspective transition"""
|
||||
start_phase_3()
|
||||
|
||||
enemy_animator.on_step = on_enemy_step
|
||||
enemy_animator.on_complete = on_enemy_complete
|
||||
enemy_animator.start()
|
||||
|
||||
def start_phase_3():
|
||||
"""Dramatic perspective shift"""
|
||||
global demo_phase
|
||||
|
||||
demo_phase = 3
|
||||
status_text.text = "Phase 3: Perspective shift..."
|
||||
|
||||
# Stop any ongoing animations
|
||||
player_animator.stop()
|
||||
enemy_animator.stop()
|
||||
|
||||
# Zoom out
|
||||
zoom_out = mcrfpy.Animation("zoom", 0.6, 2.0, "easeInExpo")
|
||||
zoom_out.start(grid)
|
||||
|
||||
# Schedule perspective switch
|
||||
mcrfpy.setTimer("switch_persp", switch_perspective, 2100)
|
||||
|
||||
def switch_perspective(dt):
|
||||
"""Switch to enemy perspective"""
|
||||
grid.perspective = 1
|
||||
perspective_text.text = "Perspective: Enemy"
|
||||
perspective_text.fill_color = mcrfpy.Color(255, 100, 100)
|
||||
|
||||
# Update camera
|
||||
update_camera_smooth(enemy, 0.5)
|
||||
|
||||
# Zoom back in
|
||||
zoom_in = mcrfpy.Animation("zoom", 1.0, 2.0, "easeOutExpo")
|
||||
zoom_in.start(grid)
|
||||
|
||||
status_text.text = "Now following enemy perspective"
|
||||
|
||||
# Clean up timer
|
||||
mcrfpy.delTimer("switch_persp")
|
||||
|
||||
# Continue enemy movement after transition
|
||||
mcrfpy.setTimer("continue_enemy", continue_enemy_movement, 2500)
|
||||
|
||||
def continue_enemy_movement(dt):
|
||||
"""Continue enemy movement after perspective shift"""
|
||||
mcrfpy.delTimer("continue_enemy")
|
||||
|
||||
# Continue path
|
||||
enemy_path_2 = [
|
||||
(4, 6), (3, 6), (3, 3), (3, 2), (3, 1)
|
||||
]
|
||||
|
||||
enemy_animator.set_path(enemy_path_2)
|
||||
|
||||
def on_step(index, x, y):
|
||||
update_camera_smooth(enemy, 0.4)
|
||||
status_text.text = f"Following enemy: step {index+1}"
|
||||
|
||||
def on_complete():
|
||||
status_text.text = "Demo complete! Press R to restart"
|
||||
|
||||
enemy_animator.on_step = on_step
|
||||
enemy_animator.on_complete = on_complete
|
||||
enemy_animator.start()
|
||||
|
||||
# Control state
|
||||
running = False
|
||||
|
||||
def handle_keys(key, state):
|
||||
"""Handle keyboard input"""
|
||||
global running
|
||||
|
||||
if state != "start":
|
||||
return
|
||||
|
||||
key = key.lower()
|
||||
|
||||
if key == "q":
|
||||
sys.exit(0)
|
||||
elif key == "space":
|
||||
if not running:
|
||||
running = True
|
||||
start_demo()
|
||||
else:
|
||||
running = False
|
||||
player_animator.stop()
|
||||
enemy_animator.stop()
|
||||
status_text.text = "Paused"
|
||||
elif key == "r":
|
||||
# Reset everything
|
||||
player.x, player.y = 3, 3
|
||||
enemy.x, enemy.y = 26, 16
|
||||
grid.perspective = 0
|
||||
perspective_text.text = "Perspective: Player"
|
||||
perspective_text.fill_color = mcrfpy.Color(100, 255, 100)
|
||||
grid.zoom = 1.0
|
||||
update_camera_smooth(player, 0.5)
|
||||
|
||||
if running:
|
||||
player_animator.stop()
|
||||
enemy_animator.stop()
|
||||
running = False
|
||||
|
||||
status_text.text = "Reset - Press SPACE to start"
|
||||
|
||||
# Initialize
|
||||
create_scene()
|
||||
setup_ui()
|
||||
|
||||
# Setup animators
|
||||
player_animator = PathAnimator(player, "player")
|
||||
enemy_animator = PathAnimator(enemy, "enemy")
|
||||
|
||||
# Set scene
|
||||
mcrfpy.setScene("fixed_demo")
|
||||
mcrfpy.keypressScene(handle_keys)
|
||||
|
||||
# Initial camera
|
||||
grid.zoom = 1.0
|
||||
update_camera_smooth(player, 0.5)
|
||||
|
||||
print("Path & Vision Demo (Fixed)")
|
||||
print("==========================")
|
||||
print("This version properly chains animations to prevent glitches.")
|
||||
print()
|
||||
print("The demo will:")
|
||||
print("1. Move player with camera following")
|
||||
print("2. Move enemy (may enter player's view)")
|
||||
print("3. Dramatic perspective shift to enemy")
|
||||
print("4. Continue following enemy")
|
||||
print()
|
||||
print("Press SPACE to start, Q to quit")
|
||||
391
tests/path_vision_sizzle_reel.py
Normal file
391
tests/path_vision_sizzle_reel.py
Normal file
|
|
@ -0,0 +1,391 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Path & Vision Sizzle Reel
|
||||
=========================
|
||||
|
||||
A choreographed demo showing:
|
||||
- Smooth entity movement along paths
|
||||
- Camera following with grid center animation
|
||||
- Field of view updates as entities move
|
||||
- Dramatic perspective transitions with zoom effects
|
||||
"""
|
||||
|
||||
import mcrfpy
|
||||
import sys
|
||||
|
||||
# Colors
|
||||
WALL_COLOR = mcrfpy.Color(40, 30, 30)
|
||||
FLOOR_COLOR = mcrfpy.Color(80, 80, 100)
|
||||
PATH_COLOR = mcrfpy.Color(120, 120, 180)
|
||||
DARK_FLOOR = mcrfpy.Color(40, 40, 50)
|
||||
|
||||
# Global state
|
||||
grid = None
|
||||
player = None
|
||||
enemy = None
|
||||
sequence_step = 0
|
||||
player_path = []
|
||||
enemy_path = []
|
||||
player_path_index = 0
|
||||
enemy_path_index = 0
|
||||
|
||||
def create_scene():
|
||||
"""Create the demo environment"""
|
||||
global grid, player, enemy
|
||||
|
||||
mcrfpy.createScene("path_vision_demo")
|
||||
|
||||
# Create larger grid for more dramatic movement
|
||||
grid = mcrfpy.Grid(grid_x=40, grid_y=25)
|
||||
grid.fill_color = mcrfpy.Color(20, 20, 30)
|
||||
|
||||
# Map layout - interconnected rooms with corridors
|
||||
map_layout = [
|
||||
"########################################", # 0
|
||||
"#......##########......################", # 1
|
||||
"#......##########......################", # 2
|
||||
"#......##########......################", # 3
|
||||
"#......#.........#.....################", # 4
|
||||
"#......#.........#.....################", # 5
|
||||
"####.###.........####.#################", # 6
|
||||
"####.....................##############", # 7
|
||||
"####.....................##############", # 8
|
||||
"####.###.........####.#################", # 9
|
||||
"#......#.........#.....################", # 10
|
||||
"#......#.........#.....################", # 11
|
||||
"#......#.........#.....################", # 12
|
||||
"#......###.....###.....################", # 13
|
||||
"#......###.....###.....################", # 14
|
||||
"#......###.....###.....#########......#", # 15
|
||||
"#......###.....###.....#########......#", # 16
|
||||
"#......###.....###.....#########......#", # 17
|
||||
"#####.############.#############......#", # 18
|
||||
"#####...........................#.....#", # 19
|
||||
"#####...........................#.....#", # 20
|
||||
"#####.############.#############......#", # 21
|
||||
"#......###########.##########.........#", # 22
|
||||
"#......###########.##########.........#", # 23
|
||||
"########################################", # 24
|
||||
]
|
||||
|
||||
# Build the map
|
||||
for y, row in enumerate(map_layout):
|
||||
for x, char in enumerate(row):
|
||||
cell = grid.at(x, y)
|
||||
if char == '#':
|
||||
cell.walkable = False
|
||||
cell.transparent = False
|
||||
cell.color = WALL_COLOR
|
||||
else:
|
||||
cell.walkable = True
|
||||
cell.transparent = True
|
||||
cell.color = FLOOR_COLOR
|
||||
|
||||
# Create player in top-left room
|
||||
player = mcrfpy.Entity(3, 3, grid=grid)
|
||||
player.sprite_index = 64 # @
|
||||
|
||||
# Create enemy in bottom-right area
|
||||
enemy = mcrfpy.Entity(35, 20, grid=grid)
|
||||
enemy.sprite_index = 69 # E
|
||||
|
||||
# Initial visibility
|
||||
player.update_visibility()
|
||||
enemy.update_visibility()
|
||||
|
||||
# Set initial perspective to player
|
||||
grid.perspective = 0
|
||||
|
||||
def setup_paths():
|
||||
"""Define the paths for entities"""
|
||||
global player_path, enemy_path
|
||||
|
||||
# Player path: Top-left room → corridor → middle room
|
||||
player_waypoints = [
|
||||
(3, 3), # Start
|
||||
(3, 8), # Move down
|
||||
(7, 8), # Enter corridor
|
||||
(16, 8), # Through corridor
|
||||
(16, 12), # Enter middle room
|
||||
(12, 12), # Move in room
|
||||
(12, 16), # Move down
|
||||
(16, 16), # Move right
|
||||
(16, 19), # Exit room
|
||||
(25, 19), # Move right
|
||||
(30, 19), # Continue
|
||||
(35, 19), # Near enemy start
|
||||
]
|
||||
|
||||
# Enemy path: Bottom-right → around → approach player area
|
||||
enemy_waypoints = [
|
||||
(35, 20), # Start
|
||||
(30, 20), # Move left
|
||||
(25, 20), # Continue
|
||||
(20, 20), # Continue
|
||||
(16, 20), # Corridor junction
|
||||
(16, 16), # Move up (might see player)
|
||||
(16, 12), # Continue up
|
||||
(16, 8), # Top corridor
|
||||
(10, 8), # Move left
|
||||
(7, 8), # Continue
|
||||
(3, 8), # Player's area
|
||||
(3, 12), # Move down
|
||||
]
|
||||
|
||||
# Calculate full paths using pathfinding
|
||||
player_path = []
|
||||
for i in range(len(player_waypoints) - 1):
|
||||
x1, y1 = player_waypoints[i]
|
||||
x2, y2 = player_waypoints[i + 1]
|
||||
|
||||
# Use grid's A* pathfinding
|
||||
segment = grid.compute_astar_path(x1, y1, x2, y2)
|
||||
if segment:
|
||||
# Add segment (avoiding duplicates)
|
||||
if not player_path or segment[0] != player_path[-1]:
|
||||
player_path.extend(segment)
|
||||
else:
|
||||
player_path.extend(segment[1:])
|
||||
|
||||
enemy_path = []
|
||||
for i in range(len(enemy_waypoints) - 1):
|
||||
x1, y1 = enemy_waypoints[i]
|
||||
x2, y2 = enemy_waypoints[i + 1]
|
||||
|
||||
segment = grid.compute_astar_path(x1, y1, x2, y2)
|
||||
if segment:
|
||||
if not enemy_path or segment[0] != enemy_path[-1]:
|
||||
enemy_path.extend(segment)
|
||||
else:
|
||||
enemy_path.extend(segment[1:])
|
||||
|
||||
print(f"Player path: {len(player_path)} steps")
|
||||
print(f"Enemy path: {len(enemy_path)} steps")
|
||||
|
||||
def setup_ui():
|
||||
"""Create UI elements"""
|
||||
ui = mcrfpy.sceneUI("path_vision_demo")
|
||||
ui.append(grid)
|
||||
|
||||
# Position and size grid
|
||||
grid.position = (50, 80)
|
||||
grid.size = (700, 500) # Adjust based on zoom
|
||||
|
||||
# Title
|
||||
title = mcrfpy.Caption("Path & Vision Sizzle Reel", 300, 20)
|
||||
title.fill_color = mcrfpy.Color(255, 255, 255)
|
||||
ui.append(title)
|
||||
|
||||
# Status
|
||||
global status_text, perspective_text
|
||||
status_text = mcrfpy.Caption("Starting demo...", 50, 50)
|
||||
status_text.fill_color = mcrfpy.Color(200, 200, 200)
|
||||
ui.append(status_text)
|
||||
|
||||
perspective_text = mcrfpy.Caption("Perspective: Player", 550, 50)
|
||||
perspective_text.fill_color = mcrfpy.Color(100, 255, 100)
|
||||
ui.append(perspective_text)
|
||||
|
||||
# Controls
|
||||
controls = mcrfpy.Caption("Space: Pause/Resume | R: Restart | Q: Quit", 250, 600)
|
||||
controls.fill_color = mcrfpy.Color(150, 150, 150)
|
||||
ui.append(controls)
|
||||
|
||||
# Animation control
|
||||
paused = False
|
||||
move_timer = 0
|
||||
zoom_transition = False
|
||||
|
||||
def move_entity_smooth(entity, target_x, target_y, duration=0.3):
|
||||
"""Smoothly animate entity to position"""
|
||||
# Create position animation
|
||||
anim_x = mcrfpy.Animation("x", float(target_x), duration, "easeInOut")
|
||||
anim_y = mcrfpy.Animation("y", float(target_y), duration, "easeInOut")
|
||||
|
||||
anim_x.start(entity)
|
||||
anim_y.start(entity)
|
||||
|
||||
def update_camera_smooth(center_x, center_y, duration=0.3):
|
||||
"""Smoothly move camera center"""
|
||||
# Convert grid coords to pixel coords (assuming 16x16 tiles)
|
||||
pixel_x = center_x * 16
|
||||
pixel_y = center_y * 16
|
||||
|
||||
anim = mcrfpy.Animation("center", (pixel_x, pixel_y), duration, "easeOut")
|
||||
anim.start(grid)
|
||||
|
||||
def start_perspective_transition():
|
||||
"""Begin the dramatic perspective shift"""
|
||||
global zoom_transition, sequence_step
|
||||
zoom_transition = True
|
||||
sequence_step = 100 # Special sequence number
|
||||
|
||||
status_text.text = "Perspective shift: Zooming out..."
|
||||
|
||||
# Zoom out with elastic easing
|
||||
zoom_out = mcrfpy.Animation("zoom", 0.5, 2.0, "easeInExpo")
|
||||
zoom_out.start(grid)
|
||||
|
||||
# Schedule the perspective switch
|
||||
mcrfpy.setTimer("switch_perspective", switch_perspective, 2100)
|
||||
|
||||
def switch_perspective(dt):
|
||||
"""Switch perspective at the peak of zoom"""
|
||||
global sequence_step
|
||||
|
||||
# Switch to enemy perspective
|
||||
grid.perspective = 1
|
||||
perspective_text.text = "Perspective: Enemy"
|
||||
perspective_text.fill_color = mcrfpy.Color(255, 100, 100)
|
||||
|
||||
status_text.text = "Perspective shift: Following enemy..."
|
||||
|
||||
# Update camera to enemy position
|
||||
update_camera_smooth(enemy.x, enemy.y, 0.1)
|
||||
|
||||
# Zoom back in
|
||||
zoom_in = mcrfpy.Animation("zoom", 1.2, 2.0, "easeOutExpo")
|
||||
zoom_in.start(grid)
|
||||
|
||||
# Resume sequence
|
||||
mcrfpy.setTimer("resume_enemy", resume_enemy_sequence, 2100)
|
||||
|
||||
# Cancel this timer
|
||||
mcrfpy.delTimer("switch_perspective")
|
||||
|
||||
def resume_enemy_sequence(dt):
|
||||
"""Resume following enemy after perspective shift"""
|
||||
global sequence_step, zoom_transition
|
||||
zoom_transition = False
|
||||
sequence_step = 101 # Continue with enemy movement
|
||||
mcrfpy.delTimer("resume_enemy")
|
||||
|
||||
def sequence_tick(dt):
|
||||
"""Main sequence controller"""
|
||||
global sequence_step, player_path_index, enemy_path_index, move_timer
|
||||
|
||||
if paused or zoom_transition:
|
||||
return
|
||||
|
||||
move_timer += dt
|
||||
if move_timer < 400: # Move every 400ms
|
||||
return
|
||||
move_timer = 0
|
||||
|
||||
if sequence_step < 50:
|
||||
# Phase 1: Follow player movement
|
||||
if player_path_index < len(player_path):
|
||||
x, y = player_path[player_path_index]
|
||||
move_entity_smooth(player, x, y)
|
||||
player.update_visibility()
|
||||
|
||||
# Camera follows player
|
||||
if grid.perspective == 0:
|
||||
update_camera_smooth(player.x, player.y)
|
||||
|
||||
player_path_index += 1
|
||||
status_text.text = f"Player moving... Step {player_path_index}/{len(player_path)}"
|
||||
|
||||
# Start enemy movement after player has moved a bit
|
||||
if player_path_index == 10:
|
||||
sequence_step = 1 # Enable enemy movement
|
||||
else:
|
||||
# Player reached destination, start perspective transition
|
||||
start_perspective_transition()
|
||||
|
||||
if sequence_step >= 1 and sequence_step < 50:
|
||||
# Phase 2: Enemy movement (concurrent with player)
|
||||
if enemy_path_index < len(enemy_path):
|
||||
x, y = enemy_path[enemy_path_index]
|
||||
move_entity_smooth(enemy, x, y)
|
||||
enemy.update_visibility()
|
||||
|
||||
# Check if enemy is visible to player
|
||||
if grid.perspective == 0:
|
||||
enemy_cell_idx = int(enemy.y) * grid.grid_x + int(enemy.x)
|
||||
if enemy_cell_idx < len(player.gridstate) and player.gridstate[enemy_cell_idx].visible:
|
||||
status_text.text = "Enemy spotted!"
|
||||
|
||||
enemy_path_index += 1
|
||||
|
||||
elif sequence_step == 101:
|
||||
# Phase 3: Continue following enemy after perspective shift
|
||||
if enemy_path_index < len(enemy_path):
|
||||
x, y = enemy_path[enemy_path_index]
|
||||
move_entity_smooth(enemy, x, y)
|
||||
enemy.update_visibility()
|
||||
|
||||
# Camera follows enemy
|
||||
update_camera_smooth(enemy.x, enemy.y)
|
||||
|
||||
enemy_path_index += 1
|
||||
status_text.text = f"Following enemy... Step {enemy_path_index}/{len(enemy_path)}"
|
||||
else:
|
||||
status_text.text = "Demo complete! Press R to restart"
|
||||
sequence_step = 200 # Done
|
||||
|
||||
def handle_keys(key, state):
|
||||
"""Handle keyboard input"""
|
||||
global paused, sequence_step, player_path_index, enemy_path_index, move_timer
|
||||
key = key.lower()
|
||||
if state != "start":
|
||||
return
|
||||
|
||||
if key == "q":
|
||||
print("Exiting sizzle reel...")
|
||||
sys.exit(0)
|
||||
elif key == "space":
|
||||
paused = not paused
|
||||
status_text.text = "PAUSED" if paused else "Running..."
|
||||
elif key == "r":
|
||||
# Reset everything
|
||||
player.x, player.y = 3, 3
|
||||
enemy.x, enemy.y = 35, 20
|
||||
player.update_visibility()
|
||||
enemy.update_visibility()
|
||||
grid.perspective = 0
|
||||
perspective_text.text = "Perspective: Player"
|
||||
perspective_text.fill_color = mcrfpy.Color(100, 255, 100)
|
||||
sequence_step = 0
|
||||
player_path_index = 0
|
||||
enemy_path_index = 0
|
||||
move_timer = 0
|
||||
update_camera_smooth(player.x, player.y, 0.5)
|
||||
|
||||
# Reset zoom
|
||||
zoom_reset = mcrfpy.Animation("zoom", 1.2, 0.5, "easeOut")
|
||||
zoom_reset.start(grid)
|
||||
|
||||
status_text.text = "Demo restarted!"
|
||||
|
||||
# Initialize everything
|
||||
print("Path & Vision Sizzle Reel")
|
||||
print("=========================")
|
||||
print("Demonstrating:")
|
||||
print("- Smooth entity movement along calculated paths")
|
||||
print("- Camera following with animated grid centering")
|
||||
print("- Field of view updates as entities move")
|
||||
print("- Dramatic perspective transitions with zoom effects")
|
||||
print()
|
||||
|
||||
create_scene()
|
||||
setup_paths()
|
||||
setup_ui()
|
||||
|
||||
# Set scene and input
|
||||
mcrfpy.setScene("path_vision_demo")
|
||||
mcrfpy.keypressScene(handle_keys)
|
||||
|
||||
# Initial camera setup
|
||||
grid.zoom = 1.2
|
||||
update_camera_smooth(player.x, player.y, 0.1)
|
||||
|
||||
# Start the sequence
|
||||
mcrfpy.setTimer("sequence", sequence_tick, 50) # Tick every 50ms
|
||||
|
||||
print("Demo started!")
|
||||
print("- Player (@) will navigate through rooms")
|
||||
print("- Enemy (E) will move on a different path")
|
||||
print("- Watch for the dramatic perspective shift!")
|
||||
print()
|
||||
print("Controls: Space=Pause, R=Restart, Q=Quit")
|
||||
46
tests/simple_interactive_visibility.py
Normal file
46
tests/simple_interactive_visibility.py
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Simple interactive visibility test"""
|
||||
|
||||
import mcrfpy
|
||||
import sys
|
||||
|
||||
# Create scene and grid
|
||||
print("Creating scene...")
|
||||
mcrfpy.createScene("vis_test")
|
||||
|
||||
print("Creating grid...")
|
||||
grid = mcrfpy.Grid(grid_x=10, grid_y=10)
|
||||
|
||||
# Initialize grid
|
||||
print("Initializing grid...")
|
||||
for y in range(10):
|
||||
for x in range(10):
|
||||
cell = grid.at(x, y)
|
||||
cell.walkable = True
|
||||
cell.transparent = True
|
||||
cell.color = mcrfpy.Color(100, 100, 120)
|
||||
|
||||
# Create entity
|
||||
print("Creating entity...")
|
||||
entity = mcrfpy.Entity(5, 5, grid=grid)
|
||||
entity.sprite_index = 64
|
||||
|
||||
print("Updating visibility...")
|
||||
entity.update_visibility()
|
||||
|
||||
# Set up UI
|
||||
print("Setting up UI...")
|
||||
ui = mcrfpy.sceneUI("vis_test")
|
||||
ui.append(grid)
|
||||
grid.position = (50, 50)
|
||||
grid.size = (300, 300)
|
||||
|
||||
# Test perspective
|
||||
print("Testing perspective...")
|
||||
grid.perspective = -1 # Omniscient
|
||||
print(f"Perspective set to: {grid.perspective}")
|
||||
|
||||
print("Setting scene...")
|
||||
mcrfpy.setScene("vis_test")
|
||||
|
||||
print("Ready!")
|
||||
39
tests/simple_visibility_test.py
Normal file
39
tests/simple_visibility_test.py
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Simple visibility test without entity append"""
|
||||
|
||||
import mcrfpy
|
||||
import sys
|
||||
|
||||
print("Simple visibility test...")
|
||||
|
||||
# Create scene and grid
|
||||
mcrfpy.createScene("simple")
|
||||
print("Scene created")
|
||||
|
||||
grid = mcrfpy.Grid(grid_x=5, grid_y=5)
|
||||
print("Grid created")
|
||||
|
||||
# Create entity without appending
|
||||
entity = mcrfpy.Entity(2, 2, grid=grid)
|
||||
print(f"Entity created at ({entity.x}, {entity.y})")
|
||||
|
||||
# Check if gridstate is initialized
|
||||
print(f"Gridstate length: {len(entity.gridstate)}")
|
||||
|
||||
# Try to access at method
|
||||
try:
|
||||
state = entity.at(0, 0)
|
||||
print(f"at(0,0) returned: {state}")
|
||||
print(f"visible: {state.visible}, discovered: {state.discovered}")
|
||||
except Exception as e:
|
||||
print(f"Error in at(): {e}")
|
||||
|
||||
# Try update_visibility
|
||||
try:
|
||||
entity.update_visibility()
|
||||
print("update_visibility() succeeded")
|
||||
except Exception as e:
|
||||
print(f"Error in update_visibility(): {e}")
|
||||
|
||||
print("Test complete")
|
||||
sys.exit(0)
|
||||
Loading…
Add table
Add a link
Reference in a new issue