2026-rff_mp/nikolaevda/task2/Zadanie2.py
2026-05-23 19:51:32 +03:00

668 lines
21 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import sys
from collections import deque
import heapq
import time
import os
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 is_passable(self):
return not self.is_wall
def __repr__(self):
return f"Cell({self.x}, {self.y})"
class Maze:
"""лабиринт"""
def __init__(self, width, height):
self.width = width
self.height = height
self.cells = [[Cell(x, y) for x in range(width)] for y in range(height)]
self.start = None
self.exit = None
def get_cell(self, x, y):
if 0 <= x < self.width and 0 <= y < self.height:
return self.cells[y][x]
return None
def get_neighbors(self, cell):
neighbors = []
directions = [(-1, 0), (1, 0), (0, -1), (0, 1)]
for dx, dy in directions:
neighbor = self.get_cell(cell.x + dx, cell.y + dy)
if neighbor and neighbor.is_passable():
neighbors.append(neighbor)
return neighbors
def set_start(self, x, y):
cell = self.get_cell(x, y)
if cell:
cell.is_start = True
self.start = cell
def set_exit(self, x, y):
cell = self.get_cell(x, y)
if cell:
cell.is_exit = True
self.exit = cell
class MazeBuilder:
"""интерфейс строителя лабиринта"""
def buildFromFile(self, filename):
raise NotImplementedError
class TextFileMazeBuilder(MazeBuilder):
"""загрузка лабиринта из текстового файла"""
def buildFromFile(self, filename):
with open(filename, 'r', encoding='utf-8') as f:
lines = [line.rstrip('\n') for line in f.readlines()]
height = len(lines)
width = max(len(line) for line in lines) if height > 0 else 0
for i in range(height):
if len(lines[i]) < width:
lines[i] = lines[i] + ' ' * (width - len(lines[i]))
maze = Maze(width, height)
start_count = 0
exit_count = 0
for y, line in enumerate(lines):
for x, ch in enumerate(line):
if ch == '#':
maze.get_cell(x, y).is_wall = True
elif ch == 'S':
maze.set_start(x, y)
start_count += 1
elif ch == 'E':
maze.set_exit(x, y)
exit_count += 1
else:
maze.get_cell(x, y).is_wall = False
if start_count != 1 or exit_count != 1:
raise ValueError(f"Ошибка: S={start_count}, E={exit_count} (нужно по одному)")
return maze
class SearchStats:
"""статистика поиска"""
def __init__(self, time_ms=0, visited_cells=0, path_length=0):
self.time_ms = time_ms
self.visited_cells = visited_cells
self.path_length = path_length
def __str__(self):
return f"Время: {self.time_ms:.2f} мс, Посещено: {self.visited_cells}, Длина пути: {self.path_length}"
class PathFindingStrategy:
"""интерфейс стратегии поиска пути"""
def findPath(self, maze, start, exit):
raise NotImplementedError
def get_name(self):
raise NotImplementedError
class BFSStrategy(PathFindingStrategy):
"""BFS - гарантирует кратчайший путь"""
def get_name(self):
return "BFS (Поиск в ширину)"
def findPath(self, maze, start, exit):
from collections import deque
if not start or not exit:
return [], 0
queue = deque([(start, [start])])
visited = {start}
while queue:
current, path = queue.popleft()
if current == exit:
return path, len(visited)
for neighbor in maze.get_neighbors(current):
if neighbor not in visited:
visited.add(neighbor)
queue.append((neighbor, path + [neighbor]))
return [], len(visited)
class DFSStrategy(PathFindingStrategy):
"""DFS - быстрый, но не обязательно кратчайший"""
def get_name(self):
return "DFS (Поиск в глубину)"
def findPath(self, maze, start, exit):
if not start or not exit:
return [], 0
stack = [(start, [start])]
visited = {start}
while stack:
current, path = stack.pop()
if current == exit:
return path, len(visited)
for neighbor in maze.get_neighbors(current):
if neighbor not in visited:
visited.add(neighbor)
stack.append((neighbor, path + [neighbor]))
return [], len(visited)
class AStarStrategy(PathFindingStrategy):
"""алгоритм A Star - оптимальный и быстрый с эвристикой"""
def get_name(self):
return "A Star"
def _heuristic(self, a, b):
return abs(a.x - b.x) + abs(a.y - b.y)
def findPath(self, maze, start, exit):
if not start or not exit:
return [], 0
import heapq
heap = []
counter = 0
start_f = self._heuristic(start, exit)
heapq.heappush(heap, (start_f, counter, start))
came_from = {}
g_score = {start: 0}
f_score = {start: start_f}
visited = set()
visited.add(start)
while heap:
current_f, _, current = heapq.heappop(heap)
if current == exit:
path = []
while current in came_from:
path.append(current)
current = came_from[current]
path.append(start)
path.reverse()
return path, len(visited)
if current_f > f_score.get(current, float('inf')):
continue
for neighbor in maze.get_neighbors(current):
tentative_g = g_score[current] + 1
if tentative_g < g_score.get(neighbor, float('inf')):
came_from[neighbor] = current
g_score[neighbor] = tentative_g
new_f = tentative_g + self._heuristic(neighbor, exit)
f_score[neighbor] = new_f
counter += 1
heapq.heappush(heap, (new_f, counter, neighbor))
visited.add(neighbor)
return [], len(visited)
class DijkstraStrategy(PathFindingStrategy):
"""алгоритм Дейкстры"""
def get_name(self):
return "Дейкстра (Dijkstra)"
def findPath(self, maze, start, exit):
if not start or not exit:
return [], 0
import heapq
heap = []
counter = 0
heapq.heappush(heap, (0, counter, start))
distances = {start: 0}
came_from = {}
visited = set()
visited.add(start)
while heap:
current_dist, _, current = heapq.heappop(heap)
if current == exit:
path = []
while current in came_from:
path.append(current)
current = came_from[current]
path.append(start)
path.reverse()
return path, len(visited)
if current_dist > distances.get(current, float('inf')):
continue
for neighbor in maze.get_neighbors(current):
new_dist = current_dist + 1
if new_dist < distances.get(neighbor, float('inf')):
distances[neighbor] = new_dist
came_from[neighbor] = current
counter += 1
heapq.heappush(heap, (new_dist, counter, neighbor))
visited.add(neighbor)
return [], len(visited)
class MazeSolver:
"""решатель лабиринта - оркестратор, использующий стратегию"""
def __init__(self, maze):
self.maze = maze
self._strategy = None
def setStrategy(self, strategy):
"""динамическая смена стратегии поиска"""
self._strategy = strategy
print(f" Стратегия изменена на: {strategy.get_name()}")
def solve(self):
"""
решение лабиринта с использованием текущей стратегии.
возвращает SearchStats (время, посещённые клетки, длина пути)
"""
if self._strategy is None:
raise ValueError("Стратегия не установлена. Используйте setStrategy()")
start_time = time.perf_counter()
path, visited = self._strategy.findPath(self.maze, self.maze.start, self.maze.exit)
end_time = time.perf_counter()
time_ms = (end_time - start_time) * 1000
stats = SearchStats(
time_ms=time_ms,
visited_cells=visited,
path_length=len(path) if path else 0
)
return path, stats
# ==================== ЭТАП 5: НАБЛЮДАТЕЛЬ (OBSERVER) ====================
class Observer:
"""Интерфейс наблюдателя"""
def update(self, event, data):
raise NotImplementedError
class ConsoleDisplay(Observer):
"""Консольная визуализация - наблюдатель"""
def __init__(self):
self._last_path = None
self._last_maze = None
def update(self, event, data):
if event == "maze_loaded":
self._draw_maze(data)
elif event == "path_found":
self._last_path = data
self._show_path(data)
elif event == "player_moved":
self._draw_maze_with_player(data)
def _draw_maze(self, maze):
"""Отрисовка лабиринта"""
os.system('cls' if os.name == 'nt' else 'clear')
print("=" * (maze.width * 2 + 4))
print("ЛАБИРИНТ")
print("=" * (maze.width * 2 + 4))
for y in range(maze.height):
line = ""
for x in range(maze.width):
cell = maze.get_cell(x, y)
if cell == maze.start:
line += "S "
elif cell == maze.exit:
line += "E "
elif cell.is_wall:
line += "# "
else:
line += ". "
print(line)
print("=" * (maze.width * 2 + 4))
print("S - старт E - выход # - стена . - проход")
def _draw_maze_with_player(self, game_state):
"""Отрисовка лабиринта с игроком"""
maze = game_state['maze']
player = game_state['player']
os.system('cls' if os.name == 'nt' else 'clear')
print("=" * (maze.width * 2 + 4))
print("ЛАБИРИНТ (P - игрок)")
print("=" * (maze.width * 2 + 4))
for y in range(maze.height):
line = ""
for x in range(maze.width):
cell = maze.get_cell(x, y)
if player and cell == player.get_position():
line += "P "
elif cell == maze.start:
line += "S "
elif cell == maze.exit:
line += "E "
elif cell.is_wall:
line += "# "
else:
line += ". "
print(line)
print("=" * (maze.width * 2 + 4))
if player:
pos = player.get_position()
print(f"Игрок: ({pos.x}, {pos.y})")
print("S - старт E - выход # - стена . - проход P - игрок")
def _show_path(self, path):
"""Показ информации о найденном пути"""
if not path:
print("\n Путь не найден!")
return
print(f"\n Путь найден! Длина: {len(path)} клеток")
# ==================== ЭТАП 5: КОМАНДА (COMMAND) ====================
class Command:
"""Интерфейс команды"""
def execute(self):
raise NotImplementedError
def undo(self):
raise NotImplementedError
class MoveCommand(Command):
"""Команда перемещения игрока"""
def __init__(self, player, direction, maze):
self._player = player
self._dx, self._dy = direction
self._maze = maze
self._executed = False
self._prev_position = None
def execute(self):
"""Выполнение перемещения"""
if self._executed:
return False
pos = self._player.get_position()
new_x = pos.x + self._dx
new_y = pos.y + self._dy
target = self._maze.get_cell(new_x, new_y)
if target and target.is_passable():
self._prev_position = pos
self._player.set_position(target)
self._executed = True
return True
return False
def undo(self):
"""Отмена перемещения"""
if not self._executed or self._prev_position is None:
return False
self._player.set_position(self._prev_position)
self._executed = False
return True
def get_name(self):
dir_names = {(-1, 0): "ВЛЕВО", (1, 0): "ВПРАВО", (0, -1): "ВВЕРХ", (0, 1): "ВНИЗ"}
return f"Перемещение {dir_names.get((self._dx, self._dy), 'НЕИЗВЕСТНО')}"
class CommandInvoker:
"""Инвокер команд (история для undo/redo)"""
def __init__(self):
self._history = []
self._redo_stack = []
def execute(self, command):
"""Выполнение команды с сохранением в истории"""
if command.execute():
self._history.append(command)
self._redo_stack.clear()
return True
return False
def undo(self):
"""Отмена последней команды"""
if not self._history:
return False
command = self._history.pop()
if command.undo():
self._redo_stack.append(command)
return True
return False
def redo(self):
"""Повтор отменённой команды"""
if not self._redo_stack:
return False
command = self._redo_stack.pop()
if command.execute():
self._history.append(command)
return True
return False
def get_history_size(self):
return len(self._history)
# ==================== ЭТАП 5: ИГРОК ====================
class Player:
"""Игрок, перемещающийся по лабиринту"""
def __init__(self, start_cell):
self._position = start_cell
self._start = start_cell
def get_position(self):
return self._position
def set_position(self, cell):
self._position = cell
def reset(self):
self._position = self._start
def is_at_exit(self, maze):
return self._position == maze.exit
def get_steps_count(self, invoker):
return invoker.get_history_size()
class GameController:
"""контроллер, объединяющий все компоненты"""
def __init__(self, maze):
self.maze = maze
self.player = Player(maze.start)
self.solver = MazeSolver(maze)
self.invoker = CommandInvoker()
self.view = ConsoleDisplay()
def run(self):
"""запуск интерактивного режима"""
self.view.update("maze_loaded", self.maze)
print("УПРАВЛЕНИЕ:")
print(" H/J/K/L или ←/↓/↑/→ - движение")
print(" U - отменить ход")
print(" R - повторить ход")
print(" B - BFS поиск пути")
print(" D - DFS поиск пути")
print(" A - A* поиск пути")
print(" P - показать путь")
print(" Q - выход")
path = None
last_strategy_name = ""
while True:
cmd = input("\nКоманда > ").lower()
if cmd == 'q':
print("До свидания!")
break
elif cmd in ['h', 'j', 'k', 'l']:
dir_map = {'h': (-1, 0), 'l': (1, 0), 'k': (0, -1), 'j': (0, 1)}
command = MoveCommand(self.player, dir_map[cmd], self.maze)
if self.invoker.execute(command):
self.view.update("player_moved", {
'maze': self.maze,
'player': self.player
})
if self.player.is_at_exit(self.maze):
print(f"\n *** ПОБЕДА! ВЫХОД ДОСТИГНУТ за {self.player.get_steps_count(self.invoker)} шагов! ***")
break
else:
print(" Стена! Нельзя пройти.")
elif cmd == 'u':
if self.invoker.undo():
self.view.update("player_moved", {
'maze': self.maze,
'player': self.player
})
print(" Отменено")
else:
print(" Нечего отменять")
elif cmd == 'r':
if self.invoker.redo():
self.view.update("player_moved", {
'maze': self.maze,
'player': self.player
})
print(" Повторено")
else:
print(" Нечего повторять")
elif cmd == 'b':
self.solver.setStrategy(BFSStrategy())
start_time = time.perf_counter()
path, stats = self.solver.solve()
self.view.update("path_found", path)
print(f" BFS: {stats}")
last_strategy_name = "BFS"
elif cmd == 'd':
self.solver.setStrategy(DFSStrategy())
path, stats = self.solver.solve()
self.view.update("path_found", path)
print(f" DFS: {stats}")
last_strategy_name = "DFS"
elif cmd == 'a':
self.solver.setStrategy(AStarStrategy())
path, stats = self.solver.solve()
self.view.update("path_found", path)
print(f" A*: {stats}")
last_strategy_name = "A*"
elif cmd == 'p':
if path:
self._show_path_on_maze(path)
else:
print(" Сначала найдите путь (B, D или A)")
else:
print(" Неизвестная команда")
def _show_path_on_maze(self, path):
"""показать путь на лабиринте"""
os.system('cls' if os.name == 'nt' else 'clear')
print("=" * (self.maze.width * 2 + 4))
print("ЛАБИРИНТ С ПУТЁМ (* - путь)")
print("=" * (self.maze.width * 2 + 4))
path_set = set(path)
for y in range(self.maze.height):
line = ""
for x in range(self.maze.width):
cell = self.maze.get_cell(x, y)
if cell == self.player.get_position():
line += "P "
elif cell == self.maze.start:
line += "S "
elif cell == self.maze.exit:
line += "E "
elif cell in path_set and cell.is_passable():
line += "* "
elif cell.is_wall:
line += "# "
else:
line += ". "
print(line)
print("=" * (self.maze.width * 2 + 4))
print("S - старт E - выход # - стена . - проход * - путь P - игрок")
input("\nНажмите Enter для продолжения...")
self.view.update("player_moved", {
'maze': self.maze,
'player': self.player
})