diff --git a/romanovpv/task 2/docs/data/builders.py b/romanovpv/task 2/docs/data/builders.py index 157d7e8..c6d64aa 100644 --- a/romanovpv/task 2/docs/data/builders.py +++ b/romanovpv/task 2/docs/data/builders.py @@ -8,63 +8,34 @@ class MazeBuilder(ABC): class TextFileMazeBuilder(MazeBuilder): def buildFromFile(self, filename): - with open( - filename, - "r", - encoding="utf-8" - ) as file: - lines = [ - line.rstrip("\n") + with open(filename, "r", encoding="utf-8") as file: + lines = [line.rstrip("\n") for line in file ] height = len(lines) width = len(lines[0]) - maze = Maze( - width, - height - ) + maze = Maze(width, height) start_count = 0 exit_count = 0 for x, line in enumerate(lines): row = [] for y, symbol in enumerate(line): if symbol == "#": - cell = Cell( - x, - y, - is_wall=True - ) + cell = Cell(x, y, is_wall=True) elif symbol == "S": - cell = Cell( - x, - y, - is_start=True - ) + cell = Cell(x, y, is_start=True) start_count += 1 elif symbol == "E": - cell = Cell( - x, - y, - is_exit=True - ) + cell = Cell(x, y, is_exit=True) exit_count += 1 elif symbol == " ": - cell = Cell( - x, - y - ) + cell = Cell(x, y) else: - raise ValueError( - f"Неизвестный символ: {symbol}" - ) + raise ValueError(f"Неизвестный символ: {symbol}") row.append(cell) maze.add_row(row) if start_count != 1: - raise ValueError( - "Должен быть ровно один старт S" - ) + raise ValueError("Должен быть ровно один старт S") if exit_count != 1: - raise ValueError( - "Должен быть ровно один выход E" - ) + raise ValueError("Должен быть ровно один выход E") return maze \ No newline at end of file diff --git a/romanovpv/task 2/docs/data/main.py b/romanovpv/task 2/docs/data/main.py index 2b2270e..265a711 100644 --- a/romanovpv/task 2/docs/data/main.py +++ b/romanovpv/task 2/docs/data/main.py @@ -1,14 +1,12 @@ -import model from builders import (TextFileMazeBuilder) -from strategies import ( - BFSStrategy, - DFSStrategy, - AStarStrategy - ) +from strategies import (BFSStrategy, DFSStrategy, AStarStrategy) from solver import (MazeSolver) +from observer_command import ConsoleView, Player, MoveCommand +import os builder = TextFileMazeBuilder() maze = builder.buildFromFile("maze.txt") +print("Лабиринт:\n") maze.printMaze() print("Выберете алгоритм") print("1 - BFS") @@ -26,7 +24,45 @@ else: exit() solver = MazeSolver(maze, strategy) +view = ConsoleView() +solver.addObserver(view) stats = solver.solve() print("Результат:") print(stats) +path, _ = strategy.findPath( + maze, + maze.start, + maze.exit +) + +if not path: + print("\nПуть не найден") + exit() + +print("\nНайденный путь:") +for cell in path: + print(f"({cell.x}, {cell.y})") + +print("\nПошаговое движение игрока") +player = Player(maze.start) + +history = [] +passed_path = [maze.start] + +view.render(maze, player, passed_path) + +for cell in path[1:]: + + input("\nEnter -> следующий шаг") + + command = MoveCommand(player, cell) + command.execute() + + history.append(command) + + passed_path.append(cell) + + os.system('cls' if os.name == 'nt' else 'clear') + + view.render(maze, player, passed_path) \ No newline at end of file diff --git a/romanovpv/task 2/docs/data/model.py b/romanovpv/task 2/docs/data/model.py index 273455c..8ea9f5b 100644 --- a/romanovpv/task 2/docs/data/model.py +++ b/romanovpv/task 2/docs/data/model.py @@ -68,10 +68,7 @@ class Maze: nx = cell.x + dx ny = cell.y + dy neighbor = self.getCell(nx, ny) - if ( - neighbor - and neighbor.isPassable() - ): + if (neighbor and neighbor.isPassable()): neighbors.append(neighbor) return neighbors def printMaze(self): diff --git a/romanovpv/task 2/docs/data/observer_command.py b/romanovpv/task 2/docs/data/observer_command.py new file mode 100644 index 0000000..9cc8e26 --- /dev/null +++ b/romanovpv/task 2/docs/data/observer_command.py @@ -0,0 +1,68 @@ +from abc import ABC, abstractmethod + +class Observer(ABC): + + @abstractmethod + def update(self, event): + pass + +class ConsoleView(Observer): + + def update(self, event): + print(f"\n[Событие] {event}") + + def render(self, maze, player=None, path=None): + + path = path or [] + + print() + + for row in maze.cells: + + line = "" + + for cell in row: + + if player and cell == player.position: + line += "P" + + elif cell.isStart: + line += "S" + + elif cell.isExit: + line += "E" + + elif cell.isWall: + line += "#" + + elif cell in path: + line += "*" + + else: + line += " " + + print(line) + + +class Command(ABC): + @abstractmethod + def execute(self): + pass + @abstractmethod + def undo(self): + pass + +class Player: + def __init__(self, start_cell): + self.position = start_cell + +class MoveCommand(Command): + def __init__(self, player, new_cell): + self.player = player + self.new_cell = new_cell + self.old_cell = None + def execute(self): + self.old_cell = self.player.position + self.player.position = self.new_cell + def undo(self): + self.player.position = self.old_cell \ No newline at end of file diff --git a/romanovpv/task 2/docs/data/solver.py b/romanovpv/task 2/docs/data/solver.py index 2d9948c..ec6d219 100644 --- a/romanovpv/task 2/docs/data/solver.py +++ b/romanovpv/task 2/docs/data/solver.py @@ -1,12 +1,7 @@ import time class SearchStats: - def __init__( - self, - time_ms, - visited_cells, - path_length - ): + def __init__(self, time_ms, visited_cells, path_length): self.time_ms = time_ms self.visited_cells = visited_cells self.path_length = path_length @@ -23,40 +18,25 @@ class SearchStats: ) class MazeSolver: - def __init__( - self, - maze, - strategy - ): + def __init__(self,maze, strategy): self.maze = maze self.strategy = strategy - def setStrategy( - self, - strategy + self.observers = [] + def setStrategy(self, strategy ): self.strategy = strategy def solve(self): - 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 - ) + self.notify("Начат поиск") + start_time = (time.perf_counter()) + path, visited = (self.strategy.findPath(self.maze,self.maze.start,self.maze.exit)) + end_time = (time.perf_counter()) + self.notify("Путь найден") + time_ms = ((end_time-start_time)*1000) visited = len(path) - stats = SearchStats( - time_ms, - visited, - len(path) - ) - return stats \ No newline at end of file + stats = SearchStats(time_ms,visited,len(path)) + return stats + def addObserver(self, observer): + self.observers.append(observer) + def notify(self, event): + for observer in self.observers: + observer.update(event) \ No newline at end of file diff --git a/romanovpv/task 2/docs/data/strategies.py b/romanovpv/task 2/docs/data/strategies.py index 573b507..d6a17f2 100644 --- a/romanovpv/task 2/docs/data/strategies.py +++ b/romanovpv/task 2/docs/data/strategies.py @@ -6,12 +6,7 @@ class PathFindingStrategy(ABC): @abstractmethod def findPath(self, maze, start, exit_cell): pass - def restorePath( - self, - parent, - start, - exit_cell - ): + def restorePath(self, parent, start, exit_cell): path = [] current = exit_cell while current != start: @@ -21,136 +16,58 @@ class PathFindingStrategy(ABC): path.reverse() return path class BFSStrategy(PathFindingStrategy): - def findPath( - self, - maze, - start, - exit_cell - ): + def findPath( self, maze, start, exit_cell): queue = deque([start]) visited = {start} parent = {} while queue: current = queue.popleft() if current == exit_cell: - return ( - self.restorePath( - parent, - start, - exit_cell), - len(visited) - ) + return (self.restorePath(parent, start, exit_cell), len(visited)) for neighbor in maze.getNeighbors(current): if neighbor not in visited: - visited.add( - neighbor - ) - parent[ - neighbor - ] = current - queue.append( - neighbor - ) + visited.add(neighbor) + parent[neighbor] = current + queue.append(neighbor) return [], len(visited) class DFSStrategy(PathFindingStrategy): - def findPath( - self, - maze, - start, - exit_cell - ): + def findPath(self, maze, start, exit_cell): stack = [start] visited = {start} parent = {} while stack: current = stack.pop() if current == exit_cell: - return (self.restorePath - ( - parent, - start, - exit_cell), - len(visited) - ) + return (self.restorePath(parent,start,exit_cell),len(visited)) for neighbor in maze.getNeighbors(current): if neighbor not in visited: - visited.add( - neighbor - ) - parent[ - neighbor - ] = current - - stack.append( - neighbor - ) + visited.add(neighbor) + parent[neighbor] = current + stack.append(neighbor) return [], len(visited) class AStarStrategy(PathFindingStrategy): - def heuristic( - self, - cell, - exit_cell - ): + def heuristic(self,cell,exit_cell): return (abs(cell.x - exit_cell.x) + abs(cell.y - exit_cell.y)) - def findPath( - self, - maze, - start, - exit_cell - ): + def findPath(self, maze, start, exit_cell): pq = [] - heapq.heappush( - pq, - ( - 0, - id(start), - start - ) - ) + heapq.heappush(pq,(0, id(start), start)) parent = {} - g_score = { - start: 0 - } + g_score = {start: 0} visited = set() while pq: - _, _, current = ( - heapq.heappop( - pq - ) - ) + _, _, current = (heapq.heappop(pq)) if current in visited: continue - visited.add( - current - ) + visited.add(current) if current == exit_cell: - return (self.restorePath( - parent, - start, - exit_cell), - len(visited) - ) + return (self.restorePath(parent, start, exit_cell), len(visited)) for neighbor in maze.getNeighbors(current): - new_cost = ( - g_score[current] - + 1 - ) + new_cost = (g_score[current]+ 1) if (neighbor not in g_score or new_cost < g_score[neighbor] ): - g_score[neighbor - ] = new_cost - parent[ - neighbor - ] = current - priority = (new_cost + self.heuristic(neighbor, exit_cell) - ) - heapq.heappush( - pq, - ( - priority, - id(neighbor), - neighbor - ) - ) + g_score[neighbor] = new_cost + parent[neighbor] = current + priority = (new_cost + self.heuristic(neighbor, exit_cell)) + heapq.heappush(pq,(priority,id(neighbor),neighbor)) return [], len(visited) \ No newline at end of file