add actual action space

This commit is contained in:
bridge
2025-09-03 22:32:42 +08:00
parent 9fd7dfd471
commit 490e973283
5 changed files with 62 additions and 55 deletions

View File

@@ -104,7 +104,7 @@
- ✅ LLM接口集成
- ✅ 角色AI系统规则AI + LLM AI
- ✅ 协程化决策机制,异步运行
- [ ] AI动作链系统长期规划和目标导向行为)
- [ ] 长期规划和目标导向行为)
- [ ] 突发动作响应系统(对外界刺激的即时反应)
- [ ] LLM驱动的NPC对话、思考、互动、事件总结
- [ ] 智能决策系统
@@ -160,41 +160,6 @@
我希望能够创造出纯粹的、快乐的、直接的、活着的修仙世界的沉浸感。不是像一些游戏公司的纯粹宣传工具,也不是像斯坦福小镇那样的纯粹研究,而是能给玩家提供真实代入感和沉浸感的实际世界。
## 项目结构
```
cultivation-world-simulator/
├── src/ # 核心源代码
│ ├── classes/ # 核心数据类
│ │ ├── avatar.py # 角色系统
│ │ ├── world.py # 世界模型
│ │ ├── tile.py # 地图系统
│ │ └── ... # 其他核心类
│ ├── front/ # 前端显示模块
│ │ └── front.py # pygame界面
│ ├── sim/ # 模拟引擎
│ │ └── simulator.py # 核心模拟器
│ ├── run/ # 运行脚本
│ │ ├── run.py # 主程序入口
│ │ └── create_map.py # 地图生成工具
│ └── utils/ # 实用工具
│ ├── config.py # 配置管理
│ ├── llm.py # LLM接口
│ └── strings.py # 字符串处理
├── assets/ # 游戏资源
│ ├── tiles/ # 地形贴图
│ ├── males/ # 男性角色头像
│ └── females/ # 女性角色头像
├── static/ # 静态配置文件
│ ├── config.yml # 基础配置
│ ├── local_config.yml # 本地配置(优先级更高)
│ └── templates/ # AI提示词模板
├── configs/ # 其他配置文件
├── tests/ # 测试代码
├── requirements.txt # Python依赖列表
└── README.md # 项目说明
```
## 技术架构
- **前端显示**: pygame (未来可能支持Web)

View File

