[5] пара лабиринтов для тестов
This commit is contained in:
parent
0895815b29
commit
31466e3743
10
VaravinVV/docs/data/task2/easy.txt
Normal file
10
VaravinVV/docs/data/task2/easy.txt
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
##########
|
||||||
|
#S ### #
|
||||||
|
## ## # #
|
||||||
|
# ## # #
|
||||||
|
# #### # #
|
||||||
|
# # # #
|
||||||
|
### #
|
||||||
|
### ### ##
|
||||||
|
# ### E#
|
||||||
|
##########
|
||||||
|
|
@ -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,10 +83,8 @@ 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)
|
||||||
|
|
@ -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(),
|
||||||
|
|
|
||||||
5
VaravinVV/docs/data/task2/noexit.txt
Normal file
5
VaravinVV/docs/data/task2/noexit.txt
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
#####
|
||||||
|
#S###
|
||||||
|
#####
|
||||||
|
###E#
|
||||||
|
#####
|
||||||
Loading…
Reference in New Issue
Block a user