Compare commits

...

6 Commits

Author SHA1 Message Date
shiva404
b1548ea182 Сделал ещё одну очевидную оптимизацию - кэширование матриц препятствий, результат превзошёл всё ожидания. 300 объектов - плавные 60 фпс, 500 объектов - 60 фпс с микрофризами. Для масштабов игры этого уже более чем достаточно. Теперь следует добиться такого же для более слабых машин, надо выбрать минимальные системные требования. 2026-03-06 04:00:43 +03:00
shiva404
fa189a4c3b Попробовал самую очевидную оптимизацию рендера, теперь отрисовываются только те клетки, которые в камере. Карта корраптится при движении камеры, надо исправить, зато масштаб работает и прирост производительности очень хороший, теперь до 200 объектов обрабатывается при стабильных 60 ФПС. Промежуточный коммит, хочу посмотреть сколько можно ещё выжать кадров на 500 объектах, потом нужно починить движение камеры. Также написал простую функцию спавна эльфов в главном цикле для создания нагрузки. Эльфы создаются в углах карты и по центру каждые сто тактов цикла. Они начинают перемещаться по карте в случайные точки, создавая относительно равномерную нагрузку поиска пути. 2026-03-05 22:00:32 +03:00
shiva404
45f2c71cb8 Добавил систему задач для юнитов, она реализована в методе update класса Creature. Подчистил код, пофиксил по мелочам баги. Остался ещё техдолг Егору и задачи из main. 2026-03-05 16:35:08 +03:00
Your Name
dafa95989f add requirements.txt (pip freeze); readme 2026-02-24 16:03:29 +03:00
Your Name
8834ac997f add .gitignore & rm cache files 2026-02-24 15:54:39 +03:00
shiva404
6b6ca341dc Добавил ускоренный A* из библиотеки pathfinding - произволительность выросла, но несильно. Пока этот вариант закомментил, сейчас реализация BFS + walkable матрица, работает гораздо лучше, с неоптимальным рендером 100 объектов держит, без рендера 300. 2026-02-24 02:03:22 +03:00
22 changed files with 1147 additions and 141 deletions

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
__pycache__/
.venv/

19
README.md Normal file
View File

@@ -0,0 +1,19 @@
# ElvenBane
## Запуск с virtualenv
#### Linux/WSL:
```
virtualenv .venv
source .venv/bin/activate
```
#### PowerShell:
```
python -m venv .venv
.venv\Scripts\Activate.ps1
```
#### Общее:
```
pip install -r requirements.txt
```

Binary file not shown.

Binary file not shown.

Binary file not shown.

473
common.py
View File

@@ -1,17 +1,22 @@
import os
import json
import uuid
import random
from dataclasses import dataclass, field
from copy import deepcopy
from collections import defaultdict
from heapq import heappush, heappop
from functools import partial
import pygame
import pygame_gui
from classes import Rock
from pathfinding.core.grid import Grid
from pathfinding.finder.a_star import AStarFinder
from collections import defaultdict
from heapq import heappush, heappop
from math import sqrt, inf
def path_exists(data, path):
current = data
for key in path:
@@ -21,6 +26,12 @@ def path_exists(data, path):
return False
return True
def find_way(cells, start, goal, walkable, rocks_only):
result = bfs_quick(cells, start, goal, walkable, rocks_only)
return result
'''
#def find_way(cells, start, goal):
# """Находит путь от start=(row, col) к goal=(row, col). row=y (строка), col=x (столбец)"""
# rows = len(cells)
@@ -146,87 +157,421 @@ def path_exists(data, path):
# print("Путь не найден (нет связи)")
# return None
def find_way(cells, start, goal):
"""Находит путь с диагональным движением, но БЕЗ прохода через углы"""
#def find_way(cells, start, goal):
# """Находит путь с диагональным движением, но БЕЗ прохода через углы"""
# 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"Старт/гол непроходимы")
# return None
#
# 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 = []
# 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:
# 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:
# target_cell = cells[nr][nc]
#
# if (nr, nc) != start and target_cell.creature_obj is not None:
# continue
# if not is_passable(target_cell):
# continue
#
# 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
#def find_way(cells, start, goal):
# """A* pathfinding — только новая библиотека"""
# rows = len(cells)
# if rows == 0:
# print("Путь не найден: пустая карта")
# return None
#
# cols = len(cells[0])
#
# # ★ Проверка границ ★
# s_row, s_col = start
# g_row, g_col = goal
# if (s_row >= rows or s_col >= cols or
# g_row >= rows or g_col >= cols):
# print(f"Путь не найден: выход за границы карты {start} -> {goal}")
# return None
#
# # ★ НАХОДИМ существо в start ★
# start_creature = cells[s_row][s_col].creature_obj
#
# # Матрица препятствий
# matrix = [[1 for _ in cells[row]] for row in range(rows)]
#
# for r in range(rows):
# for c in range(cols):
# cell_creature = cells[r][c].creature_obj
# if cell_creature and cell_creature != start_creature:
# matrix[r][c] = 0
#
# from pathfinding.core.grid import Grid
# from pathfinding.finder.a_star import AStarFinder
#
# grid = Grid(matrix=matrix)
# start_node = grid.node(s_row, s_col)
# end_node = grid.node(g_row, g_col)
#
# finder = AStarFinder()
# path_nodes, _ = finder.find_path(start_node, end_node, grid)
#
# if not path_nodes or len(path_nodes) <= 1:
# print(f"Путь не найден: {start} -> {goal}")
# return None
#
# path = [(node.x, node.y) for node in path_nodes]
#
# return path
'''
def bfs_quick(cells, start, goal, walkable, rocks_only):
"""★СУПЕРБЫСТРЫЙ BFS: массивы вместо set/deque★"""
rows = len(cells)
if rows == 0:
print("Путь не найден: пустая карта")
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):
if (s_row >= rows or s_col >= cols or
g_row >= rows or g_col >= cols):
#print(f"Путь не найден: выход за границы {start} -> {goal}")
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"Старт/гол непроходимы")
return None
## ★ МАТРИЦЫ вместо set (10x быстрее хэширования) ★
#walkable = [[True] * cols for _ in range(rows)]
#rocks_only = [[False] * cols for _ in range(rows)]
#start_creature = cells[s_row][s_col].creature_obj
#
#for r in range(rows):
# for c in range(cols):
# cell = cells[r][c]
# if (cell.creature_obj and cell.creature_obj != start_creature) or \
# (cell.terrain_obj and cell.terrain_obj.sprite_name == "rock_small"):
# walkable[r][c] = False
# if cell.terrain_obj and cell.terrain_obj.sprite_name == "rock_small":
# rocks_only[r][c] = True
directions = [
(-1, 0), (1, 0), (0, -1), (0, 1), # ортогональ (1.0)
(-1, -1), (-1, 1), (1, -1), (1, 1) # диагональ (1.414)
]
# ★ ВЫЧИСЛЯЕМЫЕ МАССИВЫ ★
visited = [[False] * cols for _ in range(rows)]
parent = [[None] * cols for _ in range(rows)]
def can_move_diagonal(current_r, current_c, dr, dc):
"""Проверяет, можно ли двигаться по диагонали (НЕ через угол)"""
nr, nc = current_r + dr, current_c + dc
# ★ БЫСТРАЯ ОЧЕРЕДЬ: индекс вместо deque ★
queue_size = 0
queue = [[0, 0] for _ in range(rows * cols)] # Предварительно выделяем
queue[0] = [s_row, s_col]
queue_size = 1
front = 0
# Ортогональные соседи ДОЛЖНЫ быть проходимыми для диагонального хода
ortho1_r, ortho1_c = current_r + dr, current_c # вертикальный сосед
ortho2_r, ortho2_c = current_r, current_c + dc # горизонтальный сосед
visited[s_row][s_col] = True
# Проверяем границы для ортососедей
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
directions = [(-1,0), (1,0), (0,-1), (0,1),
(-1,-1), (-1,1), (1,-1), (1,1)]
return (is_passable(cells[ortho1_r][ortho1_c]) and
is_passable(cells[ortho2_r][ortho2_c]))
while front < queue_size:
# ★ БЫСТРОЕ извлечение ★
r, c = queue[front]
front += 1
open_set = []
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):
if r == g_row and c == g_col:
path = []
current = (row, col)
while current in came_from:
path.append(current)
current = came_from[current]
path.append(start)
cr, cc = g_row, g_col
while True:
path.append((cr, cc))
if parent[cr][cc] is None:
break
pr, pc = parent[cr][cc]
cr, cc = pr, pc
return path[::-1]
for dr, dc in directions:
nr, nc = row + dr, col + dc
if 0 <= nr < rows and 0 <= nc < cols and is_passable(cells[nr][nc]):
nr, nc = r + dr, c + dc
if (0 <= nr < rows and 0 <= nc < cols and
walkable[nr][nc] and not visited[nr][nc]):
# ★ ПРОВЕРКА ДИАГОНАЛИ ★
if abs(dr) + abs(dc) == 2: # диагональное движение
if not can_move_diagonal(row, col, dr, dc):
continue # пропускаем запрещённую диагональ
if dr * dc == 0 or can_move_diagonal(r, c, nr, nc, rocks_only, rows, cols):
visited[nr][nc] = True
parent[nr][nc] = (r, c)
move_cost = 1.414 if abs(dr) + abs(dc) == 2 else 1.0
tentative_g = g_score[(row, col)] + move_cost
pos = (nr, nc)
# ★ БЫСТРОЕ добавление в очередь ★
queue[queue_size] = [nr, nc]
queue_size += 1
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("Путь не найден")
#print(f"Путь не найден: {start} -> {goal}")
return None
def can_move_diagonal(r, c, nr, nc, rocks_only, rows, cols):
"""Запрет среза только около rock"""
dr = nr - r
dc = nc - c
r1, c1 = r + dr, c
r2, c2 = r, c + dc
check1_ok = (0 <= r1 < rows and 0 <= c1 < cols and not rocks_only[r1][c1])
check2_ok = (0 <= r2 < rows and 0 <= c2 < cols and not rocks_only[r2][c2])
return check1_ok and check2_ok
'''
#def bfs_quick_d(obstacle_matrix, start, goal):
# rows = len(obstacle_matrix)
# if rows == 0:
# print("❌ DEBUG: ПУСТАЯ МАТРИЦА")
# return None
#
# cols = len(obstacle_matrix[0])
# s_row, s_col = start
# g_row, g_col = goal
#
# print(f"🔍 DEBUG: start={start}, goal={goal}, размер={rows}x{cols}")
#
# if (s_row >= rows or s_col >= cols or
# g_row >= rows or g_col >= cols):
# print(f"❌ DEBUG: ВЫХОД ЗА ГРАНИЦЫ: start({s_row},{s_col}) goal({g_row},{g_col})")
# return None
#
# print(f"✅ DEBUG: Границы OK. obstacle[start]={obstacle_matrix[s_row][s_col]}, obstacle[goal]={obstacle_matrix[g_row][g_col]}")
#
# visited = [[False] * cols for _ in range(rows)]
# parent = [[None] * cols for _ in range(rows)]
#
# queue_size = 0
# queue = [[0, 0] for _ in range(rows * cols)]
# queue[0] = [s_row, s_col]
# queue_size = 1
# front = 0
#
# visited[s_row][s_col] = True
# print(f"✅ DEBUG: Старт добавлен в очередь. queue_size={queue_size}")
#
# directions = [(-1,0), (1,0), (0,-1), (0,1), (-1,-1), (-1,1), (1,-1), (1,1)]
#
# DEBUG_COUNTER = 0
#
# while front < queue_size:
# r, c = queue[front]
# front += 1
# DEBUG_COUNTER += 1
#
# print(f"🔄 DEBUG[{DEBUG_COUNTER}]: обрабатываем ({r},{c}), очередь={front}/{queue_size}")
#
# if r == g_row and c == g_col:
# print(f"✅ DEBUG: НАЙДЕН ЦЕЛЬ! Путь: ({r},{c})")
# path = []
# cr, cc = g_row, g_col
# while True:
# path.append((cr, cc))
# if parent[cr][cc] is None:
# break
# pr, pc = parent[cr][cc]
# cr, cc = pr, pc
# return path[::-1]
#
# for dr, dc in directions:
# nr, nc = r + dr, c + dc
#
# print(f" 📍 Проверяем соседа ({nr},{nc}): граница={0<=nr<rows and 0<=nc<cols}, "
# f"visited={visited[nr][nc]}, obstacle={obstacle_matrix[nr][nc]}")
#
# if (0 <= nr < rows and 0 <= nc < cols and
# not visited[nr][nc] and
# not obstacle_matrix[nr][nc]):
#
# diagonal_ok = True
# if dr * dc != 0:
# diagonal_ok = can_move_diagonal(r, c, nr, nc, obstacle_matrix)
# print(f" ↘️ Диагональ: {diagonal_ok}")
#
# if diagonal_ok:
# visited[nr][nc] = True
# parent[nr][nc] = (r, c)
# queue[queue_size] = [nr, nc]
# queue_size += 1
# print(f" ✅ Добавили ({nr},{nc}) в очередь. queue_size={queue_size}")
# else:
# print(f" ❌ Диагональ заблокирована!")
# else:
# print(f" ❌ Сосед отклонен!")
#
# print(f"❌ DEBUG: ОЧЕРЕДЬ ОПУСТЕЛА! Обработано {DEBUG_COUNTER} узлов")
# print(f" Последняя клетка в очереди: {queue[front-1] if front > 0 else 'ПУСТО'}")
# print(f" Цель ({g_row},{g_col}) помечена как visited? {visited[g_row][g_col]}")
# return None
#
#
#def bfs_quick(obstacle_matrix, start, goal):
# """★СУПЕРБЫСТРЫЙ BFS 8-направлений★
# obstacle_matrix - ТОЛЬКО камни
# """
# rows = len(obstacle_matrix)
# if rows == 0:
# return None
#
# cols = len(obstacle_matrix[0])
# s_row, s_col = start
# g_row, g_col = goal
#
# if (s_row >= rows or s_col >= cols or
# g_row >= rows or g_col >= cols):
# return None
#
# # ★ МАТРИЦЫ состояния ★
# visited = [[False] * cols for _ in range(rows)]
# parent = [[None] * cols for _ in range(rows)]
#
# # ★ БЫСТРАЯ ОЧЕРЕДЬ ★
# queue_size = 0
# queue = [[0, 0] for _ in range(rows * cols)]
# queue[0] = [s_row, s_col]
# queue_size = 1
# front = 0
#
# visited[s_row][s_col] = True
#
# # ★ 8 НАПРАВЛЕНИЙ ★
# directions = [(-1,0), (1,0), (0,-1), (0,1), # Кардинальные (всегда)
# (-1,-1), (-1,1), (1,-1), (1,1)] # Диагональные
#
# while front < queue_size:
# r, c = queue[front]
# front += 1
#
# if r == g_row and c == g_col:
# # ★ Реконструкция пути ★
# path = []
# cr, cc = g_row, g_col
# while True:
# path.append((cr, cc))
# if parent[cr][cc] is None:
# break
# pr, pc = parent[cr][cc]
# cr, cc = pr, pc
# return path[::-1]
#
# for dr, dc in directions:
# nr, nc = r + dr, c + dc
#
# if not (0 <= nr < rows and 0 <= nc < cols):
# continue
# if visited[nr][nc] or obstacle_matrix[nr][nc]:
# continue
#
# diagonal_ok = True
# if dr * dc != 0:
# diagonal_ok = can_move_diagonal(r, c, nr, nc, obstacle_matrix)
# if diagonal_ok:
# visited[nr][nc] = True
# parent[nr][nc] = (r, c)
# queue[queue_size] = [nr, nc]
# queue_size += 1
#
# return None
'''
#def can_move_diagonal(r, c, nr, nc, obstacle_matrix):
# """Диагональ БЛОКИРУЕТСЯ только камнями по углам"""
# rows, cols = len(obstacle_matrix), len(obstacle_matrix[0])
# dr = nr - r
# dc = nc - c
#
# r1, c1 = r + dr, c # Вертикальная соседка
# r2, c2 = r, c + dc # Горизонтальная соседка
#
# check1_ok = (0 <= r1 < rows and 0 <= c1 < cols and not obstacle_matrix[r1][c1])
# check2_ok = (0 <= r2 < rows and 0 <= c2 < cols and not obstacle_matrix[r2][c2])
#
# return check1_ok and check2_ok
#def can_move_diagonal(r, c, nr, nc, obstacle_matrix):
# """Проверка диагонали с границами"""
# rows, cols = len(obstacle_matrix), len(obstacle_matrix[0])
# dr = nr - r
# dc = nc - c
#
# # ★ ПРОВЕРКА ГРАНИЦ ПОПЕРЕДУ ★
# r1, c1 = r + dr, c # вертикальная
# r2, c2 = r, c + dc # горизонтальная
#
# # Если УЖЕ за границей - False
# if not (0 <= r1 < rows and 0 <= c1 < cols):
# return False
# if not (0 <= r2 < rows and 0 <= c2 < cols):
# return False
#
# return not obstacle_matrix[r1][c1] and not obstacle_matrix[r2][c2]

View File

@@ -11,7 +11,9 @@
"name": "2",
"sprite_name": "sword_default"
},
"creature_obj": {}
"creature_obj": {"id": "1",
"name": "2",
"sprite_name": "elf_watching"}
},
{
"terrain_obj": {
@@ -20,17 +22,15 @@
"sprite_name": "grass_small"
},
"item_obj": {},
"creature_obj": {
"id": "1",
"creature_obj": {"id": "1",
"name": "2",
"sprite_name": "elf_watching"
}
"sprite_name": "elf_watching"}
},
{
"terrain_obj": {
"id": "1",
"name": "2",
"sprite_name": "rock_small"
"sprite_name": "grass_small"
},
"item_obj": {},
"creature_obj": {}
@@ -932,7 +932,7 @@
"terrain_obj": {
"id": "1",
"name": "2",
"sprite_name": "rock_small"
"sprite_name": "grass_small"
},
"item_obj": {},
"creature_obj": {}
@@ -950,7 +950,7 @@
"terrain_obj": {
"id": "1",
"name": "2",
"sprite_name": "rock_small"
"sprite_name": "grass_small"
},
"item_obj": {},
"creature_obj": {}
@@ -1843,7 +1843,7 @@
"terrain_obj": {
"id": "1",
"name": "2",
"sprite_name": "rock_small"
"sprite_name": "grass_small"
},
"item_obj": {},
"creature_obj": {}
@@ -1852,7 +1852,7 @@
"terrain_obj": {
"id": "1",
"name": "2",
"sprite_name": "rock_small"
"sprite_name": "grass_small"
},
"item_obj": {},
"creature_obj": {}

View File

@@ -1,4 +1,5 @@
from common import os, json, uuid, deepcopy, dataclass, field
from common import os, json, uuid, deepcopy, random
from common import dataclass, field, partial
from common import pygame, pygame_gui
import eb_objects
import eb_terrain_objects
@@ -37,6 +38,8 @@ class Map:
sprites: dict
sprites_refresh: int = 60
cells: dict = field(default_factory = dict)
walkable_matrix: list = field(default_factory = list)
rocks_matrix: list = field(default_factory = list)
color: str = "gray57"
target_color: str = "gold"
cell_size: int = 150
@@ -46,6 +49,7 @@ class Map:
cam_y: int = 0
cell_dist: int = 1
#effects[]
#action_time_multiplier
def __post_init__(self):
@@ -54,7 +58,7 @@ class Map:
buff = json.load(file)
for line in range(len(buff)):
self.cells[line] = []
for cell in buff[str(line)]:
for col, cell in enumerate(buff[str(line)]):
final_cell = Cell(cell_classes[cell["terrain_obj"]["sprite_name"]](**cell["terrain_obj"]))
if cell["item_obj"]:
@@ -62,12 +66,25 @@ class Map:
if cell["creature_obj"]:
final_cell.creature_obj = cell_classes[cell["creature_obj"]["sprite_name"]](**cell["creature_obj"])
final_cell.creature_obj.grid_pos = (line, col)
self.cells[line].append(final_cell)
self.compute_walkable_rocks()
for j in range(len(self.cells)):
for cell in self.cells[j]:
if cell.creature_obj:
cell.creature_obj.walkable_matrix = self.walkable_matrix
cell.creature_obj.rocks_matrix = self.rocks_matrix
def move_obj(self, type, start, goal):
"""Перемещает объект типа 'terrain_obj', 'item_obj' или 'creature_obj'
из клетки start=(row, col) в goal=(row, col)"""
if goal is None:
return False
s_y, s_x = start
d_y, d_x = goal
@@ -79,12 +96,14 @@ class Map:
source_cell = self.cells[s_y][s_x]
dest_cell = self.cells[d_y][d_x]
obj = getattr(source_cell, type)
check = getattr(dest_cell, type)
if obj is None:
if obj is None or check is not None:
return False
setattr(source_cell, type, None)
setattr(dest_cell, type, obj)
#obj.grid_pos = goal
return True
def get_cell_at_mouse(self, mouse_pos):
@@ -106,18 +125,109 @@ class Map:
return (row, col)
def update_map(self, time_delta):
self.compute_walkable_rocks()
for j in range(len(self.cells)):
for cell in self.cells[j]:
if cell.creature_obj and cell.creature_obj.current_target:
cell.creature_obj.update(time_delta, self.cell_size, self) # self!
if cell.creature_obj:
cell.creature_obj.update(time_delta, self.cell_size, self)
def compute_walkable_rocks(self):
"""Вычисляет матрицы walkable и rocks_only БЕЗ учета стартовой позиции"""
rows = len(self.cells)
if rows == 0:
return None, None
cols = len(self.cells[0])
# ★ ИНИЦИАЛИЗАЦИЯ ★
self.walkable_matrix = [[True] * cols for _ in range(rows)]
self.rocks_matrix = [[False] * cols for _ in range(rows)]
# ★ ПОЛНЫЙ ПРОХОД ПО КАРТЕ
for r in range(rows):
for c in range(cols):
cell = self.cells[r][c]
# Запрещаем ВСЕ существа (включая стартовое!)
if cell.creature_obj or \
(cell.terrain_obj and cell.terrain_obj.sprite_name == "rock_small"):
self.walkable_matrix[r][c] = False
# Отмечаем маленькие камни
if cell.terrain_obj and cell.terrain_obj.sprite_name == "rock_small":
self.rocks_matrix[r][c] = True
# 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),
# "w": int(self.cell_size * self.scale - self.cell_dist),
# "h": int(self.cell_size * self.scale - self.cell_dist),
# "spr_up": current_frame % self.sprites_refresh,
# "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
# creature_dd = terrain_dd.copy()
# 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]):
# base_x = i * self.cell_size + self.cam_x
# base_y = j * self.cell_size + self.cam_y
# grid_rect = pygame.Rect(
# int(base_x * self.scale), int(base_y * self.scale),
# int(self.cell_size * self.scale - self.cell_dist),
# int(self.cell_size * self.scale - self.cell_dist)
# )
# color = self.target_color if cell.is_target else self.color
# pygame.draw.rect(screen, color, grid_rect, self.bord)
def draw_map(self, screen, current_frame, grid=True):
screen_rect = screen.get_rect()
terrain_list = []
creature_list = []
# ★ 1 ПАСС: собираем terrain и creatures ★
for j in range(len(self.cells)):
for i, cell in enumerate(self.cells[j]):
# Вычисляем видимую область в координатах карты (аналогично get_cell_at_mouse)
left_map = (self.cam_x * self.scale) / self.scale / self.cell_size
top_map = (self.cam_y * self.scale) / self.scale / self.cell_size
right_map = ((self.cam_x * self.scale + screen_rect.width) / self.scale / self.cell_size)
bottom_map = ((self.cam_y * self.scale + screen_rect.height) / self.scale / self.cell_size)
min_row = max(0, int(top_map - 1)) # -1 для буфера
max_row = min(len(self.cells), int(bottom_map + 2)) # +2 для буфера
min_col = max(0, int(left_map - 1))
max_col = min(len(self.cells[0]) if self.cells and self.cells[0] else 0, int(right_map + 2))
# ★ 1 ПАСС: собираем только видимые terrain и creatures ★
for j in range(min_row, max_row):
if j not in self.cells: continue
row_cells = self.cells[j]
for i in range(min_col, min(max_col, len(row_cells))):
cell = row_cells[i]
base_x = i * self.cell_size + self.cam_x
base_y = j * self.cell_size + self.cam_y
@@ -147,22 +257,23 @@ class Map:
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]):
# ★ 4 ПАСС: grid только для видимых ячеек
for j in range(min_row, max_row):
if j not in self.cells: continue
row_cells = self.cells[j]
for i in range(min_col, min(max_col, len(row_cells))):
cell = row_cells[i]
base_x = i * self.cell_size + self.cam_x
base_y = j * self.cell_size + self.cam_y
grid_rect = pygame.Rect(
int(base_x * self.scale), int(base_y * self.scale),
int(self.cell_size * self.scale - self.cell_dist),
int(self.cell_size * self.scale - self.cell_dist)
int(self.cell_size * self.scale - self.cell_dist) # ИСПРАВЛЕНО
)
color = self.target_color if cell.is_target else self.color
pygame.draw.rect(screen, color, grid_rect, self.bord)
@dataclass
class Engine:
sprites: dict = field(default_factory = dict)
@@ -250,14 +361,6 @@ 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()
background = pygame.Surface((1600, 800))
background.fill("chartreuse4")
@@ -287,6 +390,32 @@ class Engine:
gc.collect()
mem_before = process.memory_info().rss / 1024
NUM_ELVES = 2000
elf_count = 0
space_pressed = False
spawn = False
def spawn_elves():
spawn_positions = [(0,0), (0,99), (99,0), (99,99), (50,50)]
for pos in spawn_positions:
row, col = pos
if (row < len(easy_map.cells) and
col < len(easy_map.cells[row]) and
easy_map.cells[row][col].creature_obj is None):
elf = eb_objects.Creature(id=f"elf_{random.randint(1000,9999)}",
name="Elf", sprite_name="elf_watching",
grid_pos = pos,
walkable_matrix = easy_map.walkable_matrix,
rocks_matrix = easy_map.rocks_matrix)
r_move_short = eb_objects.Action(sprite_name="elf_watching", func=partial(elf.patrol, easy_map.cells, 3), duration=0.01)
r_move_long = eb_objects.Action(sprite_name="elf_watching", func=partial(elf.move_rand, easy_map.cells, 0, 99), duration=0.01)
elf.tasks.append([r_move_short]*5 + [r_move_long])
easy_map.cells[row][col].creature_obj = elf
while running:
time_delta = clock.tick(60)/1000.0
#pygame.event.clear()
@@ -295,12 +424,42 @@ class Engine:
mem_after = process.memory_info().rss / 1024
print(f"Leak: {mem_after - mem_before:.1f} KB per 1000 frames")
if global_counter % 100 == 0 and spawn == True:
spawn_elves()
elf_count += 5
# poll for events
# pygame.QUIT event means the user clicked X to close your window
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE and elf_count < NUM_ELVES and not space_pressed:
#spawn_elf()
#elf_count += 1
if spawn == False:
spawn = True
else: spawn = False
space_pressed = True
if event.type == pygame.KEYUP:
if event.key == pygame.K_SPACE:
space_pressed = False
if event.type == pygame.MOUSEWHEEL:
scroll_y = event.y
if scroll_y > 0:
easy_map.scale += self.scale_step * 5
self.spr_scale += self.scale_step * 5
self.scale_sprites()
elif scroll_y < 0 and easy_map.scale >= self.scale_step:
easy_map.scale -= self.scale_step * 5
self.spr_scale -= self.scale_step * 5
self.scale_sprites()
if event.type == pygame_gui.UI_TEXT_ENTRY_FINISHED and event.ui_element == input_entry:
user_text = input_entry.get_text()
exec(user_text)
@@ -358,6 +517,8 @@ class Engine:
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()
@@ -374,7 +535,6 @@ class Engine:
row, col = active_cell
easy_map.cells[row][col].is_target = True
if event.type == pygame.MOUSEBUTTONDOWN and event.button == 3:
mouse_pos = pygame.mouse.get_pos()
console_rect = console_window.get_abs_rect()
@@ -384,8 +544,7 @@ class Engine:
cell_coords = easy_map.get_cell_at_mouse(mouse_pos)
if 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)
easy_map.cells[active_cell[0]][active_cell[1]].creature_obj.move(easy_map.cells, cell_coords)
if keys[pygame.K_ESCAPE]:
running = False
@@ -401,6 +560,6 @@ class Engine:
if global_counter % 10 == 0:
current_fps = clock.get_fps()
print(f"Current FPS: {current_fps:.2f}")
print(f"Elves count: {elf_count} - Current FPS: {current_fps:.2f}")
pygame.quit()

View File

@@ -1,4 +1,21 @@
from common import deepcopy, dataclass, field
from common import deepcopy, dataclass, field, random
@dataclass
class Action:
sprite_name: str
func: function
duration: float
progress: float = 0.0
#status: str = "pending"
#взаимодействие с инвентарем
#взять предмет
#бросить предмет
#передать предмет
#собрать ресурс
#активное действие с оружием
#колдовство
#class Task
@dataclass
class Object:
@@ -6,6 +23,7 @@ class Object:
name: str
sprite_name: str
sprite_state: int = 0
grid_pos: tuple = None
# current_map
# pos
# weight
@@ -31,59 +49,224 @@ class Terrain(Object):
@dataclass
class Creature(Object):
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)
direction: int = 0 # tuple?
move_progress: float = 0.0 # 0.0 = старт клетки, 1.0 = конец клетки
current_target: tuple = None # (row, col) следующая клетка
final_goal: tuple = None
move_speed: float = 0.02 # пикселей/кадр
render_offset: tuple = (0.0, 0.0)
start_pos: tuple = None # (row, col) начальная позиция сегмента пути
replan_counter: int = 0
REPLAN_INTERVAL: int = 30000
waypoints: list = field(default_factory = list)
walkable_matrix: list = field(default_factory = list)
rocks_matrix: list = field(default_factory = list)
move_progress: float = 0.0 # 0.0 = старт клетки, 1.0 = конец клетки
current_target: tuple = None # (row, col) следующая клетка
move_speed: float = 0.02 # пикселей/кадр (настройте)
render_offset: tuple = (0.0, 0.0)
start_pos: tuple = None # (row, col) начальная позиция сегмента пути
inventory: dict = field(default_factory = dict)
quick_actions: list = field(default_factory = list)
tasks: list = field(default_factory = list)
action: Action = None
action_time: float = 0.0
action_counter: int = 0
task_counter: int = 0
def move(self, cells, start, goal):
new_task: list = field(default_factory = list)
interrupt_task: list = field(default_factory = list)
interrupt_action_status: str = "completed"
def replan(self, cells, pos):
from common import find_way
self.start_pos = start
path = find_way(cells, start, goal)
path = find_way(cells, pos, self.final_goal, self.walkable_matrix, self.rocks_matrix)
if path and len(path) > 1:
self.waypoints = path[1:] # Убираем текущую позицию
self.waypoints = path[1:]
self.current_target = self.waypoints[0]
self.move_progress = 0.0
self.start_pos = start # ★ ТУТ - текущая позиция как стартовая для первого шага ★
self.render_offset = (0.0, 0.0)
else:
self.waypoints.clear()
self.current_target = None
self.final_goal = None
def update(self, time_delta, cell_size, map_obj):
def calc_step(self, time_delta, cell_size, map_obj):
if self.current_target is None or not self.waypoints:
self.render_offset = (0.0, 0.0)
return
#self.replan_counter += 1
#if self.replan_counter >= self.REPLAN_INTERVAL:
# self.replan_counter = 0
# self.replan(map_obj.cells, self.start_pos)
if self.current_target is None: return
#target_row, target_col = self.current_target
#if (target_row in map_obj.cells and
# target_col < len(map_obj.cells[target_row])):
# target_cell = map_obj.cells[target_row][target_col]
#if target_cell.creature_obj is not None:
# self.current_target = None
# self.waypoints.clear()
# self.render_offset = (0.0, 0.0)
# self.replan(map_obj.cells, self.start_pos)
# return
self.move_progress += self.move_speed * time_delta * 60
self.move_progress = min(self.move_progress, 1.0)
if self.move_progress >= 1.0:
map_obj.move_obj('creature_obj', self.start_pos, self.current_target)
self.waypoints.pop(0)
if self.waypoints:
self.start_pos = self.current_target # Новая клетка как старт
self.current_target = self.waypoints[0]
self.move_progress = 0.0
self.render_offset = (0.0, 0.0) # ← ДОБАВИТЬ!
if (map_obj.move_obj('creature_obj', self.start_pos, self.current_target)):
self.grid_pos = self.current_target
else:
self.current_target = None
self.waypoints.clear()
self.render_offset = (0.0, 0.0)
#если в матрице не считаем объекты, то:
#добавляем клетку в матрицу, матрицу периодически чистим
#посчитать как дорого обходится просчёт матрицы
self.replan(map_obj.cells, self.start_pos)
#тут сделать красивый переход в одну из соседних клеток
return
if self.waypoints: self.waypoints.pop(0)
if self.waypoints:
self.start_pos = self.current_target
self.current_target = self.waypoints[0]
self.move_progress = 0.0
self.render_offset = (0.0, 0.0)
else:
#print(111111111111111)
self.current_target = None
self.final_goal = None
self.render_offset = (0.0, 0.0)
return
# ★ ТОЛЬКО интерполяция offset ★
start_row, start_col = self.start_pos #or (0, 0)
if self.current_target is None:
self.render_offset = (0.0, 0.0)
return
start_row, start_col = self.start_pos
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)
def move(self, cells, goal):
from common import find_way
self.final_goal = goal
path = find_way(cells, self.grid_pos, self.final_goal, self.walkable_matrix, self.rocks_matrix)
if path and len(path) > 1:
self.waypoints = path[1:]
self.current_target = self.waypoints[0]
self.move_progress = 0.0
self.start_pos = self.grid_pos
self.render_offset = (0.0, 0.0)
else:
self.final_goal = None
def create_task(self):
self.new_task = []
#add actions_default_durations dict {func1: dur1, ...}
def add_action(self, sprite_name: str, func: function, duration: float):
self.new_task.append(Action(sprite_name, func, duration))
def add_task(self):
self.tasks.append(self.new_task)
def add_interr_task(self):
pass
#param resume_on_interrupt, Bool
#if True
# save goal to buffer
#if False
# action/task_counter = 0
#clear goal/wp
def update(self, time_delta, cell_size, map_obj):
self.walkable_matrix = map_obj.walkable_matrix
self.rocks_matrix = map_obj.rocks_matrix
#quick_actions? here?
#print(self.waypoints, self.final_goal, self.action_counter, self.task_counter)
if self.final_goal is not None:
#print(2)
self.calc_step(time_delta, cell_size, map_obj)
return
if self.interrupt_task and self.interrupt_action_status == "completed":
#print(3)
self.action_time = 0.0
self.action = self.interrupt_task.pop(0)
self.interrupt_action_status = "active"
#print(f" DEBUG: tasks={len(self.tasks)}, "
# f"task_len={len(self.tasks[self.task_counter]) if self.tasks else 0}, "
# f"action={self.action is not None}")
if self.action:
#print(self.action_counter, self.task_counter)
self.action_time += time_delta
self.action.progress = min(1.0, self.action_time / self.action.duration)
if self.action_time >= self.action.duration:
self.action.func()
if self.interrupt_action_status == "active":
self.interrupt_action_status == "completed"
#if not self.inter_task and goal: move to buff_goal from self.pos (add to Object)
self.action = None
self.action_time = 0.0
elif self.tasks:
#print(6)
if self.action_counter < len(self.tasks[self.task_counter]):
self.action = self.tasks[self.task_counter][self.action_counter]
self.action_counter += 1
else:
self.task_counter += 1
self.action_counter = 0
if self.task_counter == len(self.tasks):
self.task_counter = 0
self.action = self.tasks[self.task_counter][self.action_counter]
self.action_counter += 1
self.action_time = 0.0
#elif self.tasks:
# if self.action_counter >= len(self.tasks[self.task_counter]):
# self.task_counter += 1
# self.action_counter = 0
# if self.task_counter >= len(self.tasks):
# self.task_counter = 0
#
# self.action = self.tasks[self.task_counter][self.action_counter]
# self.action_counter += 1
# self.action_time = 0.0
def patrol(self, cells, area):
goal = (random.randint(self.grid_pos[0] - area,
self.grid_pos[0] + area),
random.randint(self.grid_pos[1] - area,
self.grid_pos[1] + area))
while goal == self.grid_pos:
goal = (random.randint(self.grid_pos[0] - area,
self.grid_pos[0] + area),
random.randint(self.grid_pos[1] - area,
self.grid_pos[1] + area))
self.move(cells, goal)
def move_rand(self, cells, area_start, area_end):
goal = (random.randint(area_start, area_end), random.randint(area_start, area_end))
while goal == self.grid_pos:
goal = (random.randint(area_start, area_end), random.randint(area_start, area_end))
self.move(cells, goal)
@dataclass
class Item(Object):
@@ -96,7 +279,6 @@ class Container(Item):
# content = {}
pass
@dataclass
class Building(Object):
pass

231
f.txt Normal file
View File

@@ -0,0 +1,231 @@
Elves count: 500 - Current FPS: 58.82
Elves count: 500 - Current FPS: 54.35
Elves count: 500 - Current FPS: 57.14
Elves count: 500 - Current FPS: 55.56
Elves count: 500 - Current FPS: 58.14
Elves count: 500 - Current FPS: 54.95
Elves count: 500 - Current FPS: 61.35
Elves count: 500 - Current FPS: 58.14
Elves count: 500 - Current FPS: 61.73
Elves count: 500 - Current FPS: 45.87
Elves count: 500 - Current FPS: 54.35
Elves count: 500 - Current FPS: 58.48
Elves count: 500 - Current FPS: 59.52
Elves count: 500 - Current FPS: 58.48
Elves count: 500 - Current FPS: 50.51
Elves count: 500 - Current FPS: 55.25
Elves count: 500 - Current FPS: 52.36
Elves count: 500 - Current FPS: 53.76
Elves count: 500 - Current FPS: 52.36
Elves count: 500 - Current FPS: 49.02
Elves count: 500 - Current FPS: 61.73
Elves count: 500 - Current FPS: 57.80
Elves count: 500 - Current FPS: 59.52
Elves count: 500 - Current FPS: 54.64
Elves count: 500 - Current FPS: 54.95
Elves count: 500 - Current FPS: 59.52
Elves count: 500 - Current FPS: 59.52
Elves count: 500 - Current FPS: 60.24
Elves count: 500 - Current FPS: 61.73
Elves count: 500 - Current FPS: 44.05
Elves count: 500 - Current FPS: 58.82
Elves count: 500 - Current FPS: 54.05
Elves count: 500 - Current FPS: 55.56
Elves count: 500 - Current FPS: 58.82
Elves count: 500 - Current FPS: 49.50
Elves count: 500 - Current FPS: 56.18
Elves count: 500 - Current FPS: 58.82
Elves count: 500 - Current FPS: 57.47
Elves count: 500 - Current FPS: 34.13
Elves count: 500 - Current FPS: 48.54
Elves count: 500 - Current FPS: 54.64
Elves count: 500 - Current FPS: 60.24
Elves count: 500 - Current FPS: 57.14
Elves count: 500 - Current FPS: 56.50
Elves count: 500 - Current FPS: 50.00
Elves count: 500 - Current FPS: 60.98
Elves count: 500 - Current FPS: 59.88
Elves count: 500 - Current FPS: 58.82
Elves count: 500 - Current FPS: 43.10
Leak: 2232.0 KB per 1000 frames
Elves count: 500 - Current FPS: 50.25
Elves count: 500 - Current FPS: 41.67
Elves count: 500 - Current FPS: 57.80
Elves count: 500 - Current FPS: 45.25
Elves count: 500 - Current FPS: 49.50
Elves count: 500 - Current FPS: 58.48
Elves count: 500 - Current FPS: 56.18
Elves count: 500 - Current FPS: 52.63
Elves count: 500 - Current FPS: 49.02
Elves count: 500 - Current FPS: 57.47
Elves count: 500 - Current FPS: 55.56
Elves count: 500 - Current FPS: 56.18
Elves count: 500 - Current FPS: 55.56
Elves count: 500 - Current FPS: 59.52
Elves count: 500 - Current FPS: 60.24
Elves count: 500 - Current FPS: 57.47
Elves count: 500 - Current FPS: 60.61
Elves count: 500 - Current FPS: 60.61
Elves count: 500 - Current FPS: 46.30
Elves count: 500 - Current FPS: 58.82
Elves count: 500 - Current FPS: 59.52
Elves count: 500 - Current FPS: 54.35
Elves count: 500 - Current FPS: 59.17
Elves count: 500 - Current FPS: 54.95
Elves count: 500 - Current FPS: 54.64
Elves count: 500 - Current FPS: 48.08
Elves count: 500 - Current FPS: 41.49
Elves count: 500 - Current FPS: 54.35
Elves count: 500 - Current FPS: 56.50
Elves count: 500 - Current FPS: 60.24
Elves count: 500 - Current FPS: 61.35
Elves count: 500 - Current FPS: 59.17
Elves count: 500 - Current FPS: 50.25
Elves count: 500 - Current FPS: 60.98
Elves count: 500 - Current FPS: 52.63
Elves count: 500 - Current FPS: 57.80
Elves count: 500 - Current FPS: 54.05
Elves count: 500 - Current FPS: 51.28
Elves count: 500 - Current FPS: 52.08
Elves count: 500 - Current FPS: 59.52
Elves count: 500 - Current FPS: 61.73
Elves count: 500 - Current FPS: 54.05
Elves count: 500 - Current FPS: 59.17
Elves count: 500 - Current FPS: 54.64
Elves count: 500 - Current FPS: 50.51
Elves count: 500 - Current FPS: 51.28
Elves count: 500 - Current FPS: 59.52
Elves count: 500 - Current FPS: 59.52
Elves count: 500 - Current FPS: 60.61
Elves count: 500 - Current FPS: 51.28
Elves count: 500 - Current FPS: 60.98
Elves count: 500 - Current FPS: 57.47
Elves count: 500 - Current FPS: 52.63
Elves count: 500 - Current FPS: 50.00
Elves count: 500 - Current FPS: 59.88
Elves count: 500 - Current FPS: 54.95
Elves count: 500 - Current FPS: 58.14
Elves count: 500 - Current FPS: 45.45
Elves count: 500 - Current FPS: 52.91
Elves count: 500 - Current FPS: 32.57
Elves count: 500 - Current FPS: 29.07
Elves count: 500 - Current FPS: 45.25
Elves count: 500 - Current FPS: 30.03
Elves count: 500 - Current FPS: 45.66
Elves count: 500 - Current FPS: 43.86
Elves count: 500 - Current FPS: 38.91
Elves count: 500 - Current FPS: 35.97
Elves count: 500 - Current FPS: 34.01
Elves count: 500 - Current FPS: 44.84
Elves count: 500 - Current FPS: 36.63
Elves count: 500 - Current FPS: 39.84
Elves count: 500 - Current FPS: 42.55
Elves count: 500 - Current FPS: 31.65
Elves count: 500 - Current FPS: 30.96
Elves count: 500 - Current FPS: 37.59
Elves count: 500 - Current FPS: 39.53
Elves count: 500 - Current FPS: 45.87
Elves count: 500 - Current FPS: 37.31
Elves count: 500 - Current FPS: 35.34
Elves count: 500 - Current FPS: 39.22
Elves count: 500 - Current FPS: 47.17
Elves count: 500 - Current FPS: 39.37
Elves count: 500 - Current FPS: 25.25
Elves count: 500 - Current FPS: 35.97
Elves count: 500 - Current FPS: 48.54
Elves count: 500 - Current FPS: 44.64
Elves count: 500 - Current FPS: 43.67
Elves count: 500 - Current FPS: 28.17
Elves count: 500 - Current FPS: 35.97
Elves count: 500 - Current FPS: 31.06
Elves count: 500 - Current FPS: 47.62
Elves count: 500 - Current FPS: 37.59
Elves count: 500 - Current FPS: 42.19
Elves count: 500 - Current FPS: 50.25
Elves count: 500 - Current FPS: 35.59
Elves count: 500 - Current FPS: 45.45
Elves count: 500 - Current FPS: 45.45
Elves count: 500 - Current FPS: 28.65
Elves count: 500 - Current FPS: 24.15
Elves count: 500 - Current FPS: 42.55
Leak: 3284.0 KB per 1000 frames
Elves count: 500 - Current FPS: 42.19
Elves count: 500 - Current FPS: 35.09
Elves count: 500 - Current FPS: 42.19
Elves count: 500 - Current FPS: 51.55
Elves count: 500 - Current FPS: 32.36
Elves count: 500 - Current FPS: 39.22
Elves count: 500 - Current FPS: 35.59
Elves count: 500 - Current FPS: 28.57
Elves count: 500 - Current FPS: 22.73
Elves count: 500 - Current FPS: 31.85
Elves count: 500 - Current FPS: 26.74
Elves count: 500 - Current FPS: 24.94
Elves count: 500 - Current FPS: 22.08
Elves count: 500 - Current FPS: 24.51
Elves count: 500 - Current FPS: 34.97
Elves count: 500 - Current FPS: 30.40
Elves count: 500 - Current FPS: 47.62
Elves count: 500 - Current FPS: 40.16
Elves count: 500 - Current FPS: 55.87
Elves count: 500 - Current FPS: 33.78
Elves count: 500 - Current FPS: 50.00
Elves count: 500 - Current FPS: 50.51
Elves count: 500 - Current FPS: 33.00
Elves count: 500 - Current FPS: 54.64
Elves count: 500 - Current FPS: 61.35
Elves count: 500 - Current FPS: 51.02
Elves count: 500 - Current FPS: 54.35
Elves count: 500 - Current FPS: 50.51
Elves count: 500 - Current FPS: 53.19
Elves count: 500 - Current FPS: 53.76
Elves count: 500 - Current FPS: 39.37
Elves count: 500 - Current FPS: 49.50
Elves count: 500 - Current FPS: 60.24
Elves count: 500 - Current FPS: 47.17
Elves count: 500 - Current FPS: 54.64
Elves count: 500 - Current FPS: 60.61
Elves count: 500 - Current FPS: 56.50
Elves count: 500 - Current FPS: 58.48
Elves count: 500 - Current FPS: 58.82
Elves count: 500 - Current FPS: 52.36
Elves count: 500 - Current FPS: 57.47
Elves count: 500 - Current FPS: 61.73
Elves count: 500 - Current FPS: 60.61
Elves count: 500 - Current FPS: 39.06
Elves count: 500 - Current FPS: 51.55
Elves count: 500 - Current FPS: 56.82
Elves count: 500 - Current FPS: 59.52
Elves count: 500 - Current FPS: 54.64
Elves count: 500 - Current FPS: 54.95
Elves count: 500 - Current FPS: 56.50
Elves count: 500 - Current FPS: 52.63
Elves count: 500 - Current FPS: 59.17
Elves count: 500 - Current FPS: 55.56
Elves count: 500 - Current FPS: 58.82
Elves count: 500 - Current FPS: 55.87
Elves count: 500 - Current FPS: 48.08
Elves count: 500 - Current FPS: 60.24
Elves count: 500 - Current FPS: 51.55
Elves count: 500 - Current FPS: 58.14
Elves count: 500 - Current FPS: 57.14
Elves count: 500 - Current FPS: 47.85
Elves count: 500 - Current FPS: 50.00
Elves count: 500 - Current FPS: 47.62
Elves count: 500 - Current FPS: 56.18
Elves count: 500 - Current FPS: 55.56
Elves count: 500 - Current FPS: 53.48
Elves count: 500 - Current FPS: 52.91
Elves count: 500 - Current FPS: 60.98
Elves count: 500 - Current FPS: 57.14
Elves count: 500 - Current FPS: 34.60
Elves count: 500 - Current FPS: 41.84
Elves count: 500 - Current FPS: 59.88
Elves count: 500 - Current FPS: 49.75
Elves count: 500 - Current FPS: 47.62
Elves count: 500 - Current FPS: 50.51
Elves count: 500 - Current FPS: 35.97
Elves count: 500 - Current FPS: 57.47
Elves count: 500 - Current FPS: 55.56
Elves count: 500 - Current FPS: 45.25
Elves count: 500 - Current FPS: 35.59

