McRogueFace/tests/unit/test_dijkstra_pathfinding.py

222 lines
6.8 KiB
Python

#!/usr/bin/env python3
"""
Test Dijkstra Pathfinding Implementation
========================================
Demonstrates:
1. Computing Dijkstra distance map from a root position
2. Getting distances to any position
3. Finding paths from any position back to the root
4. Multi-target pathfinding (flee/approach scenarios)
"""
import mcrfpy
from mcrfpy import libtcod
import sys
def create_test_grid():
"""Create a test grid with obstacles"""
mcrfpy.createScene("dijkstra_test")
# Create grid
grid = mcrfpy.Grid(grid_x=20, grid_y=20)
# Initialize all cells as walkable
for y in range(grid.grid_y):
for x in range(grid.grid_x):
cell = grid.at(x, y)
cell.walkable = True
cell.transparent = True
cell.tilesprite = 46 # . period
cell.color = mcrfpy.Color(50, 50, 50)
# Create some walls to make pathfinding interesting
# Vertical wall
for y in range(5, 15):
cell = grid.at(10, y)
cell.walkable = False
cell.transparent = False
cell.tilesprite = 219 # Block
cell.color = mcrfpy.Color(100, 100, 100)
# Horizontal wall
for x in range(5, 15):
if x != 10: # Leave a gap
cell = grid.at(x, 10)
cell.walkable = False
cell.transparent = False
cell.tilesprite = 219
cell.color = mcrfpy.Color(100, 100, 100)
return grid
def test_basic_dijkstra():
"""Test basic Dijkstra functionality"""
print("\n=== Testing Basic Dijkstra ===")
grid = create_test_grid()
# Compute Dijkstra map from position (5, 5)
root_x, root_y = 5, 5
print(f"Computing Dijkstra map from root ({root_x}, {root_y})")
grid.compute_dijkstra(root_x, root_y)
# Test getting distances to various points
test_points = [
(5, 5), # Root position (should be 0)
(6, 5), # Adjacent (should be 1)
(7, 5), # Two steps away
(15, 15), # Far corner
(10, 10), # On a wall (should be unreachable)
]
print("\nDistances from root:")
for x, y in test_points:
distance = grid.get_dijkstra_distance(x, y)
if distance is None:
print(f" ({x:2}, {y:2}): UNREACHABLE")
else:
print(f" ({x:2}, {y:2}): {distance:.1f}")
# Test getting paths
print("\nPaths to root:")
for x, y in [(15, 5), (15, 15), (5, 15)]:
path = grid.get_dijkstra_path(x, y)
if path:
print(f" From ({x}, {y}): {len(path)} steps")
# Show first few steps
for i, (px, py) in enumerate(path[:3]):
print(f" Step {i+1}: ({px}, {py})")
if len(path) > 3:
print(f" ... {len(path)-3} more steps")
else:
print(f" From ({x}, {y}): No path found")
def test_libtcod_interface():
"""Test the libtcod module interface"""
print("\n=== Testing libtcod Interface ===")
grid = create_test_grid()
# Use libtcod functions
print("Using libtcod.dijkstra_* functions:")
# Create dijkstra context (returns grid)
dijkstra = libtcod.dijkstra_new(grid)
print(f"Created Dijkstra context: {type(dijkstra)}")
# Compute from a position
libtcod.dijkstra_compute(grid, 10, 2)
print("Computed Dijkstra map from (10, 2)")
# Get distance using libtcod
distance = libtcod.dijkstra_get_distance(grid, 10, 17)
print(f"Distance to (10, 17): {distance}")
# Get path using libtcod
path = libtcod.dijkstra_path_to(grid, 10, 17)
print(f"Path from (10, 17) to root: {len(path) if path else 0} steps")
def test_multi_target_scenario():
"""Test fleeing/approaching multiple targets"""
print("\n=== Testing Multi-Target Scenario ===")
grid = create_test_grid()
# Place three "threats" and compute their Dijkstra maps
threats = [(3, 3), (17, 3), (10, 17)]
print("Computing threat distances...")
threat_distances = []
for i, (tx, ty) in enumerate(threats):
# Mark threat position
cell = grid.at(tx, ty)
cell.tilesprite = 84 # T for threat
cell.color = mcrfpy.Color(255, 0, 0)
# Compute Dijkstra from this threat
grid.compute_dijkstra(tx, ty)
# Store distances for all cells
distances = {}
for y in range(grid.grid_y):
for x in range(grid.grid_x):
d = grid.get_dijkstra_distance(x, y)
if d is not None:
distances[(x, y)] = d
threat_distances.append(distances)
print(f" Threat {i+1} at ({tx}, {ty}): {len(distances)} reachable cells")
# Find safest position (farthest from all threats)
print("\nFinding safest position...")
best_pos = None
best_min_dist = 0
for y in range(grid.grid_y):
for x in range(grid.grid_x):
# Skip if not walkable
if not grid.at(x, y).walkable:
continue
# Get minimum distance to any threat
min_dist = float('inf')
for threat_dist in threat_distances:
if (x, y) in threat_dist:
min_dist = min(min_dist, threat_dist[(x, y)])
# Track best position
if min_dist > best_min_dist and min_dist != float('inf'):
best_min_dist = min_dist
best_pos = (x, y)
if best_pos:
print(f"Safest position: {best_pos} (min distance to threats: {best_min_dist:.1f})")
# Mark safe position
cell = grid.at(best_pos[0], best_pos[1])
cell.tilesprite = 83 # S for safe
cell.color = mcrfpy.Color(0, 255, 0)
def run_test(runtime):
"""Timer callback to run tests after scene loads"""
test_basic_dijkstra()
test_libtcod_interface()
test_multi_target_scenario()
print("\n=== Dijkstra Implementation Test Complete ===")
print("✓ Basic Dijkstra computation works")
print("✓ Distance queries work")
print("✓ Path finding works")
print("✓ libtcod interface works")
print("✓ Multi-target scenarios work")
# Take screenshot
try:
from mcrfpy import automation
automation.screenshot("dijkstra_test.png")
print("\nScreenshot saved: dijkstra_test.png")
except:
pass
sys.exit(0)
# Main execution
print("McRogueFace Dijkstra Pathfinding Test")
print("=====================================")
# Set up scene
grid = create_test_grid()
ui = mcrfpy.sceneUI("dijkstra_test")
ui.append(grid)
# Add title
title = mcrfpy.Caption("Dijkstra Pathfinding Test", 10, 10)
title.fill_color = mcrfpy.Color(255, 255, 255)
ui.append(title)
# Set timer to run tests
mcrfpy.setTimer("test", run_test, 100)
# Show scene
mcrfpy.setScene("dijkstra_test")