[2] task2

This commit is contained in:
MariiaOs 2026-05-27 22:49:24 +03:00
parent cf755242e1
commit 3428678d8a
12 changed files with 1044 additions and 0 deletions

View File

@ -0,0 +1,283 @@
"""
Экспериментальный запуск для всех лабиринтов и алгоритмов
Создание CSV и графиков
"""
import os
import csv
from datetime import datetime
from maze_model import Maze
from maze_builder import TextFileMazeBuilder
from pathfinding_strategies import BFSStrategy, DFSStrategy, AStarStrategy
from maze_solver import MazeSolver, SearchStats
class ExperimentRunner:
def __init__(self):
self.all_results = []
self.labirints = {
'labirint1.txt': 'Маленький (10x10) с простым путём',
'labirint2.txt': 'Средний (50x50) с тупиками',
'labirint3.txt': 'Большой (100x100) запутанный',
'labirint4.txt': 'Пустой (20x20) без стен',
'labirint5.txt': 'Без выхода (20x20)'
}
self.strategies = [
BFSStrategy(),
DFSStrategy(),
AStarStrategy()
]
def run_all_experiments(self):
"""Запускает эксперименты для всех лабиринтов и алгоритмов"""
print("\n" + "="*70)
print("ЗАПУСК ЭКСПЕРИМЕНТОВ")
print("="*70)
builder = TextFileMazeBuilder()
for filename, description in self.labirints.items():
if not os.path.exists(filename):
print(f"\n⚠️ Файл {filename} не найден, пропускаем...")
continue
print(f"\n📁 Лабиринт: {description}")
print(f" Файл: {filename}")
print("-" * 50)
try:
maze = builder.build_from_file(filename)
maze_name = filename.replace('.txt', '')
for strategy in self.strategies:
print(f" Тестирование {strategy.get_name()}...", end=" ", flush=True)
solver = MazeSolver(maze, maze_name, strategy)
path, stats = solver.solve_with_stats()
self.all_results.append({
'лабиринт': description,
'стратегия': stats.algorithm_name,
'время_мс': stats.execution_time_ms,
'посещено_клеток': stats.visited_cells,
'длина_пути': stats.path_length,
'путь_найден': 'Да' if stats.path_found else 'Нет',
'размер': stats.maze_size
})
print(f"готово! время={stats.execution_time_ms:.3f}мс, путь={stats.path_length}")
except Exception as e:
print(f" ❌ Ошибка: {e}")
print("\n" + "="*70)
print("ЭКСПЕРИМЕНТЫ ЗАВЕРШЕНЫ")
print("="*70)
def save_to_csv(self, filename="experiment_results.csv"):
"""Сохраняет результаты в CSV"""
if not self.all_results:
print("Нет результатов для сохранения!")
return
with open(filename, 'w', newline='', encoding='utf-8-sig') as f:
fieldnames = ['лабиринт', 'стратегия', 'время_мс', 'посещено_клеток', 'длина_пути', 'путь_найден', 'размер']
writer = csv.DictWriter(f, fieldnames=fieldnames)
writer.writeheader()
writer.writerows(self.all_results)
print(f"\n✅ Результаты сохранены в {filename}")
# Показываем содержимое CSV
print("\n" + "="*70)
print("СОДЕРЖИМОЕ CSV ФАЙЛА:")
print("="*70)
with open(filename, 'r', encoding='utf-8-sig') as f:
print(f.read())
def create_charts(self):
"""Создаёт графики для каждого лабиринта"""
try:
import matplotlib.pyplot as plt
import numpy as np
print("\n" + "="*70)
print("ПОСТРОЕНИЕ ГРАФИКОВ")
print("="*70)
# Группируем результаты по лабиринтам
results_by_maze = {}
for result in self.all_results:
maze = result['лабиринт']
if maze not in results_by_maze:
results_by_maze[maze] = []
results_by_maze[maze].append(result)
# Для каждого лабиринта создаём отдельный график
for maze_name, maze_results in results_by_maze.items():
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
fig.suptitle(f'Сравнение алгоритмов: {maze_name}', fontsize=14, fontweight='bold')
algorithms = [r['стратегия'] for r in maze_results]
# График 1: Время выполнения
times = [r['время_мс'] for r in maze_results]
bars1 = axes[0].bar(algorithms, times, color=['blue', 'green', 'red'])
axes[0].set_ylabel('Время (мс)')
axes[0].set_title('Время выполнения')
axes[0].tick_params(axis='x', rotation=15)
for bar, val in zip(bars1, times):
axes[0].text(bar.get_x() + bar.get_width()/2, bar.get_height() + 0.5,
f'{val:.3f}', ha='center', va='bottom', fontsize=9)
# График 2: Посещённые клетки
visited = [r['посещено_клеток'] for r in maze_results]
bars2 = axes[1].bar(algorithms, visited, color=['blue', 'green', 'red'])
axes[1].set_ylabel('Количество клеток')
axes[1].set_title('Посещённые клетки')
axes[1].tick_params(axis='x', rotation=15)
for bar, val in zip(bars2, visited):
axes[1].text(bar.get_x() + bar.get_width()/2, bar.get_height() + 1,
f'{val:.0f}', ha='center', va='bottom', fontsize=9)
# График 3: Длина пути
lengths = [r['длина_пути'] for r in maze_results]
bars3 = axes[2].bar(algorithms, lengths, color=['blue', 'green', 'red'])
axes[2].set_ylabel('Шагов')
axes[2].set_title('Длина найденного пути')
axes[2].tick_params(axis='x', rotation=15)
for bar, val in zip(bars3, lengths):
axes[2].text(bar.get_x() + bar.get_width()/2, bar.get_height() + 0.5,
f'{val:.0f}', ha='center', va='bottom', fontsize=9)
plt.tight_layout()
# Сохраняем график
safe_name = maze_name.replace(' ', '_').replace('(', '').replace(')', '').replace('×', 'x')
filename = f"chart_{safe_name}.png"
plt.savefig(filename, dpi=150, bbox_inches='tight')
print(f"✅ Сохранён: {filename}")
plt.close()
# Общий сводный график
self._create_summary_chart()
except ImportError:
print("\n⚠️ Для построения графиков установите matplotlib:")
print(" pip install matplotlib numpy")
def _create_summary_chart(self):
"""Создаёт сводный график по всем лабиринтам"""
import matplotlib.pyplot as plt
import numpy as np
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
fig.suptitle('Сводное сравнение алгоритмов по всем лабиринтам', fontsize=14, fontweight='bold')
# Получаем уникальные лабиринты и алгоритмы
mazes = list(set([r['лабиринт'] for r in self.all_results]))
algorithms = ['BFS (Поиск в ширину)', 'DFS (Поиск в глубину)', 'A* (A-Star)']
# 1. Время по лабиринтам
ax1 = axes[0, 0]
x = np.arange(len(mazes))
width = 0.25
for i, algo in enumerate(algorithms):
times = []
for maze in mazes:
result = next((r for r in self.all_results if r['лабиринт'] == maze and r['стратегия'] == algo), None)
times.append(result['время_мс'] if result else 0)
bars = ax1.bar(x + (i - 1) * width, times, width, label=algo)
for bar, val in zip(bars, times):
if val > 0:
ax1.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 0.5,
f'{val:.1f}', ha='center', va='bottom', fontsize=7)
ax1.set_xlabel('Лабиринт')
ax1.set_ylabel('Время (мс)')
ax1.set_title('Сравнение времени выполнения')
ax1.set_xticks(x)
ax1.set_xticklabels([m[:20] for m in mazes], rotation=45, ha='right')
ax1.legend()
# 2. Посещённые клетки
ax2 = axes[0, 1]
for i, algo in enumerate(algorithms):
visited = []
for maze in mazes:
result = next((r for r in self.all_results if r['лабиринт'] == maze and r['стратегия'] == algo), None)
visited.append(result['посещено_клеток'] if result else 0)
bars = ax2.bar(x + (i - 1) * width, visited, width, label=algo)
for bar, val in zip(bars, visited):
if val > 0:
ax2.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 5,
f'{val:.0f}', ha='center', va='bottom', fontsize=7)
ax2.set_xlabel('Лабиринт')
ax2.set_ylabel('Посещённые клетки')
ax2.set_title('Сравнение посещённых клеток')
ax2.set_xticks(x)
ax2.set_xticklabels([m[:20] for m in mazes], rotation=45, ha='right')
ax2.legend()
# 3. Длина пути
ax3 = axes[1, 0]
for i, algo in enumerate(algorithms):
lengths = []
for maze in mazes:
result = next((r for r in self.all_results if r['лабиринт'] == maze and r['стратегия'] == algo), None)
lengths.append(result['длина_пути'] if result else 0)
bars = ax3.bar(x + (i - 1) * width, lengths, width, label=algo)
for bar, val in zip(bars, lengths):
if val > 0:
ax3.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 1,
f'{val:.0f}', ha='center', va='bottom', fontsize=7)
ax3.set_xlabel('Лабиринт')
ax3.set_ylabel('Длина пути (шагов)')
ax3.set_title('Сравнение длины пути')
ax3.set_xticks(x)
ax3.set_xticklabels([m[:20] for m in mazes], rotation=45, ha='right')
ax3.legend()
# 4. Таблица результатов
ax4 = axes[1, 1]
ax4.axis('tight')
ax4.axis('off')
# Создаём таблицу
table_data = []
for maze in mazes:
row = [maze[:25]]
for algo in algorithms:
result = next((r for r in self.all_results if r['лабиринт'] == maze and r['стратегия'] == algo), None)
if result:
row.append(f"{result['время_мс']:.1f}мс")
else:
row.append("-")
table_data.append(row)
columns = ['Лабиринт', 'BFS', 'DFS', 'A*']
table = ax4.table(cellText=table_data, colLabels=columns, cellLoc='center', loc='center')
table.auto_set_font_size(False)
table.set_fontsize(9)
table.scale(1.2, 1.5)
ax4.set_title('Сводная таблица (время в мс)', fontsize=10)
plt.tight_layout()
plt.savefig('summary_chart.png', dpi=150, bbox_inches='tight')
print("Сохранён: summary_chart.png")
plt.close()
def main():
runner = ExperimentRunner()
runner.run_all_experiments()
runner.save_to_csv("experiment_results.csv")
runner.create_charts()
if __name__ == "__main__":
main()

View File

@ -0,0 +1,18 @@
лабиринт,стратегия,время_мс,длина_пути,путь_найден,размер,датаремя
labirint1,BFS (Поиск в ширину),0.211,14,Да,10x10,2026-05-27 22:26:56
labirint2,BFS (Поиск в ширину),0.858,0,Нет,50x51,2026-05-27 22:27:10
labirint3,BFS (Поиск в ширину),0.055,0,Нет,100x100,2026-05-27 22:27:26
labirint4,BFS (Поиск в ширину),1.326,35,Да,20x20,2026-05-27 22:27:35
labirint5,BFS (Поиск в ширину),0.373,36,Да,21x20,2026-05-27 22:27:44
labirint1,DFS (Поиск в глубину),0.135,14,Да,10x10,2026-05-27 22:28:16
labirint2,DFS (Поиск в глубину),0.797,0,Нет,50x51,2026-05-27 22:28:25
labirint3,DFS (Поиск в глубину),0.047,0,Нет,100x100,2026-05-27 22:28:31
labirint4,DFS (Поиск в глубину),0.88,171,Да,20x20,2026-05-27 22:28:36
labirint5,DFS (Поиск в глубину),0.772,36,Да,21x20,2026-05-27 22:28:41
labirint1,A* (A-Star),0.311,14,Да,10x10,2026-05-27 22:28:45
labirint2,A* (A-Star),1.318,0,Нет,50x51,2026-05-27 22:28:50
labirint3,A* (A-Star),0.055,0,Нет,100x100,2026-05-27 22:28:55
labirint4,A* (A-Star),2.301,35,Да,20x20,2026-05-27 22:29:00
labirint5,A* (A-Star),0.684,36,Да,21x20,2026-05-27 22:29:04
labirint1,A* (A-Star),0.316,14,Да,10x10,2026-05-27 22:36:50
labirint1,DFS (Поиск в глубину),0.133,14,Да,10x10,2026-05-27 22:41:42
1 лабиринт стратегия время_мс длина_пути путь_найден размер дата_время
2 labirint1 BFS (Поиск в ширину) 0.211 14 Да 10x10 2026-05-27 22:26:56
3 labirint2 BFS (Поиск в ширину) 0.858 0 Нет 50x51 2026-05-27 22:27:10
4 labirint3 BFS (Поиск в ширину) 0.055 0 Нет 100x100 2026-05-27 22:27:26
5 labirint4 BFS (Поиск в ширину) 1.326 35 Да 20x20 2026-05-27 22:27:35
6 labirint5 BFS (Поиск в ширину) 0.373 36 Да 21x20 2026-05-27 22:27:44
7 labirint1 DFS (Поиск в глубину) 0.135 14 Да 10x10 2026-05-27 22:28:16
8 labirint2 DFS (Поиск в глубину) 0.797 0 Нет 50x51 2026-05-27 22:28:25
9 labirint3 DFS (Поиск в глубину) 0.047 0 Нет 100x100 2026-05-27 22:28:31
10 labirint4 DFS (Поиск в глубину) 0.88 171 Да 20x20 2026-05-27 22:28:36
11 labirint5 DFS (Поиск в глубину) 0.772 36 Да 21x20 2026-05-27 22:28:41
12 labirint1 A* (A-Star) 0.311 14 Да 10x10 2026-05-27 22:28:45
13 labirint2 A* (A-Star) 1.318 0 Нет 50x51 2026-05-27 22:28:50
14 labirint3 A* (A-Star) 0.055 0 Нет 100x100 2026-05-27 22:28:55
15 labirint4 A* (A-Star) 2.301 35 Да 20x20 2026-05-27 22:29:00
16 labirint5 A* (A-Star) 0.684 36 Да 21x20 2026-05-27 22:29:04
17 labirint1 A* (A-Star) 0.316 14 Да 10x10 2026-05-27 22:36:50
18 labirint1 DFS (Поиск в глубину) 0.133 14 Да 10x10 2026-05-27 22:41:42

