[2] Final main.py

This commit is contained in:
meosyam 2026-09-03 15:21:19 +00:00
parent d2e6975a50
commit 1746a0df23

View File

@ -1,9 +1,11 @@
import os
import sys import sys
import time import time
import csv
from collections import deque from collections import deque
import heapq import heapq
from dataclasses import dataclass from dataclasses import dataclass
from abc import ABC, abstractmethod
class Cell: class Cell:
def __init__(self, x, y, is_wall=False, is_start=False, is_exit=False): def __init__(self, x, y, is_wall=False, is_start=False, is_exit=False):
@ -75,14 +77,16 @@ class MazeBuilder:
return Maze(width, height, cells, start, exit_cell) return Maze(width, height, cells, start, exit_cell)
class PathFindingStrategy: class PathFindingStrategy(ABC):
def find_path(self, maze, start, exit): @abstractmethod
raise NotImplementedError def find_path(self, maze, start, exit, visit_callback=None):
pass
class BFSStrategy(PathFindingStrategy): class BFSStrategy(PathFindingStrategy):
def find_path(self, maze, start, exit): def find_path(self, maze, start, exit, visit_callback=None):
if start == exit: if start == exit:
if visit_callback: visit_callback(start)
return [start], 1 return [start], 1
queue = deque([start]) queue = deque([start])
visited = {start} visited = {start}
@ -90,6 +94,8 @@ class BFSStrategy(PathFindingStrategy):
visited_count = 1 visited_count = 1
while queue: while queue:
current = queue.popleft() current = queue.popleft()
if visit_callback:
visit_callback(current)
if current == exit: if current == exit:
path = [] path = []
while current: while current:
@ -107,8 +113,9 @@ class BFSStrategy(PathFindingStrategy):
class DFSStrategy(PathFindingStrategy): class DFSStrategy(PathFindingStrategy):
def find_path(self, maze, start, exit): def find_path(self, maze, start, exit, visit_callback=None):
if start == exit: if start == exit:
if visit_callback: visit_callback(start)
return [start], 1 return [start], 1
stack = [start] stack = [start]
visited = {start} visited = {start}
@ -116,6 +123,8 @@ class DFSStrategy(PathFindingStrategy):
visited_count = 1 visited_count = 1
while stack: while stack:
current = stack.pop() current = stack.pop()
if visit_callback:
visit_callback(current)
if current == exit: if current == exit:
path = [] path = []
while current: while current:
@ -137,8 +146,9 @@ class AStarStrategy(PathFindingStrategy):
def manhattan(cell, target): def manhattan(cell, target):
return abs(cell.x - target.x) + abs(cell.y - target.y) return abs(cell.x - target.x) + abs(cell.y - target.y)
def find_path(self, maze, start, exit): def find_path(self, maze, start, exit, visit_callback=None):
if start == exit: if start == exit:
if visit_callback: visit_callback(start)
return [start], 1 return [start], 1
open_set = [] open_set = []
counter = 0 counter = 0
@ -154,6 +164,8 @@ class AStarStrategy(PathFindingStrategy):
continue continue
visited.add(current) visited.add(current)
visited_count += 1 visited_count += 1
if visit_callback:
visit_callback(current)
if current == exit: if current == exit:
path = [] path = []
while current: while current:
@ -188,69 +200,291 @@ class MazeSolver:
def set_strategy(self, strategy): def set_strategy(self, strategy):
self.strategy = strategy self.strategy = strategy
def solve(self): def solve(self, visit_callback=None):
if self.strategy is None: if self.strategy is None:
raise ValueError("Strategy not set") raise ValueError("Strategy not set")
start_time = time.perf_counter() start_time = time.perf_counter()
path, visited = self.strategy.find_path(self.maze, self.maze.start, self.maze.exit) path, visited = self.strategy.find_path(self.maze, self.maze.start, self.maze.exit, visit_callback)
end_time = time.perf_counter() end_time = time.perf_counter()
time_ms = (end_time - start_time) * 1000 time_ms = (end_time - start_time) * 1000
return path, SearchStats(time_ms, visited, len(path) if path else 0) return path, SearchStats(time_ms, visited, len(path) if path else 0)
def print_maze(maze, path=None): class Observer(ABC):
path_set = set(path) if path else set() @abstractmethod
for y in range(maze.height): def update(self, event_type, data):
row = [] pass
for x in range(maze.width):
cell = maze.get_cell(x, y)
if cell in path_set and not cell.is_start and not cell.is_exit: class ConsoleView(Observer):
row.append('*') def __init__(self, maze, player=None, path=None, show_steps=False):
elif cell.is_start: self.maze = maze
row.append('S') self.player = player
elif cell.is_exit: self.path = path or []
row.append('E') self.show_steps = show_steps
elif cell.is_wall: self.visited = set()
row.append('#') self._clear_screen()
def _clear_screen(self):
os.system('cls' if os.name == 'nt' else 'clear')
def update(self, event_type, data):
if event_type == 'player_moved':
self.player = data['player']
self.render()
elif event_type == 'path_found':
self.path = data['path']
self.visited.clear()
self.render()
elif event_type == 'search_step':
if self.show_steps:
cell = data['cell']
self.visited.add(cell)
self.render()
elif event_type == 'clear_visited':
self.visited.clear()
self.render()
elif event_type == 'clear':
self._clear_screen()
def render(self):
self._clear_screen()
player_pos = self.player.current_cell if self.player else None
path_set = set(self.path) if self.path else set()
for y in range(self.maze.height):
row = []
for x in range(self.maze.width):
cell = self.maze.get_cell(x, y)
if player_pos and cell == player_pos:
row.append('@')
elif cell.is_start:
row.append('S')
elif cell.is_exit:
row.append('E')
elif cell in path_set and not cell.is_start and not cell.is_exit:
row.append('*')
elif cell in self.visited and not cell.is_start and not cell.is_exit and not cell.is_wall:
row.append('.')
elif cell.is_wall:
row.append('#')
else:
row.append(' ')
print(''.join(row))
if player_pos:
print(f"Player at ({player_pos.x},{player_pos.y})")
if self.path:
print(f"Path length: {len(self.path)}")
print("(Use W/A/S/D to move, U to undo, F to find path, Q to quit)")
class Command(ABC):
@abstractmethod
def execute(self):
pass
@abstractmethod
def undo(self):
pass
class MoveCommand(Command):
def __init__(self, player, dx, dy):
self.player = player
self.dx = dx
self.dy = dy
self.previous_cell = None
def execute(self):
self.previous_cell = self.player.current_cell
nx = self.player.current_cell.x + self.dx
ny = self.player.current_cell.y + self.dy
target = self.player.maze.get_cell(nx, ny)
if target and target.is_passable():
self.player.move_to(target)
return True
return False
def undo(self):
if self.previous_cell:
self.player.move_to(self.previous_cell)
return True
return False
class Player:
def __init__(self, maze, start_cell):
self.maze = maze
self.current_cell = start_cell
def move_to(self, cell):
self.current_cell = cell
def run_experiments():
test_files = ['maze1.txt', 'maze10x10.txt', 'maze20x20.txt', 'maze_empty.txt', 'maze_no_exit.txt']
strategies = {
'BFS': BFSStrategy(),
'DFS': DFSStrategy(),
'AStar': AStarStrategy()
}
results = []
runs = 5
for fname in test_files:
if not os.path.exists(fname):
print(f"File {fname} not found, skipping.")
continue
try:
maze = MazeBuilder.build_from_file(fname)
except Exception as e:
print(f"Error loading {fname}: {e}")
continue
print(f"Testing on {fname} ({maze.width}x{maze.height})")
for name, strategy in strategies.items():
total_time = 0.0
total_visited = 0
total_length = 0
success = True
for _ in range(runs):
solver = MazeSolver(maze, strategy)
path, stats = solver.solve()
if not path:
success = False
total_time += stats.time_ms
total_visited += stats.visited_cells
total_length += 0
else:
total_time += stats.time_ms
total_visited += stats.visited_cells
total_length += len(path)
avg_time = total_time / runs
avg_visited = total_visited / runs
avg_length = total_length / runs if success else 0
results.append({
'maze': fname,
'strategy': name,
'avg_time_ms': avg_time,
'avg_visited': avg_visited,
'avg_path_length': avg_length,
'path_found': success
})
print(f" {name}: time={avg_time:.3f}ms, visited={avg_visited:.1f}, length={avg_length:.1f}")
csv_file = 'experiment_results_2-nd-exercise.csv'
with open(csv_file, 'w', newline='') as csvfile:
fieldnames = ['maze', 'strategy', 'avg_time_ms', 'avg_visited', 'avg_path_length', 'path_found']
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
writer.writeheader()
writer.writerows(results)
print(f"Results saved to {csv_file}")
print("\nSummary Table:")
print(f"{'Maze':<15} {'Strategy':<10} {'Time(ms)':<12} {'Visited':<10} {'Length':<10} {'Found'}")
for r in results:
print(f"{r['maze']:<15} {r['strategy']:<10} {r['avg_time_ms']:<12.3f} {r['avg_visited']:<10.1f} {r['avg_path_length']:<10.1f} {r['path_found']}")
def manual_mode(maze):
player = Player(maze, maze.start)
view = ConsoleView(maze, player, show_steps=True)
command_history = []
view.render()
while True:
cmd = input().strip().lower()
if cmd == 'q':
break
elif cmd == 'u':
if command_history:
cmd_obj = command_history.pop()
cmd_obj.undo()
view.update('player_moved', {'player': player})
else: else:
row.append(' ') print("Nothing to undo")
print(''.join(row)) elif cmd == 'f':
print("Finding path from start to exit...")
strategy = BFSStrategy()
solver = MazeSolver(maze, strategy)
path, stats = solver.solve(visit_callback=lambda cell: view.update('search_step', {'cell': cell}))
view.update('clear_visited', {})
if path:
view.update('path_found', {'path': path})
print(f"Path found! Length: {len(path)}")
else:
print("No path found.")
elif cmd in ('w', 'a', 's', 'd'):
dx, dy = 0, 0
if cmd == 'w':
dy = -1
elif cmd == 's':
dy = 1
elif cmd == 'a':
dx = -1
elif cmd == 'd':
dx = 1
move_cmd = MoveCommand(player, dx, dy)
if move_cmd.execute():
command_history.append(move_cmd)
view.update('player_moved', {'player': player})
else:
print("Can't move there")
else:
print("Unknown command")
def main(): def interactive_menu():
if len(sys.argv) > 1: while True:
filename = sys.argv[1] print("\n==== Maze Explorer ====")
else: print("1. Load maze and solve (auto)")
filename = 'maze1.txt' print("2. Manual control")
print("3. Run experiments")
try: print("4. Quit")
maze = MazeBuilder.build_from_file(filename) choice = input("Choose option: ").strip()
print(f"Maze loaded ({maze.width}x{maze.height})")
print("Select algorithm: (1) BFS, (2) DFS, (3) A*")
choice = input("Choice: ").strip()
if choice == '1': if choice == '1':
strategy = BFSStrategy() filename = input("Enter maze filename (default maze1.txt): ").strip()
if not filename:
filename = 'maze1.txt'
try:
maze = MazeBuilder.build_from_file(filename)
print("Maze loaded.")
print("Select algorithm: (1) BFS, (2) DFS, (3) A*")
algo = input("Choice: ").strip()
if algo == '1':
strategy = BFSStrategy()
elif algo == '2':
strategy = DFSStrategy()
elif algo == '3':
strategy = AStarStrategy()
else:
print("Invalid, using BFS")
strategy = BFSStrategy()
solver = MazeSolver(maze, strategy)
view = ConsoleView(maze, show_steps=True)
path, stats = solver.solve(visit_callback=lambda cell: view.update('search_step', {'cell': cell}))
view.update('clear_visited', {})
if path:
view.update('path_found', {'path': path})
print(f"Path found! Length: {len(path)}, Visited: {stats.visited_cells}, Time: {stats.time_ms:.4f} ms")
else:
print("No path found.")
print(f"Visited: {stats.visited_cells}, Time: {stats.time_ms:.4f} ms")
input("Press Enter to continue...")
except Exception as e:
print(f"Error: {e}")
elif choice == '2': elif choice == '2':
strategy = DFSStrategy() filename = input("Enter maze filename (default maze1.txt): ").strip()
if not filename:
filename = 'maze1.txt'
try:
maze = MazeBuilder.build_from_file(filename)
manual_mode(maze)
except Exception as e:
print(f"Error: {e}")
elif choice == '3': elif choice == '3':
strategy = AStarStrategy() run_experiments()
input("Press Enter to continue...")
elif choice == '4':
break
else: else:
print("Invalid, using BFS") print("Invalid choice")
strategy = BFSStrategy()
solver = MazeSolver(maze, strategy)
path, stats = solver.solve()
if path:
print(f"Path found! Length: {len(path)}")
print(f"Visited cells: {stats.visited_cells}")
print(f"Time: {stats.time_ms:.4f} ms")
print_maze(maze, path)
else:
print("No path found.")
print(f"Visited cells: {stats.visited_cells}")
print(f"Time: {stats.time_ms:.4f} ms")
except Exception as e:
print(f"Error: {e}")
if __name__ == '__main__': if __name__ == '__main__':
main() interactive_menu()