387 lines
15 KiB
Python
387 lines
15 KiB
Python
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 |