View File

@ -0,0 +1,10 @@
##########
#S #
# ##### #
# # #
# ### # #
# # # #
# # ### #
# # #
# #####E#
##########

View File

@ -0,0 +1,51 @@
##################################################
#S #
# ############################################# #
# # # #
# # ######################################### # #
# # # # # #
# # # ##################################### # # #
# # # # # # # #
# # # # ################################# # # # #
# # # # # # # # # #
# # # # # ############################# # # # # #
# # # # # # # # # # # #
# # # # # # ######################### # # # # # #
# # # # # # # # # # # # # #
# # # # # # # ##################### # # # # # # #
# # # # # # # # # # # # # # # #
# # # # # # # # ################# # # # # # # # #
# # # # # # # # # # # # # # # # # #
# # # # # # # # # ############# # # # # # # # # #
# # # # # # # # # # # # # # # # # # # #
# # # # # # # # # # ######### # # # # # # # # # #
# # # # # # # # # # # # # # # # # # # # # #
# # # # # # # # # # # ##### # # # # # # # # # # #
# # # # # # # # # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # # # # # # # # #
# # # # # # # # # # # ##### # # # # # # # # # # #
# # # # # # # # # # # # # # # # # # # # # #
# # # # # # # # # # ######### # # # # # # # # # #
# # # # # # # # # # # # # # # # # # # #
# # # # # # # # # ############# # # # # # # # # #
# # # # # # # # # # # # # # # # # #
# # # # # # # # ################# # # # # # # # #
# # # # # # # # # # # # # # # #
# # # # # # # ##################### # # # # # # #
# # # # # # # # # # # # # #
# # # # # # ######################### # # # # # #
# # # # # # # # # # # #
# # # # # ############################# # # # # #
# # # # # # # # # #
# # # # ################################# # # # #
# # # # # # # #
# # # ##################################### # # #
# # # # # #
# # ######################################### # #
# # # #
# ############################################# #
# #
##################################################
# E#
##################################################

