2026-rff_mp/BudakovIS/docs/data/2-nd-exercize/main.py
2026-05-15 09:16:11 +03:00

471 lines
15 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):
self._x = x
self._y = y
self._is_wall = False
self._is_start = False
self._is_exit = False
@property
def x(self):
return self._x
@property
def y(self):
return self._y
@property
def is_wall(self):
return self._is_wall
@is_wall.setter
def is_wall(self, value):
self._is_wall = value
@property
def is_start(self):
return self._is_start
@is_start.setter
def is_start(self, value):
self._is_start = value
@property
def is_exit(self):
return self._is_exit
@is_exit.setter
def is_exit(self, value):
self._is_exit = value
def is_passable(self):
return not self._is_wall
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
@property
def width(self):
return self._width
@property
def height(self):
return self._height
@property
def start(self):
return self._start
@property
def exit(self):
return self._exit
def get_cell(self, x, y):
if 0 <= x < self._width and 0 <= y < self._height:
return self._cells[y][x]
return None
def set_cell(self, x, y, cell_type):
cell = self.get_cell(x, y)
if cell is None:
return
if cell_type == 'wall':
cell.is_wall = True
elif cell_type == 'start':
if self._start:
self._start.is_start = False
cell.is_start = True
cell.is_wall = False
self._start = cell
elif cell_type == 'exit':
if self._exit:
self._exit.is_exit = False
cell.is_exit = True
cell.is_wall = False
self._exit = cell
elif cell_type == 'path':
cell.is_wall = False
def get_neighbors(self, cell):
neighbors = []
directions = [(0, -1), (0, 1), (-1, 0), (1, 0)]
for dx, dy in directions:
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:
def build_from_file(self, filename):
raise NotImplementedError("Need to realise in calss")
class TextFileMazeBuilder(MazeBuilder):
def build_from_file(self, filename):
with open(filename, 'r') 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
start_en = 0
exit_en = 0
maze = Maze(width, height)
for y,line in enumerate(lines):
for x, ch in enumerate(line):
if ch == "#":
maze.set_cell(x,y,"wall")
elif ch == "S":
maze.set_cell(x,y,"start")
start_en+=1
elif ch == "E":
maze.set_cell(x,y,"exit")
exit_en+=1
else:
maze.set_cell(x, y, 'path')
if start_en > 1 or exit_en > 1 or start_en==0 or exit_en ==0:
sys.exit("Error while reading file(you have too many or no match start and exits)")
return maze
class PathFindingStrategy:
def find_path(self, maze, start, exit_cell):
raise NotImplementedError
def _reconstruct_path(self, came_from, start, exit_cell):
path = []
current = exit_cell
while current is not None:
path.append(current)
current = came_from.get(current)
path.reverse()
return path
class BFSStrategy(PathFindingStrategy):
def find_path(self, maze, start, exit_cell):
queue = deque()
queue.append(start)
came_from = {start: None}
visited = {start}
while queue:
current = queue.popleft()
if current == exit_cell:
return self._reconstruct_path(came_from, start, exit_cell)
for neighbor in maze.get_neighbors(current):
if neighbor not in visited:
visited.add(neighbor)
came_from[neighbor] = current
queue.append(neighbor)
return []
class DFSStrategy(PathFindingStrategy):
def find_path(self, maze, start, exit_cell):
stack = [start]
came_from = {start: None}
visited = {start}
while stack:
current = stack.pop()
if current == exit_cell:
return self._reconstruct_path(came_from, start, exit_cell)
for neighbor in maze.get_neighbors(current):
if neighbor not in visited:
visited.add(neighbor)
came_from[neighbor] = current
stack.append(neighbor)
return []
class AStarStrategy(PathFindingStrategy):
def _heuristic(self, cell, exit_cell):
return abs(cell.x - exit_cell.x) + abs(cell.y - exit_cell.y)
def find_path(self, maze, start, exit_cell):
heap = []
counter = 0
start_f = self._heuristic(start, exit_cell)
heapq.heappush(heap, (start_f, counter, start))
counter += 1
came_from = {}
g_score = {start: 0}
f_score = {start: start_f}
while heap:
current_f, _, current = heapq.heappop(heap)
if current == exit_cell:
return self._reconstruct_path(came_from, start, exit_cell)
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_cell)
f_score[neighbor] = new_f
heapq.heappush(heap, (new_f, counter, neighbor))
counter += 1
return []
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)}")
def render_player(self, player_cell):
if self._player:
self.render_maze_with_player(self._player._maze)
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, 0, len(path))
if __name__ == "__main__":
builder = TextFileMazeBuilder()
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(" ┌─────────────────────────────────────┐")
print(" │ H (влево) J (вниз) K (вверх) L (вправо) │")
print(" │ U - отмена хода Q - выход │")
print(" └─────────────────────────────────────┘")
print("\n АВТОМАТИЧЕСКИЙ ПОИСК:")
print(" ┌─────────────────────────────────────┐")
print(" │ B - BFS (поиск в ширину) │")
print(" │ D - DFS (поиск в глубину) │")
print(" │ A - A* (A звездочка) │")
print(" └─────────────────────────────────────┘")
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.path_length}")
elif key == 'd':
solver.set_strategy(DFSStrategy())
stats = solver.solve()
print(f"\n DFS: время={stats.time_ms:.3f}мс, длина пути={stats.path_length}")
elif key == 'a':
solver.set_strategy(AStarStrategy())
stats = solver.solve()
print(f"\n A*: время={stats.time_ms:.3f}мс, длина пути={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 для выхода")
print("\n Игра завершена. Спасибо за игру!")