BIN
log.txt Normal file

Binary file not shown.

37
main.py
View File

@@ -1,4 +1,6 @@
import eb_engine
#import os
#os.environ['PYTHONJIT'] = '1'
def main():
e = eb_engine.Engine()
@@ -6,3 +8,38 @@ def main():
if __name__ == "__main__":
main()
# todo:
# прокрутка баг консоль и карта
# ОПТИМИЗАЦИИ
# перепроверять путь пореже, или только после столкновения, или локальный поиск
# очередь задач и задача рандомного патруля
# устроить краш тест поиску пути, запустив много объектов на маленьком поле, успел заметить баги
# добавить функцию движения за каким-то объектом
# сделать, чтобы в случае отменненого движения не телепортировался назад, а плавно
# приступаем к логике
# сделать по аналогии с текущей клеткой текущий объект
# посмотреть как в clock = pygame.time.Clock() работает фпс
# перемещать оъект в другую клетку при половине офсета
# техдолг Егору
# убрать cells и mapobject creature - перенести нужную логику в методы Map
# система имен спрайтов и Action - реализовать
# рисовать группой спрайтов как в перпл
# нужен ли теперь start_pos? grid_pos?
# class Task с проверками выполнения экшонов
#вернуть назад апдейт. вернул - работает, сравнить с новым и решить
#final_goal = None - check all!
#рефактор goal = (random.randint(self.grid_pos[0] - area,
# self.grid_pos[0] + area),
# random.randint(self.grid_pos[1] - area,
# self.grid_pos[1] + area))
# отловить баг - иногда всё равно встают - похоже из-за реплана и коллизий с эльфами
# вроде починил, добавив final_goal = None в реплан, проверить
# совет ксюши - не считать коллизии с объектами, только при перемещении в клетку проверять

