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):
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

View File

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

View File

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

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

View File

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