add action chain
This commit is contained in:
@@ -3,16 +3,45 @@ from abc import ABC, abstractmethod
|
||||
from typing import TYPE_CHECKING
|
||||
import random
|
||||
import json
|
||||
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.event import Event, NullEvent
|
||||
from src.classes.event import Event, NULL_EVENT
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from src.classes.avatar import Avatar
|
||||
from src.classes.world import World
|
||||
|
||||
|
||||
def long_action(step_month: int):
|
||||
"""
|
||||
长态动作装饰器,用于为动作类自动添加时间管理功能
|
||||
|
||||
Args:
|
||||
step_month: 动作需要的月份数
|
||||
"""
|
||||
def decorator(cls):
|
||||
# 设置类属性,供基类使用
|
||||
cls._step_month = step_month
|
||||
|
||||
def is_finished(self, *args, **kwargs) -> bool:
|
||||
"""
|
||||
根据时间差判断动作是否完成
|
||||
接受但忽略额外的参数以保持与其他动作类型的兼容性
|
||||
"""
|
||||
if self.start_monthstamp is None:
|
||||
return False
|
||||
return (self.world.month_stamp - self.start_monthstamp) >= self.step_month
|
||||
|
||||
# 只添加 is_finished 方法
|
||||
cls.is_finished = is_finished
|
||||
|
||||
return cls
|
||||
|
||||
return decorator
|
||||
|
||||
class Action(ABC):
|
||||
"""
|
||||
角色可以执行的动作。
|
||||
@@ -28,11 +57,37 @@ class Action(ABC):
|
||||
self.world = world
|
||||
|
||||
@abstractmethod
|
||||
def execute(self) -> Event|NullEvent:
|
||||
def execute(self) -> None:
|
||||
pass
|
||||
|
||||
class DefineAction(Action):
|
||||
pass
|
||||
def __init__(self, avatar: Avatar, world: World):
|
||||
"""
|
||||
初始化动作,处理长态动作的属性设置
|
||||
"""
|
||||
super().__init__(avatar, world)
|
||||
|
||||
# 如果是长态动作,初始化相关属性
|
||||
if hasattr(self.__class__, '_step_month'):
|
||||
self.step_month = self.__class__._step_month
|
||||
self.start_monthstamp = None
|
||||
|
||||
def execute(self, *args, **kwargs) -> None:
|
||||
"""
|
||||
执行动作,处理时间管理逻辑,然后调用具体的_execute实现
|
||||
"""
|
||||
# 如果是长态动作且第一次执行,记录开始时间
|
||||
if hasattr(self, 'step_month') and self.start_monthstamp is None:
|
||||
self.start_monthstamp = self.world.month_stamp
|
||||
|
||||
self._execute(*args, **kwargs)
|
||||
|
||||
@abstractmethod
|
||||
def _execute(self, *args, **kwargs) -> None:
|
||||
"""
|
||||
具体的动作执行逻辑,由子类实现
|
||||
"""
|
||||
pass
|
||||
|
||||
class LLMAction(Action):
|
||||
"""
|
||||
@@ -44,13 +99,39 @@ class LLMAction(Action):
|
||||
"""
|
||||
pass
|
||||
|
||||
class ChunkActionMixin():
|
||||
"""
|
||||
动作片,可以理解成只是一种切分出来的动作。
|
||||
不能被avatar直接执行,而是成为avatar执行某个动作的步骤。
|
||||
"""
|
||||
pass
|
||||
|
||||
class Move(DefineAction):
|
||||
class ActualActionMixin():
|
||||
"""
|
||||
实际的可以被规则/LLM调用,让avatar去执行的动作。
|
||||
不一定是多个step,也有可能就一个step
|
||||
"""
|
||||
@abstractmethod
|
||||
def is_finished(self) -> bool:
|
||||
"""
|
||||
判断动作是否完成
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_event(self, *args, **kwargs) -> Event:
|
||||
"""
|
||||
获取动作开始时的事件
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class Move(DefineAction, ChunkActionMixin):
|
||||
"""
|
||||
最基础的移动动作,在tile之间进行切换。
|
||||
"""
|
||||
COMMENT = "移动到某个相对位置"
|
||||
def execute(self, delta_x: int, delta_y: int) -> Event|NullEvent:
|
||||
def _execute(self, delta_x: int, delta_y: int) -> None:
|
||||
"""
|
||||
移动到某个tile
|
||||
"""
|
||||
@@ -67,14 +148,13 @@ class Move(DefineAction):
|
||||
else:
|
||||
# 超出边界:不改变位置与tile
|
||||
pass
|
||||
return NullEvent()
|
||||
|
||||
class MoveToRegion(DefineAction):
|
||||
class MoveToRegion(DefineAction, ActualActionMixin):
|
||||
"""
|
||||
移动到某个region
|
||||
"""
|
||||
COMMENT = "移动到某个区域"
|
||||
def execute(self, region: Region|str) -> Event|NullEvent:
|
||||
def _execute(self, region: Region|str) -> None:
|
||||
"""
|
||||
移动到某个region
|
||||
"""
|
||||
@@ -88,14 +168,36 @@ class MoveToRegion(DefineAction):
|
||||
delta_x = max(-1, min(1, delta_x))
|
||||
delta_y = max(-1, min(1, delta_y))
|
||||
Move(self.avatar, self.world).execute(delta_x, delta_y)
|
||||
return Event(self.world.year, self.world.month, f"{self.avatar.name} 移动向 {region.name}")
|
||||
|
||||
class Cultivate(DefineAction):
|
||||
def is_finished(self, region: Region|str) -> bool:
|
||||
"""
|
||||
判断动作是否完成
|
||||
"""
|
||||
if isinstance(region, str):
|
||||
region = self.world.map.region_names[region]
|
||||
return self.avatar.is_in_region(region)
|
||||
|
||||
def get_event(self, region: Region|str) -> Event:
|
||||
"""
|
||||
获取移动动作开始时的事件
|
||||
"""
|
||||
if isinstance(region, str):
|
||||
region_name = region
|
||||
if region in self.world.map.region_names:
|
||||
region_name = self.world.map.region_names[region].name
|
||||
elif hasattr(region, 'name'):
|
||||
region_name = region.name
|
||||
else:
|
||||
region_name = str(region)
|
||||
return Event(self.world.month_stamp, f"{self.avatar.name} 开始移动向 {region_name}")
|
||||
|
||||
@long_action(step_month=10)
|
||||
class Cultivate(DefineAction, ActualActionMixin):
|
||||
"""
|
||||
修炼动作,可以增加修仙进度。
|
||||
"""
|
||||
COMMENT = "修炼,增进修为"
|
||||
def execute(self) -> Event|NullEvent:
|
||||
def _execute(self) -> None:
|
||||
"""
|
||||
修炼
|
||||
获得的exp增加取决于essence的对应灵根的大小。
|
||||
@@ -106,7 +208,6 @@ class Cultivate(DefineAction):
|
||||
essence_density = essence.get_density(essence_type)
|
||||
exp = self.get_exp(essence_density)
|
||||
self.avatar.cultivation_progress.add_exp(exp)
|
||||
return Event(self.world.year, self.world.month, f"{self.avatar.name} 在 {self.avatar.tile.region.name} 修炼")
|
||||
|
||||
def get_exp(self, essence_density: int) -> int:
|
||||
"""
|
||||
@@ -115,10 +216,17 @@ class Cultivate(DefineAction):
|
||||
"""
|
||||
base = 100
|
||||
return base * essence_density
|
||||
|
||||
def get_event(self) -> Event:
|
||||
"""
|
||||
获取修炼动作开始时的事件
|
||||
"""
|
||||
return Event(self.world.month_stamp, f"{self.avatar.name} 在 {self.avatar.tile.region.name} 开始修炼")
|
||||
|
||||
|
||||
# 突破境界class
|
||||
class Breakthrough(DefineAction):
|
||||
@long_action(step_month=1)
|
||||
class Breakthrough(DefineAction, ActualActionMixin):
|
||||
"""
|
||||
突破境界
|
||||
"""
|
||||
@@ -129,19 +237,22 @@ class Breakthrough(DefineAction):
|
||||
"""
|
||||
return 0.5
|
||||
|
||||
def execute(self) -> Event|NullEvent:
|
||||
def _execute(self) -> None:
|
||||
"""
|
||||
突破境界
|
||||
"""
|
||||
assert self.avatar.cultivation_progress.can_break_through()
|
||||
# assert self.avatar.cultivation_progress.can_break_through()
|
||||
if not self.avatar.cultivation_progress.can_break_through():
|
||||
print(f"警告,{self.avatar.name} 无法突破境界,其level为 {self.avatar.cultivation_progress.level},无法突破")
|
||||
success_rate = self.calc_success_rate()
|
||||
if random.random() < success_rate:
|
||||
self.avatar.cultivation_progress.break_through()
|
||||
is_success = True
|
||||
else:
|
||||
is_success = False
|
||||
res = "成功" if is_success else "失败"
|
||||
return Event(self.world.year, self.world.month, f"{self.avatar.name} 突破境界{res}")
|
||||
|
||||
def get_event(self) -> Event:
|
||||
"""
|
||||
获取突破动作开始时的事件
|
||||
"""
|
||||
return Event(self.world.month_stamp, f"{self.avatar.name} 开始尝试突破境界")
|
||||
|
||||
|
||||
ALL_ACTION_CLASSES = [Move, Cultivate, Breakthrough, MoveToRegion]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import random
|
||||
from src.classes.calendar import Month, Year
|
||||
from src.classes.calendar import Month, Year, MonthStamp
|
||||
from src.classes.cultivation import Realm
|
||||
|
||||
class Age:
|
||||
@@ -57,29 +57,26 @@ class Age:
|
||||
"""
|
||||
return random.random() < self.get_death_probability(realm)
|
||||
|
||||
def calculate_age(self, current_month: Month, current_year: Year, birth_month: Month, birth_year: Year) -> int:
|
||||
|
||||
|
||||
def calculate_age(self, current_month_stamp: MonthStamp, birth_month_stamp: MonthStamp) -> int:
|
||||
"""
|
||||
计算准确的年龄(整数年)
|
||||
|
||||
Args:
|
||||
current_month: 当前月份
|
||||
current_year: 当前年份
|
||||
birth_month: 出生月份
|
||||
birth_year: 出生年份
|
||||
current_month_stamp: 当前时间戳
|
||||
birth_month_stamp: 出生时间戳
|
||||
|
||||
Returns:
|
||||
整数年龄
|
||||
"""
|
||||
age = current_year - birth_year
|
||||
if current_month.value < birth_month.value:
|
||||
age -= 1
|
||||
return max(0, age)
|
||||
return max(0, (current_month_stamp - birth_month_stamp) // 12)
|
||||
|
||||
def update_age(self, current_month: Month, current_year: Year, birth_month: Month, birth_year: Year):
|
||||
def update_age(self, current_month_stamp: MonthStamp, birth_month_stamp: MonthStamp):
|
||||
"""
|
||||
更新年龄
|
||||
"""
|
||||
self.age = self.calculate_age(current_month, current_year, birth_month, birth_year)
|
||||
self.age = self.calculate_age(current_month_stamp, birth_month_stamp)
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""返回年龄的字符串表示"""
|
||||
|
||||
@@ -3,25 +3,45 @@ NPC AI的类。
|
||||
这里指的不是LLM或者Machine Learning,而是NPC的决策机制
|
||||
分为两类:规则AI和LLM AI
|
||||
"""
|
||||
from __future__ import annotations
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from src.classes.world import World
|
||||
from src.classes.tile import Region
|
||||
from src.classes.root import corres_essence_type
|
||||
from src.classes.action import ACTION_SPACE_STR
|
||||
from src.classes.event import Event, NULL_EVENT
|
||||
from src.utils.llm import get_ai_prompt_and_call_llm
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from src.classes.avatar import Avatar
|
||||
|
||||
class AI(ABC):
|
||||
"""
|
||||
AI的基类
|
||||
"""
|
||||
def __init__(self, avatar: 'Avatar'):
|
||||
def __init__(self, avatar: Avatar):
|
||||
self.avatar = avatar
|
||||
|
||||
@abstractmethod
|
||||
def decide(self, world: World) -> tuple[str, dict]:
|
||||
def decide(self, world: World) -> tuple[str, dict, Event]:
|
||||
"""
|
||||
决定做什么
|
||||
决定做什么,同时生成对应的事件
|
||||
"""
|
||||
# 先决定动作和参数
|
||||
action_name, action_params = self._decide(world)
|
||||
|
||||
# 获取动作对象并生成事件
|
||||
action = self.avatar.create_action(action_name)
|
||||
event = action.get_event(**action_params)
|
||||
|
||||
return action_name, action_params, event
|
||||
|
||||
@abstractmethod
|
||||
def _decide(self, world: World) -> tuple[str, dict]:
|
||||
"""
|
||||
决策逻辑:决定执行什么动作和参数
|
||||
由子类实现具体的决策逻辑
|
||||
"""
|
||||
pass
|
||||
|
||||
@@ -29,9 +49,9 @@ class RuleAI(AI):
|
||||
"""
|
||||
规则AI
|
||||
"""
|
||||
def decide(self, world: World) -> tuple[str, dict]:
|
||||
def _decide(self, world: World) -> tuple[str, dict]:
|
||||
"""
|
||||
决定做什么
|
||||
决策逻辑:决定执行什么动作和参数
|
||||
先做一个简单的:
|
||||
1. 找到自己灵根对应的最好的区域
|
||||
2. 检测自己是否在最好的区域
|
||||
@@ -69,17 +89,19 @@ class LLMAI(AI):
|
||||
不能每个单步step都调用一次LLM来决定下一步做什么。这样子一方面动作一直乱变,另一方面也太费token了。
|
||||
decide的作用是,拉取既有的动作链(如果没有了就call_llm),再根据动作链决定动作,以及动作之间的衔接。
|
||||
"""
|
||||
def decide(self, world: World) -> tuple[str, dict]:
|
||||
def _decide(self, world: World) -> tuple[str, dict]:
|
||||
"""
|
||||
决定做什么
|
||||
决策逻辑:通过LLM决定执行什么动作和参数
|
||||
"""
|
||||
action_space_str = ACTION_SPACE_STR
|
||||
avatar_infos_str = str(self.avatar)
|
||||
regions_str = "\n".join([str(region) for region in world.map.regions.values()])
|
||||
avatar_persona = self.avatar.persona.prompt
|
||||
dict_info = {
|
||||
"action_space": action_space_str,
|
||||
"avatar_infos": avatar_infos_str,
|
||||
"regions": regions_str
|
||||
"regions": regions_str,
|
||||
"avatar_persona": avatar_persona
|
||||
}
|
||||
res = get_ai_prompt_and_call_llm(dict_info)
|
||||
action_name, action_params = res["action_name"], res["action_params"]
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
import random
|
||||
import uuid
|
||||
from dataclasses import dataclass, field
|
||||
from enum import Enum
|
||||
from typing import Optional
|
||||
|
||||
from src.classes.calendar import Month, Year
|
||||
from src.classes.calendar import Month, Year, MonthStamp
|
||||
from src.classes.action import Action, ALL_ACTION_CLASSES
|
||||
from src.classes.world import World
|
||||
from src.classes.tile import Tile, Region
|
||||
from src.classes.cultivation import CultivationProgress, Realm
|
||||
from src.classes.root import Root
|
||||
from src.classes.age import Age
|
||||
from src.utils.strings import to_snake_case
|
||||
from src.classes.event import NULL_EVENT
|
||||
|
||||
from src.classes.ai import AI, RuleAI, LLMAI
|
||||
from src.classes.persona import Persona, personas_by_id
|
||||
from src.utils.id_generator import get_avatar_id
|
||||
|
||||
class Gender(Enum):
|
||||
MALE = "male"
|
||||
@@ -35,26 +37,27 @@ class Avatar:
|
||||
world: World
|
||||
name: str
|
||||
id: str
|
||||
birth_month: Month
|
||||
birth_year: Year
|
||||
birth_month_stamp: MonthStamp
|
||||
age: Age
|
||||
gender: Gender
|
||||
cultivation_progress: CultivationProgress = field(default_factory=lambda: CultivationProgress(0))
|
||||
pos_x: int = 0
|
||||
pos_y: int = 0
|
||||
tile: Optional[Tile] = None
|
||||
actions: dict[str, Action] = field(default_factory=dict)
|
||||
|
||||
root: Root = field(default_factory=lambda: random.choice(list(Root)))
|
||||
persona: Persona = field(default_factory=lambda: random.choice(list(personas_by_id.values())))
|
||||
ai: AI = None
|
||||
action_be_executed: Optional[Action] = None
|
||||
action_parmas_be_executed: Optional[dict] = None
|
||||
|
||||
def __post_init__(self):
|
||||
"""
|
||||
在Avatar创建后自动绑定基础动作和AI
|
||||
在Avatar创建后自动初始化tile和AI
|
||||
"""
|
||||
self.tile = self.world.map.get_tile(self.pos_x, self.pos_y)
|
||||
self.ai = LLMAI(self)
|
||||
# self.ai = RuleAI(self)
|
||||
self._bind_basic_actions()
|
||||
# self.ai = LLMAI(self)
|
||||
self.ai = RuleAI(self)
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""
|
||||
@@ -63,38 +66,48 @@ class Avatar:
|
||||
"""
|
||||
return f"Avatar(id={self.id}, 性别={self.gender}, 年龄={self.age}, name={self.name}, 区域={self.tile.region.name}, 灵根={self.root.value}, 境界={self.cultivation_progress})"
|
||||
|
||||
def _bind_basic_actions(self):
|
||||
def create_action(self, action_name: str) -> Action:
|
||||
"""
|
||||
绑定基础动作,如移动等
|
||||
根据动作名称创建新的action实例
|
||||
|
||||
Args:
|
||||
action_name: 动作类的名称(如 'Cultivate', 'Breakthrough' 等)
|
||||
|
||||
Returns:
|
||||
新创建的Action实例
|
||||
|
||||
Raises:
|
||||
ValueError: 如果找不到对应的动作类
|
||||
"""
|
||||
for action in ALL_ACTION_CLASSES:
|
||||
self.bind_action(action)
|
||||
|
||||
|
||||
def bind_action(self, action_class: type[Action]):
|
||||
"""
|
||||
绑定一个action到avatar
|
||||
"""
|
||||
# 以类名为键保存实例,保持可追踪性
|
||||
self.actions[action_class.__name__] = action_class(self, self.world)
|
||||
|
||||
# 同时挂载一个便捷方法,名称为蛇形(MoveFast -> move_fast),并转发参数
|
||||
method_name = to_snake_case(action_class.__name__)
|
||||
|
||||
def _wrapper(*args, **kwargs):
|
||||
return self.actions[action_class.__name__].execute(*args, **kwargs)
|
||||
|
||||
setattr(self, method_name, _wrapper)
|
||||
# 在所有动作类中查找对应的类
|
||||
for action_class in ALL_ACTION_CLASSES:
|
||||
if action_class.__name__ == action_name:
|
||||
return action_class(self, self.world)
|
||||
|
||||
raise ValueError(f"未找到名为 '{action_name}' 的动作类")
|
||||
|
||||
|
||||
def act(self):
|
||||
"""
|
||||
角色执行动作。
|
||||
实际上分为两步:决定做什么(decide)和实习上去做(do)
|
||||
实际上分为两步:决定做什么(decide)和实际去做(do)
|
||||
事件只在决定动作时产生,执行过程不产生事件
|
||||
"""
|
||||
action_name, action_args = self.ai.decide(self.world)
|
||||
action = self.actions[action_name]
|
||||
event = action.execute(**action_args)
|
||||
event = NULL_EVENT
|
||||
|
||||
if self.action_be_executed is None:
|
||||
# 决定动作时生成事件
|
||||
action_name, action_args, event = self.ai.decide(self.world)
|
||||
self.action_be_executed = self.create_action(action_name)
|
||||
self.action_parmas_be_executed = action_args
|
||||
|
||||
# 纯粹执行动作,不产生事件
|
||||
self.action_be_executed.execute(**self.action_parmas_be_executed)
|
||||
|
||||
if self.action_be_executed.is_finished(**self.action_parmas_be_executed):
|
||||
self.action_be_executed = None
|
||||
self.action_parmas_be_executed = None
|
||||
|
||||
return event
|
||||
|
||||
def update_cultivation(self, new_level: int):
|
||||
@@ -118,11 +131,11 @@ class Avatar:
|
||||
"""
|
||||
return self.age.death_by_old_age(self.cultivation_progress.realm)
|
||||
|
||||
def update_age(self, current_month: Month, current_year: Year):
|
||||
def update_age(self, current_month_stamp: MonthStamp):
|
||||
"""
|
||||
更新年龄
|
||||
"""
|
||||
self.age.update_age(current_month, current_year, self.birth_month, self.birth_year)
|
||||
self.age.update_age(current_month_stamp, self.birth_month_stamp)
|
||||
|
||||
def get_age_info(self) -> dict:
|
||||
"""
|
||||
@@ -145,27 +158,25 @@ class Avatar:
|
||||
def is_in_region(self, region: Region) -> bool:
|
||||
return self.tile.region == region
|
||||
|
||||
def get_new_avatar_from_ordinary(world: World, current_year: Year, name: str, age: Age):
|
||||
def get_new_avatar_from_ordinary(world: World, current_month_stamp: MonthStamp, name: str, age: Age):
|
||||
"""
|
||||
从凡人中来的新修士
|
||||
这代表其境界为最低
|
||||
"""
|
||||
# 利用uuid功能生成id
|
||||
avatar_id = str(uuid.uuid4())
|
||||
# 生成短ID,替代UUID4
|
||||
avatar_id = get_avatar_id()
|
||||
|
||||
birth_year = current_year - age.age
|
||||
birth_month = random.choice(list(Month))
|
||||
birth_month_stamp = current_month_stamp - age.age * 12 + random.randint(0, 11) # 在出生年内随机选择月份
|
||||
cultivation_progress = CultivationProgress(0)
|
||||
pos_x = random.randint(0, world.map.width)
|
||||
pos_y = random.randint(0, world.map.height)
|
||||
pos_x = random.randint(0, world.map.width - 1)
|
||||
pos_y = random.randint(0, world.map.height - 1)
|
||||
gender = random.choice(list(Gender))
|
||||
|
||||
return Avatar(
|
||||
world=world,
|
||||
name=name,
|
||||
id=avatar_id,
|
||||
birth_month=birth_month,
|
||||
birth_year=birth_year,
|
||||
birth_month_stamp=MonthStamp(birth_month_stamp),
|
||||
age=age,
|
||||
gender=gender,
|
||||
cultivation_progress=cultivation_progress,
|
||||
|
||||
@@ -24,8 +24,23 @@ class Year(int):
|
||||
def __add__(self, other: int) -> 'Year':
|
||||
return Year(int(self) + other)
|
||||
|
||||
def next_month(month: Month, year: Year) -> tuple[Month, Year]:
|
||||
if month == Month.DECEMBER:
|
||||
return Month.JANUARY, year + 1
|
||||
else:
|
||||
return Month(month.value + 1), year
|
||||
class MonthStamp(int):
|
||||
"""
|
||||
0年1月 = 0
|
||||
之后依次递增
|
||||
"""
|
||||
def get_month(self) -> Month:
|
||||
month_value = (self % 12) + 1
|
||||
return Month(month_value if month_value <= 12 else 12)
|
||||
|
||||
def get_year(self) -> Year:
|
||||
return Year(self // 12)
|
||||
|
||||
def __add__(self, other: int) -> 'MonthStamp':
|
||||
return MonthStamp(int(self) + other)
|
||||
|
||||
|
||||
|
||||
def create_month_stamp(year: Year, month: Month) -> MonthStamp:
|
||||
"""从年和月创建MonthStamp"""
|
||||
return MonthStamp(int(year) * 12 + month.value - 1)
|
||||
@@ -92,16 +92,6 @@ class CultivationProgress:
|
||||
|
||||
return exp_required + realm_bonus
|
||||
|
||||
def can_level_up(self) -> bool:
|
||||
"""
|
||||
检查是否可以升级
|
||||
|
||||
返回:
|
||||
如果经验值足够升级则返回True
|
||||
"""
|
||||
required_exp = self.get_exp_required()
|
||||
return self.exp >= required_exp
|
||||
|
||||
def get_exp_progress(self) -> tuple[int, int]:
|
||||
"""
|
||||
获取当前经验值进度
|
||||
@@ -150,5 +140,12 @@ class CultivationProgress:
|
||||
"""
|
||||
return self.level in level_to_break_through.keys()
|
||||
|
||||
def can_level_up(self) -> bool:
|
||||
"""
|
||||
检查是否可以升级
|
||||
可以突破,说明到顶了,说明不能升级。
|
||||
"""
|
||||
return not self.can_break_through()
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"{self.realm.value}{self.stage.value}({self.level}级)。可以突破:{self.can_break_through()}"
|
||||
@@ -3,17 +3,39 @@ event class
|
||||
"""
|
||||
from dataclasses import dataclass
|
||||
|
||||
from src.classes.calendar import Month, Year
|
||||
from src.classes.calendar import Month, Year, MonthStamp
|
||||
|
||||
@dataclass
|
||||
class Event:
|
||||
year: Year
|
||||
month: Month
|
||||
month_stamp: MonthStamp
|
||||
content: str
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"{self.year}年{self.month}月: {self.content}"
|
||||
year = self.month_stamp.get_year()
|
||||
month = self.month_stamp.get_month()
|
||||
return f"{year}年{month}月: {self.content}"
|
||||
|
||||
class NullEvent:
|
||||
"""
|
||||
空事件单例类
|
||||
"""
|
||||
_instance = None
|
||||
|
||||
def __new__(cls):
|
||||
if cls._instance is None:
|
||||
cls._instance = super().__new__(cls)
|
||||
return cls._instance
|
||||
|
||||
def __str__(self) -> str:
|
||||
return ""
|
||||
return ""
|
||||
|
||||
def __bool__(self) -> bool:
|
||||
"""使NullEvent实例在布尔上下文中为False"""
|
||||
return False
|
||||
|
||||
# 全局单例实例
|
||||
NULL_EVENT = NullEvent()
|
||||
|
||||
def is_null_event(event) -> bool:
|
||||
"""检查事件是否为空事件的便捷函数"""
|
||||
return event is NULL_EVENT
|
||||
29
src/classes/persona.py
Normal file
29
src/classes/persona.py
Normal file
@@ -0,0 +1,29 @@
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
# TODO: 配表化
|
||||
@dataclass
|
||||
class Persona:
|
||||
"""
|
||||
角色个性
|
||||
"""
|
||||
id: int
|
||||
name: str
|
||||
prompt: str
|
||||
|
||||
personas_by_id: dict[int, Persona] = {}
|
||||
personas_by_name: dict[str, Persona] = {}
|
||||
p1 = Persona(id=1, name="理性", prompt="你是一个理性的人,你总是会用逻辑来思考问题,做事会谋定而后动。")
|
||||
p2 = Persona(id=2, name="无常", prompt="你是一个无常的人,你总是会随机应变,性子到哪里了就是哪里。")
|
||||
p3 = Persona(id=3, name="怠惰", prompt="你是一个怠惰的人,你总是会拖延,不想努力,更热衷于享受人生。")
|
||||
p4 = Persona(id=4, name="冒险", prompt="你是一个冒险的人,你总是会冒险,喜欢刺激,总想放手一搏。")
|
||||
|
||||
personas_by_id[p1.id] = p1
|
||||
personas_by_id[p2.id] = p2
|
||||
personas_by_id[p3.id] = p3
|
||||
personas_by_id[p4.id] = p4
|
||||
personas_by_name[p1.name] = p1
|
||||
personas_by_name[p2.name] = p2
|
||||
personas_by_name[p3.name] = p3
|
||||
personas_by_name[p4.name] = p4
|
||||
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
from src.classes.tile import Map
|
||||
from src.classes.calendar import Year, Month
|
||||
from src.classes.calendar import Year, Month, MonthStamp
|
||||
|
||||
@dataclass
|
||||
class World():
|
||||
map: Map
|
||||
year: Year
|
||||
month: Month
|
||||
month_stamp: MonthStamp
|
||||
Reference in New Issue
Block a user