Diagonal pathfinding and moving works great! Fixed teleport bug and terrain overlay.

This commit is contained in:
shiva404
2026-02-20 04:53:33 +03:00
parent 7fbc1b38c2
commit 2b114ddd2d
7 changed files with 199 additions and 37 deletions

Binary file not shown.

197
common.py
View File

@@ -21,38 +21,185 @@ def path_exists(data, path):
return False return False
return True 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): def find_way(cells, start, goal):
"""Находит путь от start=(row, col) к goal=(row, col). row=y (строка), col=x (столбец)""" """Находит путь с диагональным движением, но БЕЗ прохода через углы"""
rows = len(cells) rows = len(cells)
if rows == 0: if rows == 0:
return None return None
cols = len(cells[0]) cols = len(cells[0])
def is_passable(cell): def is_passable(cell):
# Проходимо, если НЕ Rock в terrain И creature_obj отсутствует
return (cell.terrain_obj is None or not isinstance(cell.terrain_obj, Rock)) return (cell.terrain_obj is None or not isinstance(cell.terrain_obj, Rock))
# Проверка границ и проходимости старт/гол
s_row, s_col = start s_row, s_col = start
g_row, g_col = goal 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 not (0 <= s_row < rows and 0 <= s_col < cols and 0 <= g_row < rows and 0 <= g_col < cols):
return None return None
start_cell = cells[s_row][s_col] start_cell = cells[s_row][s_col]
goal_cell = cells[g_row][g_col] goal_cell = cells[g_row][g_col]
if not is_passable(start_cell) or not is_passable(goal_cell): if not is_passable(start_cell) or not is_passable(goal_cell):
print(f"Старт/гол непроходимы: start={is_passable(start_cell)}, goal={is_passable(goal_cell)}") print(f"Старт/гол непроходимы")
return None return None
# A* поиск (используем прямые row,col вместо ID для простоты)
directions = [(-1, 0), (1, 0), (0, -1), (0, 1)] # вверх, вниз, лево, право 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 = [] open_set = []
# f_score = g + h (h=манхэттен) h = max(abs(s_row - g_row), abs(s_col - g_col))
h = abs(s_row - g_row) + abs(s_col - g_col) heappush(open_set, (h, 0, s_row, s_col))
heappush(open_set, (h, 0, s_row, s_col)) # f, g, row, col
came_from = {} came_from = {}
g_score = defaultdict(lambda: float('inf')) g_score = defaultdict(lambda: float('inf'))
g_score[(s_row, s_col)] = 0 g_score[(s_row, s_col)] = 0
while open_set: while open_set:
_, _, row, col = heappop(open_set) _, _, row, col = heappop(open_set)
if (row, col) == (g_row, g_col): if (row, col) == (g_row, g_col):
# Восстанавливаем путь
path = [] path = []
current = (row, col) current = (row, col)
while current in came_from: while current in came_from:
@@ -60,16 +207,26 @@ def find_way(cells, start, goal):
current = came_from[current] current = came_from[current]
path.append(start) path.append(start)
return path[::-1] return path[::-1]
for dr, dc in directions: for dr, dc in directions:
nr, nc = row + dr, col + dc nr, nc = row + dr, col + dc
if 0 <= nr < rows and 0 <= nc < cols: if 0 <= nr < rows and 0 <= nc < cols and is_passable(cells[nr][nc]):
if is_passable(cells[nr][nc]):
tentative_g = g_score[(row, col)] + 1 # ★ ПРОВЕРКА ДИАГОНАЛИ ★
pos = (nr, nc) if abs(dr) + abs(dc) == 2: # диагональное движение
if tentative_g < g_score[pos]: if not can_move_diagonal(row, col, dr, dc):
came_from[pos] = (row, col) continue # пропускаем запрещённую диагональ
g_score[pos] = tentative_g
f = tentative_g + abs(nr - g_row) + abs(nc - g_col) move_cost = 1.414 if abs(dr) + abs(dc) == 2 else 1.0
heappush(open_set, (f, tentative_g, nr, nc)) tentative_g = g_score[(row, col)] + move_cost
print("Путь не найден (нет связи)") 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 return None

