[2] 3-rd version of maze solver(with rxpirement and other things) ;)

This commit is contained in:
root 2026-09-03 02:21:05 +03:00
parent d37835d9dd
commit b0fccd4611
3 changed files with 288 additions and 19 deletions

View File

@ -1,6 +1,11 @@
import time import time
import random
import csv
import os
from collections import deque from collections import deque
import heapq import heapq
import matplotlib.pyplot as plt
import numpy as np
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):
@ -165,10 +170,21 @@ class MazeSolver:
def __init__(self, maze, strategy=None): def __init__(self, maze, strategy=None):
self.maze = maze self.maze = maze
self.strategy = strategy self.strategy = strategy
self.observers = [] # для Observer
def set_strategy(self, strategy): def set_strategy(self, strategy):
self.strategy = strategy self.strategy = strategy
def attach(self, observer):
self.observers.append(observer)
def detach(self, observer):
self.observers.remove(observer)
def notify(self, event):
for obs in self.observers:
obs.update(event)
def solve(self): def solve(self):
if self.strategy is None: if self.strategy is None:
raise ValueError("Стратегия не установлена") raise ValueError("Стратегия не установлена")
@ -176,30 +192,249 @@ class MazeSolver:
exit_cell = self.maze.exit_cell exit_cell = self.maze.exit_cell
if start is None or exit_cell is None: if start is None or exit_cell is None:
raise ValueError("Лабиринт не содержит старта или выхода") raise ValueError("Лабиринт не содержит старта или выхода")
self.notify("Поиск начат")
start_time = time.perf_counter() start_time = time.perf_counter()
path = self.strategy.find_path(self.maze, start, exit_cell) path = self.strategy.find_path(self.maze, start, exit_cell)
end_time = time.perf_counter() end_time = time.perf_counter()
elapsed_ms = (end_time - start_time) * 1000 elapsed_ms = (end_time - start_time) * 1000
self.notify("Поиск завершён")
return path, elapsed_ms return path, elapsed_ms
# Test search class Observer:
def update(self, event):
raise NotImplementedError
class ConsoleView(Observer):
def __init__(self, maze):
self.maze = maze
def update(self, event):
if event == "Поиск начат":
print("=== Поиск начат ===")
elif event == "Поиск завершён":
print("=== Поиск завершён ===")
def render(self, path=None):
path_set = set(path) if 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 cell.is_wall:
row += '#'
elif cell.is_start:
row += 'S'
elif cell.is_exit:
row += 'E'
elif cell in path_set:
row += '*'
else:
row += ' '
print(row)
print()
class Command:
def execute(self):
raise NotImplementedError
def undo(self):
raise NotImplementedError
class MoveCommand(Command):
def __init__(self, player, dx, dy):
self.player = player
self.dx = dx
self.dy = dy
self.prev_x = player.x
self.prev_y = player.y
def execute(self):
new_x = self.player.x + self.dx
new_y = self.player.y + self.dy
maze = self.player.maze
cell = maze.get_cell(new_x, new_y)
if cell and cell.is_passable():
self.player.x = new_x
self.player.y = new_y
self.player.current_cell = cell
return True
return False
def undo(self):
self.player.x = self.prev_x
self.player.y = self.prev_y
self.player.current_cell = self.player.maze.get_cell(self.prev_x, self.prev_y)
class Player:
def __init__(self, maze, start_cell):
self.maze = maze
self.x = start_cell.x
self.y = start_cell.y
self.current_cell = start_cell
# EEEEEEEEEKSPERIMENTY
def generate_empty_maze(width, height):
maze = Maze(width, height)
start = maze.get_cell(0, 0)
exit_cell = maze.get_cell(width-1, height-1)
start.is_start = True
exit_cell.is_exit = True
maze.start_cell = start
maze.exit_cell = exit_cell
return maze
def generate_random_maze(width, height, wall_prob=0.3):
maze = Maze(width, height)
for x in range(width):
for y in range(height):
cell = maze.get_cell(x, y)
if random.random() < wall_prob:
cell.is_wall = True
start = maze.get_cell(0, 0)
exit_cell = maze.get_cell(width-1, height-1)
start.is_wall = False
start.is_start = True
exit_cell.is_wall = False
exit_cell.is_exit = True
maze.start_cell = start
maze.exit_cell = exit_cell
return maze
def generate_maze_with_dead_ends(width, height):
maze = Maze(width, height)
for x in range(width):
for y in range(height):
maze.get_cell(x, y).is_wall = True
x, y = 0, 0
while x < width and y < height:
cell = maze.get_cell(x, y)
cell.is_wall = False
if x == width-1 and y == height-1:
break
if y+1 < height and (x == width-1 or random.choice([True, False])):
y += 1
else:
x += 1
start = maze.get_cell(0, 0)
exit_cell = maze.get_cell(width-1, height-1)
start.is_start = True
exit_cell.is_exit = True
maze.start_cell = start
maze.exit_cell = exit_cell
return maze
def generate_maze_no_exit(width, height):
maze = generate_random_maze(width, height, 0.2)
exit_cell = maze.get_cell(width-1, height-1)
for dx, dy in [(-1,0), (1,0), (0,-1), (0,1)]:
nx, ny = exit_cell.x + dx, exit_cell.y + dy
neighbor = maze.get_cell(nx, ny)
if neighbor:
neighbor.is_wall = True
start = maze.get_cell(0, 0)
start.is_wall = False
start.is_start = True
maze.start_cell = start
maze.exit_cell = exit_cell
return maze
def run_experiment():
os.makedirs("results", exist_ok=True)
maze_generators = [
("empty_10x10", lambda: generate_empty_maze(10, 10)),
("empty_50x50", lambda: generate_empty_maze(50, 50)),
("empty_100x100", lambda: generate_empty_maze(100, 100)),
("random_10x10", lambda: generate_random_maze(10, 10, 0.3)),
("random_50x50", lambda: generate_random_maze(50, 50, 0.3)),
("random_100x100", lambda: generate_random_maze(100, 100, 0.3)),
("dead_ends_10x10", lambda: generate_maze_with_dead_ends(10, 10)),
("dead_ends_50x50", lambda: generate_maze_with_dead_ends(50, 50)),
("dead_ends_100x100", lambda: generate_maze_with_dead_ends(100, 100)),
("no_exit_10x10", lambda: generate_maze_no_exit(10, 10)),
("no_exit_50x50", lambda: generate_maze_no_exit(50, 50)),
]
strategies = [
("BFS", BFSStrategy()),
("DFS", DFSStrategy()),
("AStar", AStarStrategy())
]
repeats = 5
all_results = []
for maze_name, gen_func in maze_generators:
print(f"Тестирование лабиринта: {maze_name}")
maze = gen_func()
solver = MazeSolver(maze)
for strat_name, strat in strategies:
solver.set_strategy(strat)
total_time = 0
total_path_len = 0
path = []
for rep in range(repeats):
path, elapsed_ms = solver.solve()
total_time += elapsed_ms
total_path_len += len(path) if path else 0
avg_time = total_time / repeats
avg_len = total_path_len / repeats
all_results.append({
"Maze": maze_name,
"Strategy": strat_name,
"AvgTime_ms": avg_time,
"AvgPathLen": avg_len,
"PathFound": len(path) > 0 if path else False
})
print(f" {strat_name}: время {avg_time:.3f} мс, длина пути {avg_len:.1f}")
# Сохраняем CSV
csv_path = "results/experiment_results.csv"
with open(csv_path, 'w', newline='', encoding='utf-8') as f:
fieldnames = ["Maze", "Strategy", "AvgTime_ms", "AvgPathLen", "PathFound"]
writer = csv.DictWriter(f, fieldnames=fieldnames)
writer.writeheader()
writer.writerows(all_results)
print(f"Результаты сохранены в {csv_path}")
# Построение графика
maze_names = sorted(set(r["Maze"] for r in all_results))
strategy_names = ["BFS", "DFS", "AStar"]
data = {maze: {s: None for s in strategy_names} for maze in maze_names}
for r in all_results:
data[r["Maze"]][r["Strategy"]] = r["AvgTime_ms"]
fig, ax = plt.subplots(figsize=(14, 6))
x = np.arange(len(maze_names))
width = 0.25
colors = ['skyblue', 'lightgreen', 'salmon']
for i, strat in enumerate(strategy_names):
times = [data[maze][strat] if data[maze][strat] is not None else 0 for maze in maze_names]
ax.bar(x + i*width, times, width, label=strat, color=colors[i])
ax.set_xlabel('Лабиринт')
ax.set_ylabel('Среднее время (мс)')
ax.set_title('Сравнение стратегий поиска пути')
ax.set_xticks(x + width)
ax.set_xticklabels(maze_names, rotation=45, ha='right')
ax.legend()
plt.tight_layout()
plt.savefig("results/performance.png", dpi=150)
plt.show()
print("График сохранён в results/performance.png")
if __name__ == '__main__': if __name__ == '__main__':
builder = TextFileMazeBuilder() run_experiment()
maze = builder.build_from_file('test_maze.txt')
solver = MazeSolver(maze)
solver.set_strategy(BFSStrategy())
path, ms = solver.solve()
print("BFS путь:", [f"({c.x},{c.y})" for c in path])
print(f"Время: {ms:.3f} мс")
solver.set_strategy(DFSStrategy())
path, ms = solver.solve()
print("DFS путь:", [f"({c.x},{c.y})" for c in path])
print(f"Время: {ms:.3f} мс")
solver.set_strategy(AStarStrategy())
path, ms = solver.solve()
print("A* путь:", [f"({c.x},{c.y})" for c in path])
print(f"Время: {ms:.3f} мс")

