From 2d97edca8f56e56da66e32c8c7306d16b158e76b Mon Sep 17 00:00:00 2001 From: bridge Date: Thu, 2 Oct 2025 17:31:25 +0800 Subject: [PATCH] refactor mutual action --- src/classes/action.py | 42 ++++++++++++++++++------ src/classes/battle.py | 27 +++++++++++++--- src/classes/hp_and_mp.py | 62 ++++++++++++++++++++++++++++++++++++ src/classes/mutual_action.py | 45 +++++++++++++++++++------- src/run/run.py | 3 +- src/sim/simulator.py | 14 ++++++-- 6 files changed, 163 insertions(+), 30 deletions(-) diff --git a/src/classes/action.py b/src/classes/action.py index 85c0829..191dda5 100644 --- a/src/classes/action.py +++ b/src/classes/action.py @@ -650,20 +650,42 @@ class Sold(DefineAction, ActualActionMixin): return [] -class Battle(DefineAction, ChunkActionMixin): +class Battle(DefineAction, ActualActionMixin): COMMENT = "与目标进行对战,判定胜负" DOABLES_REQUIREMENTS = "任何时候都可以执行" PARAMS = {"avatar_name": "AvatarName"} - def _execute(self, avatar_name: str) -> None: - target = None + + def _get_target(self, avatar_name: str): for v in self.world.avatar_manager.avatars.values(): if v.name == avatar_name: - target = v - break + return v + return None + + def _execute(self, avatar_name: str) -> None: + target = self._get_target(avatar_name) if target is None: return - winner, loser, _ = decide_battle(self.avatar, target) - # 简化:失败者HP小额扣减 - if hasattr(loser, "hp"): - loser.hp.reduce(10) - # Battle 作为 ChunkAction,不直接由调度器执行 + winner, loser, damage = decide_battle(self.avatar, target) + loser.hp.reduce(damage) + self._last_result = (winner.name, loser.name) + + def can_start(self, avatar_name: str | None = None) -> bool: + if avatar_name is None: + return False + return self._get_target(avatar_name) is not None + + def start(self, avatar_name: str) -> Event: + target = self._get_target(avatar_name) + target_name = target.name if target is not None else avatar_name + return Event(self.world.month_stamp, f"{self.avatar.name} 对 {target_name} 发起战斗") + + def step(self, avatar_name: str) -> tuple[StepStatus, list[Event]]: + self.execute(avatar_name=avatar_name) + return StepStatus.COMPLETED, [] + + def finish(self, avatar_name: str) -> list[Event]: + res = getattr(self, "_last_result", None) + if isinstance(res, tuple) and len(res) == 2: + winner, loser = res + return [Event(self.world.month_stamp, f"{winner} 战胜了 {loser}")] + return [] diff --git a/src/classes/battle.py b/src/classes/battle.py index be702ee..6274f64 100644 --- a/src/classes/battle.py +++ b/src/classes/battle.py @@ -34,14 +34,31 @@ def calc_win_rate(attacker: "Avatar", defender: "Avatar") -> float: return max(0.1, min(0.9, base)) -def decide_battle(attacker: "Avatar", defender: "Avatar") -> Tuple["Avatar", "Avatar", float]: +def decide_battle(attacker: "Avatar", defender: "Avatar") -> Tuple["Avatar", "Avatar", int]: """ - 结算一场战斗,返回(胜者, 败者, 进攻方胜率)。 - 仅做结果判定,不做数值伤害结算。 + 结算一场战斗,返回(胜者, 败者, 伤害值)。 + 伤害值根据胜负双方境界差距给出,范围约 [30, 80]。 """ p = calc_win_rate(attacker, defender) if random.random() < p: - return attacker, defender, p + winner, loser = attacker, defender else: - return defender, attacker, p + winner, loser = defender, attacker + damage = get_damage(winner, loser) + return winner, loser, damage + +def get_escape_success_rate(attacker: "Avatar", defender: "Avatar") -> float: + """ + 逃跑成功率:临时返回常量值,后续可基于双方能力细化。 + attacker: 追击方(通常为进攻者) + defender: 逃跑方(通常为被攻击者) + """ + return 0.6 + +def get_damage(winner: "Avatar", loser: "Avatar") -> int: + """ + 根据胜负双方境界差距估算伤害:基础30,差一大境界+10,上限80。 + """ + gap = max(0, _realm_order(winner.cultivation_progress.realm) - _realm_order(loser.cultivation_progress.realm)) + return min(80, 30 + 10 * gap) \ No newline at end of file diff --git a/src/classes/hp_and_mp.py b/src/classes/hp_and_mp.py index b187a04..8aaed85 100644 --- a/src/classes/hp_and_mp.py +++ b/src/classes/hp_and_mp.py @@ -35,6 +35,37 @@ class HP: def __repr__(self) -> str: return self.__str__() + + # 比较运算符,使用cur进行比较 + def __eq__(self, other) -> bool: + if isinstance(other, HP): + return self.cur == other.cur + return self.cur == other + + def __ne__(self, other) -> bool: + if isinstance(other, HP): + return self.cur != other.cur + return self.cur != other + + def __lt__(self, other) -> bool: + if isinstance(other, HP): + return self.cur < other.cur + return self.cur < other + + def __le__(self, other) -> bool: + if isinstance(other, HP): + return self.cur <= other.cur + return self.cur <= other + + def __gt__(self, other) -> bool: + if isinstance(other, HP): + return self.cur > other.cur + return self.cur > other + + def __ge__(self, other) -> bool: + if isinstance(other, HP): + return self.cur >= other.cur + return self.cur >= other HP_MAX_BY_REALM = { Realm.Qi_Refinement: 100, @@ -75,6 +106,37 @@ class MP: def __repr__(self) -> str: return self.__str__() + + # 比较运算符,使用cur进行比较 + def __eq__(self, other) -> bool: + if isinstance(other, MP): + return self.cur == other.cur + return self.cur == other + + def __ne__(self, other) -> bool: + if isinstance(other, MP): + return self.cur != other.cur + return self.cur != other + + def __lt__(self, other) -> bool: + if isinstance(other, MP): + return self.cur < other.cur + return self.cur < other + + def __le__(self, other) -> bool: + if isinstance(other, MP): + return self.cur <= other.cur + return self.cur <= other + + def __gt__(self, other) -> bool: + if isinstance(other, MP): + return self.cur > other.cur + return self.cur > other + + def __ge__(self, other) -> bool: + if isinstance(other, MP): + return self.cur >= other.cur + return self.cur >= other def add_max(self, value_2_add:int) -> bool: self.max += value_2_add diff --git a/src/classes/mutual_action.py b/src/classes/mutual_action.py index f14b48f..5ef00ac 100644 --- a/src/classes/mutual_action.py +++ b/src/classes/mutual_action.py @@ -4,6 +4,8 @@ from pathlib import Path from typing import TYPE_CHECKING from src.classes.action import DefineAction, ActualActionMixin, LLMAction +from src.classes.battle import get_escape_success_rate +import random from src.classes.event import Event from src.utils.llm import get_prompt_and_call_llm from src.utils.config import CONFIG @@ -107,8 +109,14 @@ class MutualAction(DefineAction, LLMAction): target_avatar.clear_plans() # 2) 再结算反馈映射为对应动作 self._settle_feedback(target_avatar, feedback) - # 3) 反馈事件(进入侧边栏与双方历史) - feedback_event = Event(self.world.month_stamp, f"{target_avatar.name} 对 {self.avatar.name} 的反馈:{feedback}") + # 3) 反馈事件(进入侧边栏与双方历史,中文化文案) + fb_map = { + "MoveAwayFromAvatar": "试图远离", + "MoveAwayFromRegion": "试图离开区域", + "Battle": "战斗", + } + fb_label = fb_map.get(str(feedback).strip(), str(feedback)) + feedback_event = Event(self.world.month_stamp, f"{target_avatar.name} 对 {self.avatar.name} 的反馈:{fb_label}") self.avatar.add_event(feedback_event) target_avatar.add_event(feedback_event) # 4) 记录历史(文本记录) @@ -129,6 +137,7 @@ class DriveAway(MutualAction, ActualActionMixin): def _settle_feedback(self, target_avatar: "Avatar", feedback_name: str) -> None: fb = str(feedback_name).strip() if fb == "MoveAwayFromRegion": + # 驱赶选择离开:必定成功,不涉及概率 params = {"region": self.avatar.tile.region.name} self._set_target_immediate_action(target_avatar, fb, params) elif fb == "Battle": @@ -166,14 +175,28 @@ class MoveAwayFromAvatar(DefineAction, ActualActionMixin): break if target is None: return - dx = 1 if self.avatar.pos_x >= target.pos_x else -1 - dy = 1 if self.avatar.pos_y >= target.pos_y else -1 - nx = self.avatar.pos_x + dx - ny = self.avatar.pos_y + dy - if self.world.map.is_in_bounds(nx, ny): - self.avatar.pos_x = nx - self.avatar.pos_y = ny - self.avatar.tile = self.world.map.get_tile(nx, ny) + # 被攻击时逃跑的成功率:从 battle 模块获取(暂时固定值) + escape_rate = float(get_escape_success_rate(target, self.avatar)) + if random.random() < escape_rate: + dx = 1 if self.avatar.pos_x >= target.pos_x else -1 + dy = 1 if self.avatar.pos_y >= target.pos_y else -1 + nx = self.avatar.pos_x + dx + ny = self.avatar.pos_y + dy + if self.world.map.is_in_bounds(nx, ny): + self.avatar.pos_x = nx + self.avatar.pos_y = ny + self.avatar.tile = self.world.map.get_tile(nx, ny) + else: + # 逃跑失败:进入战斗(立即动作) + self.avatar.clear_plans() + # 入队并提交 + self.avatar.load_decide_result_chain([("Battle", {"avatar_name": avatar_name})], self.avatar.thinking, "") + start_event = self.avatar.commit_next_plan() + if start_event is not None: + self.avatar.add_event(start_event) + # 也同步给对方 + if target is not None: + target.add_event(start_event) class MoveAwayFromRegion(DefineAction, ActualActionMixin): @@ -181,7 +204,7 @@ class MoveAwayFromRegion(DefineAction, ActualActionMixin): DOABLES_REQUIREMENTS = "任何时候都可以执行" PARAMS = {"region": "RegionName"} def _execute(self, region: str) -> None: - # 简化:向地图边缘移动一步 + # 驱赶离开:若选择离开,必定成功。简化为向地图边缘移动一步 dx = 1 if self.avatar.pos_x < self.world.map.width - 1 else -1 dy = 1 if self.avatar.pos_y < self.world.map.height - 1 else -1 nx = max(0, min(self.world.map.width - 1, self.avatar.pos_x + dx)) diff --git a/src/run/run.py b/src/run/run.py index 165770f..532c498 100644 --- a/src/run/run.py +++ b/src/run/run.py @@ -56,8 +56,7 @@ def make_avatars(world: World, count: int = 12, current_month_stamp: MonthStamp name = get_random_name(gender) # 随机生成level,范围从0到120(对应四个大境界) - # level = random.randint(0, 120) - level = 29 + level = random.randint(0, 120) cultivation_progress = CultivationProgress(level) # 创建Age实例,传入年龄与当前境界 diff --git a/src/sim/simulator.py b/src/sim/simulator.py index 6d8e03c..b2c4a15 100644 --- a/src/sim/simulator.py +++ b/src/sim/simulator.py @@ -9,6 +9,7 @@ from src.classes.event import Event, is_null_event from src.classes.ai import llm_ai, rule_ai from src.utils.names import get_random_name from src.utils.config import CONFIG +from src.run.log import get_logger class Simulator: def __init__(self, world: World): @@ -55,8 +56,12 @@ class Simulator: if new_events: events.extend(new_events) - # 结算寿命逻辑 - for avatar_id, avatar in self.world.avatar_manager.avatars.items(): + # 结算战斗等导致的死亡(HP<=0)与寿命逻辑 + for avatar_id, avatar in list(self.world.avatar_manager.avatars.items()): + if avatar.hp <= 0: + death_avatar_ids.append(avatar_id) + event = Event(self.world.month_stamp, f"{avatar.name} 因重伤身亡") + events.append(event) if avatar.death_by_old_age(): death_avatar_ids.append(avatar_id) event = Event(self.world.month_stamp, f"{avatar.name} 老死了,时年{avatar.age.get_age()}岁") @@ -77,6 +82,11 @@ class Simulator: event = Event(self.world.month_stamp, f"{new_avatar.name}晋升为修士了。") events.append(event) + # 将事件写入日志 + logger = get_logger().logger + for event in events: + logger.info("EVENT: %s", str(event)) + # 最后结算年月 self.world.month_stamp = self.world.month_stamp + 1