observer_command

This commit is contained in:
Pavel 2026-05-24 22:18:38 +03:00
parent d0b791287f
commit 797c260aaa
6 changed files with 161 additions and 192 deletions

View File

@ -8,63 +8,34 @@ class MazeBuilder(ABC):
class TextFileMazeBuilder(MazeBuilder): class TextFileMazeBuilder(MazeBuilder):
def buildFromFile(self, filename): def buildFromFile(self, filename):
with open( with open(filename, "r", encoding="utf-8") as file:
filename, lines = [line.rstrip("\n")
"r",
encoding="utf-8"
) as file:
lines = [
line.rstrip("\n")
for line in file for line in file
] ]
height = len(lines) height = len(lines)
width = len(lines[0]) width = len(lines[0])
maze = Maze( maze = Maze(width, height)
width,
height
)
start_count = 0 start_count = 0
exit_count = 0 exit_count = 0
for x, line in enumerate(lines): for x, line in enumerate(lines):
row = [] row = []
for y, symbol in enumerate(line): for y, symbol in enumerate(line):
if symbol == "#": if symbol == "#":
cell = Cell( cell = Cell(x, y, is_wall=True)
x,
y,
is_wall=True
)
elif symbol == "S": elif symbol == "S":
cell = Cell( cell = Cell(x, y, is_start=True)
x,
y,
is_start=True
)
start_count += 1 start_count += 1
elif symbol == "E": elif symbol == "E":
cell = Cell( cell = Cell(x, y, is_exit=True)
x,
y,
is_exit=True
)
exit_count += 1 exit_count += 1
elif symbol == " ": elif symbol == " ":
cell = Cell( cell = Cell(x, y)
x,
y
)
else: else:
raise ValueError( raise ValueError(f"Неизвестный символ: {symbol}")
f"Неизвестный символ: {symbol}"
)
row.append(cell) row.append(cell)
maze.add_row(row) maze.add_row(row)
if start_count != 1: if start_count != 1:
raise ValueError( raise ValueError("Должен быть ровно один старт S")
"Должен быть ровно один старт S"
)
if exit_count != 1: if exit_count != 1:
raise ValueError( raise ValueError("Должен быть ровно один выход E")
"Должен быть ровно один выход E"
)
return maze return maze

View File

@ -1,14 +1,12 @@
import model
from builders import (TextFileMazeBuilder) from builders import (TextFileMazeBuilder)
from strategies import ( from strategies import (BFSStrategy, DFSStrategy, AStarStrategy)
BFSStrategy,
DFSStrategy,
AStarStrategy
)
from solver import (MazeSolver) from solver import (MazeSolver)
from observer_command import ConsoleView, Player, MoveCommand
import os
builder = TextFileMazeBuilder() builder = TextFileMazeBuilder()
maze = builder.buildFromFile("maze.txt") maze = builder.buildFromFile("maze.txt")
print("Лабиринт:\n")
maze.printMaze() maze.printMaze()
print("Выберете алгоритм") print("Выберете алгоритм")
print("1 - BFS") print("1 - BFS")
@ -26,7 +24,45 @@ else:
exit() exit()
solver = MazeSolver(maze, strategy) solver = MazeSolver(maze, strategy)
view = ConsoleView()
solver.addObserver(view)
stats = solver.solve() stats = solver.solve()
print("Результат:") print("Результат:")
print(stats) 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)

View File

@ -68,10 +68,7 @@ class Maze:
nx = cell.x + dx nx = cell.x + dx
ny = cell.y + dy ny = cell.y + dy
neighbor = self.getCell(nx, ny) neighbor = self.getCell(nx, ny)
if ( if (neighbor and neighbor.isPassable()):
neighbor
and neighbor.isPassable()
):
neighbors.append(neighbor) neighbors.append(neighbor)
return neighbors return neighbors
def printMaze(self): def printMaze(self):

View File

@ -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

View File

@ -1,12 +1,7 @@
import time import time
class SearchStats: class SearchStats:
def __init__( def __init__(self, time_ms, visited_cells, path_length):
self,
time_ms,
visited_cells,
path_length
):
self.time_ms = time_ms self.time_ms = time_ms
self.visited_cells = visited_cells self.visited_cells = visited_cells
self.path_length = path_length self.path_length = path_length
@ -23,40 +18,25 @@ class SearchStats:
) )
class MazeSolver: class MazeSolver:
def __init__( def __init__(self,maze, strategy):
self,
maze,
strategy
):
self.maze = maze self.maze = maze
self.strategy = strategy self.strategy = strategy
def setStrategy( self.observers = []
self, def setStrategy(self, strategy
strategy
): ):
self.strategy = strategy self.strategy = strategy
def solve(self): def solve(self):
start_time = ( self.notify("Начат поиск")
time.perf_counter() start_time = (time.perf_counter())
) path, visited = (self.strategy.findPath(self.maze,self.maze.start,self.maze.exit))
path, visited = ( end_time = (time.perf_counter())
self.strategy.findPath( self.notify("Путь найден")
self.maze, time_ms = ((end_time-start_time)*1000)
self.maze.start,
self.maze.exit
)
)
end_time = (
time.perf_counter()
)
time_ms = (
(end_time-start_time)
*1000
)
visited = len(path) visited = len(path)
stats = SearchStats( stats = SearchStats(time_ms,visited,len(path))
time_ms, return stats
visited, def addObserver(self, observer):
len(path) self.observers.append(observer)
) def notify(self, event):
return stats for observer in self.observers:
observer.update(event)

