forked from UNN/2026-rff_mp
[2] maze solver
This commit is contained in:
parent
82e988c965
commit
ddcf9d2c34
47
vinichukan/docs/class_diagram.mmd
Normal file
47
vinichukan/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
|
||||||
20
vinichukan/docs/diagrams.md
Normal file
20
vinichukan/docs/diagrams.md
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
# Диаграммы проекта
|
||||||
|
|
||||||
|
## 1. Диаграмма классов
|
||||||
|
См. файл `class_diagram.mmd`.
|
||||||
|
|
||||||
|
## 2. Структура каталогов
|
||||||
|
|
||||||
|
```
|
||||||
|
vinichukan/
|
||||||
|
├── src/
|
||||||
|
├── mazes/
|
||||||
|
├── experiments/
|
||||||
|
└── docs/
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. Логика работы алгоритмов
|
||||||
|
|
||||||
|
- BFS — поиск в ширину
|
||||||
|
- DFS — поиск в глубину
|
||||||
|
- A* — эвристический поиск с манхэттенской метрикой
|
||||||
131
vinichukan/docs/report.md
Normal file
131
vinichukan/docs/report.md
Normal file
|
|
@ -0,0 +1,131 @@
|
||||||
|
# Отчёт по заданию №2
|
||||||
|
### Реализация поиска пути в лабиринте с использованием паттернов проектирования
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Цель работы
|
||||||
|
|
||||||
|
Разработать архитектуру и реализацию системы поиска пути в лабиринте, применив паттерны:
|
||||||
|
|
||||||
|
- Builder — построение лабиринта из файла
|
||||||
|
- Strategy — выбор алгоритма поиска
|
||||||
|
- Observer — отображение состояния
|
||||||
|
- Command — управление игроком
|
||||||
|
|
||||||
|
Также провести экспериментальное сравнение алгоритмов BFS, DFS и A*.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Архитектура проекта
|
||||||
|
|
||||||
|
Структура каталогов:
|
||||||
|
|
||||||
|
```
|
||||||
|
vinichukan/
|
||||||
|
│
|
||||||
|
├── 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/graphs.ipynb`
|
||||||
|
|
||||||
|
|
||||||
|
- время работы алгоритмов
|
||||||
|
- количество посещённых клеток
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Выводы
|
||||||
|
|
||||||
|
1. A* показывает лучшие результаты на средних и больших лабиринтах, но имеет небольшой накладной расход.
|
||||||
|
2. DFS посещает меньше клеток, но не гарантирует кратчайший путь.
|
||||||
|
3. BFS всегда находит кратчайший путь, но исследует больше пространства.
|
||||||
|
4. На лабиринтах без выхода все алгоритмы корректно возвращают `path_len = 0`.
|
||||||
|
5. Архитектура с паттернами позволяет легко расширять проект и добавлять новые алгоритмы.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Приложения
|
||||||
|
|
||||||
|
- Исходный код
|
||||||
|
- Лабиринты
|
||||||
|
- CSV с результатами
|
||||||
|
- Диаграммы
|
||||||
55
vinichukan/experiments/benchmark.py
Normal file
55
vinichukan/experiments/benchmark.py
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
import os
|
||||||
|
import csv
|
||||||
|
from time import perf_counter
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
|
maze_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}")
|
||||||
|
|
||||||
|
# save CSV
|
||||||
|
with open("results.csv", "w", newline="", encoding="utf-8") as f:
|
||||||
|
writer = csv.writer(f)
|
||||||
|
writer.writerow(["maze", "algorithm", "time_ms", "visited", "path_len"])
|
||||||
|
writer.writerows(results)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
run_experiments()
|
||||||
95
vinichukan/experiments/graphs.ipynb
Normal file
95
vinichukan/experiments/graphs.ipynb
Normal file
File diff suppressed because one or more lines are too long
13
vinichukan/mazes/big.txt
Normal file
13
vinichukan/mazes/big.txt
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
####################################################################################################
|
||||||
|
#S # ########### # # ######### # #
|
||||||
|
# ####### ######### ########### ###### ######## ######## ######## ######## ######## ########## #####
|
||||||
|
# # # # # # # # # # # #
|
||||||
|
######## ######### ######### ######## ######## ######## ######## ######## ######## ######## #######
|
||||||
|
# # # # # # # # # # # # #
|
||||||
|
# ######## ##### # # ##### ######## ######## ######## ######## ######## ######## ######## #########
|
||||||
|
# # # # # # # # # # # # # #
|
||||||
|
######## ####### # ####### ######## ######## ######## ######## ######## ######## ######## #########
|
||||||
|
# # # # # # # # # # # #
|
||||||
|
# #### ######## ######## ######## ######## ######## ######## ######## ######## ######## ###########
|
||||||
|
# # # # # # # # # # # E#
|
||||||
|
####################################################################################################
|
||||||
1
vinichukan/mazes/empty.txt
Normal file
1
vinichukan/mazes/empty.txt
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
S E
|
||||||
5
vinichukan/mazes/medium.txt
Normal file
5
vinichukan/mazes/medium.txt
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
###############
|
||||||
|
#S # E#
|
||||||
|
# ### ####### #
|
||||||
|
# #
|
||||||
|
###############
|
||||||
3
vinichukan/mazes/no_exit.txt
Normal file
3
vinichukan/mazes/no_exit.txt
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
#######
|
||||||
|
#S #
|
||||||
|
#######
|
||||||
3
vinichukan/mazes/small.txt
Normal file
3
vinichukan/mazes/small.txt
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
##########
|
||||||
|
#S E#
|
||||||
|
##########
|
||||||
7
vinichukan/src/builder/maze_builder.py
Normal file
7
vinichukan/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
|
||||||
38
vinichukan/src/builder/text_file_maze_builder.py
Normal file
38
vinichukan/src/builder/text_file_maze_builder.py
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
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 (старт)")
|
||||||
|
|
||||||
|
# exit_ может быть None — это валидно (no_exit.txt)
|
||||||
|
|
||||||
|
return Maze(width, height, cells, start, exit_)
|
||||||
103
vinichukan/src/main.py
Normal file
103
vinichukan/src/main.py
Normal file
|
|
@ -0,0 +1,103 @@
|
||||||
|
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
vinichukan/src/model/cell.py
Normal file
19
vinichukan/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
vinichukan/src/model/maze.py
Normal file
23
vinichukan/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
|
||||||
33
vinichukan/src/solver/maze_solver.py
Normal file
33
vinichukan/src/solver/maze_solver.py
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
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
vinichukan/src/solver/search_stats.py
Normal file
8
vinichukan/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
vinichukan/src/strategy/astar_strategy.py
Normal file
43
vinichukan/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
vinichukan/src/strategy/bfs_strategy.py
Normal file
29
vinichukan/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
vinichukan/src/strategy/dfs_strategy.py
Normal file
27
vinichukan/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
vinichukan/src/strategy/path_finding_strategy.py
Normal file
9
vinichukan/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
|
||||||
11
vinichukan/src/ui/command.py
Normal file
11
vinichukan/src/ui/command.py
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
|
||||||
|
|
||||||
|
class Command(ABC):
|
||||||
|
@abstractmethod
|
||||||
|
def execute(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def undo(self):
|
||||||
|
pass
|
||||||
34
vinichukan/src/ui/console_view.py
Normal file
34
vinichukan/src/ui/console_view.py
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
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)
|
||||||
18
vinichukan/src/ui/move_command.py
Normal file
18
vinichukan/src/ui/move_command.py
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
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)
|
||||||
7
vinichukan/src/ui/observer.py
Normal file
7
vinichukan/src/ui/observer.py
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
|
||||||
|
|
||||||
|
class Observer(ABC):
|
||||||
|
@abstractmethod
|
||||||
|
def update(self, event: str):
|
||||||
|
pass
|
||||||
9
vinichukan/src/ui/player.py
Normal file
9
vinichukan/src/ui/player.py
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
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