任务编号:TRPG-LOCAL-2024
执行环境:本地 venv(已预配置)
交付形式:本地文件包 + 测试报告
开发一个通用 OSR 跑团引擎,核心目标:引擎层零业务逻辑,所有规则(种族、职业、技能)通过插件或 YAML 配置实现。
- Python:3.10+(已安装)
- 包管理器:
uv(已安装) - 虚拟环境:
.venv/已创建(位于项目根目录,勿删) - 项目文件夹:
/home/user/osr-engine已创建骨架
# Windows(已执行)
.venv\Scripts\activate
# 验证 Python 版本 (已执行)
python --version # 应显示 3.10+uv add pyyaml pytest pytest-cov black ruff mypy/osr_trpg_engine/
├── engine/ # 你负责的核心引擎代码
│ ├── __init__.py
│ ├── dice.py # 骰子解析
│ ├── entity.py # 角色实体 + 存读档
│ ├── source.py # 数值来源抽象
│ └── yaml_source.py # YAML 驱动
├── rules/ # 业务规则示例
│ ├── __init__.py
│ ├── basic1981.py # Moldvay Basic 规则
│ └── yaml/ # YAML 规则包
│ ├── human.yaml
│ └── fighter_l1.yaml
├── tests/ # 测试用例(必须全覆盖)
│ ├── test_dice.py
│ ├── test_source.py
│ └── test_entity.py
├── cli.py # 可选命令行工具
└── save/ # 存档目录(自动生成)
def roll(expr: str, variables: dict[str, int] | None = None) -> int:
"""
解析并掷骰子表达式。
语法:3d6, 1d20+5, d%, d4+str_mod+prof
安全:禁止任意代码执行
"""测试用例:
assert roll("3d6") in range(3, 19)
assert roll("1d20+5") >= 6
assert roll("d20+mod", {"mod": 3}) in range(4, 24)class Source(ABC):
@abstractmethod
def priority(self) -> int: ...
@abstractmethod
def keys(self) -> list[str]: ...
def get(self, key: str) -> Any: ...
def mode(self, key: str) -> Literal["override", "add", "mul"]: ...
class CompositeSource(Source):
def add(self, src: Source) -> None: ...
def remove(self, src: Source) -> None: ...
def get_final(self, key: str, default: Any = 0) -> Any: ...叠加规则:
- 按
priority降序排列 override:直接替换当前值add:累加当前值mul:连乘当前值
测试用例:
def test_priority_override():
low = MockSource(10, {"str": 2}, {"str": "add"})
high = MockSource(50, {"str": 18}, {"str": "override"})
comp = CompositeSource()
comp.add(low); comp.add(high)
assert comp.get_final("str") == 18class Entity:
def __init__(self, rulebook: Any): ...
def set(self, key: str, value: Any) -> Entity: ...
def get(self, key: str, default: Any = 0) -> Any: ...
def attach(self, src: Source) -> Entity: ...
def detach(self, src: Source) -> Entity: ...
def check(self, expr: str, difficulty: int | None = None) -> bool: ...
def to_dict(self) -> dict[str, Any]: ...
def save_json(self, path: str) -> None: ...
@staticmethod
def from_dict(rulebook: Any, data: dict[str, Any]) -> Entity: ...class YamlSource(Source):
def __init__(self, yaml_path: str): ...
def load_yaml_folder(folder: str) -> list[YamlSource]: ...YAML 格式:
# rules/yaml/fighter_l1.yaml
name: Fighter L1
priority: 30
values:
hd: 8
to_hit: 1
modes:
hd: override
to_hit: addrules/basic1981.py 需提供完整 Moldvay Basic 角色创建逻辑
# 每次保存后执行
black engine/ rules/ tests/
ruff check --fix engine/ rules/ tests/
mypy --strict engine/配置:pyproject.toml 已内置
- 所有函数必须带完整类型注解
- 使用
dict,list原生类型
- 类:
PascalCase - 函数/变量:
snake_case - 常量:
UPPER_SNAKE_CASE
- 复杂逻辑需行内注释
- 公共类/函数必须有三引号文档
pytest --cov=engine --cov-report=html --cov-fail-under=95- 覆盖率 ≥ 95%
- 所有分支必须测试
- 单元测试、集成测试、边界测试、异常测试
-
pytest0 failed - 覆盖率 ≥ 95%
- Black/Ruff/MyPy 零警告
- 无调试代码
-
eval()安全 - 手动验证存档/读档
步骤 1:实现骰子解析
- 文件:
engine/dice.py - 目标:让
roll("3d6+2")能正确返回整数 - 产出:通过
tests/test_dice.py所有测试
步骤 2:实现数值来源
- 文件:
engine/source.py - 目标:
CompositeSource能按优先级叠加数值 - 产出:通过
tests/test_source.py所有测试
步骤 3:实现角色实体
- 文件:
engine/entity.py - 目标:Entity 能挂 Source、读写 base、存读档
- 产出:通过
tests/test_entity.py所有测试
步骤 4:实现 YAML 驱动
- 文件:
engine/yaml_source.py - 目标:能从 YAML 文件加载 Source
- 产出:手动测试
load_yaml_folder()返回正确列表
步骤 5:编写规则示例
- 文件:
rules/basic1981.py+rules/yaml/*.yaml - 目标:
cli.py能跑通basic1981角色创建 - 产出:终端可看到生成的角色数据
步骤 6:集成测试
- 文件:
tests/test_integration.py - 目标:全流程测试覆盖率达 95%
- 产出:
htmlcov/index.html报告
步骤 7:代码规范修正
- 执行:
black,ruff,mypy - 目标:零警告、零错误
- 产出:代码整洁、类型安全
步骤 8:最终验证
- 执行:
./verify.sh(需自行创建) - 目标:所有检查通过
- 产出:准备打包交付
- 完整代码:
/home/user/osr-engine/目录 - 测试报告:覆盖率 HTML 文件 + 终端截图
- 验证脚本:
verify.sh执行成功记录
- 环境疑问:检查
.venv/是否激活 - 测试失败:确保文件命名
test_*.py - 类型错误:运行
mypy engine/查看详情