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