task2
This commit is contained in:
parent
82e988c965
commit
8ebd282344
47
BrychkinKA/docs/class_diagram.mmd
Normal file
47
BrychkinKA/docs/class_diagram.mmd
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
classDiagram
|
||||||
|
class Maze {
|
||||||
|
+width
|
||||||
|
+height
|
||||||
|
+cells
|
||||||
|
+start
|
||||||
|
+exit
|
||||||
|
+get_neighbors()
|
||||||
|
}
|
||||||
|
|
||||||
|
class Cell {
|
||||||
|
+x
|
||||||
|
+y
|
||||||
|
+is_wall
|
||||||
|
+is_start
|
||||||
|
+is_exit
|
||||||
|
}
|
||||||
|
|
||||||
|
class MazeBuilder {
|
||||||
|
<<interface>>
|
||||||
|
+build_from_file()
|
||||||
|
}
|
||||||
|
|
||||||
|
class TextFileMazeBuilder {
|
||||||
|
+build_from_file()
|
||||||
|
}
|
||||||
|
|
||||||
|
class PathFindingStrategy {
|
||||||
|
<<interface>>
|
||||||
|
+find_path()
|
||||||
|
}
|
||||||
|
|
||||||
|
class BFSStrategy
|
||||||
|
class DFSStrategy
|
||||||
|
class AStarStrategy
|
||||||
|
|
||||||
|
class MazeSolver {
|
||||||
|
+solve()
|
||||||
|
}
|
||||||
|
|
||||||
|
Maze --> Cell
|
||||||
|
TextFileMazeBuilder ..|> MazeBuilder
|
||||||
|
BFSStrategy ..|> PathFindingStrategy
|
||||||
|
DFSStrategy ..|> PathFindingStrategy
|
||||||
|
AStarStrategy ..|> PathFindingStrategy
|
||||||
|
MazeSolver --> PathFindingStrategy
|
||||||
|
MazeSolver --> Maze
|
||||||
135
BrychkinKA/docs/conclusion.md
Normal file
135
BrychkinKA/docs/conclusion.md
Normal file
|
|
@ -0,0 +1,135 @@
|
||||||
|
# Отчёт по заданию №2
|
||||||
|
|
||||||
|
### Реализация поиска пути в лабиринте с использованием паттернов проектирования
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Цель работы
|
||||||
|
|
||||||
|
Разработать архитектуру и реализацию системы поиска пути в лабиринте, применив паттерны:
|
||||||
|
|
||||||
|
- Builder — построение лабиринта из файла
|
||||||
|
- Strategy — выбор алгоритма поиска
|
||||||
|
- Observer — отображение состояния
|
||||||
|
- Command — управление игроком
|
||||||
|
|
||||||
|
Также провести экспериментальное сравнение алгоритмов BFS, DFS и A\*.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Архитектура проекта
|
||||||
|
|
||||||
|
Структура каталогов:
|
||||||
|
|
||||||
|
```
|
||||||
|
BrychkinKA/
|
||||||
|
│
|
||||||
|
├── src/
|
||||||
|
│ ├── builder/
|
||||||
|
│ ├── model/
|
||||||
|
│ ├── solver/
|
||||||
|
│ ├── strategy/
|
||||||
|
│ └── ui/
|
||||||
|
│
|
||||||
|
├── mazes/
|
||||||
|
├── experiments/
|
||||||
|
└── docs/
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Используемые паттерны
|
||||||
|
|
||||||
|
### 3.1 Builder
|
||||||
|
|
||||||
|
Абстрагирует процесс построения лабиринта из текстового файла.
|
||||||
|
|
||||||
|
### 3.2 Strategy
|
||||||
|
|
||||||
|
Позволяет переключать алгоритмы поиска пути без изменения остального кода.
|
||||||
|
|
||||||
|
### 3.3 Observer
|
||||||
|
|
||||||
|
Используется для отображения состояния лабиринта в консоли.
|
||||||
|
|
||||||
|
### 3.4 Command
|
||||||
|
|
||||||
|
Реализует управление игроком и пошаговое перемещение.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Диаграмма классов
|
||||||
|
|
||||||
|
Диаграмма находится в файле: `class_diagram.mmd`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Эксперименты
|
||||||
|
|
||||||
|
Эксперименты проводились на пяти лабиринтах:
|
||||||
|
|
||||||
|
- small.txt — простой, проходимый
|
||||||
|
- medium.txt — средний по сложности
|
||||||
|
- empty.txt — полностью свободное поле
|
||||||
|
- no_exit.txt — отсутствует выход
|
||||||
|
- big.txt — большой лабиринт, путь отсутствует
|
||||||
|
|
||||||
|
Алгоритмы:
|
||||||
|
|
||||||
|
- BFS
|
||||||
|
- DFS
|
||||||
|
- A\*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Результаты
|
||||||
|
|
||||||
|
### 6.1 Таблица результатов
|
||||||
|
|
||||||
|
| Файл | Алгоритм | Посещено | Длина пути |
|
||||||
|
| ----------- | -------- | -------- | ---------- |
|
||||||
|
| big.txt | BFS | 27 | 0 |
|
||||||
|
| big.txt | DFS | 27 | 0 |
|
||||||
|
| big.txt | A\* | 27 | 0 |
|
||||||
|
| empty.txt | BFS | 10 | 10 |
|
||||||
|
| empty.txt | DFS | 10 | 10 |
|
||||||
|
| empty.txt | A\* | 10 | 10 |
|
||||||
|
| medium.txt | BFS | 21 | 17 |
|
||||||
|
| medium.txt | DFS | 19 | 17 |
|
||||||
|
| medium.txt | A\* | 21 | 17 |
|
||||||
|
| no_exit.txt | BFS | 0 | 0 |
|
||||||
|
| no_exit.txt | DFS | 0 | 0 |
|
||||||
|
| no_exit.txt | A\* | 0 | 0 |
|
||||||
|
| small.txt | BFS | 7 | 7 |
|
||||||
|
| small.txt | DFS | 7 | 7 |
|
||||||
|
| small.txt | A\* | 7 | 7 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Графики
|
||||||
|
|
||||||
|
Графики находятся в файле:
|
||||||
|
|
||||||
|
`experiments/plot_graphs.py`
|
||||||
|
|
||||||
|
- время работы алгоритмов
|
||||||
|
- количество посещённых клеток
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Выводы
|
||||||
|
|
||||||
|
1. A\* показывает лучшие результаты на средних и больших лабиринтах, но имеет небольшой накладной расход.
|
||||||
|
2. DFS посещает меньше клеток, но не гарантирует кратчайший путь.
|
||||||
|
3. BFS всегда находит кратчайший путь, но исследует больше пространства.
|
||||||
|
4. На лабиринтах без выхода все алгоритмы корректно возвращают `path_len = 0`.
|
||||||
|
5. Архитектура с паттернами позволяет легко расширять проект и добавлять новые алгоритмы.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Приложения
|
||||||
|
|
||||||
|
- Исходный код
|
||||||
|
- Лабиринты
|
||||||
|
- CSV с результатами
|
||||||
|
- Диаграммы
|
||||||
21
BrychkinKA/docs/diagrams.md
Normal file
21
BrychkinKA/docs/diagrams.md
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
# Диаграммы проекта
|
||||||
|
|
||||||
|
## 1. Диаграмма классов
|
||||||
|
|
||||||
|
См. файл `class_diagram.mmd`.
|
||||||
|
|
||||||
|
## 2. Структура каталогов
|
||||||
|
|
||||||
|
```
|
||||||
|
vinichukan/
|
||||||
|
├── src/
|
||||||
|
├── mazes/
|
||||||
|
├── experiments/
|
||||||
|
└── docs/
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. Логика работы алгоритмов
|
||||||
|
|
||||||
|
- BFS — поиск в ширину
|
||||||
|
- DFS — поиск в глубину
|
||||||
|
- A\* — эвристический поиск с манхэттенской метрикой
|
||||||
65
BrychkinKA/experiments/benchmark.py
Normal file
65
BrychkinKA/experiments/benchmark.py
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import csv
|
||||||
|
from time import perf_counter
|
||||||
|
|
||||||
|
# Добавляем корневую папку BrychkinKA в sys.path
|
||||||
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
|
from src.builder.text_file_maze_builder import TextFileMazeBuilder
|
||||||
|
from src.strategy.bfs_strategy import BFSStrategy
|
||||||
|
from src.strategy.dfs_strategy import DFSStrategy
|
||||||
|
from src.strategy.astar_strategy import AStarStrategy
|
||||||
|
from src.solver.maze_solver import MazeSolver
|
||||||
|
|
||||||
|
|
||||||
|
def run_experiments():
|
||||||
|
builder = TextFileMazeBuilder()
|
||||||
|
|
||||||
|
strategies = {
|
||||||
|
"BFS": BFSStrategy(),
|
||||||
|
"DFS": DFSStrategy(),
|
||||||
|
"A*": AStarStrategy()
|
||||||
|
}
|
||||||
|
|
||||||
|
# Папка с лабиринтами относительно корня
|
||||||
|
root_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
maze_dir = os.path.join(root_dir, "mazes")
|
||||||
|
|
||||||
|
files = [f for f in os.listdir(maze_dir) if f.endswith(".txt")]
|
||||||
|
|
||||||
|
results = []
|
||||||
|
|
||||||
|
for maze_file in files:
|
||||||
|
maze_path = os.path.join(maze_dir, maze_file)
|
||||||
|
maze = builder.build_from_file(maze_path)
|
||||||
|
|
||||||
|
for name, strategy in strategies.items():
|
||||||
|
solver = MazeSolver(maze, strategy)
|
||||||
|
|
||||||
|
t0 = perf_counter()
|
||||||
|
stats = solver.solve()
|
||||||
|
t1 = perf_counter()
|
||||||
|
|
||||||
|
results.append([
|
||||||
|
maze_file,
|
||||||
|
name,
|
||||||
|
stats.time_ms,
|
||||||
|
stats.visited,
|
||||||
|
stats.path_len
|
||||||
|
])
|
||||||
|
|
||||||
|
print(f"{maze_file} | {name} | {stats}")
|
||||||
|
|
||||||
|
# Сохраняем results.csv в папку experiments
|
||||||
|
output_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "results.csv")
|
||||||
|
with open(output_path, "w", newline="", encoding="utf-8") as f:
|
||||||
|
writer = csv.writer(f)
|
||||||
|
writer.writerow(["maze", "algorithm", "time_ms", "visited", "path_len"])
|
||||||
|
writer.writerows(results)
|
||||||
|
|
||||||
|
print(f"\nРезультаты сохранены в {output_path}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
run_experiments()
|
||||||
76
BrychkinKA/experiments/plot_graphs.py
Normal file
76
BrychkinKA/experiments/plot_graphs.py
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
import csv
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
import os
|
||||||
|
|
||||||
|
def plot_results():
|
||||||
|
# Определяем правильный путь к results.csv
|
||||||
|
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
csv_path = os.path.join(script_dir, "results.csv")
|
||||||
|
|
||||||
|
results = []
|
||||||
|
with open(csv_path, "r", encoding="utf-8") as f:
|
||||||
|
reader = csv.DictReader(f)
|
||||||
|
for row in reader:
|
||||||
|
row['time_ms'] = float(row['time_ms'])
|
||||||
|
row['visited'] = int(row['visited'])
|
||||||
|
row['path_len'] = int(row['path_len'])
|
||||||
|
results.append(row)
|
||||||
|
|
||||||
|
mazes = sorted(set(r['maze'] for r in results))
|
||||||
|
algorithms = sorted(set(r['algorithm'] for r in results))
|
||||||
|
|
||||||
|
x_labels = []
|
||||||
|
for m in mazes:
|
||||||
|
for a in algorithms:
|
||||||
|
x_labels.append(f"{m.replace('.txt','')}\n{a}")
|
||||||
|
|
||||||
|
# График 1: Время выполнения
|
||||||
|
plt.figure(figsize=(12, 6))
|
||||||
|
times = []
|
||||||
|
for m in mazes:
|
||||||
|
for a in algorithms:
|
||||||
|
val = [r['time_ms'] for r in results if r['maze'] == m and r['algorithm'] == a]
|
||||||
|
times.append(val[0] if val else 0)
|
||||||
|
plt.bar(x_labels, times)
|
||||||
|
plt.ylabel("Время (мс)")
|
||||||
|
plt.title("Сравнение времени выполнения алгоритмов")
|
||||||
|
plt.xticks(rotation=45, ha='right')
|
||||||
|
plt.tight_layout()
|
||||||
|
plt.savefig(os.path.join(script_dir, "plot_time.png"), dpi=150)
|
||||||
|
plt.close()
|
||||||
|
print("Сохранён: experiments/plot_time.png")
|
||||||
|
|
||||||
|
# График 2: Посещённые клетки
|
||||||
|
plt.figure(figsize=(12, 6))
|
||||||
|
visited_list = []
|
||||||
|
for m in mazes:
|
||||||
|
for a in algorithms:
|
||||||
|
val = [r['visited'] for r in results if r['maze'] == m and r['algorithm'] == a]
|
||||||
|
visited_list.append(val[0] if val else 0)
|
||||||
|
plt.bar(x_labels, visited_list)
|
||||||
|
plt.ylabel("Посещено клеток")
|
||||||
|
plt.title("Сравнение количества посещённых клеток")
|
||||||
|
plt.xticks(rotation=45, ha='right')
|
||||||
|
plt.tight_layout()
|
||||||
|
plt.savefig(os.path.join(script_dir, "plot_visited.png"), dpi=150)
|
||||||
|
plt.close()
|
||||||
|
print("Сохранён: experiments/plot_visited.png")
|
||||||
|
|
||||||
|
# График 3: Длина пути
|
||||||
|
plt.figure(figsize=(12, 6))
|
||||||
|
path_list = []
|
||||||
|
for m in mazes:
|
||||||
|
for a in algorithms:
|
||||||
|
val = [r['path_len'] for r in results if r['maze'] == m and r['algorithm'] == a]
|
||||||
|
path_list.append(val[0] if val else 0)
|
||||||
|
plt.bar(x_labels, path_list)
|
||||||
|
plt.ylabel("Длина пути")
|
||||||
|
plt.title("Сравнение длины найденного пути")
|
||||||
|
plt.xticks(rotation=45, ha='right')
|
||||||
|
plt.tight_layout()
|
||||||
|
plt.savefig(os.path.join(script_dir, "plot_path.png"), dpi=150)
|
||||||
|
plt.close()
|
||||||
|
print("Сохранён: experiments/plot_path.png")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
plot_results()
|
||||||
BIN
BrychkinKA/experiments/plot_path.png
Normal file
BIN
BrychkinKA/experiments/plot_path.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 35 KiB |
BIN
BrychkinKA/experiments/plot_time.png
Normal file
BIN
BrychkinKA/experiments/plot_time.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 38 KiB |
BIN
BrychkinKA/experiments/plot_visited.png
Normal file
BIN
BrychkinKA/experiments/plot_visited.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 37 KiB |
16
BrychkinKA/experiments/results.csv
Normal file
16
BrychkinKA/experiments/results.csv
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
maze,algorithm,time_ms,visited,path_len
|
||||||
|
big.txt,BFS,0.14230050146579742,27,0
|
||||||
|
big.txt,DFS,0.1100003719329834,27,0
|
||||||
|
big.txt,A*,0.23249909281730652,27,0
|
||||||
|
empty.txt,BFS,0.07219985127449036,10,10
|
||||||
|
empty.txt,DFS,0.046100467443466187,10,10
|
||||||
|
empty.txt,A*,0.08819997310638428,10,10
|
||||||
|
medium.txt,BFS,0.09160116314888,21,17
|
||||||
|
medium.txt,DFS,0.07379986345767975,19,17
|
||||||
|
medium.txt,A*,0.15410035848617554,21,17
|
||||||
|
no_exit.txt,BFS,0.0007003545761108398,0,0
|
||||||
|
no_exit.txt,DFS,0.0027008354663848877,0,0
|
||||||
|
no_exit.txt,A*,0.0001993030309677124,0,0
|
||||||
|
small.txt,BFS,0.06789900362491608,7,7
|
||||||
|
small.txt,DFS,0.03989972174167633,7,7
|
||||||
|
small.txt,A*,0.09530037641525269,7,7
|
||||||
|
13
BrychkinKA/mazes/big.txt
Normal file
13
BrychkinKA/mazes/big.txt
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
####################################################################################################
|
||||||
|
#S # ########### # # ######### # #
|
||||||
|
# ####### ######### ########### ###### ######## ######## ######## ######## ######## ########## #####
|
||||||
|
# # # # # # # # # # # #
|
||||||
|
######## ######### ######### ######## ######## ######## ######## ######## ######## ######## #######
|
||||||
|
# # # # # # # # # # # # #
|
||||||
|
# ######## ##### # # ##### ######## ######## ######## ######## ######## ######## ######## #########
|
||||||
|
# # # # # # # # # # # # # #
|
||||||
|
######## ####### # ####### ######## ######## ######## ######## ######## ######## ######## #########
|
||||||
|
# # # # # # # # # # # #
|
||||||
|
# #### ######## ######## ######## ######## ######## ######## ######## ######## ######## ###########
|
||||||
|
# # # # # # # # # # # E#
|
||||||
|
####################################################################################################
|
||||||
1
BrychkinKA/mazes/empty.txt
Normal file
1
BrychkinKA/mazes/empty.txt
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
S E
|
||||||
5
BrychkinKA/mazes/medium.txt
Normal file
5
BrychkinKA/mazes/medium.txt
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
###############
|
||||||
|
#S # E#
|
||||||
|
# ### ####### #
|
||||||
|
# #
|
||||||
|
###############
|
||||||
3
BrychkinKA/mazes/no_exit.txt
Normal file
3
BrychkinKA/mazes/no_exit.txt
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
#######
|
||||||
|
#S #
|
||||||
|
#######
|
||||||
3
BrychkinKA/mazes/small.txt
Normal file
3
BrychkinKA/mazes/small.txt
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
##########
|
||||||
|
#S E#
|
||||||
|
##########
|
||||||
7
BrychkinKA/src/builder/maze_builder.py
Normal file
7
BrychkinKA/src/builder/maze_builder.py
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
from src.model.maze import Maze
|
||||||
|
|
||||||
|
class MazeBuilder(ABC):
|
||||||
|
@abstractmethod
|
||||||
|
def build_from_file(self, filename: str) -> Maze:
|
||||||
|
pass
|
||||||
36
BrychkinKA/src/builder/text_file_maze_builder.py
Normal file
36
BrychkinKA/src/builder/text_file_maze_builder.py
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
from src.model.cell import Cell
|
||||||
|
from src.model.maze import Maze
|
||||||
|
|
||||||
|
class TextFileMazeBuilder:
|
||||||
|
def build_from_file(self, filename):
|
||||||
|
with open(filename, "r", encoding="utf-8") as f:
|
||||||
|
lines = [line.rstrip("\n") for line in f]
|
||||||
|
|
||||||
|
height = len(lines)
|
||||||
|
width = max(len(line) for line in lines)
|
||||||
|
|
||||||
|
cells = []
|
||||||
|
start = None
|
||||||
|
exit_ = None
|
||||||
|
|
||||||
|
for y, line in enumerate(lines):
|
||||||
|
row = []
|
||||||
|
for x, ch in enumerate(line.ljust(width)):
|
||||||
|
is_wall = (ch == "#")
|
||||||
|
is_start = (ch == "S")
|
||||||
|
is_exit = (ch == "E")
|
||||||
|
|
||||||
|
cell = Cell(x, y, is_wall, is_start, is_exit)
|
||||||
|
row.append(cell)
|
||||||
|
|
||||||
|
if is_start:
|
||||||
|
start = cell
|
||||||
|
if is_exit:
|
||||||
|
exit_ = cell
|
||||||
|
|
||||||
|
cells.append(row)
|
||||||
|
|
||||||
|
if start is None:
|
||||||
|
raise ValueError("Файл должен содержать S (старт)")
|
||||||
|
|
||||||
|
return Maze(width, height, cells, start, exit_)
|
||||||
101
BrychkinKA/src/main.py
Normal file
101
BrychkinKA/src/main.py
Normal file
|
|
@ -0,0 +1,101 @@
|
||||||
|
from src.builder.text_file_maze_builder import TextFileMazeBuilder
|
||||||
|
from src.strategy.bfs_strategy import BFSStrategy
|
||||||
|
from src.strategy.dfs_strategy import DFSStrategy
|
||||||
|
from src.strategy.astar_strategy import AStarStrategy
|
||||||
|
from src.solver.maze_solver import MazeSolver
|
||||||
|
from src.ui.console_view import ConsoleView
|
||||||
|
from src.ui.player import Player
|
||||||
|
from src.ui.move_command import MoveCommand
|
||||||
|
|
||||||
|
|
||||||
|
def choose_maze():
|
||||||
|
mazes = {
|
||||||
|
"1": ("small.txt", "Small — маленький лабиринт"),
|
||||||
|
"2": ("medium.txt", "Medium — средний лабиринт"),
|
||||||
|
"3": ("big.txt", "Big — большой лабиринт(тупиковый)"),
|
||||||
|
"4": ("empty.txt", "Empty — пустой лабиринт"),
|
||||||
|
"5": ("no_exit.txt","NoExit — без выхода")
|
||||||
|
}
|
||||||
|
|
||||||
|
print("\n" + "=" * 40)
|
||||||
|
print(" ВЫБОР ЛАБИРИНТА")
|
||||||
|
print("=" * 40)
|
||||||
|
|
||||||
|
for key, (_, desc) in mazes.items():
|
||||||
|
print(f" {key}. {desc}")
|
||||||
|
|
||||||
|
print("=" * 40)
|
||||||
|
|
||||||
|
choice = input("Введите номер: ").strip()
|
||||||
|
|
||||||
|
if choice not in mazes:
|
||||||
|
print("Неверный выбор, загружаю small.txt")
|
||||||
|
return "small.txt"
|
||||||
|
|
||||||
|
filename = mazes[choice][0]
|
||||||
|
print(f"Загружен: {filename}")
|
||||||
|
return filename
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
builder = TextFileMazeBuilder()
|
||||||
|
|
||||||
|
filename = choose_maze()
|
||||||
|
maze = builder.build_from_file(f"mazes/{filename}")
|
||||||
|
|
||||||
|
view = ConsoleView()
|
||||||
|
view.update(f"Maze '{filename}' loaded")
|
||||||
|
|
||||||
|
strategies = {
|
||||||
|
"bfs": BFSStrategy(),
|
||||||
|
"dfs": DFSStrategy(),
|
||||||
|
"astar": AStarStrategy()
|
||||||
|
}
|
||||||
|
|
||||||
|
print("\nВыберите алгоритм:")
|
||||||
|
print(" bfs — поиск в ширину")
|
||||||
|
print(" dfs — поиск в глубину")
|
||||||
|
print(" astar — A*")
|
||||||
|
algo = input("Введите название: ").strip().lower()
|
||||||
|
|
||||||
|
strategy = strategies.get(algo, BFSStrategy())
|
||||||
|
|
||||||
|
solver = MazeSolver(maze, strategy)
|
||||||
|
stats = solver.solve()
|
||||||
|
print(stats)
|
||||||
|
|
||||||
|
path, visited = strategy.find_path(maze, maze.start, maze.exit)
|
||||||
|
view.render(maze, None, path)
|
||||||
|
|
||||||
|
player = Player(maze.start)
|
||||||
|
|
||||||
|
while True:
|
||||||
|
cmd = input("Ход (w/a/s/d) или q для выхода: ").strip().lower()
|
||||||
|
if cmd == "q":
|
||||||
|
break
|
||||||
|
|
||||||
|
dxdy = {
|
||||||
|
"w": (0, -1),
|
||||||
|
"s": (0, 1),
|
||||||
|
"a": (-1, 0),
|
||||||
|
"d": (1, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cmd not in dxdy:
|
||||||
|
continue
|
||||||
|
|
||||||
|
dx, dy = dxdy[cmd]
|
||||||
|
new_cell = maze.get_cell(player.current_cell.x + dx,
|
||||||
|
player.current_cell.y + dy)
|
||||||
|
|
||||||
|
if not new_cell or not new_cell.is_passable():
|
||||||
|
print("Там стена, туда нельзя.")
|
||||||
|
continue
|
||||||
|
|
||||||
|
move = MoveCommand(player, new_cell)
|
||||||
|
move.execute()
|
||||||
|
view.render(maze, player.current_cell, path)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
19
BrychkinKA/src/model/cell.py
Normal file
19
BrychkinKA/src/model/cell.py
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
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 __repr__(self):
|
||||||
|
return f"Cell({self.x},{self.y})"
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
return hash((self.x, self.y))
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
return isinstance(other, Cell) and self.x == other.x and self.y == other.y
|
||||||
|
|
||||||
|
def is_passable(self):
|
||||||
|
return not self.is_wall
|
||||||
23
BrychkinKA/src/model/maze.py
Normal file
23
BrychkinKA/src/model/maze.py
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
class Maze:
|
||||||
|
def __init__(self, width, height, cells, start, exit_):
|
||||||
|
self.width = width
|
||||||
|
self.height = height
|
||||||
|
self.cells = cells
|
||||||
|
self.start = start
|
||||||
|
self.exit = exit_
|
||||||
|
|
||||||
|
def get_cell(self, x, y):
|
||||||
|
return self.cells[y][x]
|
||||||
|
|
||||||
|
def get_neighbors(self, cell):
|
||||||
|
dirs = [(1,0), (-1,0), (0,1), (0,-1)]
|
||||||
|
result = []
|
||||||
|
|
||||||
|
for dx, dy in dirs:
|
||||||
|
nx, ny = cell.x + dx, cell.y + dy
|
||||||
|
if 0 <= nx < self.width and 0 <= ny < self.height:
|
||||||
|
n = self.get_cell(nx, ny)
|
||||||
|
if not n.is_wall:
|
||||||
|
result.append(n)
|
||||||
|
|
||||||
|
return result
|
||||||
32
BrychkinKA/src/solver/maze_solver.py
Normal file
32
BrychkinKA/src/solver/maze_solver.py
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
from src.solver.search_stats import SearchStats
|
||||||
|
|
||||||
|
class MazeSolver:
|
||||||
|
def __init__(self, maze, strategy):
|
||||||
|
self.maze = maze
|
||||||
|
self.strategy = strategy
|
||||||
|
|
||||||
|
def solve(self):
|
||||||
|
import time
|
||||||
|
t0 = time.perf_counter()
|
||||||
|
|
||||||
|
if self.maze.exit is None:
|
||||||
|
t1 = time.perf_counter()
|
||||||
|
return SearchStats(
|
||||||
|
time_ms=(t1 - t0) * 1000,
|
||||||
|
visited=0,
|
||||||
|
path_len=0
|
||||||
|
)
|
||||||
|
|
||||||
|
path, visited = self.strategy.find_path(
|
||||||
|
self.maze,
|
||||||
|
self.maze.start,
|
||||||
|
self.maze.exit
|
||||||
|
)
|
||||||
|
|
||||||
|
t1 = time.perf_counter()
|
||||||
|
|
||||||
|
return SearchStats(
|
||||||
|
time_ms=(t1 - t0) * 1000,
|
||||||
|
visited=len(visited),
|
||||||
|
path_len=len(path) if path else 0
|
||||||
|
)
|
||||||
8
BrychkinKA/src/solver/search_stats.py
Normal file
8
BrychkinKA/src/solver/search_stats.py
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
class SearchStats:
|
||||||
|
def __init__(self, time_ms, visited, path_len):
|
||||||
|
self.time_ms = time_ms
|
||||||
|
self.visited = visited
|
||||||
|
self.path_len = path_len
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"SearchStats(time={self.time_ms:.2f}ms, visited={self.visited}, path={self.path_len})"
|
||||||
43
BrychkinKA/src/strategy/astar_strategy.py
Normal file
43
BrychkinKA/src/strategy/astar_strategy.py
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
import heapq
|
||||||
|
|
||||||
|
def manhattan(a, b):
|
||||||
|
return abs(a.x - b.x) + abs(a.y - b.y)
|
||||||
|
|
||||||
|
class AStarStrategy:
|
||||||
|
def find_path(self, maze, start, exit_):
|
||||||
|
g = {start: 0}
|
||||||
|
parent = {start: None}
|
||||||
|
|
||||||
|
counter = 0
|
||||||
|
open_heap = [(0, counter, start)]
|
||||||
|
in_open = {start}
|
||||||
|
visited = set()
|
||||||
|
|
||||||
|
while open_heap:
|
||||||
|
_, _, cur = heapq.heappop(open_heap)
|
||||||
|
in_open.discard(cur)
|
||||||
|
visited.add(cur)
|
||||||
|
|
||||||
|
if cur == exit_:
|
||||||
|
return self._reconstruct(parent, start, exit_), visited
|
||||||
|
|
||||||
|
for n in maze.get_neighbors(cur):
|
||||||
|
tentative = g[cur] + 1
|
||||||
|
if tentative < g.get(n, float('inf')):
|
||||||
|
g[n] = tentative
|
||||||
|
parent[n] = cur
|
||||||
|
f = tentative + manhattan(n, exit_)
|
||||||
|
if n not in in_open:
|
||||||
|
counter += 1
|
||||||
|
heapq.heappush(open_heap, (f, counter, n))
|
||||||
|
in_open.add(n)
|
||||||
|
|
||||||
|
return None, visited
|
||||||
|
|
||||||
|
def _reconstruct(self, parent, start, exit_):
|
||||||
|
path = []
|
||||||
|
cur = exit_
|
||||||
|
while cur:
|
||||||
|
path.append(cur)
|
||||||
|
cur = parent[cur]
|
||||||
|
return list(reversed(path))
|
||||||
29
BrychkinKA/src/strategy/bfs_strategy.py
Normal file
29
BrychkinKA/src/strategy/bfs_strategy.py
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
from collections import deque
|
||||||
|
|
||||||
|
class BFSStrategy:
|
||||||
|
def find_path(self, maze, start, exit_):
|
||||||
|
queue = deque([start])
|
||||||
|
parent = {start: None}
|
||||||
|
visited = {start}
|
||||||
|
|
||||||
|
while queue:
|
||||||
|
cur = queue.popleft()
|
||||||
|
|
||||||
|
if cur == exit_:
|
||||||
|
return self._reconstruct(parent, start, exit_), visited
|
||||||
|
|
||||||
|
for n in maze.get_neighbors(cur):
|
||||||
|
if n not in visited:
|
||||||
|
visited.add(n)
|
||||||
|
parent[n] = cur
|
||||||
|
queue.append(n)
|
||||||
|
|
||||||
|
return None, visited
|
||||||
|
|
||||||
|
def _reconstruct(self, parent, start, exit_):
|
||||||
|
path = []
|
||||||
|
cur = exit_
|
||||||
|
while cur:
|
||||||
|
path.append(cur)
|
||||||
|
cur = parent[cur]
|
||||||
|
return list(reversed(path))
|
||||||
27
BrychkinKA/src/strategy/dfs_strategy.py
Normal file
27
BrychkinKA/src/strategy/dfs_strategy.py
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
class DFSStrategy:
|
||||||
|
def find_path(self, maze, start, exit_):
|
||||||
|
stack = [start]
|
||||||
|
parent = {start: None}
|
||||||
|
visited = {start}
|
||||||
|
|
||||||
|
while stack:
|
||||||
|
cur = stack.pop()
|
||||||
|
|
||||||
|
if cur == exit_:
|
||||||
|
return self._reconstruct(parent, start, exit_), visited
|
||||||
|
|
||||||
|
for n in maze.get_neighbors(cur):
|
||||||
|
if n not in visited:
|
||||||
|
visited.add(n)
|
||||||
|
parent[n] = cur
|
||||||
|
stack.append(n)
|
||||||
|
|
||||||
|
return None, visited
|
||||||
|
|
||||||
|
def _reconstruct(self, parent, start, exit_):
|
||||||
|
path = []
|
||||||
|
cur = exit_
|
||||||
|
while cur:
|
||||||
|
path.append(cur)
|
||||||
|
cur = parent[cur]
|
||||||
|
return list(reversed(path))
|
||||||
9
BrychkinKA/src/strategy/path_finding_strategy.py
Normal file
9
BrychkinKA/src/strategy/path_finding_strategy.py
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
from typing import List
|
||||||
|
from src.model.cell import Cell
|
||||||
|
from src.model.maze import Maze
|
||||||
|
|
||||||
|
class PathFindingStrategy(ABC):
|
||||||
|
@abstractmethod
|
||||||
|
def find_path(self, maze: Maze, start: Cell, exit_: Cell) -> List[Cell]:
|
||||||
|
pass
|
||||||
10
BrychkinKA/src/ui/command.py
Normal file
10
BrychkinKA/src/ui/command.py
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
|
||||||
|
class Command(ABC):
|
||||||
|
@abstractmethod
|
||||||
|
def execute(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def undo(self):
|
||||||
|
pass
|
||||||
33
BrychkinKA/src/ui/console_view.py
Normal file
33
BrychkinKA/src/ui/console_view.py
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
import os
|
||||||
|
from typing import List
|
||||||
|
from src.model.cell import Cell
|
||||||
|
from src.model.maze import Maze
|
||||||
|
from .observer import Observer
|
||||||
|
|
||||||
|
class ConsoleView(Observer):
|
||||||
|
def update(self, event: str):
|
||||||
|
print(f"[EVENT] {event}")
|
||||||
|
|
||||||
|
def render(self, maze: Maze, player_pos: Cell = None, path: List[Cell] = None):
|
||||||
|
os.system('cls' if os.name == 'nt' else 'clear')
|
||||||
|
|
||||||
|
path_set = set(path) if path else set()
|
||||||
|
|
||||||
|
for y in range(maze.height):
|
||||||
|
row = ""
|
||||||
|
for x in range(maze.width):
|
||||||
|
cell = maze.get_cell(x, y)
|
||||||
|
|
||||||
|
if cell.is_wall:
|
||||||
|
row += "#"
|
||||||
|
elif cell.is_start:
|
||||||
|
row += "S"
|
||||||
|
elif cell.is_exit:
|
||||||
|
row += "E"
|
||||||
|
elif player_pos and cell.x == player_pos.x and cell.y == player_pos.y:
|
||||||
|
row += "@"
|
||||||
|
elif cell in path_set:
|
||||||
|
row += "*"
|
||||||
|
else:
|
||||||
|
row += " "
|
||||||
|
print(row)
|
||||||
17
BrychkinKA/src/ui/move_command.py
Normal file
17
BrychkinKA/src/ui/move_command.py
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
from src.model.cell import Cell
|
||||||
|
from .command import Command
|
||||||
|
from .player import Player
|
||||||
|
|
||||||
|
class MoveCommand(Command):
|
||||||
|
def __init__(self, player: Player, new_cell: Cell):
|
||||||
|
self.player = player
|
||||||
|
self.new_cell = new_cell
|
||||||
|
self.prev_cell = None
|
||||||
|
|
||||||
|
def execute(self):
|
||||||
|
self.prev_cell = self.player.current_cell
|
||||||
|
self.player.move_to(self.new_cell)
|
||||||
|
|
||||||
|
def undo(self):
|
||||||
|
if self.prev_cell:
|
||||||
|
self.player.move_to(self.prev_cell)
|
||||||
6
BrychkinKA/src/ui/observer.py
Normal file
6
BrychkinKA/src/ui/observer.py
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
|
||||||
|
class Observer(ABC):
|
||||||
|
@abstractmethod
|
||||||
|
def update(self, event: str):
|
||||||
|
pass
|
||||||
8
BrychkinKA/src/ui/player.py
Normal file
8
BrychkinKA/src/ui/player.py
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
from src.model.cell import Cell
|
||||||
|
|
||||||
|
class Player:
|
||||||
|
def __init__(self, start_cell: Cell):
|
||||||
|
self.current_cell = start_cell
|
||||||
|
|
||||||
|
def move_to(self, cell: Cell):
|
||||||
|
self.current_cell = cell
|
||||||
Loading…
Reference in New Issue
Block a user