View File

@@ -383,7 +383,7 @@ class Engine:
easy_map.cells[active_cell[0]][active_cell[1]].creature_obj is not None): easy_map.cells[active_cell[0]][active_cell[1]].creature_obj is not None):
cell_coords = easy_map.get_cell_at_mouse(mouse_pos) cell_coords = easy_map.get_cell_at_mouse(mouse_pos)
if cell_coords: if cell_coords:
print(f"Движение: {active_cell} -> {cell_coords}") #print(f"Движение: {active_cell} -> {cell_coords}")
easy_map.cells[active_cell[0]][active_cell[1]].creature_obj.move( easy_map.cells[active_cell[0]][active_cell[1]].creature_obj.move(
easy_map.cells, active_cell, cell_coords) easy_map.cells, active_cell, cell_coords)
@@ -401,6 +401,6 @@ class Engine:
if global_counter % 10 == 0: if global_counter % 10 == 0:
current_fps = clock.get_fps() current_fps = clock.get_fps()
#print(f"Current FPS: {current_fps:.2f}") print(f"Current FPS: {current_fps:.2f}")
pygame.quit() pygame.quit()

View File

@@ -78,15 +78,13 @@ class Creature(Object):
return return
# ★ ТОЛЬКО интерполяция offset ★ # ★ ТОЛЬКО интерполяция offset ★
start_row, start_col = self.start_pos or (0, 0) start_row, start_col = self.start_pos #or (0, 0)
target_row, target_col = self.current_target target_row, target_col = self.current_target
offset_x = (target_col - start_col) * cell_size * self.move_progress offset_x = (target_col - start_col) * cell_size * self.move_progress
offset_y = (target_row - start_row) * cell_size * self.move_progress offset_y = (target_row - start_row) * cell_size * self.move_progress
self.render_offset = (offset_x, offset_y) self.render_offset = (offset_x, offset_y)
@dataclass @dataclass
class Item(Object): class Item(Object):
# passive_abilities = {} # passive_abilities = {}

17
main.py
View File

@@ -38,9 +38,11 @@ if __name__ == "__main__":
# - общие + # - общие +
main() main()
# #
# ДОДЕЛАТЬ move для Creature - хранить pos в объекте # !!! ДОБАВИТЬ ПРОКРУТКУ И МАСШТАБ КАРТЫ ДЛЯ МЫШИ !!!
# #
# ПРОВЕРИТЬ МЕНЯЕТСЯ ЛИ ПЕРЕДАННЫЙ В ОБЪЕКТ cells и еслт да, # ДОДЕЛАТЬ move для Creature - хранить pos в объекте ???
#
# ПРОВЕРИТЬ МЕНЯЕТСЯ ЛИ ПЕРЕДАННЫЙ В ОБЪЕКТ cells и если да,
# перенести всё взаимодействие с картой в объекты, карта только хранит cells # перенести всё взаимодействие с картой в объекты, карта только хранит cells
# и готовит данные для отрисовки Render'ом # и готовит данные для отрисовки Render'ом
# #
@@ -54,7 +56,14 @@ if __name__ == "__main__":
# - при вводе текста нет прокрутки к концу # - при вводе текста нет прокрутки к концу
# - плавающий баг - если повводить текст, а потом закрыть консоль, игра не закроется по эскейпу. # - плавающий баг - если повводить текст, а потом закрыть консоль, игра не закроется по эскейпу.
# #
# исправить поиск пути чтобы он учитывал других существ # исправить поиск пути чтобы он учитывал других существ
#
#
# сделать активного юнита - отряд с кружочком выделения
# групповое выделение мышью
# группировка и движение отряда - алгоритм стаи?
#
#
# #
# в дальнейшем вся отрисовка переедет в класс рендер, # в дальнейшем вся отрисовка переедет в класс рендер,
# карта будет только вовзращать поверхность для отрисовки или даже просто Cells # карта будет только вовзращать поверхность для отрисовки или даже просто Cells
@@ -67,5 +76,3 @@ if __name__ == "__main__":
# Альтернатива # Альтернатива
#if a is not None: #if a is not None:
# print("a не None") # print("a не None")
#
#