[5] пара лабиринтов для тестов

This commit is contained in:
mddcorporation 2026-05-07 15:54:10 +03:00
parent 0895815b29
commit 31466e3743
3 changed files with 42 additions and 74 deletions

View File

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

View File

@ -51,23 +51,18 @@ class Maze:
class MazeBuilder(abc.ABC): class MazeBuilder(abc.ABC):
"""Абстрактный строитель лабиринта."""
@abc.abstractmethod @abc.abstractmethod
def build_from_file(self, filename: str) -> Maze: def build_from_file(self, filename: str) -> Maze:
"""Построить лабиринт из файла."""
pass pass
class TextFileMazeBuilder(MazeBuilder): class TextFileMazeBuilder(MazeBuilder):
"""Строитель лабиринта из текстового файла.
# - стена, пробел - проход, S - старт, E - выход."""
def build_from_file(self, filename: str) -> Maze: def build_from_file(self, filename: str) -> Maze:
lines = [] lines = []
with open(filename, 'r', encoding='utf-8') as f: with open(filename, 'r', encoding='utf-8') as f:
for line in f: for line in f:
line = line.rstrip('\n') line = line.rstrip('\n')
if line: #игнорируем пустые строки if line: #игнорируем пустые строки
lines.append(line) lines.append(line)
if not lines: if not lines:
@ -77,7 +72,7 @@ class TextFileMazeBuilder(MazeBuilder):
width = max(len(line) for line in lines) width = max(len(line) for line in lines)
maze = Maze(width, height) maze = Maze(width, height)
# Инициализируем сетку пустыми клетками (по умолчанию стенами) #инициализируем сетку пустыми клетками,по умолчанию стенами
maze.grid = [[Cell(x, y, is_wall=True) for x in range(width)] for y in range(height)] maze.grid = [[Cell(x, y, is_wall=True) for x in range(width)] for y in range(height)]
start_cell = None start_cell = None
@ -88,11 +83,9 @@ class TextFileMazeBuilder(MazeBuilder):
if x >= width: if x >= width:
continue continue
if ch == '#': if ch == '#':
# стена (уже создана по умолчанию)
continue continue
elif ch == ' ': elif ch == ' ':
# проход cell = Cell(x, y, is_wall=False)
cell = Cell(x, y, is_wall=False)
elif ch == 'S': elif ch == 'S':
cell = Cell(x, y, is_wall=False, is_start=True) cell = Cell(x, y, is_wall=False, is_start=True)
start_cell = cell start_cell = cell
@ -100,49 +93,38 @@ class TextFileMazeBuilder(MazeBuilder):
cell = Cell(x, y, is_wall=False, is_exit=True) cell = Cell(x, y, is_wall=False, is_exit=True)
exit_cell = cell exit_cell = cell
else: else:
# любой другой символ считаем проходом #любой другой символ считаем проходом
cell = Cell(x, y, is_wall=False) cell = Cell(x, y, is_wall=False)
maze.set_cell(x, y, cell) maze.set_cell(x, y, cell)
# Проверяем наличие старта и выхода
if start_cell is None: if start_cell is None:
raise ValueError("В лабиринте отсутствует стартовая клетка (S)") raise ValueError("отсутствует стартовая клетка (S)") #invalid check
if exit_cell is None: if exit_cell is None:
raise ValueError("В лабиринте отсутствует выход (E)") raise ValueError("отсутствует выход (E)")
maze.start_cell = start_cell maze.start_cell = start_cell
maze.exit_cell = exit_cell maze.exit_cell = exit_cell
return maze return maze
# ============================== Паттерн Strategy ==============================
class PathFindingStrategy(abc.ABC): class PathFindingStrategy(abc.ABC):
"""Интерфейс стратегии поиска пути."""
@abc.abstractmethod @abc.abstractmethod
def find_path(self, maze: Maze, start: Cell, exit_: Cell) -> Tuple[List[Cell], int]: def find_path(self, maze: Maze, start: Cell, exit_: Cell) -> Tuple[List[Cell], int]:
"""
Возвращает (путь в виде списка клеток от start до exit_, количество посещённых клеток).
Если пути нет, возвращает ([], visited_count).
"""
pass pass
#дальше скорее математика, методы вроде ещё в том семаке разбирали
class BFSStrategy(PathFindingStrategy): class BFSStrategy(PathFindingStrategy):
"""Поиск в ширину — гарантирует кратчайший путь."""
def find_path(self, maze: Maze, start: Cell, exit_: Cell) -> Tuple[List[Cell], int]: def find_path(self, maze: Maze, start: Cell, exit_: Cell) -> Tuple[List[Cell], int]:
if start is exit_: if start is exit_:
return [start], 1 return [start], 1
queue = deque([start]) queue = deque([start]) #используйте deque 👍
visited: Set[Cell] = {start} visited: Set[Cell] = {start}
parent: Dict[Cell, Optional[Cell]] = {start: None} parent: Dict[Cell, Optional[Cell]] = {start: None}
while queue: while queue:
current = queue.popleft() current = queue.popleft()
if current is exit_: if current is exit_:
# восстановление пути #восстановление пути
path = [] path = []
cur = current cur = current
while cur is not None: while cur is not None:
@ -161,8 +143,6 @@ class BFSStrategy(PathFindingStrategy):
class DFSStrategy(PathFindingStrategy): class DFSStrategy(PathFindingStrategy):
"""Поиск в глубину — не гарантирует кратчайший путь, но быстр."""
def find_path(self, maze: Maze, start: Cell, exit_: Cell) -> Tuple[List[Cell], int]: def find_path(self, maze: Maze, start: Cell, exit_: Cell) -> Tuple[List[Cell], int]:
if start is exit_: if start is exit_:
return [start], 1 return [start], 1
@ -192,18 +172,13 @@ class DFSStrategy(PathFindingStrategy):
class AStarStrategy(PathFindingStrategy): class AStarStrategy(PathFindingStrategy):
"""Алгоритм A* с манхэттенской эвристикой."""
@staticmethod @staticmethod
def _heuristic(cell: Cell, target: Cell) -> int: def _heuristic(cell: Cell, target: Cell) -> int:
"""Манхэттенское расстояние.""" return abs(cell.x - target.x) + abs(cell.y - target.y) #самая простая эвристика
return abs(cell.x - target.x) + abs(cell.y - target.y)
def find_path(self, maze: Maze, start: Cell, exit_: Cell) -> Tuple[List[Cell], int]: def find_path(self, maze: Maze, start: Cell, exit_: Cell) -> Tuple[List[Cell], int]:
if start is exit_: if start is exit_:
return [start], 1 return [start], 1
# Приоритетная очередь: (f, counter, cell)
counter = 0 counter = 0
open_set = [(0, counter, start)] open_set = [(0, counter, start)]
g_score: Dict[Cell, int] = {start: 0} g_score: Dict[Cell, int] = {start: 0}
@ -217,7 +192,7 @@ class AStarStrategy(PathFindingStrategy):
if current in closed_set: if current in closed_set:
continue continue
closed_set.add(current) closed_set.add(current)
visited_count = len(closed_set) # количество обработанных клеток visited_count = len(closed_set)
if current is exit_: if current is exit_:
path = [] path = []
@ -242,30 +217,20 @@ class AStarStrategy(PathFindingStrategy):
return [], visited_count return [], visited_count
# ============================== Статистика ==============================
@dataclass @dataclass
class SearchStats: class SearchStats:
"""Статистика поиска.""" time_ms: float #время выполнения в мс
time_ms: float # время выполнения в миллисекундах visited_cells: int #количество посещённых клеток
visited_cells: int # количество посещённых клеток path_length: int #длина найденного пути (0 если пути нет)
path_length: int # длина найденного пути (0 если пути нет) path_found: bool #найден ли путь
path_found: bool # найден ли путь
class Observer(abc.ABC): #я забыл что я там писать хотел после наблюдателя удачи мне завтра разобрать
# ============================== Паттерн Observer ============================== @abc.abstractmethod #а я (гугл + хабр) разобрал снова балбесина!!!
class Observer(abc.ABC):
"""Интерфейс наблюдателя."""
@abc.abstractmethod
def update(self, event_type: str, data: Any = None) -> None: def update(self, event_type: str, data: Any = None) -> None:
"""Получить уведомление от субъекта."""
pass pass
class Subject: class Subject:
"""Субъект, за которым наблюдают."""
def __init__(self): def __init__(self):
self._observers: List[Observer] = [] self._observers: List[Observer] = []
@ -281,25 +246,18 @@ class Subject:
for observer in self._observers: for observer in self._observers:
observer.update(event_type, data) observer.update(event_type, data)
# ============================== MazeSolver (оркестратор) ==============================
class MazeSolver(Subject): class MazeSolver(Subject):
"""Решатель лабиринта, использующий стратегию поиска."""
def __init__(self, maze: Maze, strategy: Optional[PathFindingStrategy] = None): def __init__(self, maze: Maze, strategy: Optional[PathFindingStrategy] = None):
super().__init__() super().__init__()
self.maze = maze self.maze = maze
self._strategy = strategy self._strategy = strategy
def set_strategy(self, strategy: PathFindingStrategy) -> None: def set_strategy(self, strategy: PathFindingStrategy) -> None: #смена алгоритма
"""Сменить алгоритм поиска."""
self._strategy = strategy self._strategy = strategy
def solve(self) -> Optional[SearchStats]: def solve(self) -> Optional[SearchStats]:
"""Выполнить поиск пути с текущей стратегией.
Возвращает статистику или None, если стратегия не установлена."""
if self._strategy is None: if self._strategy is None:
print("Стратегия не установлена.") print("где стратегия?")
return None return None
self.notify("solving_start", {"strategy": self._strategy.__class__.__name__}) self.notify("solving_start", {"strategy": self._strategy.__class__.__name__})
@ -325,8 +283,7 @@ class MazeSolver(Subject):
self.notify("solving_end", {"stats": stats}) self.notify("solving_end", {"stats": stats})
return stats return stats
class ConsoleView(Observer): class ConsoleView(Observer): #красиво в консоль выводит лабиринт
"""Отображает лабиринт и найденный путь в консоли."""
def __init__(self, maze: Maze): def __init__(self, maze: Maze):
self.maze = maze self.maze = maze
self.last_path: List[Cell] = [] self.last_path: List[Cell] = []
@ -339,11 +296,9 @@ class ConsoleView(Observer):
self.last_path = [] self.last_path = []
self.render(no_path=True) self.render(no_path=True)
elif event_type == "solving_start": elif event_type == "solving_start":
print(f"\n== Поиск пути (алгоритм: {data['strategy']}) ==\n") print(f"\nпоиск пути (алгоритм: {data['strategy']})\n")
def render(self, no_path: bool = False) -> None: def render(self, no_path: bool = False) -> None:
"""Отрисовать лабиринт с текущим найденным путём."""
# Создаём множество клеток пути для быстрой проверки
path_set = set(self.last_path) if self.last_path else set() path_set = set(self.last_path) if self.last_path else set()
for y in range(self.maze.height): for y in range(self.maze.height):
@ -366,11 +321,11 @@ class ConsoleView(Observer):
print(''.join(row)) print(''.join(row))
if no_path: if no_path:
print("\nПуть не найден!") print("\nнет пути (no way)")
elif self.last_path: elif self.last_path:
print(f"\nНайден путь длиной {len(self.last_path)} клеток.") print(f"\nнайден путь длиной {len(self.last_path)} клеток")
else: else:
print("\nОжидание решения...") print("\nожидание решения")
def main(): def main():
import sys import sys
@ -381,20 +336,18 @@ def main():
filename = sys.argv[1] filename = sys.argv[1]
# Строим лабиринт из файла (Builder) #строится лабиринт из файла
builder = TextFileMazeBuilder() builder = TextFileMazeBuilder()
try: try:
maze = builder.build_from_file(filename) maze = builder.build_from_file(filename)
except Exception as e: except Exception as e:
print(f"Ошибка загрузки лабиринта: {e}") print(f"ошибка загрузки лабиринта: {e}")
return return
# Создаём решатель и прикрепляем наблюдателя #наблюдатель и решатель прикрепляются
solver = MazeSolver(maze) solver = MazeSolver(maze)
view = ConsoleView(maze) view = ConsoleView(maze)
solver.attach(view) solver.attach(view)
# Меню выбора стратегии
strategies = { strategies = {
"1": BFSStrategy(), "1": BFSStrategy(),
"2": DFSStrategy(), "2": DFSStrategy(),

View File

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