236 lines
7.0 KiB
Python
236 lines
7.0 KiB
Python
#!/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!")
|