View File

@ -0,0 +1,34 @@
Maze,Strategy,AvgTime_ms,AvgPathLen,PathFound
empty_10x10,BFS,0.13590119997388683,19.0,True
empty_10x10,DFS,0.0658330000078422,55.0,True
empty_10x10,AStar,0.12148100004196749,19.0,True
empty_50x50,BFS,4.080367400092655,99.0,True
empty_50x50,DFS,1.490334600021015,1275.0,True
empty_50x50,AStar,4.539874399961263,99.0,True
empty_100x100,BFS,11.864865199822816,199.0,True
empty_100x100,DFS,6.421647800107166,4951.0,True
empty_100x100,AStar,13.146192800104473,199.0,True
random_10x10,BFS,0.08931020001909928,19.0,True
random_10x10,DFS,0.0637794000795111,23.0,True
random_10x10,AStar,0.08288600010928349,19.0,True
random_50x50,BFS,2.3307791999286565,0.0,False
random_50x50,DFS,2.358275200094795,0.0,False
random_50x50,AStar,3.1945947999702184,0.0,False
random_100x100,BFS,8.76705820001007,0.0,False
random_100x100,DFS,8.292441200046596,0.0,False
random_100x100,AStar,13.397702600013872,0.0,False
dead_ends_10x10,BFS,0.023798799975338625,19.0,True
dead_ends_10x10,DFS,0.024454199865431292,19.0,True
dead_ends_10x10,AStar,0.02576019996922696,19.0,True
dead_ends_50x50,BFS,0.10447879994899267,99.0,True
dead_ends_50x50,DFS,0.1395262001096853,99.0,True
dead_ends_50x50,AStar,0.1517965998573345,99.0,True
dead_ends_100x100,BFS,0.27448000000731554,199.0,True
dead_ends_100x100,DFS,0.2607183998406981,199.0,True
dead_ends_100x100,AStar,0.42436699995960225,199.0,True
no_exit_10x10,BFS,0.09290340003644815,0.0,False
no_exit_10x10,DFS,0.09840180009632604,0.0,False
no_exit_10x10,AStar,0.11763999991671881,0.0,False
no_exit_50x50,BFS,2.5957402001040464,0.0,False
no_exit_50x50,DFS,2.327834000061557,0.0,False
no_exit_50x50,AStar,4.334011999981158,0.0,False
1 Maze Strategy AvgTime_ms AvgPathLen PathFound
2 empty_10x10 BFS 0.13590119997388683 19.0 True
3 empty_10x10 DFS 0.0658330000078422 55.0 True
4 empty_10x10 AStar 0.12148100004196749 19.0 True
5 empty_50x50 BFS 4.080367400092655 99.0 True
6 empty_50x50 DFS 1.490334600021015 1275.0 True
7 empty_50x50 AStar 4.539874399961263 99.0 True
8 empty_100x100 BFS 11.864865199822816 199.0 True
9 empty_100x100 DFS 6.421647800107166 4951.0 True
10 empty_100x100 AStar 13.146192800104473 199.0 True
11 random_10x10 BFS 0.08931020001909928 19.0 True
12 random_10x10 DFS 0.0637794000795111 23.0 True
13 random_10x10 AStar 0.08288600010928349 19.0 True
14 random_50x50 BFS 2.3307791999286565 0.0 False
15 random_50x50 DFS 2.358275200094795 0.0 False
16 random_50x50 AStar 3.1945947999702184 0.0 False
17 random_100x100 BFS 8.76705820001007 0.0 False
18 random_100x100 DFS 8.292441200046596 0.0 False
19 random_100x100 AStar 13.397702600013872 0.0 False
20 dead_ends_10x10 BFS 0.023798799975338625 19.0 True
21 dead_ends_10x10 DFS 0.024454199865431292 19.0 True
22 dead_ends_10x10 AStar 0.02576019996922696 19.0 True
23 dead_ends_50x50 BFS 0.10447879994899267 99.0 True
24 dead_ends_50x50 DFS 0.1395262001096853 99.0 True
25 dead_ends_50x50 AStar 0.1517965998573345 99.0 True
26 dead_ends_100x100 BFS 0.27448000000731554 199.0 True
27 dead_ends_100x100 DFS 0.2607183998406981 199.0 True
28 dead_ends_100x100 AStar 0.42436699995960225 199.0 True
29 no_exit_10x10 BFS 0.09290340003644815 0.0 False
30 no_exit_10x10 DFS 0.09840180009632604 0.0 False
31 no_exit_10x10 AStar 0.11763999991671881 0.0 False
32 no_exit_50x50 BFS 2.5957402001040464 0.0 False
33 no_exit_50x50 DFS 2.327834000061557 0.0 False
34 no_exit_50x50 AStar 4.334011999981158 0.0 False

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB