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:
John McCardle 2025-07-09 22:18:29 -04:00
commit d13153ddb4
25 changed files with 3317 additions and 225 deletions

View file

@ -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
View 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
View 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)

View 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
View 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
View 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")

View 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
View 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.")

View 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.")

View file

@ -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

View 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
View 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")

View 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")

View 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!")

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