Files
cultivation-world-simulator/src/classes/single_choice.py

210 lines
7.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from typing import Any, Dict, List, TYPE_CHECKING, Tuple, Optional, Callable
from src.utils.llm import call_llm_with_task_name
from src.utils.config import CONFIG
import json
if TYPE_CHECKING:
from src.classes.avatar import Avatar
async def make_decision(
avatar: "Avatar",
context_desc: str,
options: List[Dict[str, Any]]
) -> str:
"""
让角色在多个选项中做出单选决策。
"""
# 1. 获取角色信息 (详细模式)
avatar_infos = str(avatar.get_info(detailed=True))
# 2. 格式化选项字符串
choices_list = [f"{opt.get('key', '')}: {opt.get('desc', '')}" for opt in options]
choices_str = "\n".join(choices_list)
full_choices_str = f"【当前情境】:{context_desc}\n\n{choices_str}"
# 3. 调用 AI
template_path = CONFIG.paths.templates / "single_choice.txt"
world_info = avatar.world.static_info
result = await call_llm_with_task_name(
"single_choice",
template_path,
infos={
"world_info": world_info,
"avatar_infos": avatar_infos,
"choices": full_choices_str
}
)
# 4. 解析结果
choice = ""
if isinstance(result, dict):
choice = result.get("choice", "").strip()
elif isinstance(result, str):
clean_result = result.strip()
# 尝试解析可能包含 markdown 的 json
if "{" in clean_result and "}" in clean_result:
try:
# 提取可能的 json 部分
start = clean_result.find("{")
end = clean_result.rfind("}") + 1
json_str = clean_result[start:end]
data = json.loads(json_str)
choice = data.get("choice", "").strip()
except (json.JSONDecodeError, ValueError):
choice = clean_result
else:
choice = clean_result
# 验证 choice 是否在 options key 中
valid_keys = {opt["key"] for opt in options}
# 简单的容错
if choice not in valid_keys:
for k in valid_keys:
if k in choice:
choice = k
break
if choice not in valid_keys:
choice = options[0]["key"]
return choice
def _get_item_ops(avatar: "Avatar", item_type: str) -> dict:
"""根据物品类型返回对应的操作函数和标签"""
if item_type == "weapon":
return {
"label": "兵器",
"get_current": lambda: avatar.weapon,
"use_func": avatar.change_weapon,
"sell_func": avatar.sell_weapon,
"verbs": {
"action": "装备",
"done": "换上了",
"replace": "替换"
}
}
elif item_type == "auxiliary":
return {
"label": "辅助装备",
"get_current": lambda: avatar.auxiliary,
"use_func": avatar.change_auxiliary,
"sell_func": avatar.sell_auxiliary,
"verbs": {
"action": "装备",
"done": "换上了",
"replace": "替换"
}
}
elif item_type == "technique":
return {
"label": "功法",
"get_current": lambda: avatar.technique,
"use_func": lambda x: setattr(avatar, 'technique', x),
"sell_func": None, # 功法通常不能卖
"verbs": {
"action": "修炼",
"done": "改修了",
"replace": "替换"
}
}
elif item_type == "elixir":
return {
"label": "丹药",
"get_current": lambda: None, # 丹药没有“当前装备”的概念,都是新获得的
"use_func": avatar.consume_elixir,
"sell_func": avatar.sell_elixir,
"verbs": {
"action": "服用",
"done": "服用了",
"replace": "替换" # 丹药其实没有 replace但为了模板通用可以给个默认值
}
}
else:
raise ValueError(f"Unsupported item type: {item_type}")
async def handle_item_exchange(
avatar: "Avatar",
new_item: Any,
item_type: str, # "weapon", "auxiliary", "technique", "elixir"
context_intro: str,
can_sell_new: bool = False
) -> Tuple[bool, str]:
"""
通用处理物品(装备/功法)的获取、替换与决策逻辑。
Args:
avatar: 角色对象
new_item: 新获得的物品
item_type: 物品类型键值 ("weapon", "auxiliary", "technique")
context_intro: 决策背景描述
can_sell_new: 如果拒绝装备,是否允许卖掉新物品换灵石
Returns:
(swapped, result_text)
"""
ops = _get_item_ops(avatar, item_type)
label = ops["label"]
verbs = ops["verbs"]
current_item = ops["get_current"]()
new_name = new_item.name
new_grade = getattr(new_item, "realm", getattr(new_item, "grade", None)).value
# 1. 自动装备:当前无装备且不强制考虑卖新
if current_item is None and not can_sell_new:
ops["use_func"](new_item)
return True, f"{avatar.name} 获得了{new_grade}{label}{new_name}』并{verbs['action']}"
# 2. 需要决策:准备描述
old_name = current_item.name if current_item else ""
new_info = new_item.get_info(detailed=True)
swap_desc = f"{label}{new_info}"
if current_item:
old_info = current_item.get_info(detailed=True)
swap_desc = f"现有{label}{old_info}\n{swap_desc}"
if ops["sell_func"]:
swap_desc += f"\n(选择{verbs['replace']}将卖出旧{label}"
# 3. 构建选项
# Option A: 装备/服用新物品
opt_a_text = f"{verbs['action']}{label}{new_name}"
if current_item and ops["sell_func"]:
opt_a_text += f",卖掉旧{label}{old_name}"
elif current_item:
opt_a_text += f"{verbs['replace']}{label}{old_name}"
# Option B: 拒绝新物品
if can_sell_new and ops["sell_func"]:
opt_b_text = f"卖掉新{label}{new_name}』换取灵石,保留现状"
else:
opt_b_text = f"放弃『{new_name}"
if current_item:
opt_b_text += f",保留身上的『{old_name}"
options = [
{"key": "A", "desc": opt_a_text},
{"key": "B", "desc": opt_b_text}
]
full_context = f"{context_intro}\n{swap_desc}"
choice = await make_decision(avatar, full_context, options)
# 4. 执行决策
if choice == "A":
# 卖旧(如果有且能卖)
if current_item and ops["sell_func"]:
ops["sell_func"](current_item)
# 装新/服用
ops["use_func"](new_item)
return True, f"{avatar.name} {verbs['done']}{new_grade}{label}{new_name}』。"
else:
# 卖新(如果被要求且能卖)
if can_sell_new and ops["sell_func"]:
sold_price = ops["sell_func"](new_item)
return False, f"{avatar.name} 卖掉了新获得的{new_name},获利 {sold_price} 灵石。"
else:
return False, f"{avatar.name} 放弃了{new_name}"