import os import json import uuid import random from dataclasses import dataclass, field from copy import deepcopy from collections import defaultdict from heapq import heappush, heappop import pygame import pygame_gui from classes import Rock from pathfinding.core.grid import Grid from pathfinding.finder.a_star import AStarFinder from collections import defaultdict from heapq import heappush, heappop from math import sqrt, inf def path_exists(data, path): current = data for key in path: if isinstance(current, dict) and key in current: current = current[key] else: return False return True #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 find_way(cells, start, goal): """★СУПЕРБЫСТРЫЙ BFS: массивы вместо set/deque★""" 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 # ★ МАТРИЦЫ вместо 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 walkable[nr][nc] and not visited[nr][nc]): # ★ ПРОВЕРКА ДИАГОНАЛИ ★ if dr * dc == 0 or can_move_diagonal(r, c, nr, nc, rocks_only, 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(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