add personas

This commit is contained in:
bridge
2025-09-16 23:50:41 +08:00
parent 38cb4ab15e
commit 8b705c0806
5 changed files with 91 additions and 23 deletions

View File

@@ -1,7 +1,7 @@
import random
from dataclasses import dataclass, field
from enum import Enum
from typing import Optional
from typing import Optional, List
import json
from src.classes.calendar import MonthStamp
@@ -15,13 +15,15 @@ from src.classes.age import Age
from src.classes.event import NULL_EVENT, Event
from src.classes.typings import ACTION_NAME, ACTION_PARAMS, ACTION_PAIR, ACTION_NAME_PARAMS_PAIRS, ACTION_NAME_PARAMS_PAIR
from src.classes.persona import Persona, personas_by_id
from src.classes.persona import Persona, personas_by_id, get_random_compatible_personas
from src.classes.item import Item
from src.classes.magic_stone import MagicStone
from src.classes.hp_and_mp import HP, MP, HP_MAX_BY_REALM, MP_MAX_BY_REALM
from src.utils.id_generator import get_avatar_id
from src.utils.config import CONFIG
persona_num = CONFIG.avatar.persona_num
class Gender(Enum):
MALE = "male"
FEMALE = "female"
@@ -55,7 +57,7 @@ class Avatar:
tile: Optional[Tile] = None
root: Root = field(default_factory=lambda: random.choice(list(Root)))
persona: Persona = field(default_factory=lambda: random.choice(list(personas_by_id.values())))
personas: List[Persona] = field(default_factory=list)
cur_action_pair: Optional[ACTION_PAIR] = None
history_action_pairs: list[ACTION_PAIR] = field(default_factory=list)
next_actions: ACTION_NAME_PARAMS_PAIRS = field(default_factory=list)
@@ -77,6 +79,10 @@ class Avatar:
max_mp = MP_MAX_BY_REALM.get(self.cultivation_progress.realm, 100)
self.hp = HP(max_hp, max_hp)
self.mp = MP(max_mp, max_mp)
# 如果personas列表为空则随机分配两个不互斥的persona
if not self.personas:
self.personas = get_random_compatible_personas(persona_num)
def __hash__(self) -> int:
return hash(self.id)
@@ -86,7 +92,8 @@ class Avatar:
获取avatar的详细信息
尽量多打一些因为会用来给LLM进行决策
"""
return f"Avatar(id={self.id}, 性别={self.gender}, 年龄={self.age}, name={self.name}, 区域={self.tile.region.name}, 灵根={self.root.value}, 境界={self.cultivation_progress}, HP={self.hp}, MP={self.mp})"
personas_str = ", ".join([persona.name for persona in self.personas])
return f"Avatar(id={self.id}, 性别={self.gender}, 年龄={self.age}, name={self.name}, 区域={self.tile.region.name}, 灵根={self.root.value}, 境界={self.cultivation_progress}, HP={self.hp}, MP={self.mp}, 个性={personas_str})"
def __str__(self) -> str:
return self.get_info()
@@ -345,9 +352,14 @@ class Avatar:
获取角色提示词信息
"""
info = self.get_info()
persona = self.persona.prompt
action_space = self.get_action_space_str()
# 构建personas的提示词信息
personas_prompts = []
for i, persona in enumerate(self.personas, 1):
personas_prompts.append(f"其个性{i}{persona.prompt}")
personas_info = "\n".join(personas_prompts)
# 添加灵石信息
magic_stone_info = f"灵石持有情况:{str(self.magic_stone)}"
@@ -357,7 +369,8 @@ class Avatar:
else:
items_info = "物品持有情况:无"
return f"{info}\n其个性为:{persona}\n{magic_stone_info}\n{items_info}\n决策时需参考这个角色的个性。\n该角色的目前暂时的合法动作为:{action_space}"
personas_count = len(self.personas)
return f"{info}\n{personas_info}\n{magic_stone_info}\n{items_info}\n决策时需参考这个角色的{personas_count}个个性特点。\n该角色的目前暂时的合法动作为:{action_space}"
@property
def move_step_length(self) -> int:

View File

@@ -1,6 +1,11 @@
import random
from dataclasses import dataclass
from typing import List
from src.utils.df import game_configs
from src.utils.config import CONFIG
ids_separator = CONFIG.df.ids_separator
@dataclass
class Persona:
@@ -9,7 +14,8 @@ class Persona:
"""
id: int
name: str
prompt: str
prompt: str
exclusion_ids: List[int]
def _load_personas() -> tuple[dict[int, Persona], dict[str, Persona]]:
"""从配表加载persona数据"""
@@ -18,10 +24,17 @@ def _load_personas() -> tuple[dict[int, Persona], dict[str, Persona]]:
persona_df = game_configs["persona"]
for _, row in persona_df.iterrows():
# 解析exclusion_ids字符串转换为int列表
exclusion_ids_str = str(row["exclusion_ids"]) if str(row["exclusion_ids"]) != "nan" else ""
exclusion_ids = []
if exclusion_ids_str:
exclusion_ids = [int(x.strip()) for x in exclusion_ids_str.split(ids_separator) if x.strip()]
persona = Persona(
id=int(row["id"]),
name=str(row["name"]),
prompt=str(row["prompt"])
prompt=str(row["prompt"]),
exclusion_ids=exclusion_ids
)
personas_by_id[persona.id] = persona
personas_by_name[persona.name] = persona
@@ -31,6 +44,44 @@ def _load_personas() -> tuple[dict[int, Persona], dict[str, Persona]]:
# 从配表加载persona数据
personas_by_id, personas_by_name = _load_personas()
def get_random_compatible_personas(num_personas: int = 2) -> List[Persona]:
"""
随机选择指定数量的互相不冲突的persona
Args:
num_personas: 需要选择的persona数量默认为2
Returns:
List[Persona]: 互相不冲突的persona列表
Raises:
ValueError: 如果无法找到足够数量的兼容persona
"""
all_persona_ids = set(personas_by_id.keys())
selected_personas = []
available_ids = all_persona_ids.copy()
for i in range(num_personas):
if not available_ids:
raise ValueError(f"只能找到{i}个兼容的persona无法满足需要的{num_personas}")
# 从可用列表中随机选择一个
selected_id = random.choice(list(available_ids))
selected_persona = personas_by_id[selected_id]
selected_personas.append(selected_persona)
# 更新可用列表:移除已选择的和与其互斥的
available_ids.discard(selected_id) # 移除自己
# 移除所有与当前选择互斥的persona
for exclusion_id in selected_persona.exclusion_ids:
available_ids.discard(exclusion_id)
# 移除所有将当前选择作为互斥对象的persona
for persona_id in list(available_ids):
if selected_id in personas_by_id[persona_id].exclusion_ids:
available_ids.discard(persona_id)
return selected_personas

View File

@@ -129,9 +129,10 @@ def draw_tooltip_for_avatar(pygame_mod, screen, colors, font, avatar: Avatar):
f"HP: {avatar.hp}",
f"MP: {avatar.mp}",
f"灵根: {avatar.root.value}",
f"个性: {avatar.persona.name}",
f"个性: {', '.join([persona.name for persona in avatar.personas])}",
f"位置: ({avatar.pos_x}, {avatar.pos_y})",
]
lines.append(f"灵石: {str(avatar.magic_stone)}")
if avatar.items:
lines.append("物品:")

View File

@@ -16,4 +16,7 @@ game:
npc_birth_rate_per_month: 0.001
df:
ids_separator: ""
ids_separator: ";"
avatar:
persona_num: 2

View File

@@ -1,12 +1,12 @@
id,name,prompt
,,
1,理性,你是一个理性的人,你总是会用逻辑来思考问题,做事会谋定而后动。
2,无常,你是一个无常的人,目标飘忽不定,不会长期坚持一个目标。
3,怠惰,你是一个怠惰的人,你总是会拖延,不想努力,更热衷于享受人生。
4,冒险,你是一个冒险的人,你总是会冒险,喜欢刺激,总想放手一搏。
5,随性,你是一个随性的人,你总是会随机应变,性子到哪里了就是哪里,没有一定之规。
6,贪财,你是一个贪财的人,你对灵石和财富有着强烈的渴望。
7,采药,你是一个热爱采集的人,喜欢在山林中寻找各种奇花异草和灵药,对植物有着敏锐的直觉和深厚的兴趣。你认为大自然的恩赐需要用心去发现和珍惜。
8,猎者,你是一个热爱狩猎的人,享受在野外追踪猎物的刺激感,对各种动物的习性了如指掌。你相信通过狩猎能够磨练自己的意志和技能,获得更强大的力量。
9,爱财,你嗜财如命,对灵石和财富有着强烈的渴望
10,沉思,你是一个沉思的人,你总是会深思熟虑,思考问题比较有哲理
id,name,exclusion_ids,prompt
,,和本persona互斥的persona的id,输入给LLM的prompt
1,理性,2;5,你是一个理性的人,你总是会用逻辑来思考问题,做事会谋定而后动。
2,无常,1;9,你是一个无常的人,目标飘忽不定,不会长期坚持一个目标。
3,怠惰,4,你是一个怠惰的人,你总是会拖延,不想努力,更热衷于享受人生。
4,冒险,3;10,你是一个冒险的人,你总是会冒险,喜欢刺激,总想放手一搏。
5,随性,1,你是一个随性的人,你总是会随机应变,性子到哪里了就是哪里,没有一定之规。
6,贪财,,你是一个贪财的人,你对灵石和财富有着强烈的渴望。
7,采药,,你是一个热爱采集的人,喜欢在山林中寻找各种奇花异草和灵药,对植物有着敏锐的直觉和深厚的兴趣。你认为大自然的恩赐需要用心去发现和珍惜。
8,猎者,,你是一个热爱狩猎的人,享受在野外追踪猎物的刺激感,对各种动物的习性了如指掌。你相信通过狩猎能够磨练自己的意志和技能,获得更强大的力量。
9,沉思,2,你是一个沉思的人,你总是会深思熟虑,思考问题比较有哲理
10,惜命,4,你是一个惜命的人,你总是会珍惜自己的生命,不会轻易冒险
1 id name exclusion_ids prompt
2 和本persona互斥的persona的id 输入给LLM的prompt
3 1 理性 2;5 你是一个理性的人,你总是会用逻辑来思考问题,做事会谋定而后动。
4 2 无常 1;9 你是一个无常的人,目标飘忽不定,不会长期坚持一个目标。
5 3 怠惰 4 你是一个怠惰的人,你总是会拖延,不想努力,更热衷于享受人生。
6 4 冒险 3;10 你是一个冒险的人,你总是会冒险,喜欢刺激,总想放手一搏。
7 5 随性 1 你是一个随性的人,你总是会随机应变,性子到哪里了就是哪里,没有一定之规。
8 6 贪财 你是一个贪财的人,你对灵石和财富有着强烈的渴望。
9 7 采药 你是一个热爱采集的人,喜欢在山林中寻找各种奇花异草和灵药,对植物有着敏锐的直觉和深厚的兴趣。你认为大自然的恩赐需要用心去发现和珍惜。
10 8 猎者 你是一个热爱狩猎的人,享受在野外追踪猎物的刺激感,对各种动物的习性了如指掌。你相信通过狩猎能够磨练自己的意志和技能,获得更强大的力量。
11 9 爱财 沉思 2 你嗜财如命,对灵石和财富有着强烈的渴望。 你是一个沉思的人,你总是会深思熟虑,思考问题比较有哲理。
12 10 沉思 惜命 4 你是一个沉思的人,你总是会深思熟虑,思考问题比较有哲理。 你是一个惜命的人,你总是会珍惜自己的生命,不会轻易冒险。