Files
Elvenbane/common.py

577 lines
22 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import os
import json
import uuid
import random
from dataclasses import dataclass, field
from copy import deepcopy
from functools import partial
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, walkable, rocks_only):
result = bfs_quick(cells, start, goal, walkable, rocks_only)
return result
'''
#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(cells, start, goal, walkable, rocks_only):
"""★СУПЕРБЫСТРЫЙ 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
'''
#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<rows and 0<=nc<cols}, "
# f"visited={visited[nr][nc]}, obstacle={obstacle_matrix[nr][nc]}")
#
# if (0 <= nr < rows and 0 <= nc < cols and
# not visited[nr][nc] and
# not obstacle_matrix[nr][nc]):
#
# diagonal_ok = True
# if dr * dc != 0:
# diagonal_ok = can_move_diagonal(r, c, nr, nc, obstacle_matrix)
# print(f" ↘️ Диагональ: {diagonal_ok}")
#
# if diagonal_ok:
# visited[nr][nc] = True
# parent[nr][nc] = (r, c)
# queue[queue_size] = [nr, nc]
# queue_size += 1
# print(f" ✅ Добавили ({nr},{nc}) в очередь. queue_size={queue_size}")
# else:
# print(f" ❌ Диагональ заблокирована!")
# else:
# print(f" ❌ Сосед отклонен!")
#
# print(f"❌ DEBUG: ОЧЕРЕДЬ ОПУСТЕЛА! Обработано {DEBUG_COUNTER} узлов")
# print(f" Последняя клетка в очереди: {queue[front-1] if front > 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]