From 2b114ddd2d9ba56d50651105479e1c8e4dae47f6 Mon Sep 17 00:00:00 2001 From: shiva404 Date: Fri, 20 Feb 2026 04:53:33 +0300 Subject: [PATCH] Diagonal pathfinding and moving works great! Fixed teleport bug and terrain overlay. --- __pycache__/common.cpython-314.pyc | Bin 3704 -> 4867 bytes __pycache__/eb_engine.cpython-314.pyc | Bin 22724 -> 22703 bytes __pycache__/eb_objects.cpython-314.pyc | Bin 5608 -> 5568 bytes common.py | 197 ++++++++++++++++++++++--- eb_engine.py | 16 +- eb_objects.py | 4 +- main.py | 19 ++- 7 files changed, 199 insertions(+), 37 deletions(-) diff --git a/__pycache__/common.cpython-314.pyc b/__pycache__/common.cpython-314.pyc index e596c89afb10b94ce9dea991448b5948bfd0937c..17ef8f37ece86794ed51844e0ea072cfd0f4d118 100644 GIT binary patch literal 4867 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 delta 1965 zcmah~Uu;uV7(e$<>Hq)h+OF+(x5{W)x49L}7z`O>A~c98a5~H~7;C9BrMtP^7=xG= zh&U3B#2aHtT-*aBHDWT1ixUL#!N`M&r*6Yi7A3?N-;CkGH~sFtE0o2A)3o<|-}n3e zp5Hw^-~5l))M7H~5Ugt-emr_tf8EqaXa8mq%Ar9@QcDY?O{;sJ!ORfq8bBz{Aml|+ z%Kr>*2!-ktAL;jr7aOIWh*x21l14~KVp@J)96|@zP0(BEu5O5@k>X*d%ZB!#_;yLy z$27T^Au-aAJEXR;+`n9$sC)oyJFv^ND&&DvIC@X88cp6YATZy7n(T1X${S==y zv?1O&sH%jDy%~}ZNs%iqRraa>vxh3~@Z?Q{(6X{(zYU>SGR$s*(~wpJYDc_zP^(A? zMO9hn)5Eh2A#4FS-3)eYtW2Vxif@zq)mPlGu~OJ15U2t1R^GuxHvNf+8K@fMTEbASWa198Xv7?$X~3Wc2>*0n|A@(Tx$p;aS|nEzDI&cre&lRAwaqn zl6d88TOVhukJ#k%i(pj(k@@HCYJ$`7f*>8>9tJd|>p} za}WUp>2(wurM%;%!q`OPP)f@Es}BX?Uhb%KGs{dVF|%^km{lvdr*fnKE@e1Fsxv7GE}d>N&v* z`?BWvXgYpma(GJE0iqN!a}25y&zSvcnpPd8{?dq7&Bx3oYwbtgd}7)9LT+!#;t=aB z{_KwLJ$K(&>>VIG4wJ)2i^C~$;^gA+BsqGDn5LFAr|+BXkE~(9o2|K5|Ip~C``_QU zsHywpz^vn{Z_(GjtZC2nuCRvbfqX|kL7Xj1Y^Y?h<@T(Y+{L=KMN@ljcd5Q1w|n}~ z^lL@d^SjPA9awR5v#v$=#)~F#&QeP~6o0hzQx8O=wKIF)UZ0b8c(`eLX75JcbLNbbHs3M-$H=&O^KHw$tc(epXWCC=V@mlt z+1O2y@yTR!mrmaK3=A?1VS=f%LGRq>9(Mw5TXGuSCHuAlOQwidoMn zZS#N6F38@Xd_mRhf|&Vr0gERb Le49B!_OJi|6tF}D delta 241 zcmZ3#k@3hzMqX_`UM>b8c)D(S=Gl$B=gb*rY`$aukCAcP=G&HgSs9Bq&$OS$#^TDD z!Zz8^O_A~IWOJ8J&J7FB5c+ z+b(Rquop;gwNj{JQP8zh*!;osI5VTm<{iHLtc+JD{|eNb{4K~^{Em#`1r@Um)f;jT jn4I7~PXZUcblUHoS7LTCeIf(1CrN-?HJ=Gi;7qS$v_c1#+1nuM67`1X_4cMd6OrL Gh5`WE>mHB* delta 153 zcmX@0{X&~pn~#@^0SKZuOwUx>$g9E4s=>^_&@g!)qxj}PZZ{?=YesdDJP@PjE<9C`QLF|mp8v4Z@xin wv$23PGh@``a$z$dxlY)QF=6ssVQV0%DPqT%HaSDY3P>&%InG!#Ia@Ro09)`TivR!s diff --git a/common.py b/common.py index 5fc7d41..bfeb276 100644 --- a/common.py +++ b/common.py @@ -21,38 +21,185 @@ def path_exists(data, path): 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): - """Находит путь от 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)}") + print(f"Старт/гол непроходимы") 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 = [] - # 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 + 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: @@ -60,16 +207,26 @@ def find_way(cells, start, goal): 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("Путь не найден (нет связи)") + if 0 <= nr < rows and 0 <= nc < cols and is_passable(cells[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)) + + print("Путь не найден") return None \ No newline at end of file diff --git a/eb_engine.py b/eb_engine.py index 94499ac..0870923 100644 --- a/eb_engine.py +++ b/eb_engine.py @@ -114,13 +114,13 @@ class Map: def draw_map(self, screen, current_frame, grid=True): terrain_list = [] creature_list = [] - + # ★ 1 ПАСС: собираем terrain и creatures ★ for j in range(len(self.cells)): for i, cell in enumerate(self.cells[j]): base_x = i * self.cell_size + self.cam_x base_y = j * self.cell_size + self.cam_y - + # Terrain данные terrain_dd = { "x": int(base_x * self.scale), "y": int(base_y * self.scale), @@ -130,7 +130,7 @@ class Map: "sprites": self.sprites, "scale": self.scale, "screen": screen } terrain_list.append((cell.terrain_obj, terrain_dd)) - + # Creature данные (если есть) if cell.creature_obj: offset_x, offset_y = cell.creature_obj.render_offset @@ -138,15 +138,15 @@ class Map: creature_dd["x"] = int((base_x + offset_x) * self.scale) creature_dd["y"] = int((base_y + offset_y) * self.scale) creature_list.append((cell.creature_obj, creature_dd)) - + # ★ 2 ПАСС: рисуем terrain ★ for obj, dd in terrain_list: obj.draw(dd) - + # ★ 3 ПАСС: рисуем ВСЕХ creatures ПОСЛЕ terrain ★ for obj, dd in creature_list: obj.draw(dd) - + # ★ 4 ПАСС: grid поверх всего ★ for j in range(len(self.cells)): for i, cell in enumerate(self.cells[j]): @@ -383,7 +383,7 @@ class Engine: easy_map.cells[active_cell[0]][active_cell[1]].creature_obj is not None): cell_coords = easy_map.get_cell_at_mouse(mouse_pos) 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, cell_coords) @@ -401,6 +401,6 @@ class Engine: if global_counter % 10 == 0: current_fps = clock.get_fps() - #print(f"Current FPS: {current_fps:.2f}") + print(f"Current FPS: {current_fps:.2f}") pygame.quit() \ No newline at end of file diff --git a/eb_objects.py b/eb_objects.py index ef54ad9..db98d10 100644 --- a/eb_objects.py +++ b/eb_objects.py @@ -78,15 +78,13 @@ class Creature(Object): return # ★ ТОЛЬКО интерполяция 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 offset_x = (target_col - start_col) * cell_size * self.move_progress offset_y = (target_row - start_row) * cell_size * self.move_progress self.render_offset = (offset_x, offset_y) - - @dataclass class Item(Object): # passive_abilities = {} diff --git a/main.py b/main.py index d357117..eacb9dd 100644 --- a/main.py +++ b/main.py @@ -38,9 +38,11 @@ if __name__ == "__main__": # - общие + main() # - # ДОДЕЛАТЬ move для Creature - хранить pos в объекте + # !!! ДОБАВИТЬ ПРОКРУТКУ И МАСШТАБ КАРТЫ ДЛЯ МЫШИ !!! # - # ПРОВЕРИТЬ МЕНЯЕТСЯ ЛИ ПЕРЕДАННЫЙ В ОБЪЕКТ cells и еслт да, + # ДОДЕЛАТЬ move для Creature - хранить pos в объекте ??? + # + # ПРОВЕРИТЬ МЕНЯЕТСЯ ЛИ ПЕРЕДАННЫЙ В ОБЪЕКТ cells и если да, # перенести всё взаимодействие с картой в объекты, карта только хранит cells # и готовит данные для отрисовки Render'ом # @@ -54,7 +56,14 @@ if __name__ == "__main__": # - при вводе текста нет прокрутки к концу # - плавающий баг - если повводить текст, а потом закрыть консоль, игра не закроется по эскейпу. # - # исправить поиск пути чтобы он учитывал других существ + # исправить поиск пути чтобы он учитывал других существ + # + # + # сделать активного юнита - отряд с кружочком выделения + # групповое выделение мышью + # группировка и движение отряда - алгоритм стаи? + # + # # # в дальнейшем вся отрисовка переедет в класс рендер, # карта будет только вовзращать поверхность для отрисовки или даже просто Cells @@ -66,6 +75,4 @@ if __name__ == "__main__": # # Альтернатива #if a is not None: - # print("a не None") - # - # \ No newline at end of file + # print("a не None") \ No newline at end of file