Добавил ускоренный A* из библиотеки pathfinding - произволительность выросла, но несильно. Пока этот вариант закомментил, сейчас реализация BFS + walkable матрица, работает гораздо лучше, с неоптимальным рендером 100 объектов держит, без рендера 300.
This commit is contained in:
285
common.py
285
common.py
@@ -1,6 +1,7 @@
|
||||
import os
|
||||
import json
|
||||
import uuid
|
||||
import random
|
||||
from dataclasses import dataclass, field
|
||||
from copy import deepcopy
|
||||
|
||||
@@ -12,6 +13,14 @@ 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:
|
||||
@@ -146,87 +155,233 @@ def path_exists(data, path):
|
||||
# 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])
|
||||
|
||||
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):
|
||||
|
||||
if (s_row >= rows or s_col >= cols or
|
||||
g_row >= rows or g_col >= cols):
|
||||
print(f"Путь не найден: выход за границы {start} -> {goal}")
|
||||
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
|
||||
# ★ МАТРИЦЫ вместо 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
|
||||
|
||||
directions = [
|
||||
(-1, 0), (1, 0), (0, -1), (0, 1), # ортогональ (1.0)
|
||||
(-1, -1), (-1, 1), (1, -1), (1, 1) # диагональ (1.414)
|
||||
]
|
||||
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
|
||||
|
||||
def can_move_diagonal(current_r, current_c, dr, dc):
|
||||
"""Проверяет, можно ли двигаться по диагонали (НЕ через угол)"""
|
||||
nr, nc = current_r + dr, current_c + dc
|
||||
# ★ ВЫЧИСЛЯЕМЫЕ МАССИВЫ ★
|
||||
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
|
||||
|
||||
# Ортогональные соседи ДОЛЖНЫ быть проходимыми для диагонального хода
|
||||
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):
|
||||
if r == g_row and c == g_col:
|
||||
path = []
|
||||
current = (row, col)
|
||||
while current in came_from:
|
||||
path.append(current)
|
||||
current = came_from[current]
|
||||
path.append(start)
|
||||
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 = row + dr, col + dc
|
||||
if 0 <= nr < rows and 0 <= nc < cols and is_passable(cells[nr][nc]):
|
||||
nr, nc = r + dr, c + dc
|
||||
|
||||
if (0 <= nr < rows and 0 <= nc < cols and
|
||||
walkable[nr][nc] and not visited[nr][nc]):
|
||||
|
||||
# ★ ПРОВЕРКА ДИАГОНАЛИ ★
|
||||
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))
|
||||
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("Путь не найден")
|
||||
return None
|
||||
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
|
||||
Reference in New Issue
Block a user