diff --git a/BrychkinKA/docs/class_diagram.mmd b/BrychkinKA/docs/class_diagram.mmd new file mode 100644 index 0000000..59ae838 --- /dev/null +++ b/BrychkinKA/docs/class_diagram.mmd @@ -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 { + <> + +build_from_file() + } + + class TextFileMazeBuilder { + +build_from_file() + } + + class PathFindingStrategy { + <> + +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 \ No newline at end of file diff --git a/BrychkinKA/docs/conclusion.md b/BrychkinKA/docs/conclusion.md new file mode 100644 index 0000000..c508b2f --- /dev/null +++ b/BrychkinKA/docs/conclusion.md @@ -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 с результатами +- Диаграммы diff --git a/BrychkinKA/docs/diagrams.md b/BrychkinKA/docs/diagrams.md new file mode 100644 index 0000000..8c2a542 --- /dev/null +++ b/BrychkinKA/docs/diagrams.md @@ -0,0 +1,21 @@ +# Диаграммы проекта + +## 1. Диаграмма классов + +См. файл `class_diagram.mmd`. + +## 2. Структура каталогов + +``` +vinichukan/ +├── src/ +├── mazes/ +├── experiments/ +└── docs/ +``` + +## 3. Логика работы алгоритмов + +- BFS — поиск в ширину +- DFS — поиск в глубину +- A\* — эвристический поиск с манхэттенской метрикой diff --git a/BrychkinKA/experiments/benchmark.py b/BrychkinKA/experiments/benchmark.py new file mode 100644 index 0000000..f903979 --- /dev/null +++ b/BrychkinKA/experiments/benchmark.py @@ -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() \ No newline at end of file diff --git a/BrychkinKA/experiments/plot_graphs.py b/BrychkinKA/experiments/plot_graphs.py new file mode 100644 index 0000000..babf4cd --- /dev/null +++ b/BrychkinKA/experiments/plot_graphs.py @@ -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() \ No newline at end of file diff --git a/BrychkinKA/experiments/plot_path.png b/BrychkinKA/experiments/plot_path.png new file mode 100644 index 0000000..ac4db90 Binary files /dev/null and b/BrychkinKA/experiments/plot_path.png differ diff --git a/BrychkinKA/experiments/plot_time.png b/BrychkinKA/experiments/plot_time.png new file mode 100644 index 0000000..afd7d04 Binary files /dev/null and b/BrychkinKA/experiments/plot_time.png differ diff --git a/BrychkinKA/experiments/plot_visited.png b/BrychkinKA/experiments/plot_visited.png new file mode 100644 index 0000000..a125b14 Binary files /dev/null and b/BrychkinKA/experiments/plot_visited.png differ diff --git a/BrychkinKA/experiments/results.csv b/BrychkinKA/experiments/results.csv new file mode 100644 index 0000000..4cc5972 --- /dev/null +++ b/BrychkinKA/experiments/results.csv @@ -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 diff --git a/BrychkinKA/mazes/big.txt b/BrychkinKA/mazes/big.txt new file mode 100644 index 0000000..be534fe --- /dev/null +++ b/BrychkinKA/mazes/big.txt @@ -0,0 +1,13 @@ +#################################################################################################### +#S # ########### # # ######### # # +# ####### ######### ########### ###### ######## ######## ######## ######## ######## ########## ##### +# # # # # # # # # # # # +######## ######### ######### ######## ######## ######## ######## ######## ######## ######## ####### +# # # # # # # # # # # # # +# ######## ##### # # ##### ######## ######## ######## ######## ######## ######## ######## ######### +# # # # # # # # # # # # # # +######## ####### # ####### ######## ######## ######## ######## ######## ######## ######## ######### +# # # # # # # # # # # # +# #### ######## ######## ######## ######## ######## ######## ######## ######## ######## ########### +# # # # # # # # # # # E# +#################################################################################################### \ No newline at end of file diff --git a/BrychkinKA/mazes/empty.txt b/BrychkinKA/mazes/empty.txt new file mode 100644 index 0000000..9f5d2a7 --- /dev/null +++ b/BrychkinKA/mazes/empty.txt @@ -0,0 +1 @@ +S E \ No newline at end of file diff --git a/BrychkinKA/mazes/medium.txt b/BrychkinKA/mazes/medium.txt new file mode 100644 index 0000000..90bfd73 --- /dev/null +++ b/BrychkinKA/mazes/medium.txt @@ -0,0 +1,5 @@ +############### +#S # E# +# ### ####### # +# # +############### \ No newline at end of file diff --git a/BrychkinKA/mazes/no_exit.txt b/BrychkinKA/mazes/no_exit.txt new file mode 100644 index 0000000..84ac6ec --- /dev/null +++ b/BrychkinKA/mazes/no_exit.txt @@ -0,0 +1,3 @@ +####### +#S # +####### \ No newline at end of file diff --git a/BrychkinKA/mazes/small.txt b/BrychkinKA/mazes/small.txt new file mode 100644 index 0000000..23b3a8e --- /dev/null +++ b/BrychkinKA/mazes/small.txt @@ -0,0 +1,3 @@ +########## +#S E# +########## \ No newline at end of file diff --git a/BrychkinKA/src/builder/maze_builder.py b/BrychkinKA/src/builder/maze_builder.py new file mode 100644 index 0000000..6267c83 --- /dev/null +++ b/BrychkinKA/src/builder/maze_builder.py @@ -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 \ No newline at end of file diff --git a/BrychkinKA/src/builder/text_file_maze_builder.py b/BrychkinKA/src/builder/text_file_maze_builder.py new file mode 100644 index 0000000..d483646 --- /dev/null +++ b/BrychkinKA/src/builder/text_file_maze_builder.py @@ -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_) \ No newline at end of file diff --git a/BrychkinKA/src/main.py b/BrychkinKA/src/main.py new file mode 100644 index 0000000..043e173 --- /dev/null +++ b/BrychkinKA/src/main.py @@ -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() \ No newline at end of file diff --git a/BrychkinKA/src/model/cell.py b/BrychkinKA/src/model/cell.py new file mode 100644 index 0000000..5fad247 --- /dev/null +++ b/BrychkinKA/src/model/cell.py @@ -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 \ No newline at end of file diff --git a/BrychkinKA/src/model/maze.py b/BrychkinKA/src/model/maze.py new file mode 100644 index 0000000..398d737 --- /dev/null +++ b/BrychkinKA/src/model/maze.py @@ -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 \ No newline at end of file diff --git a/BrychkinKA/src/solver/maze_solver.py b/BrychkinKA/src/solver/maze_solver.py new file mode 100644 index 0000000..7bceabb --- /dev/null +++ b/BrychkinKA/src/solver/maze_solver.py @@ -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 + ) \ No newline at end of file diff --git a/BrychkinKA/src/solver/search_stats.py b/BrychkinKA/src/solver/search_stats.py new file mode 100644 index 0000000..1248a28 --- /dev/null +++ b/BrychkinKA/src/solver/search_stats.py @@ -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})" \ No newline at end of file diff --git a/BrychkinKA/src/strategy/astar_strategy.py b/BrychkinKA/src/strategy/astar_strategy.py new file mode 100644 index 0000000..bdc1bc2 --- /dev/null +++ b/BrychkinKA/src/strategy/astar_strategy.py @@ -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)) \ No newline at end of file diff --git a/BrychkinKA/src/strategy/bfs_strategy.py b/BrychkinKA/src/strategy/bfs_strategy.py new file mode 100644 index 0000000..650b710 --- /dev/null +++ b/BrychkinKA/src/strategy/bfs_strategy.py @@ -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)) \ No newline at end of file diff --git a/BrychkinKA/src/strategy/dfs_strategy.py b/BrychkinKA/src/strategy/dfs_strategy.py new file mode 100644 index 0000000..cfd7da2 --- /dev/null +++ b/BrychkinKA/src/strategy/dfs_strategy.py @@ -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)) \ No newline at end of file diff --git a/BrychkinKA/src/strategy/path_finding_strategy.py b/BrychkinKA/src/strategy/path_finding_strategy.py new file mode 100644 index 0000000..a38f321 --- /dev/null +++ b/BrychkinKA/src/strategy/path_finding_strategy.py @@ -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 \ No newline at end of file diff --git a/BrychkinKA/src/ui/command.py b/BrychkinKA/src/ui/command.py new file mode 100644 index 0000000..ec15f03 --- /dev/null +++ b/BrychkinKA/src/ui/command.py @@ -0,0 +1,10 @@ +from abc import ABC, abstractmethod + +class Command(ABC): + @abstractmethod + def execute(self): + pass + + @abstractmethod + def undo(self): + pass \ No newline at end of file diff --git a/BrychkinKA/src/ui/console_view.py b/BrychkinKA/src/ui/console_view.py new file mode 100644 index 0000000..f174b34 --- /dev/null +++ b/BrychkinKA/src/ui/console_view.py @@ -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) \ No newline at end of file diff --git a/BrychkinKA/src/ui/move_command.py b/BrychkinKA/src/ui/move_command.py new file mode 100644 index 0000000..1933d47 --- /dev/null +++ b/BrychkinKA/src/ui/move_command.py @@ -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) \ No newline at end of file diff --git a/BrychkinKA/src/ui/observer.py b/BrychkinKA/src/ui/observer.py new file mode 100644 index 0000000..0a3ee88 --- /dev/null +++ b/BrychkinKA/src/ui/observer.py @@ -0,0 +1,6 @@ +from abc import ABC, abstractmethod + +class Observer(ABC): + @abstractmethod + def update(self, event: str): + pass \ No newline at end of file diff --git a/BrychkinKA/src/ui/player.py b/BrychkinKA/src/ui/player.py new file mode 100644 index 0000000..69af18d --- /dev/null +++ b/BrychkinKA/src/ui/player.py @@ -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 \ No newline at end of file