From 8124537cff3e1ba92ca516c4a59753c8c11fa97e Mon Sep 17 00:00:00 2001 From: bridge Date: Mon, 8 Dec 2025 22:39:44 +0800 Subject: [PATCH] update distance --- src/classes/action/move_away_from_region.py | 4 +-- src/classes/action/move_to_direction.py | 1 + src/classes/ai.py | 4 +-- src/classes/long_term_objective.py | 4 +-- src/classes/map.py | 37 +++++++++++++++------ src/classes/tile.py | 6 ++-- src/classes/world.py | 6 ++-- src/utils/distance.py | 22 ++++++++++++ static/game_configs/persona.csv | 2 ++ static/templates/ai.txt | 1 + 10 files changed, 66 insertions(+), 21 deletions(-) create mode 100644 src/classes/action/move_to_direction.py create mode 100644 src/utils/distance.py diff --git a/src/classes/action/move_away_from_region.py b/src/classes/action/move_away_from_region.py index b13b1f8..0bc4a22 100644 --- a/src/classes/action/move_away_from_region.py +++ b/src/classes/action/move_away_from_region.py @@ -4,7 +4,7 @@ from src.classes.action import InstantAction, Move from src.classes.event import Event from src.classes.action.move_helper import clamp_manhattan_with_diagonal_priority from src.classes.region import Region, resolve_region -from src.classes.region import resolve_region +from src.utils.distance import euclidean_distance class MoveAwayFromRegion(InstantAction): @@ -19,7 +19,7 @@ class MoveAwayFromRegion(InstantAction): y = self.avatar.pos_y # 找到目标区域内距离当前坐标最近的格点 if getattr(r, "cors", None): - nearest = min(r.cors, key=lambda p: (p[0] - x) * (p[0] - x) + (p[1] - y) * (p[1] - y)) + nearest = min(r.cors, key=lambda p: euclidean_distance((x, y), p)) away_dx = x - nearest[0] away_dy = y - nearest[1] else: diff --git a/src/classes/action/move_to_direction.py b/src/classes/action/move_to_direction.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/classes/action/move_to_direction.py @@ -0,0 +1 @@ + diff --git a/src/classes/ai.py b/src/classes/ai.py index 0b03f13..87afe8b 100644 --- a/src/classes/ai.py +++ b/src/classes/ai.py @@ -70,8 +70,8 @@ class LLMAI(AI): """ general_action_infos = ACTION_INFOS_STR async def decide_one(avatar: Avatar): - # 获取基于该角色已知区域的世界信息 - world_info = world.get_info(known_region_ids=avatar.known_regions) + # 获取基于该角色已知区域的世界信息(包含距离计算) + world_info = world.get_info(avatar=avatar) # 在提示中包含处于角色观测范围内的其他角色 observed = world.get_observable_avatars(avatar) diff --git a/src/classes/long_term_objective.py b/src/classes/long_term_objective.py index 88f4514..2742d12 100644 --- a/src/classes/long_term_objective.py +++ b/src/classes/long_term_objective.py @@ -78,8 +78,8 @@ async def generate_long_term_objective(avatar: "Avatar") -> Optional[LongTermObj Returns: 生成的LongTermObjective对象,失败则返回None """ - # 准备世界信息(仅获取已知区域) - world_info = avatar.world.get_info(known_region_ids=avatar.known_regions) + # 准备世界信息(仅获取已知区域 + 距离信息) + world_info = avatar.world.get_info(avatar=avatar) # 获取 expanded_info(包含详细信息和事件历史) expanded_info = avatar.get_expanded_info(detailed=True) diff --git a/src/classes/map.py b/src/classes/map.py index 681a819..be19643 100644 --- a/src/classes/map.py +++ b/src/classes/map.py @@ -80,27 +80,44 @@ class Map(): """ return self.tiles[(x, y)].region - def get_info(self, detailed: bool = False, known_region_ids: Optional[set[int]] = None) -> dict: + def get_info(self, detailed: bool = False, avatar: object = None) -> dict: """ 返回地图信息(dict)。 - known_region_ids: 如果提供,仅返回这些ID对应的区域信息。 + avatar: 如果提供,将用于: + 1. 过滤仅返回 avatar.known_regions 中的区域 + 2. 计算并在描述中追加从 avatar 当前位置到各区域的距离 """ - # 动态分类(因为现在没有自动分类字典了) - # 或者我们简单点,不分类返回,只返回总览? - # 为了保持接口不变,我们可以现场过滤。 - + if TYPE_CHECKING: + from src.classes.avatar import Avatar + from src.classes.region import NormalRegion, CultivateRegion, CityRegion + from src.utils.distance import chebyshev_distance + + known_region_ids = avatar.known_regions if avatar else None + current_loc = (avatar.pos_x, avatar.pos_y) if avatar else None def filter_regions(cls): return { rid: r for rid, r in self.regions.items() - if rid in known_region_ids + if known_region_ids is None or rid in known_region_ids } def build_regions_info(regions_dict) -> list[str]: - if detailed: - return [r.get_detailed_info() for r in regions_dict.values()] - return [r.get_info() for r in regions_dict.values()] + infos = [] + for r in regions_dict.values(): + base_info = r.get_detailed_info() if detailed else r.get_info() + + # 如果有当前位置,追加距离信息 + dist = chebyshev_distance(current_loc, r.center_loc) + # 估算到达时间:距离 / 步长 (向上取整) + step_len = avatar.move_step_length + months = (dist + step_len - 1) // step_len + # 避免显示 0 个月 + months = max(1, months) + base_info += f"(距离:{months}月)" + + infos.append(base_info) + return infos return { "修炼区域(可以修炼以增进修为)": build_regions_info(filter_regions(CultivateRegion)), diff --git a/src/classes/tile.py b/src/classes/tile.py index 586ff5f..c12c301 100644 --- a/src/classes/tile.py +++ b/src/classes/tile.py @@ -94,9 +94,11 @@ class Tile(): region: 'Region' = None # 可以是一个region的一部分,也可以不属于任何region +from src.utils.distance import manhattan_distance + def get_avatar_distance(avatar1: 'Avatar', avatar2: 'Avatar') -> int: """ - 计算两个 Avatar 之间的汉明距离(曼哈顿距离) + 计算两个 Avatar 之间的曼哈顿距离 Args: avatar1: 第一个角色 @@ -105,4 +107,4 @@ def get_avatar_distance(avatar1: 'Avatar', avatar2: 'Avatar') -> int: Returns: 两个角色之间的距离 """ - return abs(avatar1.pos_x - avatar2.pos_x) + abs(avatar1.pos_y - avatar2.pos_y) + return manhattan_distance((avatar1.pos_x, avatar1.pos_y), (avatar2.pos_x, avatar2.pos_y)) diff --git a/src/classes/world.py b/src/classes/world.py index f1ad27e..f830fe7 100644 --- a/src/classes/world.py +++ b/src/classes/world.py @@ -23,13 +23,13 @@ class World(): # 天地灵机开始年份(用于计算持续时间) phenomenon_start_year: int = 0 - def get_info(self, detailed: bool = False, known_region_ids: Optional[set[int]] = None) -> dict: + def get_info(self, detailed: bool = False, avatar: Optional["Avatar"] = None) -> dict: """ 返回世界信息(dict),其中包含地图信息(dict)。 - 如果指定了 known_region_ids,则只返回这些 ID 对应的区域信息。 + 如果指定了 avatar,将传给 map.get_info 用于过滤区域和计算距离。 """ static_info = self.static_info - map_info = self.map.get_info(detailed=detailed, known_region_ids=known_region_ids) + map_info = self.map.get_info(detailed=detailed, avatar=avatar) world_info = {**map_info, **static_info} if self.current_phenomenon: diff --git a/src/utils/distance.py b/src/utils/distance.py new file mode 100644 index 0000000..9467be4 --- /dev/null +++ b/src/utils/distance.py @@ -0,0 +1,22 @@ +import math + +def chebyshev_distance(p1: tuple[int, int], p2: tuple[int, int]) -> int: + """ + 计算切比雪夫距离:max(|x1-x2|, |y1-y2|) + 适用于允许对角线移动的网格地图 + """ + return max(abs(p1[0] - p2[0]), abs(p1[1] - p2[1])) + +def manhattan_distance(p1: tuple[int, int], p2: tuple[int, int]) -> int: + """ + 计算曼哈顿距离:|x1-x2| + |y1-y2| + 适用于只允许四向移动的网格地图 + """ + return abs(p1[0] - p2[0]) + abs(p1[1] - p2[1]) + +def euclidean_distance(p1: tuple[int, int], p2: tuple[int, int]) -> float: + """ + 计算欧几里得距离:sqrt((x1-x2)^2 + (y1-y2)^2) + """ + return math.sqrt((p1[0] - p2[0])**2 + (p1[1] - p2[1])**2) + diff --git a/static/game_configs/persona.csv b/static/game_configs/persona.csv index 8745556..82d7fcd 100644 --- a/static/game_configs/persona.csv +++ b/static/game_configs/persona.csv @@ -56,3 +56,5 @@ id,name,exclusion_names,desc,rarity,condition,effects 54,暗影宗师,怠惰;胆小,暗器之道登峰造极,杀人于无形。出手必见血,战力惊人,但对暗器之外的事物兴趣寥寥。,SR,,"[{when: 'avatar.weapon.type == WeaponType.HIDDEN_WEAPON', extra_weapon_proficiency_gain: 1.0, extra_battle_strength_points: 3}]" 55,好色,无欲,你对美色有着强烈的渴求,容易被外貌出众的异性吸引,行事往往受欲望驱使。,N,, 56,无欲,好色,你清心寡欲,对男女之事毫无兴趣,面对美色心如止水,不为所动。,N,, +57,探险家,死宅,你对未知的世界充满好奇,热衷于探索地图上未被点亮的区域,难以忍受长时间待在同一个地方。,SR,, +58,死宅,探险家;冒险,你认为外面的世界充满危险且麻烦,除了必要的宗门任务,你极度排斥长途跋涉,更喜欢待在熟悉的区域修炼。,N,, diff --git a/static/templates/ai.txt b/static/templates/ai.txt index 692757d..1352abe 100644 --- a/static/templates/ai.txt +++ b/static/templates/ai.txt @@ -1,4 +1,5 @@ 你是一个决策者,这是一个仙侠世界,你负责来决定某角色之后的动作行为。 +角色已知的世界信息为: {world_info} 全部可执行的动作有: {general_action_infos}