[2] Add MazeSolver orchestrator and interactive console game

This commit is contained in:
SavelevMI 2026-05-21 14:10:50 +00:00
parent 6005f2f8b2
commit 86b738f6a4

View File

@ -0,0 +1,269 @@
# Основной модуль: оркестратор MazeSolver, Observer для визуализации,
# Player, Command для пошагового управления, интерактивная игра
import sys
import time
import os
from maze_core import TextFileMazeBuilder, Maze
from pathfinding import BFSStrategy, DFSStrategy, AStarStrategy
class SearchStats:
def __init__(self, time_ms, visited_cells, path_length):
self.time_ms = time_ms
self.visited_cells = visited_cells
self.path_length = path_length
class Observer:
def update(self, event_type, data):
raise NotImplementedError
class ConsoleView(Observer):
def __init__(self, player=None):
self._last_path = None
self._player = player
def update(self, event_type, data):
if event_type == "maze_loaded":
self.render_maze(data)
elif event_type == "path_found":
self._last_path = data
self.render_path(data)
elif event_type == "player_moved":
self.render_maze_with_player(data)
def render_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):
print(" ", end='')
for x in range(maze.width):
cell = maze.get_cell(x, y)
if cell == maze.start:
print('S', end=' ')
elif cell == maze.exit:
print('E', end=' ')
elif cell.is_wall:
print('#', end=' ')
else:
print('.', end=' ')
print()
print("=" * (maze.width * 2 + 4))
print(" S - вход E - выход # - стена . - проход")
def render_maze_with_player(self, maze):
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):
print(" ", end='')
for x in range(maze.width):
cell = maze.get_cell(x, y)
if self._player and cell == self._player.current:
print('P', end=' ')
elif cell == maze.start:
print('S', end=' ')
elif cell == maze.exit:
print('E', end=' ')
elif cell.is_wall:
print('#', end=' ')
else:
print('.', end=' ')
print()
print("=" * (maze.width * 2 + 4))
print(f" Позиция игрока: ({self._player.current.x}, {self._player.current.y})")
print(" S - вход E - выход # - стена . - проход P - игрок")
def render_path(self, path):
if not path:
print("\n Путь не найден!")
return
print(f"\n Путь найден! Длина: {len(path)}")
class Player:
def __init__(self, start_cell, maze):
self._current = start_cell
self._previous = None
self._maze = maze
@property
def current(self):
return self._current
def move_to(self, cell):
if cell and cell.is_passable():
self._previous = self._current
self._current = cell
return True
return False
def undo_move(self):
if self._previous:
self._current, self._previous = self._previous, None
return True
return False
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._direction = direction
self._maze = maze
self._executed = False
def execute(self):
dx, dy = self._direction
new_x = self._player.current.x + dx
new_y = self._player.current.y + dy
target_cell = self._maze.get_cell(new_x, new_y)
if target_cell and target_cell.is_passable():
self._player.move_to(target_cell)
self._executed = True
return True
return False
def undo(self):
if self._executed:
self._player.undo_move()
self._executed = False
return True
return False
class MazeSolver:
def __init__(self, maze):
self._maze = maze
self._strategy = None
self._observers = []
def attach(self, observer):
self._observers.append(observer)
def notify(self, event_type, data):
for observer in self._observers:
observer.update(event_type, data)
def set_strategy(self, strategy):
self._strategy = strategy
def solve(self):
if self._strategy is None:
return None
start_time = time.perf_counter()
path = self._strategy.find_path(self._maze, self._maze.start, self._maze.exit)
end_time = time.perf_counter()
time_ms = (end_time - start_time) * 1000
self.notify("path_found", path)
return SearchStats(time_ms, self._strategy.get_visited_count(), len(path))
def main():
builder = TextFileMazeBuilder()
# Загрузка лабиринта из файла
if len(sys.argv) > 1:
maze = builder.build_from_file(sys.argv[1])
else:
maze = builder.build_from_file("maze1.txt")
# Создание игрока и визуализации
player = Player(maze.start, maze)
view = ConsoleView(player)
view.render_maze(maze)
# Создание решателя
solver = MazeSolver(maze)
solver.attach(view)
print("\n УПРАВЛЕНИЕ:")
print(" H (влево) J (вниз) K (вверх) L (вправо)")
print(" U - отменить ход Q - выход")
print("\n АВТО-ПОИСК:")
print(" B - BFS (поиск в ширину)")
print(" D - DFS (поиск в глубину)")
print(" A - A* (звездочка)")
print("\n" + "=" * 50)
command_stack = []
while True:
key = input("\n Команда > ").lower()
if key == 'q':
print("\n До свидания!")
break
elif key == 'b':
solver.set_strategy(BFSStrategy())
stats = solver.solve()
print(f"\n BFS: время={stats.time_ms:.3f}мс, посещено={stats.visited_cells}, длина={stats.path_length}")
elif key == 'd':
solver.set_strategy(DFSStrategy())
stats = solver.solve()
print(f"\n DFS: время={stats.time_ms:.3f}мс, посещено={stats.visited_cells}, длина={stats.path_length}")
elif key == 'a':
solver.set_strategy(AStarStrategy())
stats = solver.solve()
print(f"\n A*: время={stats.time_ms:.3f}мс, посещено={stats.visited_cells}, длина={stats.path_length}")
elif key in ['h', 'j', 'k', 'l']:
dirs = {'h': (-1, 0), 'l': (1, 0), 'k': (0, -1), 'j': (0, 1)}
cmd = MoveCommand(player, dirs[key], maze)
if cmd.execute():
command_stack.append(cmd)
view.render_maze_with_player(maze)
if player.current == maze.exit:
print("\n ПОЗДРАВЛЯЮ! ВЫ НАШЛИ ВЫХОД!")
print(f" Всего ходов: {len(command_stack)}")
break
else:
print("\n Туда нельзя там стена!")
elif key == 'u':
if command_stack:
cmd = command_stack.pop()
cmd.undo()
view.render_maze_with_player(maze)
print("\n Ход отменён")
else:
print("\n Нечего отменять")
else:
print("\n Неизвестная команда. Используйте H,J,K,L для движения, U для отмены, Q для выхода")
if __name__ == "__main__":
main()