diff --git a/__pycache__/classes.cpython-314.pyc b/__pycache__/classes.cpython-314.pyc new file mode 100644 index 0000000..b35fa02 Binary files /dev/null and b/__pycache__/classes.cpython-314.pyc differ diff --git a/__pycache__/common.cpython-314.pyc b/__pycache__/common.cpython-314.pyc index ac8ccb8..e596c89 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 7fc20b4..27c024e 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 5017241..32348cb 100644 Binary files a/__pycache__/eb_objects.cpython-314.pyc and b/__pycache__/eb_objects.cpython-314.pyc differ diff --git a/classes.py b/classes.py new file mode 100644 index 0000000..5d3ee5a --- /dev/null +++ b/classes.py @@ -0,0 +1 @@ +from eb_terrain_objects import Rock \ No newline at end of file diff --git a/common.py b/common.py index 5947690..5fc7d41 100644 --- a/common.py +++ b/common.py @@ -3,9 +3,15 @@ import json import uuid from dataclasses import dataclass, field from copy import deepcopy + +from collections import defaultdict +from heapq import heappush, heappop + import pygame import pygame_gui +from classes import Rock + def path_exists(data, path): current = data for key in path: @@ -13,4 +19,57 @@ def path_exists(data, path): current = current[key] else: return False - return True \ No newline at end of file + 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 \ No newline at end of file diff --git a/def_map.json b/def_map.json index 9b9c343..0f2b5e2 100644 --- a/def_map.json +++ b/def_map.json @@ -30,7 +30,7 @@ "terrain_obj": { "id": "1", "name": "2", - "sprite_name": "grass_small" + "sprite_name": "rock_small" }, "item_obj": {}, "creature_obj": {} @@ -928,6 +928,15 @@ "item_obj": {}, "creature_obj": {} }, + { + "terrain_obj": { + "id": "1", + "name": "2", + "sprite_name": "rock_small" + }, + "item_obj": {}, + "creature_obj": {} + }, { "terrain_obj": { "id": "1", @@ -937,6 +946,15 @@ "item_obj": {}, "creature_obj": {} }, + { + "terrain_obj": { + "id": "1", + "name": "2", + "sprite_name": "rock_small" + }, + "item_obj": {}, + "creature_obj": {} + }, { "terrain_obj": { "id": "1", @@ -977,7 +995,7 @@ "terrain_obj": { "id": "1", "name": "2", - "sprite_name": "grass_small" + "sprite_name": "rock_small" }, "item_obj": {}, "creature_obj": {} @@ -1013,25 +1031,7 @@ "terrain_obj": { "id": "1", "name": "2", - "sprite_name": "grass_small" - }, - "item_obj": {}, - "creature_obj": {} - }, - { - "terrain_obj": { - "id": "1", - "name": "2", - "sprite_name": "grass_small" - }, - "item_obj": {}, - "creature_obj": {} - }, - { - "terrain_obj": { - "id": "1", - "name": "2", - "sprite_name": "grass_small" + "sprite_name": "rock_small" }, "item_obj": {}, "creature_obj": {} @@ -1843,7 +1843,25 @@ "terrain_obj": { "id": "1", "name": "2", - "sprite_name": "grass_small" + "sprite_name": "rock_small" + }, + "item_obj": {}, + "creature_obj": {} + }, + { + "terrain_obj": { + "id": "1", + "name": "2", + "sprite_name": "rock_small" + }, + "item_obj": {}, + "creature_obj": {} + }, + { + "terrain_obj": { + "id": "1", + "name": "2", + "sprite_name": "rock_small" }, "item_obj": {}, "creature_obj": {} @@ -1888,7 +1906,16 @@ "terrain_obj": { "id": "1", "name": "2", - "sprite_name": "grass_small" + "sprite_name": "rock_small" + }, + "item_obj": {}, + "creature_obj": {} + }, + { + "terrain_obj": { + "id": "1", + "name": "2", + "sprite_name": "rock_small" }, "item_obj": {}, "creature_obj": {} @@ -1924,34 +1951,7 @@ "terrain_obj": { "id": "1", "name": "2", - "sprite_name": "grass_small" - }, - "item_obj": {}, - "creature_obj": {} - }, - { - "terrain_obj": { - "id": "1", - "name": "2", - "sprite_name": "grass_small" - }, - "item_obj": {}, - "creature_obj": {} - }, - { - "terrain_obj": { - "id": "1", - "name": "2", - "sprite_name": "grass_small" - }, - "item_obj": {}, - "creature_obj": {} - }, - { - "terrain_obj": { - "id": "1", - "name": "2", - "sprite_name": "grass_small" + "sprite_name": "rock_small" }, "item_obj": {}, "creature_obj": {} @@ -2763,7 +2763,7 @@ "terrain_obj": { "id": "1", "name": "2", - "sprite_name": "grass_small" + "sprite_name": "rock_small" }, "item_obj": {}, "creature_obj": {} @@ -2826,7 +2826,16 @@ "terrain_obj": { "id": "1", "name": "2", - "sprite_name": "grass_small" + "sprite_name": "rock_small" + }, + "item_obj": {}, + "creature_obj": {} + }, + { + "terrain_obj": { + "id": "1", + "name": "2", + "sprite_name": "rock_small" }, "item_obj": {}, "creature_obj": {} @@ -2844,7 +2853,7 @@ "terrain_obj": { "id": "1", "name": "2", - "sprite_name": "grass_small" + "sprite_name": "rock_small" }, "item_obj": {}, "creature_obj": {} @@ -2853,16 +2862,7 @@ "terrain_obj": { "id": "1", "name": "2", - "sprite_name": "grass_small" - }, - "item_obj": {}, - "creature_obj": {} - }, - { - "terrain_obj": { - "id": "1", - "name": "2", - "sprite_name": "grass_small" + "sprite_name": "rock_small" }, "item_obj": {}, "creature_obj": {} @@ -3638,7 +3638,16 @@ "terrain_obj": { "id": "1", "name": "2", - "sprite_name": "grass_small" + "sprite_name": "rock_small" + }, + "item_obj": {}, + "creature_obj": {} + }, + { + "terrain_obj": { + "id": "1", + "name": "2", + "sprite_name": "rock_small" }, "item_obj": {}, "creature_obj": {} @@ -3674,7 +3683,7 @@ "terrain_obj": { "id": "1", "name": "2", - "sprite_name": "grass_small" + "sprite_name": "rock_small" }, "item_obj": {}, "creature_obj": {} @@ -3683,7 +3692,7 @@ "terrain_obj": { "id": "1", "name": "2", - "sprite_name": "grass_small" + "sprite_name": "rock_small" }, "item_obj": {}, "creature_obj": {} @@ -3692,16 +3701,7 @@ "terrain_obj": { "id": "1", "name": "2", - "sprite_name": "grass_small" - }, - "item_obj": {}, - "creature_obj": {} - }, - { - "terrain_obj": { - "id": "1", - "name": "2", - "sprite_name": "grass_small" + "sprite_name": "rock_small" }, "item_obj": {}, "creature_obj": {} @@ -4549,7 +4549,7 @@ "terrain_obj": { "id": "1", "name": "2", - "sprite_name": "grass_small" + "sprite_name": "rock_small" }, "item_obj": {}, "creature_obj": {} @@ -6335,7 +6335,25 @@ "terrain_obj": { "id": "1", "name": "2", - "sprite_name": "grass_small" + "sprite_name": "rock_small" + }, + "item_obj": {}, + "creature_obj": {} + }, + { + "terrain_obj": { + "id": "1", + "name": "2", + "sprite_name": "rock_small" + }, + "item_obj": {}, + "creature_obj": {} + }, + { + "terrain_obj": { + "id": "1", + "name": "2", + "sprite_name": "rock_small" }, "item_obj": {}, "creature_obj": {} @@ -6380,7 +6398,16 @@ "terrain_obj": { "id": "1", "name": "2", - "sprite_name": "grass_small" + "sprite_name": "rock_small" + }, + "item_obj": {}, + "creature_obj": {} + }, + { + "terrain_obj": { + "id": "1", + "name": "2", + "sprite_name": "rock_small" }, "item_obj": {}, "creature_obj": {} @@ -6416,7 +6443,7 @@ "terrain_obj": { "id": "1", "name": "2", - "sprite_name": "grass_small" + "sprite_name": "rock_small" }, "item_obj": {}, "creature_obj": {} @@ -6425,7 +6452,7 @@ "terrain_obj": { "id": "1", "name": "2", - "sprite_name": "grass_small" + "sprite_name": "rock_small" }, "item_obj": {}, "creature_obj": {} @@ -6434,34 +6461,7 @@ "terrain_obj": { "id": "1", "name": "2", - "sprite_name": "grass_small" - }, - "item_obj": {}, - "creature_obj": {} - }, - { - "terrain_obj": { - "id": "1", - "name": "2", - "sprite_name": "grass_small" - }, - "item_obj": {}, - "creature_obj": {} - }, - { - "terrain_obj": { - "id": "1", - "name": "2", - "sprite_name": "grass_small" - }, - "item_obj": {}, - "creature_obj": {} - }, - { - "terrain_obj": { - "id": "1", - "name": "2", - "sprite_name": "grass_small" + "sprite_name": "rock_small" }, "item_obj": {}, "creature_obj": {} diff --git a/eb_engine.py b/eb_engine.py index 33a8a4d..d5cd630 100644 --- a/eb_engine.py +++ b/eb_engine.py @@ -15,9 +15,11 @@ main_dir = os.path.dirname(os.path.abspath(__file__)) sprites_dir = os.path.join(main_dir, "res", "sprites") #class Render +#class ObjectManager #class MapManager #class Event #class EventManager +#class Control @dataclass class Cell: @@ -60,22 +62,45 @@ class Map: self.cells[line].append(final_cell) - def move_obj(self, type, s_x, s_y, d_x, d_y): - if d_y >= len(self.cells) or d_x >= len(self.cells[s_y]) or s_y >= len(self.cells) or s_x >= len(self.cells[s_y]): - return False + def move_obj(self, type, start, goal): + """Перемещает объект типа 'terrain_obj', 'item_obj' или 'creature_obj' + из клетки start=(row, col) в goal=(row, col)""" + s_y, s_x = start + d_y, d_x = goal + + # Проверка границ + if (s_y >= len(self.cells) or s_x >= len(self.cells[s_y]) or + d_y >= len(self.cells) or d_x >= len(self.cells[d_y])): + return False + source_cell = self.cells[s_y][s_x] dest_cell = self.cells[d_y][d_x] obj = getattr(source_cell, type) if obj is None: return False - + setattr(source_cell, type, None) setattr(dest_cell, type, obj) return True - def get_way(self, s_x, s_y, d_x, d_y): - pass + def get_cell_at_mouse(self, mouse_pos): + """Возвращает индексы клетки (row, col) по позиции мыши или None если вне карты""" + mx, my = mouse_pos + # Переводим экранные координаты в координаты карты с учетом камеры и масштаба + map_x = (mx - self.cam_x * self.scale) / self.scale / self.size + map_y = (my - self.cam_y * self.scale) / self.scale / self.size + + # Получаем индексы ближайшей клетки + col = int(map_x) + row = int(map_y) + + # Проверяем границы карты + if (row < 0 or row >= len(self.cells) or + col < 0 or col >= len(self.cells[row])): + return None + + return (row, col) def draw_map(self, screen, current_frame, grid = True): for j in range(len(self.cells)): @@ -97,8 +122,19 @@ class Map: cell.creature_obj.draw(dd) if grid: - pygame.draw.rect(screen, self.color, pygame.Rect(dd["x"], dd["y"], dd["w"], dd["h"]), self.bord) - + if cell.is_target: + pygame.draw.rect(screen, self.target_color, pygame.Rect(dd["x"], dd["y"], dd["w"], dd["h"]), self.bord) + else: + pygame.draw.rect(screen, self.color, pygame.Rect(dd["x"], dd["y"], dd["w"], dd["h"]), self.bord) + + def update_map(self, current_frame): + for j in range(len(self.cells)): + for i, cell in enumerate(self.cells[j]): + if cell.creature_obj is not None and len(cell.creature_obj.waypoints) > 1 and current_frame % 57 == 0: + del cell.creature_obj.waypoints[0] + self.move_obj("creature_obj", (j, i), (cell.creature_obj.waypoints[0])) + + @dataclass class Engine: sprites: dict = field(default_factory = dict) @@ -186,6 +222,11 @@ class Engine: def main_loop(self): easy_map = Map("def_map.json", self.cached_sprites) + #print(easy_map.find_way((1, 1), (5, 5))) + #print(easy_map.find_way((0, 0), (1, 1))) + #print(easy_map.find_way((0, 0), (0, 1))) + #print(easy_map.find_way((0, 0), (0, 0))) + #sp = eb_objects.Sprite(self.sprites, "elf_watching") #gr = pygame.image.load(file_path).convert_alpha() @@ -210,6 +251,8 @@ class Engine: global_counter = 0 global_counter_cap = 100000 + active_cell = [] + # profiling process = psutil.Process(os.getpid()) @@ -262,12 +305,13 @@ class Engine: # fill the screen with a color to wipe away anything from last frame #self.screen.fill("chartreuse4") self.screen.blit(background, (0, 0)) - + easy_map.update_map(global_counter) easy_map.draw_map(self.screen, current_frame + 1) manager.draw_ui(self.screen) if not console_active: keys = pygame.key.get_pressed() + if keys[pygame.K_w]: easy_map.cam_y += self.camera_step if keys[pygame.K_s]: @@ -276,6 +320,7 @@ class Engine: easy_map.cam_x += self.camera_step if keys[pygame.K_d]: easy_map.cam_x -= self.camera_step + if keys[pygame.K_q]: easy_map.scale += self.scale_step self.spr_scale += self.scale_step @@ -284,6 +329,27 @@ class Engine: easy_map.scale -= self.scale_step self.spr_scale -= self.scale_step self.scale_sprites() + + if event.type == pygame.MOUSEBUTTONDOWN and event.button == 1: # Левая кнопка + mouse_pos = pygame.mouse.get_pos() + console_rect = console_window.get_abs_rect() + if not console_rect.collidepoint(mouse_pos): + cell_coords = easy_map.get_cell_at_mouse(mouse_pos) + if cell_coords: + if active_cell: + easy_map.cells[active_cell[0]][active_cell[1]].is_target = False + active_cell = cell_coords + easy_map.cells[active_cell[0]][active_cell[1]].is_target = True + + + if event.type == pygame.MOUSEBUTTONDOWN and event.button == 3: + mouse_pos = pygame.mouse.get_pos() + cell_coords = easy_map.get_cell_at_mouse(mouse_pos) + console_rect = console_window.get_abs_rect() + if not console_rect.collidepoint(mouse_pos) and 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.move(easy_map.cells, (active_cell[0], active_cell[1]), (cell_coords[0], cell_coords[1])) + + if keys[pygame.K_ESCAPE]: running = False @@ -298,6 +364,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 17321fd..eaf6527 100644 --- a/eb_objects.py +++ b/eb_objects.py @@ -1,4 +1,4 @@ -from common import deepcopy, dataclass, field, path_exists +from common import deepcopy, dataclass, field @dataclass class Object: @@ -6,7 +6,8 @@ class Object: name: str sprite_name: str sprite_state: int = 0 - + # current_map + # pos # weight # effects = {} @@ -30,7 +31,7 @@ class Terrain(Object): @dataclass class Creature(Object): - current_action: int = 0 + waypoints: list = field(default_factory = list) quick_actions: list = field(default_factory = list) tasks: list = field(default_factory = list) inventory: dict = field(default_factory = dict) @@ -38,6 +39,10 @@ class Creature(Object): def update(self): pass + def move(self, cells, start, goal): + from common import find_way + self.waypoints = find_way(cells, start, goal) + @dataclass class Item(Object): @@ -53,4 +58,4 @@ class Container(Item): @dataclass class Building(Object): - pass \ No newline at end of file + pass diff --git a/main.py b/main.py index f63a54d..d357117 100644 --- a/main.py +++ b/main.py @@ -37,8 +37,35 @@ if __name__ == "__main__": # - deepcopy + # - общие + main() + # + # ДОДЕЛАТЬ move для Creature - хранить pos в объекте + # + # ПРОВЕРИТЬ МЕНЯЕТСЯ ЛИ ПЕРЕДАННЫЙ В ОБЪЕКТ cells и еслт да, + # перенести всё взаимодействие с картой в объекты, карта только хранит cells + # и готовит данные для отрисовки Render'ом + # + # ИГРОВОЙ ТАКТ? или только для действий их длительность? + # + # ПОСМОТРЕТЬ ПО КОММИТАМ ЗАЧЕМ БЫЛ НУЖЕН path_exists, удалить? + # # добавил гуй, динамическая консоль, всё работает, но: # - слоп, почистить # - мини-баг - если первые вводимые буквы совпадают с клавишами управления, один раз успевает проскочить до лока. некритично. # - при вводе текста нет прокрутки к концу - # - плавающий баг - если повводить текст, а потом закрыть консоль, игра не закроется по эскейпу. \ No newline at end of file + # - плавающий баг - если повводить текст, а потом закрыть консоль, игра не закроется по эскейпу. + # + # исправить поиск пути чтобы он учитывал других существ + # + # в дальнейшем вся отрисовка переедет в класс рендер, + # карта будет только вовзращать поверхность для отрисовки или даже просто Cells + # active_cell переедет в класс Control + # + # НАЙТИ В КОДЕ ГДЕ Я ТАК НЕ СДЕЛАЛ И ИСПРАВИТЬ - НАШЕЛ ОДНУ, ПОИСКАТЬ ЕЩЕ + #if a is None: + # print("a это точно None") + # + # Альтернатива + #if a is not None: + # print("a не None") + # + # \ No newline at end of file