diff --git a/GutovVM/docs/data/lab_2_data/main.py b/GutovVM/docs/data/lab_2_data/main.py index 3a0aa14..7385dca 100644 --- a/GutovVM/docs/data/lab_2_data/main.py +++ b/GutovVM/docs/data/lab_2_data/main.py @@ -2,6 +2,9 @@ import numpy as np import abc from collections import deque import heapq +import time +import os +import keyboard #Классы клетки и лабиринта @@ -51,33 +54,33 @@ class Maze: #Тестирование классов клетки и лабиринта -cell1 = Cell((1,2), isExit = True, isWall = True, isStart = False) +# cell1 = Cell((1,2), isExit = True, isWall = True, isStart = False) -print(cell1.isPassable()) -print(cell1.isStart) -print(cell1.coords) +# print(cell1.isPassable()) +# print(cell1.isStart) +# print(cell1.coords) -width, height = 3,3 +# width, height = 3,3 -cells = np.full((width,height), None, dtype=object) +# cells = np.full((width,height), None, dtype=object) -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) +# 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) -print(cells) +# print(cells) -maze1 = Maze(cells, width, height, cells[0], cells[-1]) +# maze1 = Maze(cells, width, height, cells[0], cells[-1]) -for column in cells: - for cell in column: - print(cell.coords) - print(maze1.getNeighbors(cell)) +# for column in cells: +# for cell in column: +# print(cell.coords) +# print(maze1.getNeighbors(cell)) -print('\n') +# print('\n') #Интерфейс постройки лабиринта @@ -95,7 +98,7 @@ class TextFileMazeBuilder(MazeBuilder): rows = file.read().splitlines() - print(rows) + #print(rows) width = 0 height = 0 @@ -104,13 +107,15 @@ class TextFileMazeBuilder(MazeBuilder): if len(row) > width: width = len(row) - print(width, height) + #print(width, height) st = (0,0) ex = (width,height) cells = np.full((width,height), None, dtype=object) + flagst = False + flagex = False for y in range(height): for x in range(width): isWall = False @@ -122,24 +127,30 @@ class TextFileMazeBuilder(MazeBuilder): elif rows[-(y+1)][x] == 'S': isStart = True st = (x,y) - print('Старт в',x,y) + flagst = True + #print('Старт в',x,y) elif rows[-(y+1)][x] == 'E': isExit = True ex = (x,y) - print('Выход в',x,y) + flagex = True + #print('Выход в',x,y) elif rows[-(y+1)][x] != ' ': raise ValueError("Неверный формат лабиринта! Пожалуйста, используйте только символы #,S,E и пробелы") cells[x][y] = Cell((x,y), isWall, isStart, isExit) - return Maze(cells, width, height, cells[st[0]][st[1]], cells[ex[0]][ex[1]]) + if flagst and flagex: + + return Maze(cells, width, height, cells[st[0]][st[1]], cells[ex[0]][ex[1]]) + + raise ValueError('В лабаиринте должны быть вход и выход (S и E)') -builder = TextFileMazeBuilder +# builder = TextFileMazeBuilder -maze = builder.buildFromFile('maze1.txt') +# maze = builder.buildFromFile('maze1.txt') -print(maze) +# print(maze) #Интерфейс поиска пути @@ -147,17 +158,17 @@ print(maze) class PathFindingStrategy(abc.ABC): @abc.abstractmethod - def findPath(maze, st, ex): pass + def findPath(self, maze, st, ex): pass #Поиск в глубину class DFS(PathFindingStrategy): - def findPath(maze,st,ex): + def findPath(self,maze,st,ex): stack = [st] - visited = {st.coords} #по координатам надёжнее, а то вдруг адрес изменится + self.visited = {st.coords} #по координатам надёжнее, а то вдруг адрес изменится pathmap = {} @@ -176,27 +187,27 @@ class DFS(PathFindingStrategy): return path for n in maze.getNeighbors(cell): - if n != None and n.coords not in visited: - visited.add(n.coords) + if n != None and n.coords not in self.visited: + self.visited.add(n.coords) pathmap[n.coords] = cell stack.append(n) return None -path = DFS.findPath(maze,maze.st,maze.ex) +# path = DFS().findPath(maze,maze.st,maze.ex) -print('путь поиском в глубину:') -for cell in path: - print(cell.coords) +# print('путь поиском в глубину:') +# for cell in path: +# print(cell.coords) class BFS(PathFindingStrategy): - def findPath(maze,st,ex): + def findPath(self,maze,st,ex): queue = deque([st]) - visited = {st.coords} #по координатам надёжнее, а то вдруг адрес изменится + self.visited = {st.coords} #по координатам надёжнее, а то вдруг адрес изменится pathmap = {} @@ -215,27 +226,27 @@ class BFS(PathFindingStrategy): return path for n in maze.getNeighbors(cell): - if n != None and n.coords not in visited: - visited.add(n.coords) + if n != None and n.coords not in self.visited: + self.visited.add(n.coords) pathmap[n.coords] = cell queue.append(n) return None -path = BFS.findPath(maze,maze.st,maze.ex) +# path = BFS().findPath(maze,maze.st,maze.ex) -print('путь поиском в ширину:') -for cell in path: - print(cell.coords) +# print('путь поиском в ширину:') +# for cell in path: +# print(cell.coords) class Astar(PathFindingStrategy): - def findPath(maze,st,ex): + def findPath(self,maze,st,ex): c = 0 hp_queue = [(0,c,st)] - g_score = {st.coords: 0} + self.g_score = {st.coords: 0} pathmap = {} @@ -254,14 +265,15 @@ class Astar(PathFindingStrategy): cell = pathmap[cell.coords] path.append(st) path = path[::-1] + self.visited = self.g_score #экий костыль return path for n in maze.getNeighbors(cell): - new_g_score = g_score[cell.coords] + 1 + new_g_score = self.g_score[cell.coords] + 1 - if n is not None and new_g_score < g_score.get(n.coords, float('inf')): + if n is not None and new_g_score < self.g_score.get(n.coords, float('inf')): pathmap[n.coords] = cell - g_score[n.coords] = new_g_score + self.g_score[n.coords] = new_g_score h_score = abs(n.coords[0]-ex.coords[0]) + abs(n.coords[1]-ex.coords[1]) @@ -273,11 +285,269 @@ class Astar(PathFindingStrategy): c += 1 heapq.heappush(hp_queue, (full_score, c, n)) hp_queue_coords.add(n.coords) - + + self.visited = self.g_score #экий костыль 2: возвращение ситхов return None -path = Astar.findPath(maze,maze.st,maze.ex) +# 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() + -print('путь с A*:') -for cell in path: - print(cell.coords) \ No newline at end of file