View File

@ -0,0 +1,100 @@
####################################################################################################
####################################################################################################
##S# # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # #
### # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ##
## # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # #
## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # #
### # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ##
## # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # #
####################################################################################################
### # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ##
## # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # #
## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # #
### # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ##
## # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # #
## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # #
####################################################################################################
## # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # #
## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # #
### # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ##
## # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # #
## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # #
### # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ##
####################################################################################################
## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # #
### # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ##
## # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # #
## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # #
### # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ##
## # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # #
####################################################################################################
### # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ##
## # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # #
## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # #
### # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ##
## # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # #
## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # #
####################################################################################################
## # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # #
## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # #
### # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ##
## # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # #
## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # #
### # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ##
####################################################################################################
## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # #
### # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ##
## # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # #
## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # #
### # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ##
## # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # #
####################################################################################################
### # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ##
## # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # #
## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # #
### # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ##
## # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # #
## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # #
####################################################################################################
## # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # #
## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # #
### # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ##
## # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # #
## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # #
### # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ##
####################################################################################################
## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # #
### # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ##
## # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # #
## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # #
### # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ##
## # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # #
####################################################################################################
### # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ##
## # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # #
## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # #
### # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ##
## # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # #
## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # #
####################################################################################################
## # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # #
## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # #
### # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ##
## # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # #
## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # #
### # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ##
####################################################################################################
## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # #
### # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ##
## # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # #
## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # #
### # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ##
## # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # #
####################################################################################################
### # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ##
## # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # #
## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # #
### # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ##
## # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # #
## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # # # ## # # ## # E#
####################################################################################################