26
plan.txt Normal file
View File

@@ -0,0 +1,26 @@
1. Доработка системы задач: класс Task, новые задачи, проработка старых;
2. Доработка движка и приведение его структуры в порядок:
- вынос всех функций карты в карту, объекты знают свою позицию и им этого хватит.
взаимодействия с картой и другими объектами должно происходить только в методе движения, остальные методы должны абстрагироваться от карты;
- вынос рендера в класс Render. Карта занимается только своими клеткам. Метод
draw_map должен только готовить текстуру для Render, который будет ее рисовать;
- мини-движок поиска пути. class Pathfinder, который хранит в себе кэши,
выбирает оптимальный способ поиска пути для конкретной ситуации. Для этого
нужно ещё раз протестировать все способы, привести к единообразию интерфейсов.
Задокументировать. Возможно понадобится вынос поиска пути в отдельный движок
на другом языке и многопоточностью;
- привести в порядок Main Loop;
- вынести хранение и обработку объектов из карты в ObjectManager;
- сделать фундамент для MapManager, пока не развивать, но надо исходить из того
что одновременно может рендериться несколько карт;
3. Оптимизация.
- проверяем, есть ли объект в клетке только перед непосредственным перемещением в ячейку
Cells, а не каждый calc_step; + (исправить визуальный баг, см комменты calc_step)
- перестаём учитывать объекты в алгоритме поиска пути; - (попробовать ещё раз, подумать)
- заранее передаем в алгоритм карту препятствий; +

View File

@@ -50,7 +50,6 @@ scale_image() вызывается 150×150=22,500 раз в секунду пр
# проверить у ллм на ошибки - РЕГУЛЯРНАЯ АКТИВНОСТЬ:
# - deepcopy +
# - общие +
# !!! ДОБАВИТЬ ПРОКРУТКУ И МАСШТАБ КАРТЫ ДЛЯ МЫШИ !!!
#
# ДОДЕЛАТЬ move для Creature - хранить pos в объекте ???
#

6
requirements.txt Normal file
View File

@@ -0,0 +1,6 @@
pathfinding==1.0.20
psutil==7.2.2
pygame-ce==2.5.6
pygame_gui==0.6.14
python-i18n==0.3.9
typing_extensions==4.15.0