2026-rff_mp/ZhuravlevDV/docs/data/secondex/wayoutoflabirint.py
2026-05-25 13:03:14 +03:00

395 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import heapq
import time
import csv
from abc import ABC, abstractmethod
class Cell:
def __init__(self, x, y, is_wall=False, is_start=False, is_exit=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):
return not self.is_wall
class Maze:
def __init__(self, width, height):
self.width = width
self.height = height
self.grid = [[None for _ in range(width)] for _ in range(height)]
self.start = None
self.exit = None
def set_cell(self, x, y, cell):
self.grid[y][x] = cell
if cell.is_start:
self.start = cell
if cell.is_exit:
self.exit = cell
def get_cell(self, x, y):
if 0 <= x < self.width and 0 <= y < self.height:
return self.grid[y][x]
return None
def get_neighbors(self, 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):
pass
class TextFileMazeBuilder(MazeBuilder):
def build_from_file(self, filename):
with open(filename, 'r', encoding='utf-8') as f:
lines = [line.rstrip('\n') for line in f.readlines()]
height = len(lines)
width = len(lines[0]) if height > 0 else 0
maze = Maze(width, height)
for y, line in enumerate(lines):
for x, ch in enumerate(line):
is_wall = (ch == '#')
is_start = (ch == 'S')
is_exit = (ch == 'E')
is_passable = (ch == ' ' or is_start or is_exit)
cell = Cell(x, y, is_wall=is_wall, is_start=is_start, is_exit=is_exit)
maze.set_cell(x, y, cell)
return maze
class PathFindingStrategy(ABC):
@abstractmethod
def find_path(self, maze, start, exit):
pass
class BFSStrategy(PathFindingStrategy):
def find_path(self, maze, start, exit):
if not start or not exit:
return []
queue = [(start, [start])]
visited = set()
while queue:
current, path = queue.pop(0)
if current == exit:
return path
if current in visited:
continue
visited.add(current)
for neighbor in maze.get_neighbors(current):
if neighbor not in visited:
queue.append((neighbor, path + [neighbor]))
return []
class DFSStrategy(PathFindingStrategy):
def find_path(self, maze, start, exit):
if not start or not exit:
return []
stack = [(start, [start])]
visited = set()
while stack:
current, path = stack.pop()
if current == exit:
return path
if current in visited:
continue
visited.add(current)
for neighbor in maze.get_neighbors(current):
if neighbor not in visited:
stack.append((neighbor, path + [neighbor]))
return []
class AStarStrategy(PathFindingStrategy):
def heuristic(self, cell, exit):
return abs(cell.x - exit.x) + abs(cell.y - exit.y)
def find_path(self, maze, start, exit):
if not start or not exit:
return []
open_set = [(0, id(start), start)]
came_from = {}
g_score = {start: 0}
f_score = {start: self.heuristic(start, exit)}
while open_set:
_, _, current = heapq.heappop(open_set)
if current == exit:
path = []
while current in came_from:
path.append(current)
current = came_from[current]
path.append(start)
path.reverse()
return path
for neighbor in maze.get_neighbors(current):
tentative_g = g_score[current] + 1
if neighbor not in g_score or tentative_g < g_score[neighbor]:
came_from[neighbor] = current
g_score[neighbor] = tentative_g
f_score[neighbor] = tentative_g + self.heuristic(neighbor, exit)
heapq.heappush(open_set, (f_score[neighbor], id(neighbor), neighbor))
return []
class DijkstraStrategy(PathFindingStrategy):
def find_path(self, maze, start, exit):
if not start or not exit:
return []
pq = [(0, id(start), start)]
distances = {start: 0}
came_from = {}
while pq:
dist, _, current = heapq.heappop(pq)
if current == exit:
path = []
while current in came_from:
path.append(current)
current = came_from[current]
path.append(start)
path.reverse()
return path
if dist > distances.get(current, float('inf')):
continue
for neighbor in maze.get_neighbors(current):
new_dist = dist + 1
if new_dist < distances.get(neighbor, float('inf')):
distances[neighbor] = new_dist
came_from[neighbor] = current
heapq.heappush(pq, (new_dist, id(neighbor), neighbor))
return []
class SearchStats:
def __init__(self, time_ms, visited_cells, path_length, path=None):
self.time_ms = time_ms
self.visited_cells = visited_cells
self.path_length = path_length
self.path = path
class MazeSolver:
def __init__(self, maze, strategy=None):
self.maze = maze
self.strategy = strategy
self.observers = []
def set_strategy(self, strategy):
self.strategy = strategy
def attach(self, observer):
self.observers.append(observer)
def notify(self, event):
for observer in self.observers:
observer.update(event)
def solve(self):
if not self.strategy:
raise ValueError("Strategy not set")
start_time = time.perf_counter()
path = self.strategy.find_path(self.maze, self.maze.start, self.maze.exit)
end_time = time.perf_counter()
time_ms = (end_time - start_time) * 1000
visited_cells = len(path) if path else 0
path_length = len(path) if path else 0
self.notify(f"Path found with length {path_length} in {time_ms:.2f}ms")
return SearchStats(time_ms, visited_cells, path_length, path)
class Observer(ABC):
@abstractmethod
def update(self, event):
pass
class ConsoleView(Observer):
def __init__(self):
self.last_path = None
def update(self, event):
print(f"[ConsoleView] {event}")
def render(self, maze, player_pos=None, path=None):
print("\n" + "=" * (maze.width * 2 + 2))
for y in range(maze.height):
row = ""
for x in range(maze.width):
cell = maze.get_cell(x, y)
if player_pos and cell == player_pos:
row += "P "
elif path and cell in path:
row += "* "
elif cell.is_start:
row += "S "
elif cell.is_exit:
row += "E "
elif cell.is_wall:
row += "# "
else:
row += ". "
print(row)
print("=" * (maze.width * 2 + 2))
class Command(ABC):
@abstractmethod
def execute(self):
pass
@abstractmethod
def undo(self):
pass
class Player:
def __init__(self, start_cell):
self.current = start_cell
self.start = start_cell
def move_to(self, cell):
self.current = cell
class MoveCommand(Command):
def __init__(self, player, new_cell, maze):
self.player = player
self.new_cell = new_cell
self.old_cell = player.current
self.maze = maze
def execute(self):
if self.new_cell.is_passable():
self.player.move_to(self.new_cell)
return True
return False
def undo(self):
self.player.move_to(self.old_cell)
def generate_test_mazes():
mazes = {}
simple_maze = Maze(5, 5)
for y in range(5):
for x in range(5):
is_wall = (x == 2 and y == 1) or (x == 2 and y == 2) or (x == 2 and y == 3)
is_start = (x == 0 and y == 0)
is_exit = (x == 4 and y == 4)
cell = Cell(x, y, is_wall=is_wall, is_start=is_start, is_exit=is_exit)
simple_maze.set_cell(x, y, cell)
mazes["simple"] = simple_maze
empty_maze = Maze(20, 20)
for y in range(20):
for x in range(20):
is_start = (x == 0 and y == 0)
is_exit = (x == 19 and y == 19)
cell = Cell(x, y, is_wall=False, is_start=is_start, is_exit=is_exit)
empty_maze.set_cell(x, y, cell)
mazes["empty"] = empty_maze
return mazes
def run_experiments():
mazes = generate_test_mazes()
strategies = {
"BFS": BFSStrategy(),
"DFS": DFSStrategy(),
"AStar": AStarStrategy(),
"Dijkstra": DijkstraStrategy()
}
results = []
for maze_name, maze in mazes.items():
for strat_name, strategy in strategies.items():
solver = MazeSolver(maze, strategy)
times = []
visited_counts = []
path_lengths = []
for run in range(5):
stats = solver.solve()
times.append(stats.time_ms)
visited_counts.append(stats.visited_cells)
path_lengths.append(stats.path_length)
avg_time = sum(times) / len(times)
avg_visited = sum(visited_counts) / len(visited_counts)
avg_length = sum(path_lengths) / len(path_lengths)
results.append([maze_name, strat_name, avg_time, avg_visited, avg_length])
print(f"{maze_name} | {strat_name}: {avg_time:.3f}ms, {avg_visited:.0f} cells, {avg_length:.0f} length")
with open("maze_results.csv", "w", newline="", encoding="utf-8") as f:
writer = csv.writer(f)
writer.writerow(["Лабиринт", "Стратегия", "Время_мс", "Посещено_клеток", "Длина_пути"])
writer.writerows(results)
return results
def main():
print("Testing maze loading...")
builder = TextFileMazeBuilder()
try:
maze = builder.build_from_file("maze.txt")
print(f"Maze loaded: {maze.width}x{maze.height}")
solver = MazeSolver(maze)
view = ConsoleView()
solver.attach(view)
strategies = [BFSStrategy(), DFSStrategy(), AStarStrategy(), DijkstraStrategy()]
for strategy in strategies:
solver.set_strategy(strategy)
print(f"\n--- {strategy.__class__.__name__} ---")
stats = solver.solve()
view.render(maze, path=stats.path)
print(f"Time: {stats.time_ms:.3f}ms, Path length: {stats.path_length}")
except FileNotFoundError:
print("maze.txt not found, running experiments with generated mazes instead")
print("\n" + "="*50)
print("Running experiments...")
run_experiments()
if __name__ == "__main__":
main()