diff --git a/__pycache__/common.cpython-314.pyc b/__pycache__/common.cpython-314.pyc index e596c89..17ef8f3 100644 Binary files a/__pycache__/common.cpython-314.pyc and b/__pycache__/common.cpython-314.pyc differ diff --git a/__pycache__/eb_engine.cpython-314.pyc b/__pycache__/eb_engine.cpython-314.pyc index 4c908b3..a73de40 100644 Binary files a/__pycache__/eb_engine.cpython-314.pyc and b/__pycache__/eb_engine.cpython-314.pyc differ diff --git a/__pycache__/eb_objects.cpython-314.pyc b/__pycache__/eb_objects.cpython-314.pyc index 32beb50..8f5f73e 100644 Binary files a/__pycache__/eb_objects.cpython-314.pyc and b/__pycache__/eb_objects.cpython-314.pyc differ 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