2026-05-24 19:12:39 +00:00
|
|
|
|
import numpy as np
|
|
|
|
|
|
import abc
|
|
|
|
|
|
from collections import deque
|
2026-05-24 22:11:16 +00:00
|
|
|
|
import heapq
|
2026-05-26 19:18:10 +00:00
|
|
|
|
import time
|
|
|
|
|
|
import os
|
|
|
|
|
|
import keyboard
|
2026-05-24 19:12:39 +00:00
|
|
|
|
|
|
|
|
|
|
#Классы клетки и лабиринта
|
|
|
|
|
|
|
|
|
|
|
|
class Cell:
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(self, coords, isWall = False, isStart = False,
|
|
|
|
|
|
isExit = False):
|
|
|
|
|
|
self.coords = coords
|
|
|
|
|
|
self.isWall = isWall
|
|
|
|
|
|
self.isStart = isStart
|
|
|
|
|
|
self.isExit = isExit
|
|
|
|
|
|
|
|
|
|
|
|
def isPassable(self):
|
|
|
|
|
|
if self.isWall:
|
|
|
|
|
|
return False
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
class Maze:
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(self, cells, width, height, st, ex):
|
|
|
|
|
|
self.cells = cells
|
|
|
|
|
|
self.width = width
|
|
|
|
|
|
self.height = height
|
|
|
|
|
|
self.st = st
|
|
|
|
|
|
self.ex = ex
|
|
|
|
|
|
|
|
|
|
|
|
def getCell(self,x,y):
|
|
|
|
|
|
try:
|
|
|
|
|
|
return self.cells[x][y]
|
|
|
|
|
|
except:
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
def getNeighbors(self,cell):
|
|
|
|
|
|
x,y = cell.coords
|
|
|
|
|
|
res = []
|
|
|
|
|
|
for i,j in (x,y+1),(x,y-1),(x-1,y),(x+1,y):
|
|
|
|
|
|
cellij = self.getCell(i,j)
|
|
|
|
|
|
if i <= self.width-1 and j <= self.height-1 and 0 <= i and 0 <= j and cellij is not None:
|
|
|
|
|
|
if cellij.isPassable():
|
|
|
|
|
|
res.append(cellij)
|
|
|
|
|
|
else:
|
|
|
|
|
|
res.append(None)
|
|
|
|
|
|
else:
|
|
|
|
|
|
res.append(None)
|
|
|
|
|
|
|
|
|
|
|
|
return res
|
|
|
|
|
|
|
|
|
|
|
|
#Тестирование классов клетки и лабиринта
|
|
|
|
|
|
|
2026-05-26 19:18:10 +00:00
|
|
|
|
# cell1 = Cell((1,2), isExit = True, isWall = True, isStart = False)
|
2026-05-24 19:12:39 +00:00
|
|
|
|
|
2026-05-26 19:18:10 +00:00
|
|
|
|
# print(cell1.isPassable())
|
|
|
|
|
|
# print(cell1.isStart)
|
|
|
|
|
|
# print(cell1.coords)
|
2026-05-24 19:12:39 +00:00
|
|
|
|
|
2026-05-26 19:18:10 +00:00
|
|
|
|
# width, height = 3,3
|
2026-05-24 19:12:39 +00:00
|
|
|
|
|
2026-05-26 19:18:10 +00:00
|
|
|
|
# cells = np.full((width,height), None, dtype=object)
|
2026-05-24 19:12:39 +00:00
|
|
|
|
|
2026-05-26 19:18:10 +00:00
|
|
|
|
# for x in range(width):
|
|
|
|
|
|
# for y in range(height):
|
|
|
|
|
|
# if x != 0 and x != width-1 and y != 0 and y != height-1:
|
|
|
|
|
|
# cells[x][y] = Cell((x,y), isWall = False)
|
|
|
|
|
|
# else:
|
|
|
|
|
|
# cells[x][y] = Cell((x,y), isWall = True)
|
2026-05-24 19:12:39 +00:00
|
|
|
|
|
2026-05-26 19:18:10 +00:00
|
|
|
|
# print(cells)
|
2026-05-24 19:12:39 +00:00
|
|
|
|
|
2026-05-26 19:18:10 +00:00
|
|
|
|
# maze1 = Maze(cells, width, height, cells[0], cells[-1])
|
2026-05-24 19:12:39 +00:00
|
|
|
|
|
2026-05-26 19:18:10 +00:00
|
|
|
|
# for column in cells:
|
|
|
|
|
|
# for cell in column:
|
|
|
|
|
|
# print(cell.coords)
|
|
|
|
|
|
# print(maze1.getNeighbors(cell))
|
2026-05-24 19:12:39 +00:00
|
|
|
|
|
2026-05-26 19:18:10 +00:00
|
|
|
|
# print('\n')
|
2026-05-24 19:12:39 +00:00
|
|
|
|
|
|
|
|
|
|
#Интерфейс постройки лабиринта
|
|
|
|
|
|
|
|
|
|
|
|
class MazeBuilder(abc.ABC):
|
|
|
|
|
|
|
|
|
|
|
|
@abc.abstractmethod
|
|
|
|
|
|
def buildFromFile(filename): pass
|
|
|
|
|
|
|
|
|
|
|
|
#Наследуем от него класс постройки из текстового файла
|
|
|
|
|
|
|
|
|
|
|
|
class TextFileMazeBuilder(MazeBuilder):
|
|
|
|
|
|
|
|
|
|
|
|
def buildFromFile(filename):
|
|
|
|
|
|
with open(filename, "r") as file:
|
|
|
|
|
|
|
|
|
|
|
|
rows = file.read().splitlines()
|
|
|
|
|
|
|
2026-05-26 19:18:10 +00:00
|
|
|
|
#print(rows)
|
2026-05-24 19:12:39 +00:00
|
|
|
|
|
|
|
|
|
|
width = 0
|
|
|
|
|
|
height = 0
|
|
|
|
|
|
for row in rows:
|
|
|
|
|
|
height += 1
|
|
|
|
|
|
if len(row) > width:
|
|
|
|
|
|
width = len(row)
|
|
|
|
|
|
|
2026-05-26 19:18:10 +00:00
|
|
|
|
#print(width, height)
|
2026-05-24 19:12:39 +00:00
|
|
|
|
|
|
|
|
|
|
st = (0,0)
|
|
|
|
|
|
ex = (width,height)
|
|
|
|
|
|
|
|
|
|
|
|
cells = np.full((width,height), None, dtype=object)
|
|
|
|
|
|
|
2026-05-26 19:18:10 +00:00
|
|
|
|
flagst = False
|
|
|
|
|
|
flagex = False
|
2026-05-24 19:12:39 +00:00
|
|
|
|
for y in range(height):
|
|
|
|
|
|
for x in range(width):
|
|
|
|
|
|
isWall = False
|
|
|
|
|
|
isStart = False
|
|
|
|
|
|
isExit = False
|
|
|
|
|
|
|
|
|
|
|
|
if rows[-(y+1)][x] == '#':
|
|
|
|
|
|
isWall = True
|
|
|
|
|
|
elif rows[-(y+1)][x] == 'S':
|
|
|
|
|
|
isStart = True
|
|
|
|
|
|
st = (x,y)
|
2026-05-26 19:18:10 +00:00
|
|
|
|
flagst = True
|
|
|
|
|
|
#print('Старт в',x,y)
|
2026-05-24 19:12:39 +00:00
|
|
|
|
elif rows[-(y+1)][x] == 'E':
|
|
|
|
|
|
isExit = True
|
|
|
|
|
|
ex = (x,y)
|
2026-05-26 19:18:10 +00:00
|
|
|
|
flagex = True
|
|
|
|
|
|
#print('Выход в',x,y)
|
2026-05-24 19:12:39 +00:00
|
|
|
|
elif rows[-(y+1)][x] != ' ':
|
|
|
|
|
|
raise ValueError("Неверный формат лабиринта! Пожалуйста, используйте только символы #,S,E и пробелы")
|
|
|
|
|
|
|
|
|
|
|
|
cells[x][y] = Cell((x,y), isWall, isStart, isExit)
|
|
|
|
|
|
|
2026-05-26 19:18:10 +00:00
|
|
|
|
if flagst and flagex:
|
|
|
|
|
|
|
|
|
|
|
|
return Maze(cells, width, height, cells[st[0]][st[1]], cells[ex[0]][ex[1]])
|
|
|
|
|
|
|
|
|
|
|
|
raise ValueError('В лабаиринте должны быть вход и выход (S и E)')
|
2026-05-24 19:12:39 +00:00
|
|
|
|
|
|
|
|
|
|
|
2026-05-26 19:18:10 +00:00
|
|
|
|
# builder = TextFileMazeBuilder
|
2026-05-24 19:12:39 +00:00
|
|
|
|
|
2026-05-26 19:18:10 +00:00
|
|
|
|
# maze = builder.buildFromFile('maze1.txt')
|
2026-05-24 19:12:39 +00:00
|
|
|
|
|
2026-05-26 19:18:10 +00:00
|
|
|
|
# print(maze)
|
2026-05-24 19:12:39 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#Интерфейс поиска пути
|
|
|
|
|
|
|
|
|
|
|
|
class PathFindingStrategy(abc.ABC):
|
|
|
|
|
|
|
|
|
|
|
|
@abc.abstractmethod
|
2026-05-26 19:18:10 +00:00
|
|
|
|
def findPath(self, maze, st, ex): pass
|
2026-05-24 19:12:39 +00:00
|
|
|
|
|
|
|
|
|
|
#Поиск в глубину
|
|
|
|
|
|
|
|
|
|
|
|
class DFS(PathFindingStrategy):
|
|
|
|
|
|
|
2026-05-26 19:18:10 +00:00
|
|
|
|
def findPath(self,maze,st,ex):
|
2026-05-24 19:12:39 +00:00
|
|
|
|
|
|
|
|
|
|
stack = [st]
|
|
|
|
|
|
|
2026-05-26 19:18:10 +00:00
|
|
|
|
self.visited = {st.coords} #по координатам надёжнее, а то вдруг адрес изменится
|
2026-05-24 19:12:39 +00:00
|
|
|
|
|
|
|
|
|
|
pathmap = {}
|
|
|
|
|
|
|
|
|
|
|
|
while stack:
|
|
|
|
|
|
cell = stack.pop()
|
|
|
|
|
|
|
2026-05-24 22:11:16 +00:00
|
|
|
|
if cell.coords == ex.coords:
|
2026-05-24 19:12:39 +00:00
|
|
|
|
|
|
|
|
|
|
#маршрут выстраивается в обратном порядке и разворачивается
|
|
|
|
|
|
path = []
|
|
|
|
|
|
while cell.coords != st.coords:
|
|
|
|
|
|
path.append(cell)
|
|
|
|
|
|
cell = pathmap[cell.coords]
|
|
|
|
|
|
path.append(st)
|
|
|
|
|
|
path = path[::-1]
|
|
|
|
|
|
return path
|
|
|
|
|
|
|
|
|
|
|
|
for n in maze.getNeighbors(cell):
|
2026-05-26 19:18:10 +00:00
|
|
|
|
if n != None and n.coords not in self.visited:
|
|
|
|
|
|
self.visited.add(n.coords)
|
2026-05-24 19:12:39 +00:00
|
|
|
|
pathmap[n.coords] = cell
|
|
|
|
|
|
stack.append(n)
|
|
|
|
|
|
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
2026-05-26 19:18:10 +00:00
|
|
|
|
# path = DFS().findPath(maze,maze.st,maze.ex)
|
2026-05-24 19:12:39 +00:00
|
|
|
|
|
2026-05-26 19:18:10 +00:00
|
|
|
|
# print('путь поиском в глубину:')
|
|
|
|
|
|
# for cell in path:
|
|
|
|
|
|
# print(cell.coords)
|
2026-05-24 19:12:39 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class BFS(PathFindingStrategy):
|
|
|
|
|
|
|
2026-05-26 19:18:10 +00:00
|
|
|
|
def findPath(self,maze,st,ex):
|
2026-05-24 19:12:39 +00:00
|
|
|
|
|
|
|
|
|
|
queue = deque([st])
|
|
|
|
|
|
|
2026-05-26 19:18:10 +00:00
|
|
|
|
self.visited = {st.coords} #по координатам надёжнее, а то вдруг адрес изменится
|
2026-05-24 19:12:39 +00:00
|
|
|
|
|
|
|
|
|
|
pathmap = {}
|
|
|
|
|
|
|
|
|
|
|
|
while queue:
|
|
|
|
|
|
cell = queue.popleft()
|
|
|
|
|
|
|
2026-05-24 22:11:16 +00:00
|
|
|
|
if cell.coords == ex.coords:
|
|
|
|
|
|
|
2026-05-24 19:12:39 +00:00
|
|
|
|
|
|
|
|
|
|
path = []
|
|
|
|
|
|
while cell.coords != st.coords:
|
|
|
|
|
|
path.append(cell)
|
|
|
|
|
|
cell = pathmap[cell.coords]
|
|
|
|
|
|
path.append(st)
|
|
|
|
|
|
path = path[::-1]
|
|
|
|
|
|
return path
|
|
|
|
|
|
|
|
|
|
|
|
for n in maze.getNeighbors(cell):
|
2026-05-26 19:18:10 +00:00
|
|
|
|
if n != None and n.coords not in self.visited:
|
|
|
|
|
|
self.visited.add(n.coords)
|
2026-05-24 19:12:39 +00:00
|
|
|
|
pathmap[n.coords] = cell
|
|
|
|
|
|
queue.append(n)
|
|
|
|
|
|
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
2026-05-26 19:18:10 +00:00
|
|
|
|
# path = BFS().findPath(maze,maze.st,maze.ex)
|
2026-05-24 19:12:39 +00:00
|
|
|
|
|
2026-05-26 19:18:10 +00:00
|
|
|
|
# print('путь поиском в ширину:')
|
|
|
|
|
|
# for cell in path:
|
|
|
|
|
|
# print(cell.coords)
|
2026-05-24 22:11:16 +00:00
|
|
|
|
|
|
|
|
|
|
class Astar(PathFindingStrategy):
|
|
|
|
|
|
|
2026-05-26 19:18:10 +00:00
|
|
|
|
def findPath(self,maze,st,ex):
|
2026-05-24 22:11:16 +00:00
|
|
|
|
|
|
|
|
|
|
c = 0
|
|
|
|
|
|
hp_queue = [(0,c,st)]
|
|
|
|
|
|
|
2026-05-26 19:18:10 +00:00
|
|
|
|
self.g_score = {st.coords: 0}
|
2026-05-24 22:11:16 +00:00
|
|
|
|
|
|
|
|
|
|
pathmap = {}
|
|
|
|
|
|
|
|
|
|
|
|
hp_queue_coords = {st.coords} #нам важна скорость
|
|
|
|
|
|
|
|
|
|
|
|
while hp_queue:
|
|
|
|
|
|
|
|
|
|
|
|
cell = heapq.heappop(hp_queue)[2]
|
|
|
|
|
|
hp_queue_coords.remove(cell.coords)
|
|
|
|
|
|
|
|
|
|
|
|
if cell.coords == ex.coords:
|
|
|
|
|
|
|
|
|
|
|
|
path = []
|
|
|
|
|
|
while cell.coords != st.coords:
|
|
|
|
|
|
path.append(cell)
|
|
|
|
|
|
cell = pathmap[cell.coords]
|
|
|
|
|
|
path.append(st)
|
|
|
|
|
|
path = path[::-1]
|
2026-05-26 19:18:10 +00:00
|
|
|
|
self.visited = self.g_score #экий костыль
|
2026-05-24 22:11:16 +00:00
|
|
|
|
return path
|
|
|
|
|
|
|
|
|
|
|
|
for n in maze.getNeighbors(cell):
|
2026-05-26 19:18:10 +00:00
|
|
|
|
new_g_score = self.g_score[cell.coords] + 1
|
2026-05-24 22:11:16 +00:00
|
|
|
|
|
2026-05-26 19:18:10 +00:00
|
|
|
|
if n is not None and new_g_score < self.g_score.get(n.coords, float('inf')):
|
2026-05-24 22:11:16 +00:00
|
|
|
|
pathmap[n.coords] = cell
|
2026-05-26 19:18:10 +00:00
|
|
|
|
self.g_score[n.coords] = new_g_score
|
2026-05-24 22:11:16 +00:00
|
|
|
|
|
|
|
|
|
|
h_score = abs(n.coords[0]-ex.coords[0]) + abs(n.coords[1]-ex.coords[1])
|
|
|
|
|
|
|
|
|
|
|
|
#f = g + h
|
|
|
|
|
|
#h - манхэттенское расстояние
|
|
|
|
|
|
full_score = new_g_score + h_score
|
|
|
|
|
|
|
|
|
|
|
|
if n.coords not in hp_queue_coords:
|
|
|
|
|
|
c += 1
|
|
|
|
|
|
heapq.heappush(hp_queue, (full_score, c, n))
|
|
|
|
|
|
hp_queue_coords.add(n.coords)
|
2026-05-26 19:18:10 +00:00
|
|
|
|
|
|
|
|
|
|
self.visited = self.g_score #экий костыль 2: возвращение ситхов
|
2026-05-24 22:11:16 +00:00
|
|
|
|
return None
|
|
|
|
|
|
|
2026-05-26 19:18:10 +00:00
|
|
|
|
# path = Astar().findPath(maze,maze.st,maze.ex)
|
|
|
|
|
|
|
|
|
|
|
|
# print('путь с A*:')
|
|
|
|
|
|
# for cell in path:
|
|
|
|
|
|
# print(cell.coords)
|
|
|
|
|
|
|
|
|
|
|
|
#Класс статистики поиска пути и класс оркестратор
|
|
|
|
|
|
|
|
|
|
|
|
class SearchStats():
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(self, timeMs, visitedCells, pathLength):
|
|
|
|
|
|
self.timeMs = timeMs
|
|
|
|
|
|
self.visitedCells = visitedCells
|
|
|
|
|
|
self.pathLength = pathLength
|
|
|
|
|
|
|
|
|
|
|
|
class MazeSolver():
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(self, maze, strategy):
|
|
|
|
|
|
self.maze = maze
|
|
|
|
|
|
self.strategy = strategy
|
|
|
|
|
|
self.observers = [ConsoleView(maze)]
|
|
|
|
|
|
|
|
|
|
|
|
for observer in self.observers:
|
|
|
|
|
|
observer.update(MazeEvent('maze_loaded',maze,maze.st.coords))
|
|
|
|
|
|
|
|
|
|
|
|
def setStrategy(self,strategy):
|
|
|
|
|
|
self.strategy = strategy
|
|
|
|
|
|
|
|
|
|
|
|
def solve(self):
|
|
|
|
|
|
|
|
|
|
|
|
start = time.perf_counter()
|
|
|
|
|
|
path = self.strategy.findPath(self.maze,self.maze.st,self.maze.ex)
|
|
|
|
|
|
end = time.perf_counter()
|
|
|
|
|
|
|
|
|
|
|
|
elapsed = end - start
|
|
|
|
|
|
|
|
|
|
|
|
visitedCells = len(self.strategy.visited)
|
|
|
|
|
|
|
|
|
|
|
|
pathLength = len(path)
|
|
|
|
|
|
|
|
|
|
|
|
for observer in self.observers:
|
|
|
|
|
|
observer.update(MazeEvent('path_found',self.maze,path[-1].coords,path))
|
|
|
|
|
|
|
|
|
|
|
|
return SearchStats(elapsed, visitedCells, pathLength)
|
|
|
|
|
|
|
|
|
|
|
|
# MS = MazeSolver(maze, DFS())
|
|
|
|
|
|
# Stats = MS.solve()
|
|
|
|
|
|
# print(Stats.timeMs)
|
|
|
|
|
|
# print(Stats.visitedCells)
|
|
|
|
|
|
# print(Stats.pathLength)
|
|
|
|
|
|
|
|
|
|
|
|
# MS = MazeSolver(maze, BFS())
|
|
|
|
|
|
# Stats = MS.solve()
|
|
|
|
|
|
# print(Stats.timeMs)
|
|
|
|
|
|
# print(Stats.visitedCells)
|
|
|
|
|
|
# print(Stats.pathLength)
|
|
|
|
|
|
|
|
|
|
|
|
# MS = MazeSolver(maze, Astar())
|
|
|
|
|
|
# Stats = MS.solve()
|
|
|
|
|
|
# print(Stats.timeMs)
|
|
|
|
|
|
# print(Stats.visitedCells)
|
|
|
|
|
|
# print(Stats.pathLength)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#Класс для событий
|
|
|
|
|
|
|
|
|
|
|
|
class MazeEvent():
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(self,event_type, maze, player_position = None, path = []):
|
|
|
|
|
|
if player_position is None:
|
|
|
|
|
|
player_position = maze.st.coords
|
|
|
|
|
|
self.event_type = event_type
|
|
|
|
|
|
self.maze = maze
|
|
|
|
|
|
self.player_position = player_position
|
|
|
|
|
|
self.path = path
|
|
|
|
|
|
|
|
|
|
|
|
#Интерфейс наблюдатель
|
|
|
|
|
|
|
|
|
|
|
|
class Observer(abc.ABC):
|
|
|
|
|
|
|
|
|
|
|
|
@abc.abstractmethod
|
|
|
|
|
|
def update(self, event):
|
|
|
|
|
|
|
|
|
|
|
|
if not isinstance(event, (str, MazeEvent)):
|
|
|
|
|
|
raise TypeError('Только строки и объекты события')
|
|
|
|
|
|
|
|
|
|
|
|
elif isinstance(event, MazeEvent) and event.event_type not in ('path_found','move','maze_loaded'):
|
|
|
|
|
|
raise TypeError('Только события "path_found","move","maze_loaded"')
|
|
|
|
|
|
|
|
|
|
|
|
#Класс консольного просмотра
|
|
|
|
|
|
|
|
|
|
|
|
class ConsoleView(Observer):
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(self, maze, player_position = (0,0), path = []):
|
|
|
|
|
|
|
|
|
|
|
|
self.maze = maze
|
|
|
|
|
|
self.player_position = player_position
|
|
|
|
|
|
self.path = path
|
|
|
|
|
|
|
|
|
|
|
|
def update(self, event):
|
|
|
|
|
|
|
|
|
|
|
|
super().update(event) #проверка через сам интерфейс
|
|
|
|
|
|
|
|
|
|
|
|
if isinstance(event, str):
|
|
|
|
|
|
print('')
|
|
|
|
|
|
print(event+'\n')
|
|
|
|
|
|
self.render(self.maze, self.player_position, self.path)
|
|
|
|
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
print('')
|
|
|
|
|
|
print(event.event_type+'\n')
|
|
|
|
|
|
if event.player_position is not None:
|
|
|
|
|
|
self.player_position = event.player_position
|
|
|
|
|
|
if event.path is not None and event.path:
|
|
|
|
|
|
self.path = event.path
|
|
|
|
|
|
self.render(event.maze, self.player_position, self.path)
|
|
|
|
|
|
|
|
|
|
|
|
def render(self, maze, player_position, path):
|
|
|
|
|
|
|
|
|
|
|
|
os.system('cls' if os.name == 'nt' else 'clear')
|
|
|
|
|
|
|
|
|
|
|
|
#из-за системы координат надо всё опять транспонировать
|
|
|
|
|
|
|
|
|
|
|
|
res = []
|
|
|
|
|
|
for row in maze.cells.T[::-1]:
|
|
|
|
|
|
subres = []
|
|
|
|
|
|
for cell in row:
|
|
|
|
|
|
if cell.isWall:
|
|
|
|
|
|
subres += '#'
|
|
|
|
|
|
elif cell.isStart:
|
|
|
|
|
|
subres += 'S'
|
|
|
|
|
|
elif cell.isExit:
|
|
|
|
|
|
subres += 'E'
|
|
|
|
|
|
else:
|
|
|
|
|
|
subres += ' '
|
|
|
|
|
|
res.append(subres)
|
|
|
|
|
|
|
|
|
|
|
|
for cell in path:
|
|
|
|
|
|
x,y = cell.coords
|
|
|
|
|
|
if res[-(y+1)][x] != 'S':
|
|
|
|
|
|
res[-(y+1)][x] = '*'
|
|
|
|
|
|
|
|
|
|
|
|
res[-(player_position[1]+1)][player_position[0]] = 'X'
|
|
|
|
|
|
|
|
|
|
|
|
for row in res:
|
|
|
|
|
|
print(''.join(row))
|
|
|
|
|
|
|
|
|
|
|
|
# builder = TextFileMazeBuilder
|
|
|
|
|
|
|
|
|
|
|
|
# maze = builder.buildFromFile('maze1.txt')
|
|
|
|
|
|
|
|
|
|
|
|
# print(maze)
|
|
|
|
|
|
|
|
|
|
|
|
# CV = ConsoleView(maze, (0,0))
|
|
|
|
|
|
# CV.update('Что-то случилось')
|
|
|
|
|
|
|
|
|
|
|
|
# ME = MazeEvent('maze_loaded', maze, (0,0))
|
|
|
|
|
|
|
|
|
|
|
|
# CV.update(ME)
|
|
|
|
|
|
|
|
|
|
|
|
# CV.update('Что-то случилось')
|
|
|
|
|
|
|
|
|
|
|
|
#Интерфейс для команд
|
|
|
|
|
|
|
|
|
|
|
|
class Command(abc.ABC):
|
|
|
|
|
|
|
|
|
|
|
|
@abc.abstractmethod
|
|
|
|
|
|
def execute(self): pass
|
|
|
|
|
|
|
|
|
|
|
|
@abc.abstractmethod
|
|
|
|
|
|
def undo(self): pass
|
|
|
|
|
|
|
|
|
|
|
|
#Класс команды движения
|
|
|
|
|
|
|
|
|
|
|
|
class MoveCommand(Command):
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
|
|
self.previousCell = (0,0)
|
|
|
|
|
|
|
|
|
|
|
|
def execute(self,player,direction):
|
|
|
|
|
|
|
|
|
|
|
|
self.previousCell = player.currentCell
|
|
|
|
|
|
|
|
|
|
|
|
resCell = (self.previousCell[0]+direction.dir[0],self.previousCell[1]+direction.dir[1])
|
|
|
|
|
|
|
|
|
|
|
|
player.moveTo(resCell)
|
|
|
|
|
|
|
|
|
|
|
|
def undo(self,player):
|
|
|
|
|
|
|
|
|
|
|
|
player.moveTo(self.previousCell)
|
|
|
|
|
|
|
|
|
|
|
|
#Класс игрока
|
|
|
|
|
|
|
|
|
|
|
|
class Player():
|
|
|
|
|
|
|
|
|
|
|
|
#Он хранит не текущую клетку, а только её координаты. Поскольку
|
|
|
|
|
|
#нам надо перемещать игрока динамически, а команда для перемещения
|
|
|
|
|
|
#не принимает лабиринт в качестве аргумента, следующую клетку мы
|
|
|
|
|
|
#как объект получить не можем, а можем получить только её координаты.
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(self, currentCell):
|
|
|
|
|
|
|
|
|
|
|
|
self.currentCell = currentCell
|
|
|
|
|
|
|
|
|
|
|
|
def moveTo(self, cell):
|
|
|
|
|
|
|
|
|
|
|
|
self.currentCell = cell
|
|
|
|
|
|
|
|
|
|
|
|
#Класс направление
|
|
|
|
|
|
|
|
|
|
|
|
class Direction():
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(self, x,y):
|
|
|
|
|
|
self.dir = (x,y)
|
|
|
|
|
|
|
|
|
|
|
|
builder = TextFileMazeBuilder
|
|
|
|
|
|
|
|
|
|
|
|
maze = builder.buildFromFile('maze1.txt')
|
|
|
|
|
|
|
|
|
|
|
|
MS = MazeSolver(maze, DFS())
|
|
|
|
|
|
|
|
|
|
|
|
MS.solve()
|
|
|
|
|
|
|
|
|
|
|
|
MC = MoveCommand()
|
|
|
|
|
|
|
|
|
|
|
|
CV = MS.observers[0]
|
|
|
|
|
|
|
|
|
|
|
|
player1 = Player(CV.player_position)
|
|
|
|
|
|
|
|
|
|
|
|
instruct = '\nПеремещайтесь на W/A/S/D. Для отмены используйте ctrl+Z. Для выхода из режима перемещения команда X.\n'
|
|
|
|
|
|
|
|
|
|
|
|
def move(player, direction):
|
|
|
|
|
|
|
|
|
|
|
|
resCoords = (player.currentCell[0]+direction.dir[0], player.currentCell[1]+direction.dir[1])
|
|
|
|
|
|
resCell = maze.getCell(resCoords[0], resCoords[1])
|
|
|
|
|
|
if resCell == None or resCell.isWall:
|
|
|
|
|
|
return
|
|
|
|
|
|
MC.execute(player, direction)
|
|
|
|
|
|
CV.update(MazeEvent('move', maze, player.currentCell))
|
|
|
|
|
|
print(instruct)
|
|
|
|
|
|
|
|
|
|
|
|
def undo(player):
|
|
|
|
|
|
|
|
|
|
|
|
MC.undo(player)
|
|
|
|
|
|
CV.update(MazeEvent('move', maze, player.currentCell))
|
|
|
|
|
|
|
|
|
|
|
|
print(instruct)
|
|
|
|
|
|
|
|
|
|
|
|
keyboard.add_hotkey('w', move, args=[player1, Direction(0,1)])
|
|
|
|
|
|
|
|
|
|
|
|
keyboard.add_hotkey('s', move, args=[player1, Direction(0,-1)])
|
|
|
|
|
|
|
|
|
|
|
|
keyboard.add_hotkey('a', move, args=[player1, Direction(-1,0)])
|
|
|
|
|
|
|
|
|
|
|
|
keyboard.add_hotkey('d', move, args=[player1, Direction(1,0)])
|
|
|
|
|
|
|
|
|
|
|
|
keyboard.add_hotkey('ctrl+z', undo, args=[player1])
|
|
|
|
|
|
|
|
|
|
|
|
keyboard.wait('x')
|
|
|
|
|
|
keyboard.unhook_all()
|
|
|
|
|
|
|
2026-05-24 22:11:16 +00:00
|
|
|
|
|