View File

@ -0,0 +1,20 @@
####################
#S #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# E#
####################

View File

@ -0,0 +1,20 @@
####################
#S #
# ############### #
# # # #
# # ########### # #
# # # # # #
# # # ####### # # #
# # # # # # # #
# # # # ### # # # #
# # # # # # # # #
# # # # ##### # # #
# # # # # # #
# # # ######### # #
# # # # #
# # ############# #
# # #
# ###################
# #
# E#
####################

View File

@ -0,0 +1,161 @@
import os
import csv
from datetime import datetime
from maze_model import Maze
from maze_builder import TextFileMazeBuilder
from pathfinding_strategies import BFSStrategy, DFSStrategy, AStarStrategy
from maze_solver import MazeSolver
def get_maze_file():
maze_files = [f for f in os.listdir('.') if f.endswith('.txt') and f != 'experiment_results.csv']
if not maze_files:
print("\nНет файлов лабиринтов! Поместите файлы labirint1.txt и т.д. в папку.")
exit(1)
print("\nДоступные файлы лабиринтов:")
for i, f in enumerate(maze_files, 1):
print(f" {i}. {f}")
while True:
choice = input(f"\nВыберите файл (1-{len(maze_files)}): ").strip()
try:
idx = int(choice) - 1
if 0 <= idx < len(maze_files):
return maze_files[idx]
except ValueError:
pass
print(f"Неверный выбор. Введите число от 1 до {len(maze_files)}")
def display_maze_with_path(maze: Maze, path=None):
print("\n+" + "-" * maze.width + "+")
for y in range(maze.height):
row = "|"
for x in range(maze.width):
cell = maze.get_cell(x, y)
if cell == maze.start_cell:
row += "S"
elif cell == maze.exit_cell:
row += "E"
elif path and cell in path:
row += "."
elif cell.is_wall:
row += "#"
else:
row += " "
row += "|"
print(row)
print("+" + "-" * maze.width + "+")
def save_to_csv(maze_name: str, algorithm_name: str, time_ms: float, path_length: int, path_found: bool, maze_size: str):
csv_filename = "experiment_results.csv"
file_exists = os.path.exists(csv_filename)
with open(csv_filename, 'a', newline='', encoding='utf-8-sig') as f:
writer = csv.writer(f)
if not file_exists:
writer.writerow(['лабиринт', 'стратегия', 'время_мс', 'длина_пути', 'путь_найден', 'размер', 'датаремя'])
writer.writerow([
maze_name,
algorithm_name,
round(time_ms, 3),
path_length,
'Да' if path_found else 'Нет',
maze_size,
datetime.now().strftime("%Y-%m-%d %H:%M:%S")
])
print(f"Результат сохранён в {csv_filename}")
def print_all_results():
csv_filename = "experiment_results.csv"
with open(csv_filename, 'r', encoding='utf-8-sig') as f:
reader = csv.reader(f)
headers = next(reader)
print(f"{headers[0]:<25} {headers[1]:<22} {headers[2]:<10} {headers[3]:<10} {headers[4]:<8} {headers[5]:<10}")
for row in reader:
print(f"{row[0]:<25} {row[1]:<22} {row[2]:<10} {row[3]:<10} {row[4]:<8} {row[5]:<10}")
def main():
filename = get_maze_file()
try:
builder = TextFileMazeBuilder()
maze = builder.build_from_file(filename)
maze_name = filename.replace('.txt', '')
maze_size = f"{maze.width}x{maze.height}"
print(f"\nЛабиринт загружен из файла: {filename}")
print(f" Размер: {maze_size}")
print(f" Старт: ({maze.start_cell.x}, {maze.start_cell.y})")
print(f" Выход: ({maze.exit_cell.x}, {maze.exit_cell.y})")
except FileNotFoundError:
print(f"\nФайл '{filename}' не найден!")
return
except ValueError as e:
print(f"\nОшибка в файле лабиринта: {e}")
return
print("\nЗагруженный лабиринт:")
maze.display()
print("ВЫБОР АЛГОРИТМА ПОИСКА")
print("1. BFS (Поиск в ширину)")
print("2. DFS (Поиск в глубину)")
print("3. A* (A-Star)")
choice = input("\nВыберите алгоритм (1-3): ").strip()
if choice == '1':
strategy = BFSStrategy()
elif choice == '2':
strategy = DFSStrategy()
elif choice == '3':
strategy = AStarStrategy()
else:
print("Неверный выбор!")
return
solver = MazeSolver(maze, strategy)
path, stats = solver.solve_with_stats()
print(stats.detailed_report())
if path:
print("\nЛабиринт с найденным путём (точки):")
display_maze_with_path(maze, path)
else:
print("Путь не найден!")
# Сохраняем результат в CSV
save_to_csv(
maze_name=maze_name,
algorithm_name=stats.algorithm_name,
time_ms=stats.execution_time_ms,
path_length=stats.path_length,
path_found=stats.path_found,
maze_size=maze_size
)
print("\nПрограмма завершена!")
if __name__ == "__main__":
main()

