forked from UNN/2026-rff_mp
653 lines
23 KiB
Python
653 lines
23 KiB
Python
|
|
import os
|
||
|
|
import time
|
||
|
|
import heapq
|
||
|
|
from collections import deque
|
||
|
|
from typing import List, Dict, Optional, Tuple
|
||
|
|
from abc import ABC, abstractmethod
|
||
|
|
from dataclasses import dataclass
|
||
|
|
|
||
|
|
|
||
|
|
class Cell:
|
||
|
|
def __init__(self, x: int, y: int, is_wall: bool = True, is_start: bool = False, is_exit: bool = False):
|
||
|
|
self.x = x
|
||
|
|
self.y = y
|
||
|
|
self.is_wall = is_wall
|
||
|
|
self.is_start = is_start
|
||
|
|
self.is_exit = is_exit
|
||
|
|
|
||
|
|
def is_passable(self) -> bool:
|
||
|
|
return not self.is_wall
|
||
|
|
|
||
|
|
def __eq__(self, other):
|
||
|
|
if not isinstance(other, Cell):
|
||
|
|
return False
|
||
|
|
return self.x == other.x and self.y == other.y
|
||
|
|
|
||
|
|
def __hash__(self):
|
||
|
|
return hash((self.x, self.y))
|
||
|
|
|
||
|
|
def __repr__(self):
|
||
|
|
return f"Cell({self.x}, {self.y})"
|
||
|
|
|
||
|
|
|
||
|
|
class Maze:
|
||
|
|
def __init__(self, width: int = 0, height: int = 0):
|
||
|
|
self.width = width
|
||
|
|
self.height = height
|
||
|
|
self.grid: List[List[Cell]] = []
|
||
|
|
self.start: Optional[Cell] = None
|
||
|
|
self.exit: Optional[Cell] = None
|
||
|
|
|
||
|
|
def set_cell(self, x: int, y: int, cell: Cell) -> None:
|
||
|
|
if 0 <= x < self.width and 0 <= y < self.height:
|
||
|
|
self.grid[y][x] = cell
|
||
|
|
|
||
|
|
def get_cell(self, x: int, y: int) -> Optional[Cell]:
|
||
|
|
if 0 <= x < self.width and 0 <= y < self.height:
|
||
|
|
return self.grid[y][x]
|
||
|
|
return None
|
||
|
|
|
||
|
|
def get_neighbors(self, cell: Cell) -> List[Cell]:
|
||
|
|
neighbors = []
|
||
|
|
directions = [(0, -1), (0, 1), (-1, 0), (1, 0)]
|
||
|
|
|
||
|
|
for dx, dy in directions:
|
||
|
|
nx, ny = cell.x + dx, cell.y + dy
|
||
|
|
neighbor = self.get_cell(nx, ny)
|
||
|
|
if neighbor and neighbor.is_passable():
|
||
|
|
neighbors.append(neighbor)
|
||
|
|
|
||
|
|
return neighbors
|
||
|
|
class MazeBuilder(ABC):
|
||
|
|
@abstractmethod
|
||
|
|
def build_from_file(self, filename: str) -> Maze:
|
||
|
|
pass
|
||
|
|
|
||
|
|
|
||
|
|
class TextFileMazeBuilder(MazeBuilder):
|
||
|
|
|
||
|
|
def build_from_file(self, filename: str) -> Maze:
|
||
|
|
with open(filename, 'r', encoding='utf-8') as file:
|
||
|
|
lines = [line.rstrip('\n') for line in file.readlines()]
|
||
|
|
|
||
|
|
while lines and not lines[0].strip():
|
||
|
|
lines.pop(0)
|
||
|
|
while lines and not lines[-1].strip():
|
||
|
|
lines.pop()
|
||
|
|
|
||
|
|
height = len(lines)
|
||
|
|
width = max(len(line) for line in lines) if height > 0 else 0
|
||
|
|
|
||
|
|
maze = Maze(width, height)
|
||
|
|
maze.grid = [[None for _ in range(width)] for _ in range(height)]
|
||
|
|
|
||
|
|
start_count = 0
|
||
|
|
exit_count = 0
|
||
|
|
|
||
|
|
for y, line in enumerate(lines):
|
||
|
|
for x in range(width):
|
||
|
|
char = line[x] if x < len(line) else '#'
|
||
|
|
|
||
|
|
if char == '#':
|
||
|
|
cell = Cell(x, y, is_wall=True)
|
||
|
|
elif char == ' ':
|
||
|
|
cell = Cell(x, y, is_wall=False)
|
||
|
|
elif char == 'S':
|
||
|
|
cell = Cell(x, y, is_wall=False, is_start=True)
|
||
|
|
maze.start = cell
|
||
|
|
start_count += 1
|
||
|
|
elif char == 'E':
|
||
|
|
cell = Cell(x, y, is_wall=False, is_exit=True)
|
||
|
|
maze.exit = cell
|
||
|
|
exit_count += 1
|
||
|
|
else:
|
||
|
|
cell = Cell(x, y, is_wall=True)
|
||
|
|
|
||
|
|
maze.set_cell(x, y, cell)
|
||
|
|
|
||
|
|
if start_count == 0:
|
||
|
|
raise ValueError("Лабиринт должен содержать старт (S)")
|
||
|
|
if start_count > 1:
|
||
|
|
raise ValueError("Лабиринт может содержать только один старт (S)")
|
||
|
|
if exit_count == 0:
|
||
|
|
raise ValueError("Лабиринт должен содержать выход (E)")
|
||
|
|
if exit_count > 1:
|
||
|
|
raise ValueError("Лабиринт может содержать только один выход (E)")
|
||
|
|
|
||
|
|
return maze
|
||
|
|
class PathFindingStrategy(ABC):
|
||
|
|
|
||
|
|
@abstractmethod
|
||
|
|
def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> Tuple[List[Cell], int]:
|
||
|
|
pass
|
||
|
|
|
||
|
|
def _reconstruct_path(self, parents: Dict[Cell, Cell], start: Cell, exit_cell: Cell) -> List[Cell]:
|
||
|
|
path = []
|
||
|
|
current = exit_cell
|
||
|
|
|
||
|
|
while current != start:
|
||
|
|
path.append(current)
|
||
|
|
if current not in parents:
|
||
|
|
return []
|
||
|
|
current = parents[current]
|
||
|
|
path.append(start)
|
||
|
|
path.reverse()
|
||
|
|
return path
|
||
|
|
|
||
|
|
@property
|
||
|
|
def name(self) -> str:
|
||
|
|
return self.__class__.__name__.replace('Strategy', '')
|
||
|
|
|
||
|
|
|
||
|
|
class BFSStrategy(PathFindingStrategy):
|
||
|
|
|
||
|
|
def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> Tuple[List[Cell], int]:
|
||
|
|
queue = deque([start])
|
||
|
|
visited = {start}
|
||
|
|
parents: Dict[Cell, Cell] = {}
|
||
|
|
visited_count = 1
|
||
|
|
|
||
|
|
while queue:
|
||
|
|
current = queue.popleft()
|
||
|
|
|
||
|
|
if current == exit_cell:
|
||
|
|
return self._reconstruct_path(parents, start, exit_cell), visited_count
|
||
|
|
|
||
|
|
for neighbor in maze.get_neighbors(current):
|
||
|
|
if neighbor not in visited:
|
||
|
|
visited.add(neighbor)
|
||
|
|
visited_count += 1
|
||
|
|
parents[neighbor] = current
|
||
|
|
queue.append(neighbor)
|
||
|
|
|
||
|
|
return [], visited_count
|
||
|
|
|
||
|
|
|
||
|
|
class DFSStrategy(PathFindingStrategy):
|
||
|
|
|
||
|
|
def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> Tuple[List[Cell], int]:
|
||
|
|
stack = [(start, [start])]
|
||
|
|
visited = {start}
|
||
|
|
visited_count = 1
|
||
|
|
|
||
|
|
while stack:
|
||
|
|
current, path = stack.pop()
|
||
|
|
|
||
|
|
if current == exit_cell:
|
||
|
|
return path, visited_count
|
||
|
|
|
||
|
|
for neighbor in maze.get_neighbors(current):
|
||
|
|
if neighbor not in visited:
|
||
|
|
visited.add(neighbor)
|
||
|
|
visited_count += 1
|
||
|
|
stack.append((neighbor, path + [neighbor]))
|
||
|
|
|
||
|
|
return [], visited_count
|
||
|
|
|
||
|
|
|
||
|
|
class AStarStrategy(PathFindingStrategy):
|
||
|
|
|
||
|
|
def _heuristic(self, cell: Cell, target: Cell) -> int:
|
||
|
|
return abs(cell.x - target.x) + abs(cell.y - target.y)
|
||
|
|
|
||
|
|
def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> Tuple[List[Cell], int]:
|
||
|
|
counter = 0
|
||
|
|
open_set = [(0, counter, start)]
|
||
|
|
came_from: Dict[Cell, Cell] = {}
|
||
|
|
|
||
|
|
g_score: Dict[Cell, float] = {start: 0}
|
||
|
|
f_score: Dict[Cell, float] = {start: self._heuristic(start, exit_cell)}
|
||
|
|
|
||
|
|
open_set_hash = {start}
|
||
|
|
visited_count = 1
|
||
|
|
|
||
|
|
while open_set:
|
||
|
|
current = heapq.heappop(open_set)[2]
|
||
|
|
open_set_hash.remove(current)
|
||
|
|
|
||
|
|
if current == exit_cell:
|
||
|
|
path = self._reconstruct_path(came_from, start, exit_cell)
|
||
|
|
return path, visited_count
|
||
|
|
|
||
|
|
for neighbor in maze.get_neighbors(current):
|
||
|
|
tentative_g_score = g_score[current] + 1
|
||
|
|
|
||
|
|
if tentative_g_score < g_score.get(neighbor, float('inf')):
|
||
|
|
came_from[neighbor] = current
|
||
|
|
g_score[neighbor] = tentative_g_score
|
||
|
|
f_score[neighbor] = tentative_g_score + self._heuristic(neighbor, exit_cell)
|
||
|
|
|
||
|
|
if neighbor not in open_set_hash:
|
||
|
|
visited_count += 1
|
||
|
|
counter += 1
|
||
|
|
heapq.heappush(open_set, (f_score[neighbor], counter, neighbor))
|
||
|
|
open_set_hash.add(neighbor)
|
||
|
|
|
||
|
|
return [], visited_count
|
||
|
|
@dataclass
|
||
|
|
class SearchStats:
|
||
|
|
execution_time_ms: float
|
||
|
|
path_length: int
|
||
|
|
visited_cells: int
|
||
|
|
success: bool
|
||
|
|
|
||
|
|
|
||
|
|
class MazeSolver:
|
||
|
|
|
||
|
|
def __init__(self, maze: Maze, strategy: PathFindingStrategy):
|
||
|
|
self.maze = maze
|
||
|
|
self.strategy = strategy
|
||
|
|
|
||
|
|
def set_strategy(self, strategy: PathFindingStrategy) -> None:
|
||
|
|
self.strategy = strategy
|
||
|
|
|
||
|
|
def solve(self) -> Tuple[List[Cell], SearchStats]:
|
||
|
|
if not self.maze.start or not self.maze.exit:
|
||
|
|
raise ValueError("Лабиринт должен содержать старт и выход")
|
||
|
|
|
||
|
|
start_time = time.perf_counter()
|
||
|
|
path, visited_cells = self.strategy.find_path(self.maze, self.maze.start, self.maze.exit)
|
||
|
|
end_time = time.perf_counter()
|
||
|
|
|
||
|
|
execution_time = (end_time - start_time) * 1000
|
||
|
|
|
||
|
|
stats = SearchStats(
|
||
|
|
execution_time_ms=execution_time,
|
||
|
|
path_length=len(path),
|
||
|
|
visited_cells=visited_cells,
|
||
|
|
success=len(path) > 0
|
||
|
|
)
|
||
|
|
|
||
|
|
return path, stats
|
||
|
|
class MazeVisualizer:
|
||
|
|
|
||
|
|
@staticmethod
|
||
|
|
def render(maze: Maze, path: List[Cell] = None, player_pos: Cell = None):
|
||
|
|
print("\n" + "=" * (maze.width + 2))
|
||
|
|
|
||
|
|
for y in range(maze.height):
|
||
|
|
row = "|"
|
||
|
|
for x in range(maze.width):
|
||
|
|
cell = maze.get_cell(x, y)
|
||
|
|
if cell:
|
||
|
|
if player_pos and cell == player_pos:
|
||
|
|
row += "P"
|
||
|
|
elif path and cell in path and not cell.is_start and not cell.is_exit:
|
||
|
|
row += "."
|
||
|
|
elif cell.is_start:
|
||
|
|
row += "S"
|
||
|
|
elif cell.is_exit:
|
||
|
|
row += "E"
|
||
|
|
elif cell.is_wall:
|
||
|
|
row += "#"
|
||
|
|
else:
|
||
|
|
row += " "
|
||
|
|
else:
|
||
|
|
row += " "
|
||
|
|
row += "|"
|
||
|
|
print(row)
|
||
|
|
print("=" * (maze.width + 2))
|
||
|
|
def create_test_mazes():
|
||
|
|
|
||
|
|
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||
|
|
|
||
|
|
small_maze = """##########
|
||
|
|
#S #
|
||
|
|
# ### ## #
|
||
|
|
# # #
|
||
|
|
### # ####
|
||
|
|
# # #
|
||
|
|
# ### # #
|
||
|
|
# # E#
|
||
|
|
##########"""
|
||
|
|
|
||
|
|
medium_maze = """####################
|
||
|
|
#S #
|
||
|
|
# ### ########### #
|
||
|
|
# # # # # #
|
||
|
|
# # # # ####### # #
|
||
|
|
# # # # # #
|
||
|
|
# # ######### # # #
|
||
|
|
# # # # #
|
||
|
|
# # ########### # #
|
||
|
|
# # # #
|
||
|
|
# ############### #
|
||
|
|
# E#
|
||
|
|
####################"""
|
||
|
|
|
||
|
|
large_maze = """##################################################
|
||
|
|
#S #
|
||
|
|
# ############################################# #
|
||
|
|
# # # #
|
||
|
|
# # ######################################### # #
|
||
|
|
# # # # # #
|
||
|
|
# # # ##################################### # # #
|
||
|
|
# # # # # # # #
|
||
|
|
# # # # ################################# # # # #
|
||
|
|
# # # # # # # # # #
|
||
|
|
# # # # # ############################# # # # # #
|
||
|
|
# # # # # # # # # # # #
|
||
|
|
# # # # # # ######################### # # # # # #
|
||
|
|
# # # # # # # # # # # # # #
|
||
|
|
# # # # # # # ##################### # # # # # # #
|
||
|
|
# # # # # # # # # # # # # # # #
|
||
|
|
# # # # # # # # ################# # # # # # # # #
|
||
|
|
# # # # # # # # # # # # # # # # # #
|
||
|
|
# # # # # # # # # ############# # # # # # # # # #
|
||
|
|
# # # # # # # # # # # # # # # # # # # #
|
||
|
|
# # # # # # # # # # ######### # # # # # # # # # #
|
||
|
|
# # # # # # # # # # # # # # # # # # # # # #
|
||
|
|
# # # # # # # # # # # ##### # # # # # # # # # # #
|
||
|
|
# # # # # # # # # # # # # # # # # # # # # # # #
|
||
|
|
# # # # # # # # # # # # # # # # # # # # # # # #E#
|
||
|
|
##################################################"""
|
||
|
|
|
||
|
|
empty_maze_lines = ["#" + "#" * 38 + "#"]
|
||
|
|
empty_maze_lines.append("#S" + " " * 37 + "#")
|
||
|
|
for i in range(35):
|
||
|
|
empty_maze_lines.append("#" + " " * 38 + "#")
|
||
|
|
empty_maze_lines.append("#" + " " * 37 + "E#")
|
||
|
|
empty_maze_lines.append("#" + "#" * 38 + "#")
|
||
|
|
empty_maze = "\n".join(empty_maze_lines)
|
||
|
|
|
||
|
|
no_exit_maze = """##########
|
||
|
|
#S #
|
||
|
|
# ### ## #
|
||
|
|
# # #
|
||
|
|
### # ####
|
||
|
|
# # #
|
||
|
|
# ### # #
|
||
|
|
# #
|
||
|
|
##########"""
|
||
|
|
|
||
|
|
mazes = [
|
||
|
|
("01_small_maze.txt", small_maze),
|
||
|
|
("02_medium_maze.txt", medium_maze),
|
||
|
|
("03_large_maze.txt", large_maze),
|
||
|
|
("04_empty_maze.txt", empty_maze),
|
||
|
|
("05_no_exit_maze.txt", no_exit_maze)
|
||
|
|
]
|
||
|
|
|
||
|
|
print("\n" + "="*60)
|
||
|
|
print("CREATING TEST MAZES")
|
||
|
|
print("="*60)
|
||
|
|
|
||
|
|
for filename, content in mazes:
|
||
|
|
filepath = os.path.join(current_dir, filename)
|
||
|
|
with open(filepath, 'w', encoding='utf-8') as f:
|
||
|
|
f.write(content)
|
||
|
|
print(f"Created: {filename}")
|
||
|
|
|
||
|
|
print("="*60)
|
||
|
|
|
||
|
|
|
||
|
|
def list_available_mazes():
|
||
|
|
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||
|
|
maze_files = [f for f in os.listdir(current_dir) if f.endswith('.txt') and ('maze' in f.lower() or f.startswith('0'))]
|
||
|
|
|
||
|
|
if not maze_files:
|
||
|
|
return []
|
||
|
|
|
||
|
|
print("\nAVAILABLE MAZES:")
|
||
|
|
print("-" * 50)
|
||
|
|
for i, file in enumerate(maze_files, 1):
|
||
|
|
print(f" {i}. {file}")
|
||
|
|
|
||
|
|
return maze_files
|
||
|
|
class Benchmark:
|
||
|
|
|
||
|
|
def __init__(self):
|
||
|
|
self.strategies = [
|
||
|
|
BFSStrategy(),
|
||
|
|
DFSStrategy(),
|
||
|
|
AStarStrategy()
|
||
|
|
]
|
||
|
|
|
||
|
|
def run_experiment(self, maze: Maze, runs: int = 5) -> Dict:
|
||
|
|
results = {}
|
||
|
|
|
||
|
|
for strategy in self.strategies:
|
||
|
|
times = []
|
||
|
|
path_lengths = []
|
||
|
|
visited_counts = []
|
||
|
|
|
||
|
|
for _ in range(runs):
|
||
|
|
solver = MazeSolver(maze, strategy)
|
||
|
|
path, stats = solver.solve()
|
||
|
|
|
||
|
|
if stats.success:
|
||
|
|
times.append(stats.execution_time_ms)
|
||
|
|
path_lengths.append(stats.path_length)
|
||
|
|
visited_counts.append(stats.visited_cells)
|
||
|
|
|
||
|
|
if times:
|
||
|
|
results[strategy.name] = {
|
||
|
|
'avg_time_ms': sum(times) / len(times),
|
||
|
|
'avg_path_length': sum(path_lengths) / len(path_lengths),
|
||
|
|
'avg_visited_cells': sum(visited_counts) / len(visited_counts),
|
||
|
|
'success_rate': len(times) / runs * 100
|
||
|
|
}
|
||
|
|
else:
|
||
|
|
results[strategy.name] = {
|
||
|
|
'avg_time_ms': float('inf'),
|
||
|
|
'avg_path_length': 0,
|
||
|
|
'avg_visited_cells': 0,
|
||
|
|
'success_rate': 0
|
||
|
|
}
|
||
|
|
|
||
|
|
return results
|
||
|
|
|
||
|
|
@staticmethod
|
||
|
|
def print_results(results: Dict, maze_name: str):
|
||
|
|
print(f"\n{'='*60}")
|
||
|
|
print(f"RESULTS FOR MAZE: {maze_name}")
|
||
|
|
print(f"{'='*60}")
|
||
|
|
print(f"{'Algorithm':<12} {'Time(ms)':<12} {'PathLen':<12} {'Visited':<12} {'Success':<8}")
|
||
|
|
print(f"{'-'*60}")
|
||
|
|
|
||
|
|
for strategy_name, stats in results.items():
|
||
|
|
print(f"{strategy_name:<12} {stats['avg_time_ms']:>8.3f} "
|
||
|
|
f"{stats['avg_path_length']:>8.1f} "
|
||
|
|
f"{stats['avg_visited_cells']:>8.1f} "
|
||
|
|
f"{stats['success_rate']:>6.1f}%")
|
||
|
|
def interactive_mode():
|
||
|
|
builder = TextFileMazeBuilder()
|
||
|
|
visualizer = MazeVisualizer()
|
||
|
|
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||
|
|
|
||
|
|
while True:
|
||
|
|
print("\n" + "="*60)
|
||
|
|
print("MAZE PATHFINDING PROGRAM")
|
||
|
|
print("="*60)
|
||
|
|
print("1. Load maze and find path")
|
||
|
|
print("2. Compare all algorithms on maze")
|
||
|
|
print("3. Create test mazes (5 pieces)")
|
||
|
|
print("4. Run benchmark on all mazes")
|
||
|
|
print("5. Show available mazes")
|
||
|
|
print("6. Exit")
|
||
|
|
print("="*60)
|
||
|
|
|
||
|
|
choice = input("Choose action (1-6): ").strip()
|
||
|
|
|
||
|
|
if choice == '6':
|
||
|
|
print("\nGoodbye!")
|
||
|
|
break
|
||
|
|
|
||
|
|
elif choice == '3':
|
||
|
|
create_test_mazes()
|
||
|
|
input("\nPress Enter to continue...")
|
||
|
|
|
||
|
|
elif choice == '5':
|
||
|
|
maze_files = list_available_mazes()
|
||
|
|
if not maze_files:
|
||
|
|
print("\nNo available mazes. Create them first (action 3)")
|
||
|
|
input("\nPress Enter to continue...")
|
||
|
|
|
||
|
|
elif choice == '1':
|
||
|
|
print("\nAvailable mazes:")
|
||
|
|
maze_files = list_available_mazes()
|
||
|
|
|
||
|
|
if not maze_files:
|
||
|
|
print("\nNo available mazes. Create them first (action 3)")
|
||
|
|
continue
|
||
|
|
|
||
|
|
filename = input("\nEnter path to maze file: ").strip()
|
||
|
|
|
||
|
|
if not os.path.exists(filename):
|
||
|
|
test_path = os.path.join(current_dir, filename)
|
||
|
|
if os.path.exists(test_path):
|
||
|
|
filename = test_path
|
||
|
|
|
||
|
|
try:
|
||
|
|
maze = builder.build_from_file(filename)
|
||
|
|
print("\nLOADED MAZE:")
|
||
|
|
visualizer.render(maze)
|
||
|
|
|
||
|
|
print("\nChoose algorithm:")
|
||
|
|
print(" 1. BFS - finds SHORTEST path")
|
||
|
|
print(" 2. DFS - FAST but path may be longer")
|
||
|
|
print(" 3. A* - OPTIMAL balance")
|
||
|
|
|
||
|
|
algo_choice = input("\nYour choice (1-3): ").strip()
|
||
|
|
|
||
|
|
strategies = {
|
||
|
|
'1': BFSStrategy(),
|
||
|
|
'2': DFSStrategy(),
|
||
|
|
'3': AStarStrategy()
|
||
|
|
}
|
||
|
|
|
||
|
|
if algo_choice in strategies:
|
||
|
|
print("\nSearching for path...")
|
||
|
|
solver = MazeSolver(maze, strategies[algo_choice])
|
||
|
|
path, stats = solver.solve()
|
||
|
|
|
||
|
|
if stats.success:
|
||
|
|
print(f"\nPATH FOUND!")
|
||
|
|
print(f"\nSTATISTICS:")
|
||
|
|
print(f" Time: {stats.execution_time_ms:.3f} ms")
|
||
|
|
print(f" Path length: {stats.path_length} steps")
|
||
|
|
print(f" Visited cells: {stats.visited_cells}")
|
||
|
|
print(f" Efficiency: {stats.visited_cells/stats.path_length:.1f} cells per step")
|
||
|
|
|
||
|
|
print("\nPATH ON MAP (. = path):")
|
||
|
|
visualizer.render(maze, path)
|
||
|
|
else:
|
||
|
|
print("\nPATH NOT FOUND! Exit unreachable from start.")
|
||
|
|
else:
|
||
|
|
print("Invalid choice!")
|
||
|
|
|
||
|
|
except FileNotFoundError:
|
||
|
|
print(f"\nFile '{filename}' not found!")
|
||
|
|
except Exception as e:
|
||
|
|
print(f"\nError: {e}")
|
||
|
|
|
||
|
|
input("\nPress Enter to continue...")
|
||
|
|
|
||
|
|
elif choice == '2':
|
||
|
|
print("\nAvailable mazes:")
|
||
|
|
maze_files = list_available_mazes()
|
||
|
|
|
||
|
|
if not maze_files:
|
||
|
|
print("\nNo available mazes. Create them first (action 3)")
|
||
|
|
continue
|
||
|
|
|
||
|
|
filename = input("\nEnter path to maze file: ").strip()
|
||
|
|
|
||
|
|
if not os.path.exists(filename):
|
||
|
|
test_path = os.path.join(current_dir, filename)
|
||
|
|
if os.path.exists(test_path):
|
||
|
|
filename = test_path
|
||
|
|
|
||
|
|
try:
|
||
|
|
maze = builder.build_from_file(filename)
|
||
|
|
print("\nLOADED MAZE:")
|
||
|
|
visualizer.render(maze)
|
||
|
|
|
||
|
|
print("\nRunning algorithm comparison (3 runs each)...")
|
||
|
|
benchmark = Benchmark()
|
||
|
|
results = benchmark.run_experiment(maze, runs=3)
|
||
|
|
|
||
|
|
maze_name = os.path.basename(filename)
|
||
|
|
benchmark.print_results(results, maze_name)
|
||
|
|
|
||
|
|
print("\nANALYSIS:")
|
||
|
|
print("-" * 40)
|
||
|
|
fastest = min(results.items(), key=lambda x: x[1]['avg_time_ms'])
|
||
|
|
print(f"Fastest: {fastest[0]} ({fastest[1]['avg_time_ms']:.3f} ms)")
|
||
|
|
|
||
|
|
shortest = min(results.items(), key=lambda x: x[1]['avg_path_length'])
|
||
|
|
print(f"Shortest path: {shortest[0]} ({shortest[1]['avg_path_length']:.0f} steps)")
|
||
|
|
|
||
|
|
efficient = min(results.items(), key=lambda x: x[1]['avg_visited_cells'])
|
||
|
|
print(f"Most efficient: {efficient[0]} (checked {efficient[1]['avg_visited_cells']:.0f} cells)")
|
||
|
|
|
||
|
|
except FileNotFoundError:
|
||
|
|
print(f"\nFile '{filename}' not found!")
|
||
|
|
except Exception as e:
|
||
|
|
print(f"\nError: {e}")
|
||
|
|
|
||
|
|
input("\nPress Enter to continue...")
|
||
|
|
|
||
|
|
elif choice == '4':
|
||
|
|
print("\nRUNNING FULL BENCHMARK")
|
||
|
|
print("="*60)
|
||
|
|
|
||
|
|
test_files = [
|
||
|
|
"01_small_maze.txt",
|
||
|
|
"02_medium_maze.txt",
|
||
|
|
"03_large_maze.txt",
|
||
|
|
"04_empty_maze.txt",
|
||
|
|
"05_no_exit_maze.txt"
|
||
|
|
]
|
||
|
|
|
||
|
|
benchmark = Benchmark()
|
||
|
|
all_results = {}
|
||
|
|
|
||
|
|
for test_file in test_files:
|
||
|
|
filepath = os.path.join(current_dir, test_file)
|
||
|
|
|
||
|
|
if not os.path.exists(filepath):
|
||
|
|
print(f"\nFile {test_file} not found. Creating test mazes...")
|
||
|
|
create_test_mazes()
|
||
|
|
break
|
||
|
|
|
||
|
|
try:
|
||
|
|
print(f"\nTesting: {test_file}")
|
||
|
|
maze = builder.build_from_file(filepath)
|
||
|
|
results = benchmark.run_experiment(maze, runs=5)
|
||
|
|
benchmark.print_results(results, test_file)
|
||
|
|
all_results[test_file] = results
|
||
|
|
except Exception as e:
|
||
|
|
print(f"Error testing {test_file}: {e}")
|
||
|
|
|
||
|
|
if all_results:
|
||
|
|
csv_filename = os.path.join(current_dir, "benchmark_results.csv")
|
||
|
|
with open(csv_filename, "w", encoding='utf-8') as f:
|
||
|
|
f.write("Maze,Algorithm,AvgTimeMs,AvgPathLength,AvgVisitedCells,SuccessPercent\n")
|
||
|
|
for maze_name, results in all_results.items():
|
||
|
|
for strategy_name, stats in results.items():
|
||
|
|
f.write(f"{maze_name},{strategy_name},"
|
||
|
|
f"{stats['avg_time_ms']:.3f},{stats['avg_path_length']:.1f},"
|
||
|
|
f"{stats['avg_visited_cells']:.1f},{stats['success_rate']:.1f}\n")
|
||
|
|
|
||
|
|
print(f"\nResults saved to: {csv_filename}")
|
||
|
|
|
||
|
|
input("\nPress Enter to continue...")
|
||
|
|
def main():
|
||
|
|
print("="*60)
|
||
|
|
print("MAZE PATHFINDING PROGRAM")
|
||
|
|
print("Patterns: Builder, Strategy")
|
||
|
|
print("Algorithms: BFS, DFS, A*")
|
||
|
|
print("="*60)
|
||
|
|
|
||
|
|
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||
|
|
existing_mazes = [f for f in os.listdir(current_dir) if f.endswith('.txt') and ('maze' in f.lower() or f.startswith('0'))]
|
||
|
|
|
||
|
|
if not existing_mazes:
|
||
|
|
print("First run: create test mazes (action 3)\n")
|
||
|
|
|
||
|
|
interactive_mode()
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
main()
|