View File

@ -6,12 +6,7 @@ class PathFindingStrategy(ABC):
@abstractmethod @abstractmethod
def findPath(self, maze, start, exit_cell): def findPath(self, maze, start, exit_cell):
pass pass
def restorePath( def restorePath(self, parent, start, exit_cell):
self,
parent,
start,
exit_cell
):
path = [] path = []
current = exit_cell current = exit_cell
while current != start: while current != start:
@ -21,136 +16,58 @@ class PathFindingStrategy(ABC):
path.reverse() path.reverse()
return path return path
class BFSStrategy(PathFindingStrategy): class BFSStrategy(PathFindingStrategy):
def findPath( def findPath( self, maze, start, exit_cell):
self,
maze,
start,
exit_cell
):
queue = deque([start]) queue = deque([start])
visited = {start} visited = {start}
parent = {} parent = {}
while queue: while queue:
current = queue.popleft() current = queue.popleft()
if current == exit_cell: if current == exit_cell:
return ( return (self.restorePath(parent, start, exit_cell), len(visited))
self.restorePath(
parent,
start,
exit_cell),
len(visited)
)
for neighbor in maze.getNeighbors(current): for neighbor in maze.getNeighbors(current):
if neighbor not in visited: if neighbor not in visited:
visited.add( visited.add(neighbor)
neighbor parent[neighbor] = current
) queue.append(neighbor)
parent[
neighbor
] = current
queue.append(
neighbor
)
return [], len(visited) return [], len(visited)
class DFSStrategy(PathFindingStrategy): class DFSStrategy(PathFindingStrategy):
def findPath( def findPath(self, maze, start, exit_cell):
self,
maze,
start,
exit_cell
):
stack = [start] stack = [start]
visited = {start} visited = {start}
parent = {} parent = {}
while stack: while stack:
current = stack.pop() current = stack.pop()
if current == exit_cell: if current == exit_cell:
return (self.restorePath return (self.restorePath(parent,start,exit_cell),len(visited))
(
parent,
start,
exit_cell),
len(visited)
)
for neighbor in maze.getNeighbors(current): for neighbor in maze.getNeighbors(current):
if neighbor not in visited: if neighbor not in visited:
visited.add( visited.add(neighbor)
neighbor parent[neighbor] = current
) stack.append(neighbor)
parent[
neighbor
] = current
stack.append(
neighbor
)
return [], len(visited) return [], len(visited)
class AStarStrategy(PathFindingStrategy): class AStarStrategy(PathFindingStrategy):
def heuristic( def heuristic(self,cell,exit_cell):
self,
cell,
exit_cell
):
return (abs(cell.x - exit_cell.x) + abs(cell.y - exit_cell.y)) return (abs(cell.x - exit_cell.x) + abs(cell.y - exit_cell.y))
def findPath( def findPath(self, maze, start, exit_cell):
self,
maze,
start,
exit_cell
):
pq = [] pq = []
heapq.heappush( heapq.heappush(pq,(0, id(start), start))
pq,
(
0,
id(start),
start
)
)
parent = {} parent = {}
g_score = { g_score = {start: 0}
start: 0
}
visited = set() visited = set()
while pq: while pq:
_, _, current = ( _, _, current = (heapq.heappop(pq))
heapq.heappop(
pq
)
)
if current in visited: if current in visited:
continue continue
visited.add( visited.add(current)
current
)
if current == exit_cell: if current == exit_cell:
return (self.restorePath( return (self.restorePath(parent, start, exit_cell), len(visited))
parent,
start,
exit_cell),
len(visited)
)
for neighbor in maze.getNeighbors(current): for neighbor in maze.getNeighbors(current):
new_cost = ( new_cost = (g_score[current]+ 1)
g_score[current]
+ 1
)
if (neighbor not in g_score or new_cost < g_score[neighbor] ): if (neighbor not in g_score or new_cost < g_score[neighbor] ):
g_score[neighbor g_score[neighbor] = new_cost
] = new_cost parent[neighbor] = current
parent[ priority = (new_cost + self.heuristic(neighbor, exit_cell))
neighbor heapq.heappush(pq,(priority,id(neighbor),neighbor))
] = current
priority = (new_cost + self.heuristic(neighbor, exit_cell)
)
heapq.heappush(
pq,
(
priority,
id(neighbor),
neighbor
)
)
return [], len(visited) return [], len(visited)