View File

@ -0,0 +1,84 @@
from abc import ABC, abstractmethod
from maze_model import Maze
class MazeBuilder(ABC):
@abstractmethod
def build_from_file(self, filename: str) -> Maze:
pass
@abstractmethod
def build_from_string(self, content: str) -> Maze:
pass
class TextFileMazeBuilder(MazeBuilder):
def __init__(self):
self._maze = None
self._lines = []
self._start_found = False
self._exit_found = False
def build_from_file(self, filename: str) -> Maze:
try:
with open(filename, 'r', encoding='utf-8') as file:
content = file.read()
return self.build_from_string(content)
except FileNotFoundError:
raise FileNotFoundError(f"Файл не найден: {filename}")
def build_from_string(self, content: str) -> Maze:
self._reset()
self._lines = [line.rstrip('\n\r') for line in content.split('\n')]
while self._lines and not self._lines[-1].strip():
self._lines.pop()
if not self._lines:
raise ValueError("Пустой файл лабиринта")
height = len(self._lines)
width = max(len(line) for line in self._lines)
for i, line in enumerate(self._lines):
if len(line) != width:
self._lines[i] = line.ljust(width)
self._maze = Maze(width, height)
for y, line in enumerate(self._lines):
for x, char in enumerate(line):
self._parse_cell(x, y, char)
self._validate_maze()
return self._maze
def _reset(self):
self._maze = None
self._lines = []
self._start_found = False
self._exit_found = False
def _parse_cell(self, x: int, y: int, char: str):
cell = self._maze.get_cell(x, y)
if char == '#':
cell.is_wall = True
elif char == 'S':
if self._start_found:
raise ValueError(f"Найден второй старт в ({x}, {y})")
self._maze.set_start(x, y)
self._start_found = True
elif char == 'E':
if self._exit_found:
raise ValueError(f"Найден второй выход в ({x}, {y})")
self._maze.set_exit(x, y)
self._exit_found = True
elif char == ' ':
pass
else:
raise ValueError(f"Неизвестный символ '{char}' в ({x}, {y})")
def _validate_maze(self):
if not self._start_found:
raise ValueError("В лабиринте не найден старт (символ 'S')")
if not self._exit_found:
raise ValueError("В лабиринте не найден выход (символ 'E')")

