refactor regions
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -136,6 +136,7 @@ Thumbs.db
|
||||
# Logs and caches
|
||||
*.log
|
||||
*.cache
|
||||
logs/
|
||||
|
||||
# Temporary files
|
||||
~$*
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
|
||||
### 🗺️ 地块系统
|
||||
- ✅ 基础tile地块系统
|
||||
- ✅ 基础区域、修行区域、城市区域
|
||||
- [ ] 同tile内NPC相互交互
|
||||
- [ ] 城市/宗派据点的机制
|
||||
- [ ] 灵气分布与产出设计
|
||||
|
||||
@@ -7,7 +7,7 @@ import inspect
|
||||
|
||||
from src.classes.essence import Essence, EssenceType
|
||||
from src.classes.root import Root, corres_essence_type
|
||||
from src.classes.tile import Region
|
||||
from src.classes.region import Region
|
||||
from src.classes.event import Event, NULL_EVENT
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -172,7 +172,8 @@ class MoveToRegion(DefineAction, ActualActionMixin):
|
||||
移动到某个region
|
||||
"""
|
||||
if isinstance(region, str):
|
||||
region = self.world.map.region_names[region]
|
||||
from src.classes.region import regions_by_name
|
||||
region = regions_by_name[region]
|
||||
cur_loc = (self.avatar.pos_x, self.avatar.pos_y)
|
||||
region_center_loc = region.center_loc
|
||||
delta_x = region_center_loc[0] - cur_loc[0]
|
||||
@@ -187,7 +188,8 @@ class MoveToRegion(DefineAction, ActualActionMixin):
|
||||
判断动作是否完成
|
||||
"""
|
||||
if isinstance(region, str):
|
||||
region = self.world.map.region_names[region]
|
||||
from src.classes.region import regions_by_name
|
||||
region = regions_by_name[region]
|
||||
return self.avatar.is_in_region(region)
|
||||
|
||||
def get_event(self, region: Region|str) -> Event:
|
||||
@@ -196,8 +198,9 @@ class MoveToRegion(DefineAction, ActualActionMixin):
|
||||
"""
|
||||
if isinstance(region, str):
|
||||
region_name = region
|
||||
if region in self.world.map.region_names:
|
||||
region_name = self.world.map.region_names[region].name
|
||||
from src.classes.region import regions_by_name
|
||||
if region in regions_by_name:
|
||||
region_name = regions_by_name[region].name
|
||||
elif hasattr(region, 'name'):
|
||||
region_name = region.name
|
||||
else:
|
||||
@@ -249,8 +252,9 @@ class Cultivate(DefineAction, ActualActionMixin):
|
||||
"""
|
||||
判断修炼动作是否可以执行
|
||||
"""
|
||||
return self.avatar.cultivation_progress.can_cultivate()
|
||||
|
||||
root = self.avatar.root
|
||||
_corres_essence_type = corres_essence_type[root]
|
||||
return self.avatar.cultivation_progress.can_cultivate() and self.avatar.tile.region.essence.get_density(_corres_essence_type) > 0
|
||||
|
||||
# 突破境界class
|
||||
@long_action(step_month=1)
|
||||
|
||||
@@ -9,7 +9,7 @@ from typing import TYPE_CHECKING
|
||||
import random
|
||||
|
||||
from src.classes.world import World
|
||||
from src.classes.tile import Region
|
||||
from src.classes.region import Region
|
||||
from src.classes.root import corres_essence_type
|
||||
from src.classes.event import Event, NULL_EVENT
|
||||
from src.utils.llm import get_ai_prompt_and_call_llm_async
|
||||
|
||||
@@ -7,7 +7,8 @@ import json
|
||||
from src.classes.calendar import MonthStamp
|
||||
from src.classes.action import Action, ALL_ACTUAL_ACTION_CLASSES, ALL_ACTION_CLASSES, ALL_ACTUAL_ACTION_NAMES
|
||||
from src.classes.world import World
|
||||
from src.classes.tile import Tile, Region
|
||||
from src.classes.tile import Tile
|
||||
from src.classes.region import Region
|
||||
from src.classes.cultivation import CultivationProgress
|
||||
from src.classes.root import Root
|
||||
from src.classes.age import Age
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from enum import Enum
|
||||
from collections import defaultdict
|
||||
|
||||
|
||||
class EssenceType(Enum):
|
||||
@@ -14,6 +15,32 @@ class EssenceType(Enum):
|
||||
def __str__(self) -> str:
|
||||
"""返回灵气类型的中文名称"""
|
||||
return essence_names.get(self, self.value)
|
||||
|
||||
@classmethod
|
||||
def from_str(cls, essence_str: str) -> 'EssenceType':
|
||||
"""
|
||||
从字符串创建EssenceType实例
|
||||
|
||||
Args:
|
||||
essence_str: 灵气的字符串表示,如 "金", "木", "水", "火", "土"
|
||||
|
||||
Returns:
|
||||
对应的EssenceType枚举值
|
||||
|
||||
Raises:
|
||||
ValueError: 如果字符串不匹配任何已知的灵气类型
|
||||
"""
|
||||
# 首先尝试匹配中文名称
|
||||
for essence_type, chinese_name in essence_names.items():
|
||||
if chinese_name == essence_str:
|
||||
return essence_type
|
||||
|
||||
# 然后尝试匹配英文值
|
||||
for essence_type in cls:
|
||||
if essence_type.value == essence_str:
|
||||
return essence_type
|
||||
|
||||
raise ValueError(f"Unknown essence type: {essence_str}")
|
||||
|
||||
essence_names = {
|
||||
EssenceType.GOLD: "金",
|
||||
@@ -31,7 +58,9 @@ class Essence():
|
||||
浓度从0~10。
|
||||
"""
|
||||
def __init__(self, density: dict[EssenceType, int]):
|
||||
self.density = density
|
||||
self.density = defaultdict(int)
|
||||
for essence_type, density in density.items():
|
||||
self.density[essence_type] = density
|
||||
|
||||
def get_density(self, essence_type: EssenceType) -> int:
|
||||
return self.density[essence_type]
|
||||
|
||||
@@ -12,6 +12,9 @@ class Item:
|
||||
desc: str
|
||||
grade: int
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash(self.id)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.name
|
||||
|
||||
|
||||
307
src/classes/region.py
Normal file
307
src/classes/region.py
Normal file
@@ -0,0 +1,307 @@
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Union, TypeVar, Type
|
||||
from enum import Enum
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
from src.utils.df import game_configs
|
||||
from src.classes.essence import EssenceType, Essence
|
||||
|
||||
|
||||
def get_tiles_from_shape(shape: 'Shape', north_west_cor: str, south_east_cor: str) -> list[tuple[int, int]]:
|
||||
"""
|
||||
根据形状和两个角点坐标,计算出对应的所有坐标点
|
||||
|
||||
Args:
|
||||
shape: 区域形状
|
||||
north_west_cor: 西北角坐标,格式: "x,y"
|
||||
south_east_cor: 东南角坐标,格式: "x,y"
|
||||
|
||||
Returns:
|
||||
所有坐标点的列表
|
||||
"""
|
||||
nw_coords = tuple(map(int, north_west_cor.split(',')))
|
||||
se_coords = tuple(map(int, south_east_cor.split(',')))
|
||||
|
||||
min_x, min_y = nw_coords
|
||||
max_x, max_y = se_coords
|
||||
|
||||
coordinates = []
|
||||
|
||||
if shape == Shape.SQUARE or shape == Shape.RECTANGLE:
|
||||
# 正方形和长方形:填充整个矩形区域
|
||||
for x in range(min_x, max_x + 1):
|
||||
for y in range(min_y, max_y + 1):
|
||||
coordinates.append((x, y))
|
||||
|
||||
elif shape == Shape.MEANDERING:
|
||||
# 蜿蜒形状(如河流):创建一条从西北到东南的蜿蜒路径
|
||||
# 计算河流的宽度(根据距离动态调整)
|
||||
distance_x = max_x - min_x
|
||||
distance_y = max_y - min_y
|
||||
total_distance = max(distance_x, distance_y)
|
||||
|
||||
# 河流宽度:距离越长,河流越宽
|
||||
if total_distance < 10:
|
||||
width = 1
|
||||
elif total_distance < 30:
|
||||
width = 2
|
||||
else:
|
||||
width = 3
|
||||
|
||||
# 生成中心路径点
|
||||
path_points = []
|
||||
if distance_x >= distance_y:
|
||||
# 主要沿X轴方向流动
|
||||
for x in range(min_x, max_x + 1):
|
||||
# 计算对应的y坐标,添加一些蜿蜒效果
|
||||
progress = (x - min_x) / max(distance_x, 1)
|
||||
base_y = min_y + int(progress * distance_y)
|
||||
|
||||
# 添加蜿蜒效果:使用简单的正弦波
|
||||
import math
|
||||
wave_amplitude = min(3, distance_y // 4) if distance_y > 0 else 0
|
||||
wave_y = int(wave_amplitude * math.sin(progress * math.pi * 2))
|
||||
y = max(min_y, min(max_y, base_y + wave_y))
|
||||
|
||||
path_points.append((x, y))
|
||||
else:
|
||||
# 主要沿Y轴方向流动
|
||||
for y in range(min_y, max_y + 1):
|
||||
progress = (y - min_y) / max(distance_y, 1)
|
||||
base_x = min_x + int(progress * distance_x)
|
||||
|
||||
# 添加蜿蜒效果
|
||||
import math
|
||||
wave_amplitude = min(3, distance_x // 4) if distance_x > 0 else 0
|
||||
wave_x = int(wave_amplitude * math.sin(progress * math.pi * 2))
|
||||
x = max(min_x, min(max_x, base_x + wave_x))
|
||||
|
||||
path_points.append((x, y))
|
||||
|
||||
# 为每个路径点添加宽度
|
||||
for px, py in path_points:
|
||||
for dx in range(-width//2, width//2 + 1):
|
||||
for dy in range(-width//2, width//2 + 1):
|
||||
nx, ny = px + dx, py + dy
|
||||
# 确保在边界内
|
||||
if min_x <= nx <= max_x and min_y <= ny <= max_y:
|
||||
coordinates.append((nx, ny))
|
||||
|
||||
# 去重并排序
|
||||
return sorted(list(set(coordinates)))
|
||||
|
||||
|
||||
@dataclass
|
||||
class Region(ABC):
|
||||
"""
|
||||
区域抽象基类
|
||||
理想中,一些地块应当在一起组成一个区域。
|
||||
比如,某山;某湖、江、海;某森林;某平原;某城市;
|
||||
一些分布,比如物产,按照Region来分布。
|
||||
再比如,灵气,应当也是按照region分布的。
|
||||
默认,一个region内部的属性,是共通的。
|
||||
同时,NPC应当对Region有观测和认知。
|
||||
"""
|
||||
id: int
|
||||
name: str
|
||||
desc: str
|
||||
shape: 'Shape'
|
||||
north_west_cor: str # 西北角坐标,格式: "x,y"
|
||||
south_east_cor: str # 东南角坐标,格式: "x,y"
|
||||
|
||||
# 这些字段将在__post_init__中设置
|
||||
cors: list[tuple[int, int]] = field(init=False) # 存储所有坐标点
|
||||
center_loc: tuple[int, int] = field(init=False)
|
||||
area: int = field(init=False)
|
||||
|
||||
def __post_init__(self):
|
||||
"""初始化计算字段"""
|
||||
# 先计算所有坐标点
|
||||
self.cors = get_tiles_from_shape(self.shape, self.north_west_cor, self.south_east_cor)
|
||||
|
||||
# 基于坐标点计算面积
|
||||
self.area = len(self.cors)
|
||||
|
||||
# 计算中心位置(基于实际坐标点的平均值)
|
||||
if self.cors:
|
||||
avg_x = sum(coord[0] for coord in self.cors) // len(self.cors)
|
||||
avg_y = sum(coord[1] for coord in self.cors) // len(self.cors)
|
||||
self.center_loc = (avg_x, avg_y)
|
||||
else:
|
||||
# 如果没有坐标点,使用边界框中心作为fallback
|
||||
nw_coords = tuple(map(int, self.north_west_cor.split(',')))
|
||||
se_coords = tuple(map(int, self.south_east_cor.split(',')))
|
||||
self.center_loc = (
|
||||
(nw_coords[0] + se_coords[0]) // 2,
|
||||
(nw_coords[1] + se_coords[1]) // 2
|
||||
)
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash(self.id)
|
||||
|
||||
def __eq__(self, other) -> bool:
|
||||
if not isinstance(other, Region):
|
||||
return False
|
||||
return self.id == other.id
|
||||
|
||||
@abstractmethod
|
||||
def get_region_type(self) -> str:
|
||||
"""返回区域类型的字符串表示"""
|
||||
pass
|
||||
|
||||
|
||||
class Shape(Enum):
|
||||
"""
|
||||
区域形状类型
|
||||
"""
|
||||
SQUARE = "square" # 正方形
|
||||
RECTANGLE = "rectangle" # 长方形
|
||||
MEANDERING = "meandering" # 蜿蜒的(如河流)
|
||||
|
||||
@classmethod
|
||||
def from_str(cls, shape_str: str) -> 'Shape':
|
||||
"""
|
||||
从字符串创建Shape实例
|
||||
|
||||
Args:
|
||||
shape_str: 形状的字符串表示,如 "square", "rectangle", "meandering"
|
||||
|
||||
Returns:
|
||||
对应的Shape枚举值
|
||||
|
||||
Raises:
|
||||
ValueError: 如果字符串不匹配任何已知的形状类型
|
||||
"""
|
||||
for shape in cls:
|
||||
if shape.value == shape_str:
|
||||
return shape
|
||||
raise ValueError(f"Unknown shape type: {shape_str}")
|
||||
|
||||
|
||||
@dataclass
|
||||
class NormalRegion(Region):
|
||||
"""
|
||||
普通区域 - 平原、大河之类的,没有灵气或灵气很低
|
||||
"""
|
||||
|
||||
def get_region_type(self) -> str:
|
||||
return "normal"
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"普通区域:{self.name} - {self.desc}"
|
||||
|
||||
|
||||
@dataclass
|
||||
class CultivateRegion(Region):
|
||||
"""
|
||||
修炼区域 - 有灵气的区域,可以修炼
|
||||
"""
|
||||
essence_type: EssenceType # 最高灵气类型
|
||||
essence_density: int # 最高灵气密度
|
||||
essence: Essence = field(init=False) # 灵气对象,根据 essence_type 和 essence_density 生成
|
||||
|
||||
def __post_init__(self):
|
||||
# 先调用父类的 __post_init__
|
||||
super().__post_init__()
|
||||
|
||||
# 创建灵气对象,主要灵气类型设置为指定密度,其他类型设置为0
|
||||
essence_density_dict = {essence_type: 0 for essence_type in EssenceType}
|
||||
essence_density_dict[self.essence_type] = self.essence_density
|
||||
self.essence = Essence(essence_density_dict)
|
||||
|
||||
def get_region_type(self) -> str:
|
||||
return "cultivate"
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"修炼区域:{self.name}({self.essence_type}行灵气:{self.essence_density})- {self.desc}"
|
||||
|
||||
|
||||
@dataclass
|
||||
class CityRegion(Region):
|
||||
"""
|
||||
城市区域 - 不能修炼,但会有特殊操作
|
||||
"""
|
||||
|
||||
def get_region_type(self) -> str:
|
||||
return "city"
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"城市区域:{self.name} - {self.desc}"
|
||||
|
||||
|
||||
T = TypeVar('T', NormalRegion, CultivateRegion, CityRegion)
|
||||
|
||||
def _load_regions(region_type: Type[T], config_name: str) -> tuple[dict[int, T], dict[str, T]]:
|
||||
"""
|
||||
通用的区域加载函数
|
||||
|
||||
Args:
|
||||
region_type: 区域类型 (NormalRegion, CultivateRegion, CityRegion)
|
||||
config_name: 配置文件名 ("normal_region", "cultivate_region", "city_region")
|
||||
|
||||
Returns:
|
||||
(按ID索引的字典, 按名称索引的字典)
|
||||
"""
|
||||
regions_by_id: dict[int, T] = {}
|
||||
regions_by_name: dict[str, T] = {}
|
||||
|
||||
region_df = game_configs[config_name]
|
||||
for _, row in region_df.iterrows():
|
||||
# 构建基础参数
|
||||
base_params = {
|
||||
"id": int(row["id"]),
|
||||
"name": str(row["name"]),
|
||||
"desc": str(row["desc"]),
|
||||
"shape": Shape.from_str(str(row["shape"])),
|
||||
"north_west_cor": str(row["north-west-cor"]),
|
||||
"south_east_cor": str(row["south-east-cor"])
|
||||
}
|
||||
|
||||
# 如果是修炼区域,添加额外参数
|
||||
if region_type == CultivateRegion:
|
||||
base_params["essence_type"] = EssenceType.from_str(str(row["root_type"]))
|
||||
base_params["essence_density"] = int(row["root_density"])
|
||||
|
||||
region = region_type(**base_params)
|
||||
regions_by_id[region.id] = region
|
||||
regions_by_name[region.name] = region
|
||||
|
||||
return regions_by_id, regions_by_name
|
||||
|
||||
|
||||
def load_all_regions() -> tuple[
|
||||
dict[int, Union[NormalRegion, CultivateRegion, CityRegion]],
|
||||
dict[str, Union[NormalRegion, CultivateRegion, CityRegion]]
|
||||
]:
|
||||
"""
|
||||
统一加载所有类型的区域数据
|
||||
返回: (按ID索引的字典, 按名称索引的字典)
|
||||
"""
|
||||
all_regions_by_id: dict[int, Union[NormalRegion, CultivateRegion, CityRegion]] = {}
|
||||
all_regions_by_name: dict[str, Union[NormalRegion, CultivateRegion, CityRegion]] = {}
|
||||
|
||||
# 加载普通区域
|
||||
normal_by_id, normal_by_name = _load_regions(NormalRegion, "normal_region")
|
||||
all_regions_by_id.update(normal_by_id)
|
||||
all_regions_by_name.update(normal_by_name)
|
||||
|
||||
# 加载修炼区域
|
||||
cultivate_by_id, cultivate_by_name = _load_regions(CultivateRegion, "cultivate_region")
|
||||
all_regions_by_id.update(cultivate_by_id)
|
||||
all_regions_by_name.update(cultivate_by_name)
|
||||
|
||||
# 加载城市区域
|
||||
city_by_id, city_by_name = _load_regions(CityRegion, "city_region")
|
||||
all_regions_by_id.update(city_by_id)
|
||||
all_regions_by_name.update(city_by_name)
|
||||
|
||||
return all_regions_by_id, all_regions_by_name
|
||||
|
||||
|
||||
# 从配表加载所有区域数据
|
||||
regions_by_id, regions_by_name = load_all_regions()
|
||||
|
||||
# 分别加载各类型区域数据
|
||||
normal_regions_by_id, normal_regions_by_name = _load_regions(NormalRegion, "normal_region")
|
||||
cultivate_regions_by_id, cultivate_regions_by_name = _load_regions(CultivateRegion, "cultivate_region")
|
||||
city_regions_by_id, city_regions_by_name = _load_regions(CityRegion, "city_region")
|
||||
@@ -18,6 +18,25 @@ class Root(Enum):
|
||||
WATER = "水"
|
||||
FIRE = "火"
|
||||
EARTH = "土"
|
||||
|
||||
@classmethod
|
||||
def from_str(cls, root_str: str) -> 'Root':
|
||||
"""
|
||||
从字符串创建Root实例
|
||||
|
||||
Args:
|
||||
root_str: 灵根的字符串表示,如 "金", "木", "水", "火", "土"
|
||||
|
||||
Returns:
|
||||
对应的Root枚举值
|
||||
|
||||
Raises:
|
||||
ValueError: 如果字符串不匹配任何已知的灵根类型
|
||||
"""
|
||||
for root in cls:
|
||||
if root.value == root_str:
|
||||
return root
|
||||
raise ValueError(f"Unknown root type: {root_str}")
|
||||
|
||||
corres_essence_type = {
|
||||
Root.GOLD: EssenceType.GOLD,
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import itertools
|
||||
from enum import Enum
|
||||
from dataclasses import dataclass, field
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from src.classes.essence import Essence, EssenceType
|
||||
if TYPE_CHECKING:
|
||||
from src.classes.region import Region
|
||||
|
||||
class TileType(Enum):
|
||||
PLAIN = "plain" # 平原
|
||||
@@ -22,52 +23,6 @@ class TileType(Enum):
|
||||
RUINS = "ruins" # 遗迹
|
||||
FARM = "farm" # 农田
|
||||
|
||||
region_id_counter = itertools.count(1)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Region():
|
||||
"""
|
||||
理想中,一些地块应当在一起组成一个区域。
|
||||
比如,某山;某湖、江、海;某森林;某平原;某城市;
|
||||
一些分布,比如物产,按照Region来分布。
|
||||
再比如,灵气,应当也是按照region分布的。
|
||||
默认,一个region内部的属性,是共通的。
|
||||
同时,NPC应当对Region有观测和认知。
|
||||
"""
|
||||
name: str
|
||||
description: str
|
||||
essence: Essence
|
||||
id: int = field(init=False)
|
||||
center_loc: tuple[int, int] = field(init=False)
|
||||
area: int = field(init=False)
|
||||
|
||||
def __post_init__(self):
|
||||
self.id = next(region_id_counter)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"区域。名字:{self.name},描述:{self.description},最浓的灵气:{self.get_most_dense_essence()}, 灵气值:{self.get_most_dense_essence_value()}"
|
||||
|
||||
def get_most_dense_essence(self) -> EssenceType:
|
||||
return max(self.essence.density.items(), key=lambda x: x[1])[0]
|
||||
|
||||
def get_most_dense_essence_value(self) -> int:
|
||||
most_dense_essence = self.get_most_dense_essence()
|
||||
return self.essence.density[most_dense_essence]
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash(self.id)
|
||||
|
||||
def __eq__(self, other) -> bool:
|
||||
if not isinstance(other, Region):
|
||||
return False
|
||||
return self.id == other.id
|
||||
# 物产
|
||||
# 灵气
|
||||
# 其他
|
||||
|
||||
default_region = Region(name="平原", description="最普通的平原,没有什么可说的", essence=Essence(density={EssenceType.GOLD: 1, EssenceType.WOOD: 1, EssenceType.WATER: 1, EssenceType.FIRE: 1, EssenceType.EARTH: 1}))
|
||||
default_region.area = 1 # 默认区域面积为1
|
||||
|
||||
@dataclass
|
||||
class Tile():
|
||||
@@ -75,7 +30,7 @@ class Tile():
|
||||
type: TileType
|
||||
x: int
|
||||
y: int
|
||||
region: Region # 可以是一个region的一部分,也可以不属于任何region
|
||||
region: 'Region' = None # 可以是一个region的一部分,也可以不属于任何region
|
||||
|
||||
class Map():
|
||||
"""
|
||||
@@ -83,10 +38,18 @@ class Map():
|
||||
"""
|
||||
def __init__(self, width: int, height: int):
|
||||
self.tiles = {}
|
||||
self.regions = {} # region_id -> region
|
||||
self.region_names = {} # region_name -> region
|
||||
self.width = width
|
||||
self.height = height
|
||||
|
||||
# 加载所有region数据到Map中
|
||||
self._load_regions()
|
||||
|
||||
def _load_regions(self):
|
||||
"""从配置文件加载所有区域数据到Map实例中"""
|
||||
# 延迟导入避免循环导入
|
||||
from src.classes.region import load_all_regions
|
||||
|
||||
self.regions, self.region_names = load_all_regions()
|
||||
|
||||
def is_in_bounds(self, x: int, y: int) -> bool:
|
||||
"""
|
||||
@@ -95,25 +58,11 @@ class Map():
|
||||
return 0 <= x < self.width and 0 <= y < self.height
|
||||
|
||||
def create_tile(self, x: int, y: int, tile_type: TileType):
|
||||
self.tiles[(x, y)] = Tile(tile_type, x, y, region=default_region)
|
||||
self.tiles[(x, y)] = Tile(tile_type, x, y, region=None)
|
||||
|
||||
def get_tile(self, x: int, y: int) -> Tile:
|
||||
return self.tiles[(x, y)]
|
||||
|
||||
def create_region(self, name: str, description: str, essence: Essence, locs: list[tuple[int, int]]):
|
||||
"""
|
||||
创建一个region。
|
||||
"""
|
||||
region = Region(name=name, description=description, essence=essence)
|
||||
center_loc = self.get_center_locs(locs)
|
||||
for loc in locs:
|
||||
self.tiles[loc].region = region
|
||||
region.center_loc = center_loc
|
||||
region.area = len(locs)
|
||||
self.regions[region.id] = region
|
||||
self.region_names[name] = region
|
||||
return region
|
||||
|
||||
def get_center_locs(self, locs: list[tuple[int, int]]) -> tuple[int, int]:
|
||||
"""
|
||||
获取locs的中心位置。
|
||||
@@ -139,7 +88,7 @@ class Map():
|
||||
|
||||
return min(locs, key=distance_squared)
|
||||
|
||||
def get_region(self, x: int, y: int) -> Region | None:
|
||||
def get_region(self, x: int, y: int) -> 'Region | None':
|
||||
"""
|
||||
获取一个region。
|
||||
"""
|
||||
|
||||
@@ -284,14 +284,14 @@ class Front:
|
||||
def _draw_region_labels(self):
|
||||
"""绘制区域标签"""
|
||||
pygame = self.pygame
|
||||
map_obj = self.world.map
|
||||
ts = self.tile_size
|
||||
m = self.margin
|
||||
mouse_x, mouse_y = pygame.mouse.get_pos()
|
||||
|
||||
# 绘制每个region的标签
|
||||
from src.classes.region import regions_by_id
|
||||
hovered_region = None
|
||||
for region in map_obj.regions.values():
|
||||
for region in regions_by_id.values():
|
||||
name = getattr(region, "name", None)
|
||||
if not name:
|
||||
continue
|
||||
@@ -458,25 +458,24 @@ class Front:
|
||||
|
||||
def _draw_tooltip_for_region(self, region, mouse_x: int, mouse_y: int):
|
||||
"""绘制Region的tooltip"""
|
||||
# 如果region为None,不显示tooltip
|
||||
if region is None:
|
||||
return
|
||||
|
||||
lines = [
|
||||
f"区域: {region.name}",
|
||||
f"描述: {region.description}",
|
||||
f"描述: {region.desc}",
|
||||
]
|
||||
|
||||
# 添加灵气信息
|
||||
if hasattr(region, 'essence') and region.essence:
|
||||
essence_items = []
|
||||
for essence_type, density in region.essence.density.items():
|
||||
if density > 0:
|
||||
essence_name = str(essence_type)
|
||||
essence_items.append((density, essence_name))
|
||||
|
||||
if essence_items:
|
||||
essence_items.sort(reverse=True)
|
||||
lines.append("灵气分布:")
|
||||
for density, name in essence_items:
|
||||
stars = "★" * density + "☆" * (10 - density)
|
||||
lines.append(f" {name}: {stars}")
|
||||
# 根据region类型添加灵气信息
|
||||
from src.classes.region import CultivateRegion
|
||||
|
||||
if isinstance(region, CultivateRegion):
|
||||
# 修炼区域:只显示最高灵气类型和密度
|
||||
stars = "★" * region.essence_density + "☆" * (10 - region.essence_density)
|
||||
lines.append(f"主要灵气: {region.essence_type} {stars}")
|
||||
|
||||
# 普通区域和城市区域不显示灵气信息
|
||||
|
||||
self._draw_tooltip(lines, mouse_x, mouse_y, self.tooltip_font)
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ def create_cultivation_world_map() -> Map:
|
||||
_create_base_terrain(game_map)
|
||||
|
||||
# 创建区域
|
||||
_create_regions(game_map)
|
||||
_assign_regions_to_tiles(game_map)
|
||||
|
||||
return game_map
|
||||
|
||||
@@ -208,347 +208,20 @@ def _add_other_terrains(game_map: Map):
|
||||
if game_map.tiles[(x, y)].type == TileType.PLAIN:
|
||||
game_map.tiles[(x, y)].type = TileType.SWAMP
|
||||
|
||||
def _create_regions(game_map: Map):
|
||||
"""创建区域并分配灵气"""
|
||||
|
||||
# 收集各地形的坐标
|
||||
terrain_coords = {}
|
||||
def _assign_regions_to_tiles(game_map: Map):
|
||||
"""将区域分配给地图中的tiles"""
|
||||
# 初始化所有tiles的region为None
|
||||
for x in range(game_map.width):
|
||||
for y in range(game_map.height):
|
||||
tile_type = game_map.tiles[(x, y)].type
|
||||
if tile_type not in terrain_coords:
|
||||
terrain_coords[tile_type] = []
|
||||
terrain_coords[tile_type].append((x, y))
|
||||
game_map.tiles[(x, y)].region = None
|
||||
|
||||
# 西域流沙 (大漠)
|
||||
if TileType.DESERT in terrain_coords:
|
||||
essence = Essence({
|
||||
EssenceType.FIRE: 8,
|
||||
EssenceType.EARTH: 7,
|
||||
EssenceType.GOLD: 6,
|
||||
EssenceType.WATER: 1,
|
||||
EssenceType.WOOD: 2
|
||||
})
|
||||
game_map.create_region(
|
||||
"西域流沙",
|
||||
"茫茫大漠,黄沙漫天。此地火行灵气浓郁,土行次之,乃是修炼火系功法的绝佳之地。",
|
||||
essence,
|
||||
terrain_coords[TileType.DESERT]
|
||||
)
|
||||
|
||||
# 南疆蛮荒 (雨林)
|
||||
if TileType.RAINFOREST in terrain_coords:
|
||||
essence = Essence({
|
||||
EssenceType.WOOD: 8, # 木行主属性,但低于洞府的10
|
||||
EssenceType.WATER: 6,
|
||||
EssenceType.EARTH: 5,
|
||||
EssenceType.FIRE: 3,
|
||||
EssenceType.GOLD: 2
|
||||
})
|
||||
game_map.create_region(
|
||||
"南疆蛮荒",
|
||||
"古木参天,藤蔓缠绕。此地木行灵气极为浓郁,水行次之,是修炼木系功法和炼制灵药的宝地。",
|
||||
essence,
|
||||
terrain_coords[TileType.RAINFOREST]
|
||||
)
|
||||
|
||||
# 极北冰原 (冰原)
|
||||
if TileType.GLACIER in terrain_coords:
|
||||
essence = Essence({
|
||||
EssenceType.WATER: 8,
|
||||
EssenceType.GOLD: 6,
|
||||
EssenceType.EARTH: 4,
|
||||
EssenceType.FIRE: 1,
|
||||
EssenceType.WOOD: 3
|
||||
})
|
||||
game_map.create_region(
|
||||
"极北冰原",
|
||||
"千里冰封,万年不化。此地水行灵气充沛,金行次之,寒气逼人,唯有修炼寒冰功法者方可久居。",
|
||||
essence,
|
||||
terrain_coords[TileType.GLACIER]
|
||||
)
|
||||
|
||||
# 无边碧海 (海洋)
|
||||
if TileType.SEA in terrain_coords:
|
||||
essence = Essence({
|
||||
EssenceType.WATER: 8, # 水行主属性,但低于洞府的10
|
||||
EssenceType.GOLD: 4,
|
||||
EssenceType.WOOD: 3,
|
||||
EssenceType.EARTH: 2,
|
||||
EssenceType.FIRE: 1
|
||||
})
|
||||
game_map.create_region(
|
||||
"无边碧海",
|
||||
"浩瀚无垠,波涛汹涌。此地水行灵气达到极致,蕴含无穷玄机,是水系修士向往的圣地。",
|
||||
essence,
|
||||
terrain_coords[TileType.SEA]
|
||||
)
|
||||
|
||||
# 天河奔流 (大河)
|
||||
if TileType.WATER in terrain_coords:
|
||||
essence = Essence({
|
||||
EssenceType.WATER: 7,
|
||||
EssenceType.WOOD: 5,
|
||||
EssenceType.EARTH: 4,
|
||||
EssenceType.GOLD: 3,
|
||||
EssenceType.FIRE: 2
|
||||
})
|
||||
game_map.create_region(
|
||||
"天河奔流",
|
||||
"一江春水向东流,奔腾不息入东海。此河贯穿东西,水行灵气丰沛,滋养两岸万物生灵。",
|
||||
essence,
|
||||
terrain_coords[TileType.WATER]
|
||||
)
|
||||
|
||||
# 青峰山脉 (山脉)
|
||||
if TileType.MOUNTAIN in terrain_coords:
|
||||
essence = Essence({
|
||||
EssenceType.EARTH: 8,
|
||||
EssenceType.GOLD: 7,
|
||||
EssenceType.FIRE: 5,
|
||||
EssenceType.WOOD: 3,
|
||||
EssenceType.WATER: 2
|
||||
})
|
||||
game_map.create_region(
|
||||
"青峰山脉",
|
||||
"连绵起伏,直插云霄。此地土行灵气深厚,金行次之,乃是修炼土系功法和寻找天材地宝的胜地。",
|
||||
essence,
|
||||
terrain_coords[TileType.MOUNTAIN]
|
||||
)
|
||||
|
||||
# 万丈雪峰 (雪山)
|
||||
if TileType.SNOW_MOUNTAIN in terrain_coords:
|
||||
essence = Essence({
|
||||
EssenceType.WATER: 7, # 水行主属性,但低于洞府的10
|
||||
EssenceType.GOLD: 6, # 金行次要,但低于洞府的10
|
||||
EssenceType.EARTH: 6,
|
||||
EssenceType.FIRE: 1,
|
||||
EssenceType.WOOD: 2
|
||||
})
|
||||
game_map.create_region(
|
||||
"万丈雪峰",
|
||||
"雪峰皑皑,寒风刺骨。此地水行与金行灵气并重,是修炼冰系神通和淬炼法宝的绝佳之地。",
|
||||
essence,
|
||||
terrain_coords[TileType.SNOW_MOUNTAIN]
|
||||
)
|
||||
|
||||
# 碧野千里 (草原)
|
||||
if TileType.GRASSLAND in terrain_coords:
|
||||
essence = Essence({
|
||||
EssenceType.WOOD: 5, # 木行属性适中
|
||||
EssenceType.EARTH: 5, # 土行属性适中
|
||||
EssenceType.WATER: 5,
|
||||
EssenceType.GOLD: 3,
|
||||
EssenceType.FIRE: 4
|
||||
})
|
||||
game_map.create_region(
|
||||
"碧野千里",
|
||||
"芳草萋萋,一望无际。此地木土并重,灵气平和,是修炼基础功法和放牧灵兽的理想之地。",
|
||||
essence,
|
||||
terrain_coords[TileType.GRASSLAND]
|
||||
)
|
||||
|
||||
# 青云林海 (森林)
|
||||
if TileType.FOREST in terrain_coords:
|
||||
essence = Essence({
|
||||
EssenceType.WOOD: 7, # 木行主属性,但低于洞府的10
|
||||
EssenceType.WATER: 4,
|
||||
EssenceType.EARTH: 4,
|
||||
EssenceType.GOLD: 3,
|
||||
EssenceType.FIRE: 3
|
||||
})
|
||||
game_map.create_region(
|
||||
"青云林海",
|
||||
"古树参天,绿意盎然。此地木行灵气浓郁,是修炼木系功法、采集灵草和驯服林间灵兽的宝地。",
|
||||
essence,
|
||||
terrain_coords[TileType.FOREST]
|
||||
)
|
||||
|
||||
# 炎狱火山 (火山)
|
||||
if TileType.VOLCANO in terrain_coords:
|
||||
essence = Essence({
|
||||
EssenceType.FIRE: 8, # 火行主属性,但低于洞府的10
|
||||
EssenceType.EARTH: 7,
|
||||
EssenceType.GOLD: 4,
|
||||
EssenceType.WATER: 1,
|
||||
EssenceType.WOOD: 1
|
||||
})
|
||||
game_map.create_region(
|
||||
"炎狱火山",
|
||||
"烈焰冲天,岩浆奔流。此地火行灵气达到极致,是修炼火系神通和锻造法宝的圣地,常人不可近。",
|
||||
essence,
|
||||
terrain_coords[TileType.VOLCANO]
|
||||
)
|
||||
|
||||
# 为每个2*2城市、洞穴和遗迹创建独立区域
|
||||
_create_city_regions(game_map)
|
||||
_create_wuxing_caves_regions(game_map)
|
||||
_create_ruins_regions(game_map)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# 沃土良田 (农田)
|
||||
if TileType.FARM in terrain_coords:
|
||||
essence = Essence({
|
||||
EssenceType.WOOD: 6, # 木行属性较强
|
||||
EssenceType.EARTH: 6, # 土行属性较强
|
||||
EssenceType.WATER: 6,
|
||||
EssenceType.GOLD: 2,
|
||||
EssenceType.FIRE: 3
|
||||
})
|
||||
game_map.create_region(
|
||||
"沃土良田",
|
||||
"土地肥沃,五谷丰登。此地木土并重,水行充沛,是种植灵药和培育灵植的理想之地。",
|
||||
essence,
|
||||
terrain_coords[TileType.FARM]
|
||||
)
|
||||
|
||||
# 平原地带 (平原)
|
||||
if TileType.PLAIN in terrain_coords:
|
||||
essence = Essence({
|
||||
EssenceType.EARTH: 5,
|
||||
EssenceType.WOOD: 4,
|
||||
EssenceType.WATER: 4,
|
||||
EssenceType.GOLD: 3,
|
||||
EssenceType.FIRE: 3
|
||||
})
|
||||
game_map.create_region(
|
||||
"平原地带",
|
||||
"地势平坦,灵气平和。此地五行均衡,是初学修炼者打基础和建立宗门的理想之地。",
|
||||
essence,
|
||||
terrain_coords[TileType.PLAIN]
|
||||
)
|
||||
|
||||
# 迷雾沼泽 (沼泽)
|
||||
if TileType.SWAMP in terrain_coords:
|
||||
essence = Essence({
|
||||
EssenceType.WATER: 6, # 水行属性较强
|
||||
EssenceType.WOOD: 5, # 木行属性适中
|
||||
EssenceType.EARTH: 5,
|
||||
EssenceType.FIRE: 2,
|
||||
EssenceType.GOLD: 3
|
||||
})
|
||||
game_map.create_region(
|
||||
"迷雾沼泽",
|
||||
"雾气缭绕,泥泞不堪。此地水木灵气浓郁,但瘴气丛生,是修炼毒功和寻找奇异灵草的危险之地。",
|
||||
essence,
|
||||
terrain_coords[TileType.SWAMP]
|
||||
)
|
||||
|
||||
def _create_city_regions(game_map: Map):
|
||||
"""为每个2*2城市创建独立区域"""
|
||||
cities = [
|
||||
{"name": "青云城", "base_x": 34, "base_y": 21, "description": "繁华都市,人烟稠密,商贾云集。此地金行灵气较为集中,是交易天材地宝、寻找机缘的重要场所。"},
|
||||
{"name": "沙月城", "base_x": 14, "base_y": 19, "description": "沙漠绿洲中的贸易重镇,各路商队在此集结。金行灵气充沛,是修士补给和交流的重要据点。"},
|
||||
{"name": "翠林城", "base_x": 54, "base_y": 14, "description": "森林深处的修仙重镇,木行灵气与金行灵气并重。众多修士在此栖居,是修炼和炼宝的理想之地。"}
|
||||
]
|
||||
|
||||
for city in cities:
|
||||
base_x, base_y = city["base_x"], city["base_y"]
|
||||
city_coords = []
|
||||
|
||||
for dx in range(2):
|
||||
for dy in range(2):
|
||||
x, y = base_x + dx, base_y + dy
|
||||
if game_map.is_in_bounds(x, y):
|
||||
city_coords.append((x, y))
|
||||
|
||||
essence = Essence({
|
||||
EssenceType.GOLD: 6,
|
||||
EssenceType.EARTH: 5,
|
||||
EssenceType.WOOD: 5,
|
||||
EssenceType.WATER: 4,
|
||||
EssenceType.FIRE: 4
|
||||
})
|
||||
|
||||
game_map.create_region(
|
||||
city["name"],
|
||||
city["description"],
|
||||
essence,
|
||||
city_coords
|
||||
)
|
||||
|
||||
def _create_wuxing_caves_regions(game_map: Map):
|
||||
"""为每个2*2五行洞府创建独立区域"""
|
||||
wuxing_caves = [
|
||||
{"name": "太白金府", "base_x": 26, "base_y": 12, "element": EssenceType.GOLD,
|
||||
"description": "青峰山脉深处的金行洞府,金精气凝,刀剑鸣音不绝,乃金系修士的最高圣地。"},
|
||||
{"name": "青木洞天", "base_x": 48, "base_y": 18, "element": EssenceType.WOOD,
|
||||
"description": "青云林海中的木行洞府,生机盎然,灵药遍地,乃木系修士的最高圣地。"},
|
||||
{"name": "玄水秘境", "base_x": 67, "base_y": 25, "element": EssenceType.WATER,
|
||||
"description": "无边碧海深处的水行洞府,碧波万里,水精凝神,乃水系修士的最高圣地。"},
|
||||
{"name": "离火洞府", "base_x": 50, "base_y": 33, "element": EssenceType.FIRE,
|
||||
"description": "炎狱火山旁的火行洞府,烈焰冲天,真火精纯,乃火系修士的最高圣地。"},
|
||||
{"name": "厚土玄宫", "base_x": 30, "base_y": 16, "element": EssenceType.EARTH,
|
||||
"description": "青峰山脉的土行洞府,厚德载物,山岳共鸣,乃土系修士的最高圣地。"}
|
||||
]
|
||||
|
||||
for cave in wuxing_caves:
|
||||
base_x, base_y = cave["base_x"], cave["base_y"]
|
||||
cave_coords = []
|
||||
|
||||
for dx in range(2):
|
||||
for dy in range(2):
|
||||
x, y = base_x + dx, base_y + dy
|
||||
if game_map.is_in_bounds(x, y):
|
||||
cave_coords.append((x, y))
|
||||
|
||||
# 每个洞府的主属性灵气为10(最高值),其他属性较低
|
||||
essence_config = {essence_type: 2 for essence_type in EssenceType}
|
||||
essence_config[cave["element"]] = 10 # 主属性达到最高值
|
||||
|
||||
essence = Essence(essence_config)
|
||||
|
||||
game_map.create_region(
|
||||
cave["name"],
|
||||
cave["description"],
|
||||
essence,
|
||||
cave_coords
|
||||
)
|
||||
|
||||
def _create_ruins_regions(game_map: Map):
|
||||
"""为每个2*2遗迹创建独立区域"""
|
||||
ruins = [
|
||||
{"name": "古越遗迹", "base_x": 25, "base_y": 40, "description": "雨林深处的上古遗迹,古藤缠绕,木行灵气与金行灵气交融。蕴藏古老功法与灵药配方。"},
|
||||
{"name": "沧海遗迹", "base_x": 66, "base_y": 47, "description": "沉没在海中的远古文明遗迹,水行灵气浓郁,潮汐间偶有宝物现世。"}
|
||||
]
|
||||
|
||||
for ruin in ruins:
|
||||
base_x, base_y = ruin["base_x"], ruin["base_y"]
|
||||
ruin_coords = []
|
||||
|
||||
for dx in range(2):
|
||||
for dy in range(2):
|
||||
x, y = base_x + dx, base_y + dy
|
||||
if game_map.is_in_bounds(x, y):
|
||||
ruin_coords.append((x, y))
|
||||
|
||||
# 根据遗迹位置调整灵气配置
|
||||
if ruin["name"] == "古越遗迹": # 雨林深处
|
||||
essence = Essence({
|
||||
EssenceType.WOOD: 8,
|
||||
EssenceType.GOLD: 6,
|
||||
EssenceType.WATER: 5,
|
||||
EssenceType.EARTH: 4,
|
||||
EssenceType.FIRE: 3
|
||||
})
|
||||
else: # 沧海遗迹,海中
|
||||
essence = Essence({
|
||||
EssenceType.WATER: 9,
|
||||
EssenceType.GOLD: 6,
|
||||
EssenceType.EARTH: 3,
|
||||
EssenceType.WOOD: 3,
|
||||
EssenceType.FIRE: 2
|
||||
})
|
||||
|
||||
game_map.create_region(
|
||||
ruin["name"],
|
||||
ruin["description"],
|
||||
essence,
|
||||
ruin_coords
|
||||
)
|
||||
# 遍历所有region,为对应的坐标分配正确的region
|
||||
# 现在从map实例获取region信息,而不是直接从region.py导入
|
||||
for region in game_map.regions.values():
|
||||
for coord_x, coord_y in region.cors:
|
||||
# 确保坐标在地图范围内
|
||||
if game_map.is_in_bounds(coord_x, coord_y):
|
||||
game_map.tiles[(coord_x, coord_y)].region = region
|
||||
|
||||
if __name__ == "__main__":
|
||||
# 创建地图
|
||||
|
||||
198
src/run/log.py
Normal file
198
src/run/log.py
Normal file
@@ -0,0 +1,198 @@
|
||||
"""
|
||||
通用日志模块
|
||||
功能:
|
||||
1. 自动记录各种调用的输入和输出(目前主要用于LLM)
|
||||
2. 启动时自动删除一周以前的日志文件
|
||||
3. 日志文件按日期分组
|
||||
"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
import json
|
||||
import time
|
||||
from datetime import datetime, timedelta
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
class Logger:
|
||||
"""通用日志记录器"""
|
||||
|
||||
def __init__(self, log_dir: str = "logs"):
|
||||
"""
|
||||
初始化日志记录器
|
||||
|
||||
Args:
|
||||
log_dir: 日志文件存储目录
|
||||
"""
|
||||
self.log_dir = Path(log_dir)
|
||||
self.log_dir.mkdir(exist_ok=True)
|
||||
|
||||
# 清理旧日志文件
|
||||
self._cleanup_old_logs()
|
||||
|
||||
# 设置当前日志文件
|
||||
self._setup_current_logger()
|
||||
|
||||
def _cleanup_old_logs(self):
|
||||
"""删除一周以前的日志文件"""
|
||||
cutoff_date = datetime.now() - timedelta(days=7)
|
||||
|
||||
for log_file in self.log_dir.glob("*.log"):
|
||||
try:
|
||||
# 从文件名中提取日期
|
||||
date_str = log_file.stem.split("_")[-1] # 取最后一部分作为日期
|
||||
file_date = datetime.strptime(date_str, "%Y%m%d")
|
||||
|
||||
if file_date < cutoff_date:
|
||||
log_file.unlink()
|
||||
print(f"已删除过期日志文件: {log_file}")
|
||||
except (ValueError, OSError) as e:
|
||||
print(f"处理日志文件 {log_file} 时出错: {e}")
|
||||
|
||||
def _setup_current_logger(self):
|
||||
"""设置当前日期的日志记录器"""
|
||||
current_date = datetime.now().strftime("%Y%m%d")
|
||||
log_filename = f"app_{current_date}.log"
|
||||
self.log_file_path = self.log_dir / log_filename
|
||||
|
||||
# 创建日志记录器
|
||||
self.logger = logging.getLogger(f"app_logger_{current_date}")
|
||||
self.logger.setLevel(logging.INFO)
|
||||
|
||||
# 清除现有的处理器(避免重复记录)
|
||||
self.logger.handlers.clear()
|
||||
|
||||
# 创建文件处理器
|
||||
handler = logging.FileHandler(
|
||||
self.log_file_path,
|
||||
encoding='utf-8',
|
||||
mode='a'
|
||||
)
|
||||
|
||||
# 设置日志格式
|
||||
formatter = logging.Formatter(
|
||||
'%(asctime)s - %(levelname)s - %(message)s',
|
||||
datefmt='%Y-%m-%d %H:%M:%S'
|
||||
)
|
||||
handler.setFormatter(formatter)
|
||||
|
||||
self.logger.addHandler(handler)
|
||||
|
||||
# 不向根日志记录器传播
|
||||
self.logger.propagate = False
|
||||
|
||||
def log_llm_interaction(self,
|
||||
model_name: str,
|
||||
prompt: str,
|
||||
response: str,
|
||||
duration: Optional[float] = None,
|
||||
additional_info: Optional[dict] = None):
|
||||
"""
|
||||
记录LLM交互
|
||||
|
||||
Args:
|
||||
model_name: 使用的模型名称
|
||||
prompt: 输入的提示词
|
||||
response: LLM的响应
|
||||
duration: 调用耗时(秒)
|
||||
additional_info: 额外信息
|
||||
"""
|
||||
log_data = {
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
"model_name": model_name,
|
||||
"prompt": prompt,
|
||||
"response": response,
|
||||
"prompt_length": len(prompt),
|
||||
"response_length": len(response),
|
||||
"duration": duration
|
||||
}
|
||||
|
||||
if additional_info:
|
||||
log_data.update(additional_info)
|
||||
|
||||
# 记录日志
|
||||
log_message = f"LLM_INTERACTION: {json.dumps(log_data, ensure_ascii=False)}"
|
||||
self.logger.info(log_message)
|
||||
|
||||
def log_error(self, error_message: str, prompt: str = None):
|
||||
"""
|
||||
记录错误
|
||||
|
||||
Args:
|
||||
error_message: 错误信息
|
||||
prompt: 相关的提示词(可选)
|
||||
"""
|
||||
log_data = {
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
"error": error_message,
|
||||
"prompt": prompt if prompt else None
|
||||
}
|
||||
|
||||
log_message = f"LLM_ERROR: {json.dumps(log_data, ensure_ascii=False)}"
|
||||
self.logger.error(log_message)
|
||||
|
||||
def get_today_stats(self) -> dict:
|
||||
"""
|
||||
获取今日统计信息
|
||||
|
||||
Returns:
|
||||
dict: 包含今日调用次数、总耗时等信息
|
||||
"""
|
||||
if not self.log_file_path.exists():
|
||||
return {
|
||||
"total_calls": 0,
|
||||
"total_duration": 0,
|
||||
"total_prompt_length": 0,
|
||||
"total_response_length": 0,
|
||||
"errors": 0
|
||||
}
|
||||
|
||||
stats = {
|
||||
"total_calls": 0,
|
||||
"total_duration": 0,
|
||||
"total_prompt_length": 0,
|
||||
"total_response_length": 0,
|
||||
"errors": 0
|
||||
}
|
||||
|
||||
with open(self.log_file_path, 'r', encoding='utf-8') as f:
|
||||
for line in f:
|
||||
if "LLM_INTERACTION:" in line:
|
||||
try:
|
||||
json_str = line.split("LLM_INTERACTION: ", 1)[1]
|
||||
data = json.loads(json_str)
|
||||
stats["total_calls"] += 1
|
||||
stats["total_duration"] += data.get("duration", 0) or 0
|
||||
stats["total_prompt_length"] += data.get("prompt_length", 0)
|
||||
stats["total_response_length"] += data.get("response_length", 0)
|
||||
except (json.JSONDecodeError, IndexError):
|
||||
pass
|
||||
elif "LLM_ERROR:" in line:
|
||||
stats["errors"] += 1
|
||||
|
||||
return stats
|
||||
|
||||
|
||||
# 全局日志记录器实例
|
||||
_logger = None
|
||||
|
||||
def get_logger() -> Logger:
|
||||
"""获取全局日志记录器实例"""
|
||||
global _logger
|
||||
if _logger is None:
|
||||
_logger = Logger()
|
||||
return _logger
|
||||
|
||||
# LLM专用的便捷函数
|
||||
def log_llm_call(model_name: str, prompt: str, response: str, duration: float = None):
|
||||
"""便捷函数:记录LLM调用"""
|
||||
logger = get_logger()
|
||||
logger.log_llm_interaction(model_name, prompt, response, duration)
|
||||
|
||||
def log_llm_error(error_message: str, prompt: str = None):
|
||||
"""便捷函数:记录LLM错误"""
|
||||
logger = get_logger()
|
||||
logger.log_error(error_message, prompt)
|
||||
|
||||
# 向后兼容的别名
|
||||
get_llm_logger = get_logger
|
||||
@@ -23,6 +23,7 @@ from src.run.create_map import create_cultivation_world_map
|
||||
from src.utils.names import get_random_name
|
||||
from src.utils.id_generator import get_avatar_id
|
||||
from src.utils.config import CONFIG
|
||||
from src.run.log import get_logger
|
||||
|
||||
|
||||
def clamp(value: int, lo: int, hi: int) -> int:
|
||||
@@ -90,6 +91,10 @@ def make_avatars(world: World, count: int = 12, current_month_stamp: MonthStamp
|
||||
|
||||
async def main():
|
||||
# 为了每次更丰富,使用随机种子;如需复现可将 seed 固定
|
||||
|
||||
# 初始化日志系统(会自动清理旧日志)
|
||||
logger = get_logger()
|
||||
print(f"日志系统已初始化,日志文件:{logger.log_file_path}")
|
||||
|
||||
game_map = create_cultivation_world_map()
|
||||
world = World(map=game_map, month_stamp=create_month_stamp(Year(100), Month.JANUARY))
|
||||
|
||||
@@ -5,7 +5,8 @@ from src.utils.config import CONFIG
|
||||
|
||||
|
||||
def load_csv(path: Path) -> pd.DataFrame:
|
||||
df = pd.read_csv(path)
|
||||
# 跳过第二行说明行,只读取标题行和实际数据行
|
||||
df = pd.read_csv(path, skiprows=[1])
|
||||
row_types = {
|
||||
"id": int,
|
||||
"name": str,
|
||||
|
||||
@@ -7,6 +7,7 @@ import json5
|
||||
|
||||
from src.utils.config import CONFIG
|
||||
from src.utils.io import read_txt
|
||||
from src.run.log import log_llm_call
|
||||
|
||||
def get_prompt(template: str, infos: dict) -> str:
|
||||
"""
|
||||
@@ -36,7 +37,9 @@ def call_llm(prompt: str) -> str:
|
||||
)
|
||||
|
||||
# 返回生成的内容
|
||||
return response.choices[0].message.content
|
||||
result = response.choices[0].message.content
|
||||
log_llm_call(model_name, prompt, result) # 记录日志
|
||||
return result
|
||||
|
||||
async def call_llm_async(prompt: str) -> str:
|
||||
"""
|
||||
@@ -48,7 +51,8 @@ async def call_llm_async(prompt: str) -> str:
|
||||
str: LLM返回的结果
|
||||
"""
|
||||
# 使用asyncio.to_thread包装同步调用
|
||||
return await asyncio.to_thread(call_llm, prompt)
|
||||
result = await asyncio.to_thread(call_llm, prompt)
|
||||
return result
|
||||
|
||||
def parse_llm_response(res: str) -> dict:
|
||||
"""
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
id,name,desc,grade
|
||||
,,,
|
||||
1,灵兔,天性机敏的灵性兔子,毛色雪白,蕴含微弱灵力,性情温和易驯服,1
|
||||
2,魔狼,凶猛的魔性狼族,体型巨大,拥有强大的魔力和锋利的爪牙,2
|
||||
|
||||
|
5
static/game_configs/city_region.csv
Normal file
5
static/game_configs/city_region.csv
Normal file
@@ -0,0 +1,5 @@
|
||||
id,name,desc,shape,north-west-cor,south-east-cor
|
||||
"ID必须以3开头",,,,
|
||||
301,青云城,繁华都市,人烟稠密,商贾云集。此地是交易天材地宝、寻找机缘的重要场所。,square,"34,21","35,22"
|
||||
302,沙月城,沙漠绿洲中的贸易重镇,各路商队在此集结,是修士补给和交流的重要据点。,square,"14,19","15,20"
|
||||
303,翠林城,森林深处的修仙重镇,众多修士在此栖居,是修炼和炼宝的理想之地。,square,"54,14","55,15"
|
||||
|
9
static/game_configs/cultivate_region.csv
Normal file
9
static/game_configs/cultivate_region.csv
Normal file
@@ -0,0 +1,9 @@
|
||||
id,name,desc,shape,north-west-cor,south-east-cor,root_type,root_density
|
||||
"ID必须以2开头",,,,,,,
|
||||
201,太白金府,青峰山脉深处的金行洞府,金精气凝,刀剑鸣音不绝,乃金系修士的最高圣地。,square,"26,12","27,13",金,10
|
||||
202,青木洞天,青云林海中的木行洞府,生机盎然,灵药遍地,乃木系修士的最高圣地。,square,"48,18","49,19",木,10
|
||||
203,玄水秘境,无边碧海深处的水行洞府,碧波万里,水精凝神,乃水系修士的最高圣地。,square,"67,25","68,26",水,10
|
||||
204,离火洞府,炎狱火山旁的火行洞府,烈焰冲天,真火精纯,乃火系修士的最高圣地。,square,"50,33","51,34",火,10
|
||||
205,厚土玄宫,青峰山脉的土行洞府,厚德载物,山岳共鸣,乃土系修士的最高圣地。,square,"30,16","31,17",土,10
|
||||
206,古越遗迹,雨林深处的上古遗迹,古藤缠绕,木行灵气与金行灵气交融。蕴藏古老功法与灵药配方。,square,"25,40","26,41",木,8
|
||||
207,沧海遗迹,沉没在海中的远古文明遗迹,水行灵气浓郁,潮汐间偶有宝物现世。,square,"66,47","67,48",水,9
|
||||
|
@@ -1,4 +1,5 @@
|
||||
id,name,desc,grade
|
||||
,,,
|
||||
1,灵兔皮毛,灵性充沛的兔子皮毛,质地柔软,常用于制作防护装备,1
|
||||
2,魔狼皮毛,蕴含魔力的狼族皮毛,坚韧耐用,适合制作高级护甲,2
|
||||
3,奇草,生长在灵气充沛之地的奇异草药,具有神奇的治愈效果,1
|
||||
|
||||
|
15
static/game_configs/normal_region.csv
Normal file
15
static/game_configs/normal_region.csv
Normal file
@@ -0,0 +1,15 @@
|
||||
id,name,desc,shape,north-west-cor,south-east-cor
|
||||
"ID必须以1开头",,,,
|
||||
101,平原地带,地势平坦,灵气平和。是初学修炼者打基础和建立宗门的理想之地。,rectangle,"19,9","64,34"
|
||||
102,西域流沙,茫茫大漠,黄沙漫天。此地气候干燥,日夜温差极大,是沙漠商队的必经之路。,rectangle,"0,0","18,49"
|
||||
103,南疆蛮荒,古木参天,藤蔓缠绕。此地森林茂密,野兽众多,是采集药材和狩猎的危险之地。,rectangle,"19,35","64,45"
|
||||
104,极北冰原,千里冰封,万年不化。此地严寒刺骨,风雪交加,只有最坚韧的冒险者才能在此生存。,rectangle,"19,0","64,8"
|
||||
105,无边碧海,浩瀚无垠,波涛汹涌。此地风浪险恶,暗礁密布,是海商和渔民的挑战之海。,rectangle,"65,0","69,49"
|
||||
106,天河奔流,一江春水向东流,奔腾不息入东海。此河贯穿东西,水流湍急,是重要的交通要道。,meandering,"18,25","67,47"
|
||||
107,青峰山脉,连绵起伏,直插云霄。此地山势险峻,多有奇石异洞,是探险者寻宝的热门之地。,rectangle,"28,5","32,20"
|
||||
108,万丈雪峰,雪峰皑皑,寒风刺骨。此地终年积雪,山路崎岖,是登山者的终极挑战。,rectangle,"25,2","34,7"
|
||||
109,碧野千里,芳草萋萋,一望无际。此地水草丰美,牛羊成群,是游牧民族的天然牧场。,rectangle,"20,12","39,24"
|
||||
110,青云林海,古树参天,绿意盎然。此地森林广袤,物产丰富,是伐木工和猎人的主要活动区域。,rectangle,"40,10","59,29"
|
||||
111,炎狱火山,烈焰冲天,岩浆奔流。此地火山活跃,地热丰富,是铁匠锻造的理想之地,但也极其危险。,square,"52,32","54,34"
|
||||
112,沃土良田,土地肥沃,五谷丰登。此地土壤深厚,雨水充沛,是农民耕种的黄金宝地。,rectangle,"33,25","37,29"
|
||||
113,迷雾沼泽,雾气缭绕,泥泞不堪。此地地形复杂,瘴气丛生,是盗贼和亡命之徒的藏身之所。,rectangle,"42,30","45,33"
|
||||
|
@@ -1,4 +1,5 @@
|
||||
id,name,prompt
|
||||
,,
|
||||
1,理性,你是一个理性的人,你总是会用逻辑来思考问题,做事会谋定而后动。
|
||||
2,无常,你是一个无常的人,目标飘忽不定,不会长期坚持一个目标。
|
||||
3,怠惰,你是一个怠惰的人,你总是会拖延,不想努力,更热衷于享受人生。
|
||||
|
||||
|
@@ -1,3 +1,4 @@
|
||||
id,name,desc,grade
|
||||
,,,
|
||||
1,奇草,生长在灵气充沛之地的奇异草药,叶片呈淡蓝色,具有神奇的治愈效果,1
|
||||
2,灵木,千年古树吸收天地灵气而成,木质坚硬且蕴含浓郁灵力,2
|
||||
|
||||
|
@@ -1,5 +1,4 @@
|
||||
你是一个决策者,这是一个修仙的仙侠世界,你负责来决定一些NPC的下一步行为。
|
||||
世界地图上存在的区域为:
|
||||
{global_info}
|
||||
你需要进行决策的NPC的dict[AvatarId, info]为
|
||||
{avatar_infos}
|
||||
|
||||
Reference in New Issue
Block a user