[0] основной файл, нужно сделать лабиринты + отчёт

This commit is contained in:
mddcorporation 2026-05-07 15:09:26 +03:00
parent 937738c8eb
commit 049944be2a
5 changed files with 471 additions and 0 deletions

3
VaravinVV/.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,3 @@
{
"python-envs.defaultEnvManager": "ms-python.python:system"
}

View File

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

View File

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

View File

@ -0,0 +1,429 @@
import abc
import heapq
import time
from collections import deque
from dataclasses import dataclass
from typing import List, Optional, Dict, Set, Tuple, Any
class Cell:
#тут что такое клетка
def __init__(self, x: int, y: int, is_wall: bool = False,
is_exit: bool = False, is_start: bool = False):
self.x = x
self.y = y
self.is_wall = is_wall
self.is_exit = is_exit
self.is_start = is_start
def is_passable(self) -> bool: #можно ли пройти через клетку
return not self.is_wall
def __repr__(self) -> str:
return f"Cell({self.x},{self.y})"
class Maze:
def __init__(self, width: int, height: int): #что содержит лабиринт, начало конец и тд
self.width = width
self.height = height
self.grid: List[List[Cell]] = []
self.start_cell: Optional[Cell] = None
self.exit_cell: Optional[Cell] = None
def set_cell(self, x: int, y: int, cell: Cell) -> None: #ставим клетку куда надо или не ставим если в границы не попала
if not (0 <= x < self.width and 0 <= y < self.height):
raise IndexError("координаты вне границ лабиринта")
self.grid[y][x] = cell
def get_cell(self, x: int, y: int) -> Optional[Cell]: #тут уже из коррдинат клетку вытаскиваем
if 0 <= x < self.width and 0 <= y < self.height:
return self.grid[y][x]
return None
def get_neighbors(self, cell: Cell) -> List[Cell]: #если соседняя клетка проходима - добавляем
neighbors = []
for dx, dy in [(0, -1), (0, 1), (-1, 0), (1, 0)]:
nx, ny = cell.x + dx, cell.y + dy
neighbor = self.get_cell(nx, ny)
if neighbor and neighbor.is_passable():
neighbors.append(neighbor)
return neighbors
class MazeBuilder(abc.ABC):
"""Абстрактный строитель лабиринта."""
@abc.abstractmethod
def build_from_file(self, filename: str) -> Maze:
"""Построить лабиринт из файла."""
pass
class TextFileMazeBuilder(MazeBuilder):
"""Строитель лабиринта из текстового файла.
# - стена, пробел - проход, S - старт, E - выход."""
def build_from_file(self, filename: str) -> Maze:
lines = []
with open(filename, 'r', encoding='utf-8') as f:
for line in f:
line = line.rstrip('\n')
if line: #игнорируем пустые строки
lines.append(line)
if not lines:
raise ValueError("Файл пуст")
height = len(lines)
width = max(len(line) for line in lines)
maze = Maze(width, height)
# Инициализируем сетку пустыми клетками (по умолчанию стенами)
maze.grid = [[Cell(x, y, is_wall=True) for x in range(width)] for y in range(height)]
start_cell = None
exit_cell = None
for y, line in enumerate(lines):
for x, ch in enumerate(line):
if x >= width:
continue
if ch == '#':
# стена (уже создана по умолчанию)
continue
elif ch == ' ':
# проход
cell = Cell(x, y, is_wall=False)
elif ch == 'S':
cell = Cell(x, y, is_wall=False, is_start=True)
start_cell = cell
elif ch == 'E':
cell = Cell(x, y, is_wall=False, is_exit=True)
exit_cell = cell
else:
# любой другой символ считаем проходом
cell = Cell(x, y, is_wall=False)
maze.set_cell(x, y, cell)
# Проверяем наличие старта и выхода
if start_cell is None:
raise ValueError("В лабиринте отсутствует стартовая клетка (S)")
if exit_cell is None:
raise ValueError("В лабиринте отсутствует выход (E)")
maze.start_cell = start_cell
maze.exit_cell = exit_cell
return maze
# ============================== Паттерн Strategy ==============================
class PathFindingStrategy(abc.ABC):
"""Интерфейс стратегии поиска пути."""
@abc.abstractmethod
def find_path(self, maze: Maze, start: Cell, exit_: Cell) -> Tuple[List[Cell], int]:
"""
Возвращает (путь в виде списка клеток от start до exit_, количество посещённых клеток).
Если пути нет, возвращает ([], visited_count).
"""
pass
class BFSStrategy(PathFindingStrategy):
"""Поиск в ширину — гарантирует кратчайший путь."""
def find_path(self, maze: Maze, start: Cell, exit_: Cell) -> Tuple[List[Cell], int]:
if start is exit_:
return [start], 1
queue = deque([start])
visited: Set[Cell] = {start}
parent: Dict[Cell, Optional[Cell]] = {start: None}
while queue:
current = queue.popleft()
if current is exit_:
# восстановление пути
path = []
cur = current
while cur is not None:
path.append(cur)
cur = parent[cur]
path.reverse()
return path, len(visited)
for neighbor in maze.get_neighbors(current):
if neighbor not in visited:
visited.add(neighbor)
parent[neighbor] = current
queue.append(neighbor)
return [], len(visited)
class DFSStrategy(PathFindingStrategy):
"""Поиск в глубину — не гарантирует кратчайший путь, но быстр."""
def find_path(self, maze: Maze, start: Cell, exit_: Cell) -> Tuple[List[Cell], int]:
if start is exit_:
return [start], 1
stack = [start]
visited: Set[Cell] = {start}
parent: Dict[Cell, Optional[Cell]] = {start: None}
while stack:
current = stack.pop()
if current is exit_:
path = []
cur = current
while cur is not None:
path.append(cur)
cur = parent[cur]
path.reverse()
return path, len(visited)
for neighbor in maze.get_neighbors(current):
if neighbor not in visited:
visited.add(neighbor)
parent[neighbor] = current
stack.append(neighbor)
return [], len(visited)
class AStarStrategy(PathFindingStrategy):
"""Алгоритм A* с манхэттенской эвристикой."""
@staticmethod
def _heuristic(cell: Cell, target: Cell) -> int:
"""Манхэттенское расстояние."""
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]:
if start is exit_:
return [start], 1
# Приоритетная очередь: (f, counter, cell)
counter = 0
open_set = [(0, counter, start)]
g_score: Dict[Cell, int] = {start: 0}
f_score: Dict[Cell, int] = {start: self._heuristic(start, exit_)}
parent: Dict[Cell, Optional[Cell]] = {start: None}
closed_set: Set[Cell] = set()
visited_count = 0
while open_set:
_, _, current = heapq.heappop(open_set)
if current in closed_set:
continue
closed_set.add(current)
visited_count = len(closed_set) # количество обработанных клеток
if current is exit_:
path = []
cur = current
while cur is not None:
path.append(cur)
cur = parent[cur]
path.reverse()
return path, visited_count
for neighbor in maze.get_neighbors(current):
if neighbor in closed_set:
continue
tentative_g = g_score[current] + 1
if neighbor not in g_score or tentative_g < g_score[neighbor]:
parent[neighbor] = current
g_score[neighbor] = tentative_g
f = tentative_g + self._heuristic(neighbor, exit_)
f_score[neighbor] = f
counter += 1
heapq.heappush(open_set, (f, counter, neighbor))
return [], visited_count
# ============================== Статистика ==============================
@dataclass
class SearchStats:
"""Статистика поиска."""
time_ms: float # время выполнения в миллисекундах
visited_cells: int # количество посещённых клеток
path_length: int # длина найденного пути (0 если пути нет)
path_found: bool # найден ли путь
# ============================== Паттерн Observer ==============================
class Observer(abc.ABC):
"""Интерфейс наблюдателя."""
@abc.abstractmethod
def update(self, event_type: str, data: Any = None) -> None:
"""Получить уведомление от субъекта."""
pass
class Subject:
"""Субъект, за которым наблюдают."""
def __init__(self):
self._observers: List[Observer] = []
def attach(self, observer: Observer) -> None:
if observer not in self._observers:
self._observers.append(observer)
def detach(self, observer: Observer) -> None:
if observer in self._observers:
self._observers.remove(observer)
def notify(self, event_type: str, data: Any = None) -> None:
for observer in self._observers:
observer.update(event_type, data)
# ============================== MazeSolver (оркестратор) ==============================
class MazeSolver(Subject):
"""Решатель лабиринта, использующий стратегию поиска."""
def __init__(self, maze: Maze, strategy: Optional[PathFindingStrategy] = None):
super().__init__()
self.maze = maze
self._strategy = strategy
def set_strategy(self, strategy: PathFindingStrategy) -> None:
"""Сменить алгоритм поиска."""
self._strategy = strategy
def solve(self) -> Optional[SearchStats]:
"""Выполнить поиск пути с текущей стратегией.
Возвращает статистику или None, если стратегия не установлена."""
if self._strategy is None:
print("Стратегия не установлена.")
return None
self.notify("solving_start", {"strategy": self._strategy.__class__.__name__})
start_time = time.perf_counter()
path, visited = self._strategy.find_path(self.maze, self.maze.start_cell, self.maze.exit_cell)
end_time = time.perf_counter()
time_ms = (end_time - start_time) * 1000.0
path_found = len(path) > 0
stats = SearchStats(
time_ms=time_ms,
visited_cells=visited,
path_length=len(path) if path_found else 0,
path_found=path_found
)
if path_found:
self.notify("path_found", {"path": path, "stats": stats})
else:
self.notify("no_path", {"stats": stats})
self.notify("solving_end", {"stats": stats})
return stats
class ConsoleView(Observer):
"""Отображает лабиринт и найденный путь в консоли."""
def __init__(self, maze: Maze):
self.maze = maze
self.last_path: List[Cell] = []
def update(self, event_type: str, data: Any = None) -> None:
if event_type == "path_found":
self.last_path = data["path"]
self.render()
elif event_type == "no_path":
self.last_path = []
self.render(no_path=True)
elif event_type == "solving_start":
print(f"\n== Поиск пути (алгоритм: {data['strategy']}) ==\n")
def render(self, no_path: bool = False) -> None:
"""Отрисовать лабиринт с текущим найденным путём."""
# Создаём множество клеток пути для быстрой проверки
path_set = set(self.last_path) if self.last_path else set()
for y in range(self.maze.height):
row = []
for x in range(self.maze.width):
cell = self.maze.get_cell(x, y)
if cell is None:
row.append('?')
continue
if cell is self.maze.start_cell:
row.append('S')
elif cell is self.maze.exit_cell:
row.append('E')
elif cell in path_set:
row.append('*')
elif cell.is_wall:
row.append('#')
else:
row.append(' ')
print(''.join(row))
if no_path:
print("\nПуть не найден!")
elif self.last_path:
print(f"\nНайден путь длиной {len(self.last_path)} клеток.")
else:
print("\nОжидание решения...")
def main():
import sys
if len(sys.argv) < 2:
print("для запуска: python main.py <имя_лабиринта>.txt")
return
filename = sys.argv[1]
# Строим лабиринт из файла (Builder)
builder = TextFileMazeBuilder()
try:
maze = builder.build_from_file(filename)
except Exception as e:
print(f"Ошибка загрузки лабиринта: {e}")
return
# Создаём решатель и прикрепляем наблюдателя
solver = MazeSolver(maze)
view = ConsoleView(maze)
solver.attach(view)
# Меню выбора стратегии
strategies = {
"1": BFSStrategy(),
"2": DFSStrategy(),
"3": AStarStrategy()
}
print("\nвыберите алгоритм поиска:")
print("1. BFS")
print("2. DFS")
print("3. A*")
choice = input("введите (1/2/3): ").strip()
strategy = strategies.get(choice)
if not strategy:
print("неверный выбор, по умолчанию используется BFS.")
strategy = BFSStrategy()
solver.set_strategy(strategy)
stats = solver.solve()
if stats:
print("\nстатистика по поиску пути в данном лабиринте")
print(f"выбранный алгоритм: {strategy.__class__.__name__}")
print(f"время выполнения: {stats.time_ms:.3f} мс")
print(f"посещено клеток: {stats.visited_cells}")
print(f"путь найден?: {'да' if stats.path_found else 'нет'}")
if stats.path_found:
print(f"длина пути: {stats.path_length}")
if __name__ == "__main__":
main()

View File

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