View File

@ -0,0 +1,103 @@
from typing import List, Optional
class Cell:
def __init__(self, x: int, y: int):
self.x = x
self.y = y
self.is_wall = False
self.is_start = False
self.is_exit = False
def is_passable(self) -> bool:
return not self.is_wall
def __repr__(self) -> str:
if self.is_start:
return "S"
elif self.is_exit:
return "E"
elif self.is_wall:
return "#"
else:
return "."
def __eq__(self, other) -> bool:
if not isinstance(other, Cell):
return False
return self.x == other.x and self.y == other.y
def __hash__(self) -> int:
return hash((self.x, self.y))
def __lt__(self, other):
return (self.x, self.y) < (other.x, other.y)
class Maze:
def __init__(self, width: int, height: int):
self.width = width
self.height = height
self._cells: List[List[Cell]] = []
self.start_cell: Optional[Cell] = None
self.exit_cell: Optional[Cell] = None
for y in range(height):
row = []
for x in range(width):
row.append(Cell(x, y))
self._cells.append(row)
def get_cell(self, x: int, y: int) -> Optional[Cell]:
if 0 <= x < self.width and 0 <= y < self.height:
return self._cells[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
def set_wall(self, x: int, y: int, is_wall: bool = True) -> None:
cell = self.get_cell(x, y)
if cell:
cell.is_wall = is_wall
def set_start(self, x: int, y: int) -> None:
cell = self.get_cell(x, y)
if cell:
if self.start_cell:
self.start_cell.is_start = False
cell.is_start = True
self.start_cell = cell
def set_exit(self, x: int, y: int) -> None:
cell = self.get_cell(x, y)
if cell:
if self.exit_cell:
self.exit_cell.is_exit = False
cell.is_exit = True
self.exit_cell = cell
def display(self) -> None:
print("+" + "-" * self.width + "+")
for y in range(self.height):
row_str = "|"
for x in range(self.width):
cell = self._cells[y][x]
if cell.is_start:
row_str += "S"
elif cell.is_exit:
row_str += "E"
elif cell.is_wall:
row_str += "#"
else:
row_str += " "
row_str += "|"
print(row_str)
print("+" + "-" * self.width + "+")

View File

@ -0,0 +1,71 @@
import time
from typing import List, Optional
from dataclasses import dataclass
from maze_model import Maze, Cell
from pathfinding_strategies import PathFindingStrategy
@dataclass
class SearchStats:
algorithm_name: str
path_length: int
execution_time_ms: float
path_found: bool
def __post_init__(self):
self.execution_time_ms = round(self.execution_time_ms, 3)
def summary(self) -> str:
if self.path_found:
return f"{self.algorithm_name}: путь найден | длина={self.path_length} | время={self.execution_time_ms} мс"
return f"{self.algorithm_name}: путь НЕ найден | время={self.execution_time_ms} мс"
def detailed_report(self) -> str:
separator = "=" * 50
report = f"\n{separator}\nОтчёт о поиске пути\n{separator}\n"
report += f"Алгоритм: {self.algorithm_name}\n"
report += f"Путь найден: {'Да' if self.path_found else 'Нет'}\n"
if self.path_found:
report += f"Длина пути: {self.path_length} шагов\n"
report += f"Время выполнения: {self.execution_time_ms} мс\n{separator}"
return report
class MazeSolver:
def __init__(self, maze: Maze, strategy: PathFindingStrategy = None):
self._maze = maze
self._strategy = strategy
self._last_stats: Optional[SearchStats] = None
self._validate_maze()
def _validate_maze(self):
if not self._maze.start_cell:
raise ValueError("Лабиринт не имеет стартовой клетки")
if not self._maze.exit_cell:
raise ValueError("Лабиринт не имеет выходной клетки")
def set_strategy(self, strategy: PathFindingStrategy) -> None:
self._strategy = strategy
def solve(self) -> List[Cell]:
if self._strategy is None:
raise ValueError("Стратегия не установлена")
start_time = time.perf_counter()
path = self._strategy.find_path(self._maze, self._maze.start_cell, self._maze.exit_cell)
end_time = time.perf_counter()
self._last_stats = SearchStats(
algorithm_name=self._strategy.get_name(),
path_length=len(path),
execution_time_ms=(end_time - start_time) * 1000,
path_found=len(path) > 0
)
return path
def solve_with_stats(self) -> tuple:
path = self.solve()
return path, self._last_stats
def get_last_stats(self) -> Optional[SearchStats]:
return self._last_stats

View File

@ -0,0 +1,123 @@
from abc import ABC, abstractmethod
from typing import List, Dict, Optional
from collections import deque
import heapq
from maze_model import Maze, Cell
class PathFindingStrategy(ABC):
@abstractmethod
def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> List[Cell]:
pass
@abstractmethod
def get_name(self) -> str:
pass
class BFSStrategy(PathFindingStrategy):
def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> List[Cell]:
if start == exit_cell:
return [start]
queue = deque([start])
came_from: Dict[Cell, Optional[Cell]] = {start: None}
while queue:
current = queue.popleft()
if current == exit_cell:
return self._reconstruct_path(came_from, start, exit_cell)
for neighbor in maze.get_neighbors(current):
if neighbor not in came_from:
came_from[neighbor] = current
queue.append(neighbor)
return []
def _reconstruct_path(self, came_from, start, exit_cell):
path = []
current = exit_cell
while current is not None:
path.append(current)
current = came_from.get(current)
path.reverse()
return path
def get_name(self) -> str:
return "BFS (Поиск в ширину)"
class DFSStrategy(PathFindingStrategy):
def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> List[Cell]:
if start == exit_cell:
return [start]
stack = [start]
came_from: Dict[Cell, Optional[Cell]] = {start: None}
while stack:
current = stack.pop()
if current == exit_cell:
return self._reconstruct_path(came_from, start, exit_cell)
for neighbor in maze.get_neighbors(current):
if neighbor not in came_from:
came_from[neighbor] = current
stack.append(neighbor)
return []
def _reconstruct_path(self, came_from, start, exit_cell):
path = []
current = exit_cell
while current is not None:
path.append(current)
current = came_from.get(current)
path.reverse()
return path
def get_name(self) -> str:
return "DFS (Поиск в глубину)"
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) -> List[Cell]:
if start == exit_cell:
return [start]
counter = 0
open_set = [(0, counter, start)]
g_score: Dict[Cell, float] = {start: 0}
came_from: Dict[Cell, Optional[Cell]] = {start: None}
open_set_cells = {start}
while open_set:
_, _, current = heapq.heappop(open_set)
open_set_cells.remove(current)
if current == exit_cell:
return self._reconstruct_path(came_from, start, exit_cell)
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 = tentative_g + self._heuristic(neighbor, exit_cell)
if neighbor not in open_set_cells:
counter += 1
heapq.heappush(open_set, (f, counter, neighbor))
open_set_cells.add(neighbor)
return []
def _reconstruct_path(self, came_from, start, exit_cell):
path = []
current = exit_cell
while current is not None:
path.append(current)
current = came_from.get(current)
path.reverse()
return path
def get_name(self) -> str:
return "A* (A-Star)"