Diagonal pathfinding and moving works great! Fixed teleport bug and terrain overlay.
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
187
common.py
187
common.py
@@ -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
|
# ★ ПРОВЕРКА ДИАГОНАЛИ ★
|
||||||
|
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)
|
pos = (nr, nc)
|
||||||
|
|
||||||
if tentative_g < g_score[pos]:
|
if tentative_g < g_score[pos]:
|
||||||
came_from[pos] = (row, col)
|
came_from[pos] = (row, col)
|
||||||
g_score[pos] = tentative_g
|
g_score[pos] = tentative_g
|
||||||
f = tentative_g + abs(nr - g_row) + abs(nc - g_col)
|
h = max(abs(nr - g_row), abs(nc - g_col))
|
||||||
|
f = tentative_g + h
|
||||||
heappush(open_set, (f, tentative_g, nr, nc))
|
heappush(open_set, (f, tentative_g, nr, nc))
|
||||||
print("Путь не найден (нет связи)")
|
|
||||||
|
print("Путь не найден")
|
||||||
return None
|
return None
|
||||||
@@ -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()
|
||||||
@@ -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 = {}
|
||||||
|
|||||||
15
main.py
15
main.py
@@ -38,9 +38,11 @@ if __name__ == "__main__":
|
|||||||
# - общие +
|
# - общие +
|
||||||
main()
|
main()
|
||||||
#
|
#
|
||||||
# ДОДЕЛАТЬ move для Creature - хранить pos в объекте
|
# !!! ДОБАВИТЬ ПРОКРУТКУ И МАСШТАБ КАРТЫ ДЛЯ МЫШИ !!!
|
||||||
#
|
#
|
||||||
# ПРОВЕРИТЬ МЕНЯЕТСЯ ЛИ ПЕРЕДАННЫЙ В ОБЪЕКТ cells и еслт да,
|
# ДОДЕЛАТЬ move для Creature - хранить pos в объекте ???
|
||||||
|
#
|
||||||
|
# ПРОВЕРИТЬ МЕНЯЕТСЯ ЛИ ПЕРЕДАННЫЙ В ОБЪЕКТ cells и если да,
|
||||||
# перенести всё взаимодействие с картой в объекты, карта только хранит cells
|
# перенести всё взаимодействие с картой в объекты, карта только хранит cells
|
||||||
# и готовит данные для отрисовки Render'ом
|
# и готовит данные для отрисовки Render'ом
|
||||||
#
|
#
|
||||||
@@ -56,6 +58,13 @@ if __name__ == "__main__":
|
|||||||
#
|
#
|
||||||
# исправить поиск пути чтобы он учитывал других существ
|
# исправить поиск пути чтобы он учитывал других существ
|
||||||
#
|
#
|
||||||
|
#
|
||||||
|
# сделать активного юнита - отряд с кружочком выделения
|
||||||
|
# групповое выделение мышью
|
||||||
|
# группировка и движение отряда - алгоритм стаи?
|
||||||
|
#
|
||||||
|
#
|
||||||
|
#
|
||||||
# в дальнейшем вся отрисовка переедет в класс рендер,
|
# в дальнейшем вся отрисовка переедет в класс рендер,
|
||||||
# карта будет только вовзращать поверхность для отрисовки или даже просто Cells
|
# карта будет только вовзращать поверхность для отрисовки или даже просто Cells
|
||||||
# active_cell переедет в класс Control
|
# active_cell переедет в класс Control
|
||||||
@@ -67,5 +76,3 @@ if __name__ == "__main__":
|
|||||||
# Альтернатива
|
# Альтернатива
|
||||||
#if a is not None:
|
#if a is not None:
|
||||||
# print("a не None")
|
# print("a не None")
|
||||||
#
|
|
||||||
#
|
|
||||||
Reference in New Issue
Block a user