@@ -128,12 +128,21 @@ class ActualActionMixin():
"""
pass
@property
@abstractmethod
def is_doable(self) -> bool:
"""
判断动作是否可以执行
"""
pass
class Move(DefineAction, ChunkActionMixin):
"""
最基础的移动动作在tile之间进行切换。
"""
COMMENT = "移动到某个相对位置"
PARAMS = {"delta_x": "int", "delta_y": "int"}
def _execute(self, delta_x: int, delta_y: int) -> None:
"""
移动到某个tile
@@ -157,6 +166,7 @@ class MoveToRegion(DefineAction, ActualActionMixin):
移动到某个region
"""
COMMENT = "移动到某个区域"
PARAMS = {"region": "region_name"}
def _execute(self, region: Region|str) -> None:
"""
移动到某个region
@@ -194,12 +204,20 @@ class MoveToRegion(DefineAction, ActualActionMixin):
region_name = str(region)
return Event(self.world.month_stamp, f"{self.avatar.name} 开始移动向 {region_name}")
@property
def is_doable(self) -> bool:
"""
判断移动到区域动作是否可以执行
"""
return True
@long_action(step_month=10)
class Cultivate(DefineAction, ActualActionMixin):
"""
修炼动作,可以增加修仙进度。
"""
COMMENT = "修炼,增进修为"
PARAMS = {}
def _execute(self) -> None:
"""
修炼
@@ -226,6 +244,13 @@ class Cultivate(DefineAction, ActualActionMixin):
"""
return Event(self.world.month_stamp, f"{self.avatar.name}{self.avatar.tile.region.name} 开始修炼")
@property
def is_doable(self) -> bool:
"""
判断修炼动作是否可以执行
"""
return self.avatar.cultivation_progress.can_cultivate()
# 突破境界class
@long_action(step_month=1)
@@ -234,6 +259,7 @@ class Breakthrough(DefineAction, ActualActionMixin):
突破境界
"""
COMMENT = "尝试突破境界"
PARAMS = {}
def calc_success_rate(self) -> float:
"""
计算突破境界的成功率
@@ -255,12 +281,20 @@ class Breakthrough(DefineAction, ActualActionMixin):
"""
return Event(self.world.month_stamp, f"{self.avatar.name} 开始尝试突破境界")
@property
def is_doable(self) -> bool:
"""
判断突破动作是否可以执行
"""
return self.avatar.cultivation_progress.can_break_through()
@long_action(step_month=6)
class Play(DefineAction, ActualActionMixin):
"""
游戏娱乐动作,持续半年时间
"""
COMMENT = "游戏娱乐,放松身心"
PARAMS = {}
def _execute(self) -> None:
"""
@@ -276,12 +310,11 @@ class Play(DefineAction, ActualActionMixin):
"""
return Event(self.world.month_stamp, f"{self.avatar.name} 开始玩耍")
@property
def is_doable(self) -> bool:
return True
ALL_ACTION_CLASSES = [Move, Cultivate, Breakthrough, MoveToRegion, Play]
ACTION_SPACE = [
# {"action": "Move", "params": {"delta_x": int, "delta_y": int}, "comment": Move.COMMENT},
{"action": "Cultivate", "params": {}, "comment": Cultivate.COMMENT},
{"action": "Breakthrough", "params": {}, "comment": Breakthrough.COMMENT},
{"action": "MoveToRegion", "params": {"region": "region_name"}, "comment": MoveToRegion.COMMENT},
{"action": "Play", "params": {}, "comment": Play.COMMENT},
]
ACTION_SPACE_STR = json.dumps(ACTION_SPACE, ensure_ascii=False)
ALL_ACTUAL_ACTION_CLASSES = [Cultivate, Breakthrough, MoveToRegion, Play]
ALL_ACTION_NAMES = ["Move", "Cultivate", "Breakthrough", "MoveToRegion", "Play"]
ALL_ACTUAL_ACTION_NAMES = ["Cultivate", "Breakthrough", "MoveToRegion", "Play"]

View File

@@ -11,7 +11,6 @@ import random
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_async
from src.classes.typings import ACTION_NAME, ACTION_PARAMS, ACTION_PAIR
@@ -84,9 +83,7 @@ class RuleAI(AI):
class LLMAI(AI):
"""
LLM AI
"""
# TODO动作链
"""
一些思考:
AI动作应该分两类
1. 动作链,一定时间内的长期规划,动作按照这个动作链来执行(以及何时终止并执行下一个动作)
2. 突发情况比如突然有人要攻击NPC这个时候的反应
@@ -97,7 +94,7 @@ class LLMAI(AI):
"""
异步决策逻辑通过LLM决定执行什么动作和参数
"""
action_space_str = ACTION_SPACE_STR
action_space_str = self.avatar.get_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

View File

@@ -2,9 +2,10 @@ import random
from dataclasses import dataclass, field
from enum import Enum
from typing import Optional
import json
from src.classes.calendar import MonthStamp
from src.classes.action import Action, ALL_ACTION_CLASSES
from src.classes.action import Action, ALL_ACTUAL_ACTION_CLASSES, ALL_ACTION_CLASSES, ALL_ACTUAL_ACTION_NAMES
from src.classes.world import World
from src.classes.tile import Tile, Region
from src.classes.cultivation import CultivationProgress
@@ -190,6 +191,20 @@ class Avatar:
"""
return "\n".join([f"{action.__class__.__name__}: {action_params}" for action, action_params in self.history_action_pairs])
def get_action_space_str(self) -> str:
action_space = self.get_action_space()
action_space_str = json.dumps(action_space, ensure_ascii=False)
return action_space_str
def get_action_space(self) -> list[dict]:
"""
获取动作空间
"""
actual_actions = [self.create_action(action_cls_name) for action_cls_name in ALL_ACTUAL_ACTION_NAMES]
doable_actions = [action for action in actual_actions if action.is_doable]
action_space = [{"action": action.__class__.__name__, "params": action.PARAMS, "comment": action.COMMENT} for action in doable_actions]
return action_space
def get_new_avatar_from_ordinary(world: World, current_month_stamp: MonthStamp, name: str, age: Age):
"""
从凡人中来的新修士

View File

@@ -1,15 +1,12 @@
你是一个决策者这是一个修仙的仙侠世界你负责来决定一些NPC的下一步行为。
每个角色均拥有的动作空间和需要的参数为:
{action_space}
世界地图上存在的区域为:
{regions}
你需要进行决策的NPC的基本信息为
{avatar_infos}
其个性为:{avatar_persona}
决策时需参考这个角色的个性。
该角色的动作空间及其参数为:
{action_space}
注意只返回json格式的动作
返回格式: