diff --git a/eb_engine.py b/eb_engine.py index 03b6e8a..81abef1 100644 --- a/eb_engine.py +++ b/eb_engine.py @@ -4,6 +4,7 @@ from common import pygame, pygame_gui import eb_objects import eb_terrain_objects import eb_creature_objects +from pathfinder import pathfinder #from pympler import muppy, summary import gc, psutil, os @@ -38,8 +39,6 @@ class Map: sprites: dict sprites_refresh: int = 60 cells: dict = field(default_factory = dict) - walkable_matrix: list = field(default_factory = list) - rocks_matrix: list = field(default_factory = list) color: str = "gray57" target_color: str = "gold" cell_size: int = 150 @@ -70,13 +69,6 @@ class Map: self.cells[line].append(final_cell) - self.compute_walkable_rocks() - for j in range(len(self.cells)): - for cell in self.cells[j]: - if cell.creature_obj: - cell.creature_obj.walkable_matrix = self.walkable_matrix - cell.creature_obj.rocks_matrix = self.rocks_matrix - def draw_obj(self, obj, draw_data): if draw_data["spr_up"] == 0: if obj.sprite_state == len(draw_data["sprites"][obj.sprite_name]) - 1: @@ -134,41 +126,15 @@ class Map: return (row, col) def update_map(self, time_delta): - self.compute_walkable_rocks() + pathfinder.set_map(self.cells) for j in range(len(self.cells)): for cell in self.cells[j]: if cell.creature_obj: - cell.creature_obj.walkable_matrix = self.walkable_matrix - cell.creature_obj.rocks_matrix = self.rocks_matrix if cell.creature_obj.final_goal is not None: cell.creature_obj.calc_step(time_delta, self.cell_size, self) continue cell.creature_obj.update(time_delta) - def compute_walkable_rocks(self): - """Вычисляет матрицы walkable и rocks_only БЕЗ учета стартовой позиции""" - rows = len(self.cells) - if rows == 0: - return None, None - - cols = len(self.cells[0]) - - # ★ ИНИЦИАЛИЗАЦИЯ ★ - self.walkable_matrix = [[True] * cols for _ in range(rows)] - self.rocks_matrix = [[False] * cols for _ in range(rows)] - - # ★ ПОЛНЫЙ ПРОХОД ПО КАРТЕ ★ - for r in range(rows): - for c in range(cols): - cell = self.cells[r][c] - # Запрещаем ВСЕ существа (включая стартовое!) - if cell.creature_obj or \ - (cell.terrain_obj and cell.terrain_obj.sprite_name == "rock_small"): - self.walkable_matrix[r][c] = False - - # Отмечаем маленькие камни - if cell.terrain_obj and cell.terrain_obj.sprite_name == "rock_small": - self.rocks_matrix[r][c] = True def OLDSTABLE_draw_map(self, screen, current_frame, grid=True): terrain_list = [] @@ -421,12 +387,10 @@ class Engine: elf = eb_objects.Creature(id=f"elf_{random.randint(1000,9999)}", name="Elf", sprite_name="elf_watching", - grid_pos = pos, - walkable_matrix = easy_map.walkable_matrix, - rocks_matrix = easy_map.rocks_matrix) + grid_pos = pos) - r_move_short = eb_objects.Action(sprite_name="elf_watching", func=partial(elf.patrol, easy_map.cells, 3), duration=0.01) - r_move_long = eb_objects.Action(sprite_name="elf_watching", func=partial(elf.move_rand, easy_map.cells, 0, 99), duration=0.01) + r_move_short = eb_objects.Action(sprite_name="elf_watching", func=partial(elf.patrol, 3), duration=0.01) + r_move_long = eb_objects.Action(sprite_name="elf_watching", func=partial(elf.move_rand, 0, 99), duration=0.01) elf.tasks.append([r_move_short]*5 + [r_move_long]) easy_map.cells[row][col].creature_obj = elf @@ -558,7 +522,7 @@ class Engine: cell_coords = easy_map.get_cell_at_mouse(mouse_pos) if cell_coords: #print(f"Движение: {active_cell} -> {cell_coords}") - easy_map.cells[active_cell[0]][active_cell[1]].creature_obj.move(easy_map.cells, cell_coords) + easy_map.cells[active_cell[0]][active_cell[1]].creature_obj.move(cell_coords) if keys[pygame.K_ESCAPE]: running = False diff --git a/eb_objects.py b/eb_objects.py index db90a8c..dc21190 100644 --- a/eb_objects.py +++ b/eb_objects.py @@ -1,4 +1,5 @@ from common import deepcopy, dataclass, field, random +from pathfinder import pathfinder @dataclass class Action: @@ -44,8 +45,8 @@ class Creature(Object): render_offset: tuple = (0.0, 0.0) start_pos: tuple = None # (row, col) начальная позиция сегмента пути waypoints: list = field(default_factory = list) - walkable_matrix: list = field(default_factory = list) - rocks_matrix: list = field(default_factory = list) + #walkable_matrix: list = field(default_factory = list) + #rocks_matrix: list = field(default_factory = list) inventory: dict = field(default_factory = dict) quick_actions: list = field(default_factory = list) @@ -62,8 +63,7 @@ class Creature(Object): interrupt_action_status: str = "completed" def replan(self, cells, pos): - from common import find_way - path = find_way(cells, pos, self.final_goal, self.walkable_matrix, self.rocks_matrix) + path = pathfinder.find_way(pos, self.final_goal) if path and len(path) > 1: self.waypoints = path[1:] self.current_target = self.waypoints[0] @@ -131,10 +131,9 @@ class Creature(Object): offset_y = (target_row - start_row) * cell_size * self.move_progress self.render_offset = (offset_x, offset_y) - def move(self, cells, goal): - from common import find_way + def move(self, goal): self.final_goal = goal - path = find_way(cells, self.grid_pos, self.final_goal, self.walkable_matrix, self.rocks_matrix) + path = pathfinder.find_way(self.grid_pos, self.final_goal) if path and len(path) > 1: self.waypoints = path[1:] self.current_target = self.waypoints[0] @@ -205,12 +204,12 @@ class Creature(Object): # self.action_counter = 0 # if self.task_counter >= len(self.tasks): # self.task_counter = 0 -# + # # self.action = self.tasks[self.task_counter][self.action_counter] # self.action_counter += 1 # self.action_time = 0.0 - def patrol(self, cells, area): + def patrol(self, area): goal = (random.randint(self.grid_pos[0] - area, self.grid_pos[0] + area), random.randint(self.grid_pos[1] - area, @@ -220,13 +219,13 @@ class Creature(Object): self.grid_pos[0] + area), random.randint(self.grid_pos[1] - area, self.grid_pos[1] + area)) - self.move(cells, goal) + self.move(goal) - def move_rand(self, cells, area_start, area_end): + def move_rand(self, area_start, area_end): goal = (random.randint(area_start, area_end), random.randint(area_start, area_end)) while goal == self.grid_pos: goal = (random.randint(area_start, area_end), random.randint(area_start, area_end)) - self.move(cells, goal) + self.move(goal) @dataclass class Item(Object): diff --git a/main.py b/main.py index 78d3d29..923ea30 100644 --- a/main.py +++ b/main.py @@ -12,7 +12,6 @@ if __name__ == "__main__": # прокрутка баг консоль и карта # устроить краш тест поиску пути, запустив много объектов на маленьком поле, успел заметить баги # добавить функцию движения за каким-то объектом - # сделать, чтобы в случае отменненого движения не телепортировался назад, а плавно # приступаем к логике # сделать по аналогии с текущей клеткой текущий объект @@ -24,4 +23,5 @@ if __name__ == "__main__": # система имен спрайтов и Action - реализовать # рисовать группой спрайтов # нужен ли теперь start_pos? grid_pos? - # class Task с проверками выполнения экшонов \ No newline at end of file + # class Task с проверками выполнения экшонов + # Нужен ли cells в виде словаря списков? может иначе сделать? \ No newline at end of file diff --git a/pathfinder.py b/pathfinder.py new file mode 100644 index 0000000..4d8e5ea --- /dev/null +++ b/pathfinder.py @@ -0,0 +1,587 @@ +from dataclasses import dataclass, field + +class Pathfinder: + _instance = None + cells = None + walkable_matrix = field(default_factory = list) + rocks_matrix = field(default_factory = list) + + def __new__(cls): + if cls._instance is None: + cls._instance = super().__new__(cls) + return cls._instance + + def set_map(self, cells): + self.cells = cells + self.update_matrices() + + def update_matrices(self): + rows = len(self.cells) + if rows == 0: + return None, None + + cols = len(self.cells[0]) + + self.walkable_matrix = [[True] * cols for _ in range(rows)] + self.rocks_matrix = [[False] * cols for _ in range(rows)] + + for r in range(rows): + for c in range(cols): + cell = self.cells[r][c] + if cell.creature_obj or \ + (cell.terrain_obj and cell.terrain_obj.sprite_name == "rock_small"): + self.walkable_matrix[r][c] = False + + if cell.terrain_obj and cell.terrain_obj.sprite_name == "rock_small": + self.rocks_matrix[r][c] = True + + def find_way(self, start, goal): + result = self.bfs_quick(start, goal) + return result + + def bfs_quick(self, start, goal): + """★СУПЕРБЫСТРЫЙ BFS: массивы вместо set/deque★""" + rows = len(self.cells) + if rows == 0: + print("Путь не найден: пустая карта") + return None + + cols = len(self.cells[0]) + s_row, s_col = start + g_row, g_col = goal + + if (s_row >= rows or s_col >= cols or + g_row >= rows or g_col >= cols): + #print(f"Путь не найден: выход за границы {start} -> {goal}") + return None + + ## ★ МАТРИЦЫ вместо set (10x быстрее хэширования) ★ + #walkable = [[True] * cols for _ in range(rows)] + #rocks_only = [[False] * cols for _ in range(rows)] + #start_creature = cells[s_row][s_col].creature_obj + # + #for r in range(rows): + # for c in range(cols): + # cell = cells[r][c] + # if (cell.creature_obj and cell.creature_obj != start_creature) or \ + # (cell.terrain_obj and cell.terrain_obj.sprite_name == "rock_small"): + # walkable[r][c] = False + # if cell.terrain_obj and cell.terrain_obj.sprite_name == "rock_small": + # rocks_only[r][c] = True + + # ★ ВЫЧИСЛЯЕМЫЕ МАССИВЫ ★ + visited = [[False] * cols for _ in range(rows)] + parent = [[None] * cols for _ in range(rows)] + + # ★ БЫСТРАЯ ОЧЕРЕДЬ: индекс вместо deque ★ + queue_size = 0 + queue = [[0, 0] for _ in range(rows * cols)] # Предварительно выделяем + queue[0] = [s_row, s_col] + queue_size = 1 + front = 0 + + visited[s_row][s_col] = True + + directions = [(-1,0), (1,0), (0,-1), (0,1), + (-1,-1), (-1,1), (1,-1), (1,1)] + + while front < queue_size: + # ★ БЫСТРОЕ извлечение ★ + r, c = queue[front] + front += 1 + + if r == g_row and c == g_col: + path = [] + cr, cc = g_row, g_col + while True: + path.append((cr, cc)) + if parent[cr][cc] is None: + break + pr, pc = parent[cr][cc] + cr, cc = pr, pc + return path[::-1] + + for dr, dc in directions: + nr, nc = r + dr, c + dc + + if (0 <= nr < rows and 0 <= nc < cols and + self.walkable_matrix[nr][nc] and not visited[nr][nc]): + + # ★ ПРОВЕРКА ДИАГОНАЛИ ★ + if dr * dc == 0 or self.can_move_diagonal(r, c, nr, nc, self.rocks_matrix, rows, cols): + visited[nr][nc] = True + parent[nr][nc] = (r, c) + + # ★ БЫСТРОЕ добавление в очередь ★ + queue[queue_size] = [nr, nc] + queue_size += 1 + + #print(f"Путь не найден: {start} -> {goal}") + return None + + def can_move_diagonal(self, r, c, nr, nc, rocks_only, rows, cols): + """Запрет среза только около rock""" + dr = nr - r + dc = nc - c + r1, c1 = r + dr, c + r2, c2 = r, c + dc + + check1_ok = (0 <= r1 < rows and 0 <= c1 < cols and not rocks_only[r1][c1]) + check2_ok = (0 <= r2 < rows and 0 <= c2 < cols and not rocks_only[r2][c2]) + + return check1_ok and check2_ok + +pathfinder = Pathfinder() + +''' +#def find_way(cells, start, goal): +# """Находит путь от start=(row, col) к goal=(row, col). row=y (строка), col=x (столбец)""" +# rows = len(cells) +# if rows == 0: +# return None +# cols = len(cells[0]) +# def is_passable(cell): +# # Проходимо, если НЕ Rock в terrain И creature_obj отсутствует +# return (cell.terrain_obj is None or not isinstance(cell.terrain_obj, Rock)) +# # Проверка границ и проходимости старт/гол +# s_row, s_col = start +# g_row, g_col = goal +# if not (0 <= s_row < rows and 0 <= s_col < cols and 0 <= g_row < rows and 0 <= g_col < cols): +# return None +# start_cell = cells[s_row][s_col] +# goal_cell = cells[g_row][g_col] +# if not is_passable(start_cell) or not is_passable(goal_cell): +# print(f"Старт/гол непроходимы: start={is_passable(start_cell)}, goal={is_passable(goal_cell)}") +# return None +# # A* поиск (используем прямые row,col вместо ID для простоты) +# directions = [(-1, 0), (1, 0), (0, -1), (0, 1)] # вверх, вниз, лево, право +# open_set = [] +# # f_score = g + h (h=манхэттен) +# h = abs(s_row - g_row) + abs(s_col - g_col) +# heappush(open_set, (h, 0, s_row, s_col)) # f, g, row, col +# came_from = {} +# g_score = defaultdict(lambda: float('inf')) +# g_score[(s_row, s_col)] = 0 +# while open_set: +# _, _, row, col = heappop(open_set) +# if (row, col) == (g_row, g_col): +# # Восстанавливаем путь +# path = [] +# current = (row, col) +# while current in came_from: +# path.append(current) +# current = came_from[current] +# path.append(start) +# return path[::-1] +# for dr, dc in directions: +# nr, nc = row + dr, col + dc +# if 0 <= nr < rows and 0 <= nc < cols: +# if is_passable(cells[nr][nc]): +# tentative_g = g_score[(row, col)] + 1 +# pos = (nr, nc) +# if tentative_g < g_score[pos]: +# came_from[pos] = (row, col) +# g_score[pos] = tentative_g +# f = tentative_g + abs(nr - g_row) + abs(nc - g_col) +# heappush(open_set, (f, tentative_g, nr, nc)) +# print("Путь не найден (нет связи)") +# return None + +#def find_way(cells, start, goal): +# """Находит путь от start=(row, col) к goal=(row, col) с диагональным движением""" +# rows = len(cells) +# if rows == 0: +# return None +# cols = len(cells[0]) +# +# def is_passable(cell): +# return (cell.terrain_obj is None or not isinstance(cell.terrain_obj, Rock)) +# +# s_row, s_col = start +# g_row, g_col = goal +# if not (0 <= s_row < rows and 0 <= s_col < cols and 0 <= g_row < rows and 0 <= g_col < cols): +# return None +# +# start_cell = cells[s_row][s_col] +# goal_cell = cells[g_row][g_col] +# if not is_passable(start_cell) or not is_passable(goal_cell): +# print(f"Старт/гол непроходимы: start={is_passable(start_cell)}, goal={is_passable(goal_cell)}") +# return None +# +# # ★ 8 НАПРАВЛЕНИЙ: 4 основных + 4 диагональных ★ +# directions = [ +# (-1, 0), (1, 0), (0, -1), (0, 1), # вверх, вниз, лево, право (стоимость 1.0) +# (-1, -1), (-1, 1), (1, -1), (1, 1) # диагонали (стоимость √2 ≈ 1.414) +# ] +# +# open_set = [] +# h = abs(s_row - g_row) + abs(s_col - g_col) # эвристика манхэттен +# heappush(open_set, (h, 0, s_row, s_col)) # f, g, row, col +# +# came_from = {} +# g_score = defaultdict(lambda: float('inf')) +# g_score[(s_row, s_col)] = 0 +# +# while open_set: +# _, _, row, col = heappop(open_set) +# if (row, col) == (g_row, g_col): +# # Восстанавливаем путь +# path = [] +# current = (row, col) +# while current in came_from: +# path.append(current) +# current = came_from[current] +# path.append(start) +# return path[::-1] +# +# for dr, dc in directions: +# nr, nc = row + dr, col + dc +# if 0 <= nr < rows and 0 <= nc < cols: +# if is_passable(cells[nr][nc]): +# # ★ РАЗНЫЕ СТОИМОСТИ ДЛЯ ДИАГОНАЛЕЙ ★ +# if abs(dr) + abs(dc) == 2: # диагональ +# move_cost = 1.414 # √2 +# else: # ортогональ +# move_cost = 1.0 +# +# tentative_g = g_score[(row, col)] + move_cost +# pos = (nr, nc) +# +# if tentative_g < g_score[pos]: +# came_from[pos] = (row, col) +# g_score[pos] = tentative_g +# +# # ★ ЧЕБЫШЕВ для диагоналей лучше манхэттена ★ +# h = max(abs(nr - g_row), abs(nc - g_col)) +# f = tentative_g + h +# heappush(open_set, (f, tentative_g, nr, nc)) +# +# print("Путь не найден (нет связи)") +# return None + +#def find_way(cells, start, goal): +# """Находит путь с диагональным движением, но БЕЗ прохода через углы""" +# rows = len(cells) +# if rows == 0: +# return None +# cols = len(cells[0]) +# +# def is_passable(cell): +# return (cell.terrain_obj is None or not isinstance(cell.terrain_obj, Rock)) +# +# s_row, s_col = start +# g_row, g_col = goal +# if not (0 <= s_row < rows and 0 <= s_col < cols and 0 <= g_row < rows and 0 <= g_col < cols): +# return None +# +# start_cell = cells[s_row][s_col] +# goal_cell = cells[g_row][g_col] +# if not is_passable(start_cell) or not is_passable(goal_cell): +# print(f"Старт/гол непроходимы") +# return None +# +# directions = [ +# (-1, 0), (1, 0), (0, -1), (0, 1), # ортогональ (1.0) +# (-1, -1), (-1, 1), (1, -1), (1, 1) # диагональ (1.414) +# ] +# +# def can_move_diagonal(current_r, current_c, dr, dc): +# """Проверяет, можно ли двигаться по диагонали (НЕ через угол)""" +# nr, nc = current_r + dr, current_c + dc +# +# # Ортогональные соседи ДОЛЖНЫ быть проходимыми для диагонального хода +# ortho1_r, ortho1_c = current_r + dr, current_c # вертикальный сосед +# ortho2_r, ortho2_c = current_r, current_c + dc # горизонтальный сосед +# +# # Проверяем границы для ортососедей +# if not (0 <= ortho1_r < rows and 0 <= ortho1_c < cols): +# return False +# if not (0 <= ortho2_r < rows and 0 <= ortho2_c < cols): +# return False +# +# return (is_passable(cells[ortho1_r][ortho1_c]) and +# is_passable(cells[ortho2_r][ortho2_c])) +# +# open_set = [] +# h = max(abs(s_row - g_row), abs(s_col - g_col)) +# heappush(open_set, (h, 0, s_row, s_col)) +# +# came_from = {} +# g_score = defaultdict(lambda: float('inf')) +# g_score[(s_row, s_col)] = 0 +# +# while open_set: +# _, _, row, col = heappop(open_set) +# if (row, col) == (g_row, g_col): +# path = [] +# current = (row, col) +# while current in came_from: +# path.append(current) +# current = came_from[current] +# path.append(start) +# return path[::-1] +# +# for dr, dc in directions: +# nr, nc = row + dr, col + dc +# if 0 <= nr < rows and 0 <= nc < cols: +# target_cell = cells[nr][nc] +# +# if (nr, nc) != start and target_cell.creature_obj is not None: +# continue +# if not is_passable(target_cell): +# continue +# +# if abs(dr) + abs(dc) == 2: +# if not can_move_diagonal(row, col, dr, dc): +# continue +# +# move_cost = 1.414 if abs(dr) + abs(dc) == 2 else 1.0 +# tentative_g = g_score[(row, col)] + move_cost +# pos = (nr, nc) +# +# if tentative_g < g_score[pos]: +# came_from[pos] = (row, col) +# g_score[pos] = tentative_g +# h = max(abs(nr - g_row), abs(nc - g_col)) +# f = tentative_g + h +# heappush(open_set, (f, tentative_g, nr, nc)) +# +# print("Путь не найден") +# return None + + +#def find_way(cells, start, goal): +# """A* pathfinding — только новая библиотека""" +# rows = len(cells) +# if rows == 0: +# print("Путь не найден: пустая карта") +# return None +# +# cols = len(cells[0]) +# +# # ★ Проверка границ ★ +# s_row, s_col = start +# g_row, g_col = goal +# if (s_row >= rows or s_col >= cols or +# g_row >= rows or g_col >= cols): +# print(f"Путь не найден: выход за границы карты {start} -> {goal}") +# return None +# +# # ★ НАХОДИМ существо в start ★ +# start_creature = cells[s_row][s_col].creature_obj +# +# # Матрица препятствий +# matrix = [[1 for _ in cells[row]] for row in range(rows)] +# +# for r in range(rows): +# for c in range(cols): +# cell_creature = cells[r][c].creature_obj +# if cell_creature and cell_creature != start_creature: +# matrix[r][c] = 0 +# +# from pathfinding.core.grid import Grid +# from pathfinding.finder.a_star import AStarFinder +# +# grid = Grid(matrix=matrix) +# start_node = grid.node(s_row, s_col) +# end_node = grid.node(g_row, g_col) +# +# finder = AStarFinder() +# path_nodes, _ = finder.find_path(start_node, end_node, grid) +# +# if not path_nodes or len(path_nodes) <= 1: +# print(f"Путь не найден: {start} -> {goal}") +# return None +# +# path = [(node.x, node.y) for node in path_nodes] +# +# return path + +''' + +''' +#def bfs_quick_d(obstacle_matrix, start, goal): +# rows = len(obstacle_matrix) +# if rows == 0: +# print("❌ DEBUG: ПУСТАЯ МАТРИЦА") +# return None +# +# cols = len(obstacle_matrix[0]) +# s_row, s_col = start +# g_row, g_col = goal +# +# print(f"🔍 DEBUG: start={start}, goal={goal}, размер={rows}x{cols}") +# +# if (s_row >= rows or s_col >= cols or +# g_row >= rows or g_col >= cols): +# print(f"❌ DEBUG: ВЫХОД ЗА ГРАНИЦЫ: start({s_row},{s_col}) goal({g_row},{g_col})") +# return None +# +# print(f"✅ DEBUG: Границы OK. obstacle[start]={obstacle_matrix[s_row][s_col]}, obstacle[goal]={obstacle_matrix[g_row][g_col]}") +# +# visited = [[False] * cols for _ in range(rows)] +# parent = [[None] * cols for _ in range(rows)] +# +# queue_size = 0 +# queue = [[0, 0] for _ in range(rows * cols)] +# queue[0] = [s_row, s_col] +# queue_size = 1 +# front = 0 +# +# visited[s_row][s_col] = True +# print(f"✅ DEBUG: Старт добавлен в очередь. queue_size={queue_size}") +# +# directions = [(-1,0), (1,0), (0,-1), (0,1), (-1,-1), (-1,1), (1,-1), (1,1)] +# +# DEBUG_COUNTER = 0 +# +# while front < queue_size: +# r, c = queue[front] +# front += 1 +# DEBUG_COUNTER += 1 +# +# print(f"🔄 DEBUG[{DEBUG_COUNTER}]: обрабатываем ({r},{c}), очередь={front}/{queue_size}") +# +# if r == g_row and c == g_col: +# print(f"✅ DEBUG: НАЙДЕН ЦЕЛЬ! Путь: ({r},{c})") +# path = [] +# cr, cc = g_row, g_col +# while True: +# path.append((cr, cc)) +# if parent[cr][cc] is None: +# break +# pr, pc = parent[cr][cc] +# cr, cc = pr, pc +# return path[::-1] +# +# for dr, dc in directions: +# nr, nc = r + dr, c + dc +# +# print(f" 📍 Проверяем соседа ({nr},{nc}): граница={0<=nr 0 else 'ПУСТО'}") +# print(f" Цель ({g_row},{g_col}) помечена как visited? {visited[g_row][g_col]}") +# return None +# +# +#def bfs_quick(obstacle_matrix, start, goal): +# """★СУПЕРБЫСТРЫЙ BFS 8-направлений★ +# obstacle_matrix - ТОЛЬКО камни +# """ +# rows = len(obstacle_matrix) +# if rows == 0: +# return None +# +# cols = len(obstacle_matrix[0]) +# s_row, s_col = start +# g_row, g_col = goal +# +# if (s_row >= rows or s_col >= cols or +# g_row >= rows or g_col >= cols): +# return None +# +# # ★ МАТРИЦЫ состояния ★ +# visited = [[False] * cols for _ in range(rows)] +# parent = [[None] * cols for _ in range(rows)] +# +# # ★ БЫСТРАЯ ОЧЕРЕДЬ ★ +# queue_size = 0 +# queue = [[0, 0] for _ in range(rows * cols)] +# queue[0] = [s_row, s_col] +# queue_size = 1 +# front = 0 +# +# visited[s_row][s_col] = True +# +# # ★ 8 НАПРАВЛЕНИЙ ★ +# directions = [(-1,0), (1,0), (0,-1), (0,1), # Кардинальные (всегда) +# (-1,-1), (-1,1), (1,-1), (1,1)] # Диагональные +# +# while front < queue_size: +# r, c = queue[front] +# front += 1 +# +# if r == g_row and c == g_col: +# # ★ Реконструкция пути ★ +# path = [] +# cr, cc = g_row, g_col +# while True: +# path.append((cr, cc)) +# if parent[cr][cc] is None: +# break +# pr, pc = parent[cr][cc] +# cr, cc = pr, pc +# return path[::-1] +# +# for dr, dc in directions: +# nr, nc = r + dr, c + dc +# +# if not (0 <= nr < rows and 0 <= nc < cols): +# continue +# if visited[nr][nc] or obstacle_matrix[nr][nc]: +# continue +# +# diagonal_ok = True +# if dr * dc != 0: +# diagonal_ok = can_move_diagonal(r, c, nr, nc, obstacle_matrix) +# if diagonal_ok: +# visited[nr][nc] = True +# parent[nr][nc] = (r, c) +# queue[queue_size] = [nr, nc] +# queue_size += 1 +# +# return None +''' +#def can_move_diagonal(r, c, nr, nc, obstacle_matrix): +# """Диагональ БЛОКИРУЕТСЯ только камнями по углам""" +# rows, cols = len(obstacle_matrix), len(obstacle_matrix[0]) +# dr = nr - r +# dc = nc - c +# +# r1, c1 = r + dr, c # Вертикальная соседка +# r2, c2 = r, c + dc # Горизонтальная соседка +# +# check1_ok = (0 <= r1 < rows and 0 <= c1 < cols and not obstacle_matrix[r1][c1]) +# check2_ok = (0 <= r2 < rows and 0 <= c2 < cols and not obstacle_matrix[r2][c2]) +# +# return check1_ok and check2_ok + +#def can_move_diagonal(r, c, nr, nc, obstacle_matrix): +# """Проверка диагонали с границами""" +# rows, cols = len(obstacle_matrix), len(obstacle_matrix[0]) +# dr = nr - r +# dc = nc - c +# +# # ★ ПРОВЕРКА ГРАНИЦ ПОПЕРЕДУ ★ +# r1, c1 = r + dr, c # вертикальная +# r2, c2 = r, c + dc # горизонтальная +# +# # Если УЖЕ за границей - False +# if not (0 <= r1 < rows and 0 <= c1 < cols): +# return False +# if not (0 <= r2 < rows and 0 <= c2 < cols): +# return False +# +# return not obstacle_matrix[r1][c1] and not obstacle_matrix[r2][c2] \ No newline at end of file