From b58baa7ef8f7ab3a05bc3351e6a9be761b9df4bc Mon Sep 17 00:00:00 2001 From: lch Date: Tue, 12 May 2026 13:02:23 +0800 Subject: [PATCH 1/9] 1 --- docs/contests/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/contests/README.md b/docs/contests/README.md index a29730cc..b4d31ac8 100644 --- a/docs/contests/README.md +++ b/docs/contests/README.md @@ -15,4 +15,4 @@ slug: ./ + [THUAI6](THUAI6/README.md):清华大学第六届人工智能挑战赛电子系赛道(原电子系第 24 届队式程序设计大赛 teamstyle24) + [THUAI7](THUAI7/README.md):清华大学第七届人工智能挑战赛电子系赛道(原电子系第 25 届队式程序设计大赛 teamstyle25) + [THUAI8](THUAI8/README.md):清华大学第八届人工智能挑战赛电子系赛道(原电子系第 26 届队式程序设计大赛 teamstyle26) -+ [THUAI9](THUAI8/README.md):清华大学第九届人工智能挑战赛电子系赛道(原电子系第 27 届队式程序设计大赛 teamstyle27) ++ [THUAI9](THUAI9/README.md):清华大学第九届人工智能挑战赛电子系赛道(原电子系第 27 届队式程序设计大赛 teamstyle27) From dd645f52bb1939938211ecd0094cc818e697bb2d Mon Sep 17 00:00:00 2001 From: lch Date: Tue, 12 May 2026 13:48:23 +0800 Subject: [PATCH 2/9] 2 --- docs/contests/RL9/pve/README.md | 25 +++++ docs/contests/RL9/pve/faq/README.md | 43 +++++++++ docs/contests/RL9/pve/game/actions.md | 36 +++++++ docs/contests/RL9/pve/game/reward.md | 36 +++++++ docs/contests/RL9/pve/game/state.md | 37 +++++++ docs/contests/RL9/pve/interface/gym.md | 106 +++++++++++++++++++++ docs/contests/RL9/pve/intro/rule.md | 67 +++++++++++++ docs/contests/RL9/pve/training/training.md | 103 ++++++++++++++++++++ sidebars.js | 36 +++++++ 9 files changed, 489 insertions(+) create mode 100644 docs/contests/RL9/pve/README.md create mode 100644 docs/contests/RL9/pve/faq/README.md create mode 100644 docs/contests/RL9/pve/game/actions.md create mode 100644 docs/contests/RL9/pve/game/reward.md create mode 100644 docs/contests/RL9/pve/game/state.md create mode 100644 docs/contests/RL9/pve/interface/gym.md create mode 100644 docs/contests/RL9/pve/intro/rule.md create mode 100644 docs/contests/RL9/pve/training/training.md diff --git a/docs/contests/RL9/pve/README.md b/docs/contests/RL9/pve/README.md new file mode 100644 index 00000000..df73aba7 --- /dev/null +++ b/docs/contests/RL9/pve/README.md @@ -0,0 +1,25 @@ +--- +title: THUAI9 +slug: ./ +--- +## 赛事名称 + +AI 工厂模拟(THUAI9) + +## PVE 概要 + +强化学习竞技环境。选手训练智能体在地图上移动、低买高卖、采集资源,在有限时间内最大化累计得分。提供 easy / medium / hard 三级难度,支持 MaskablePPO 训练。比赛以多 seed 平均得分排名。 + +--- + +## 选手包使用说明 + +不熟悉强化学习的可以访问https://github.com/konpoku/THUAI9-RL,在小项目中学习一下 + +详细的使用说明参照选手包里的logic\pve\docs\CONTESTANT_GUIDE.md + +--- + +## 相关链接 + ++ THUAI9 GitHub 仓库:[https://github.com/lch24/THUAI9](https://github.com/lch24/THUAI9) diff --git a/docs/contests/RL9/pve/faq/README.md b/docs/contests/RL9/pve/faq/README.md new file mode 100644 index 00000000..5e8d990d --- /dev/null +++ b/docs/contests/RL9/pve/faq/README.md @@ -0,0 +1,43 @@ +# 常见问题(PVE / Python / RL) + +## 环境相关 + +> Q: 需要什么 Python 版本? +> +> A: Python 3.9+。依赖见 `logic/pve/requirements.txt`。 + +> Q: 运行 `import GameLogic` 报错? +> +> A: 确保在 `logic/pve/` 目录下运行,或将 `logic/pve/` 加入 `PYTHONPATH`。 + +> Q: 如何切换难度? +> +> A: `GameConfig.easy()` / `.medium()` / `.hard()` 预设,或传入 YAML 文件路径。 + +## 训练相关 + +> Q: PPO 训练不收敛? +> +> A: 尝试:(1) 使用 `MaskablePPO`;(2) 降低 `price_volatility`;(3) easy 地图上先训练。 + +> Q: reward 和 score 的区别? +> +> A: reward 是训练辅助信号(含塑形奖励和惩罚),score 是最终排名依据(仅卖出得分×10)。比赛看 score。 + +> Q: 动作掩码有什么用? +> +> A: 在训练时告诉 PPO 哪些动作当前无效,避免探索无效方向。对规则策略同样有用。 + +## 接口相关 + +> Q: 能直接读 `env.unit` 吗? +> +> A: **不能**。评测机只暴露 `reset/step/action_masks` 三个标准接口。所有信息通过 `obs` 和 `info` 获取。 + +> Q: 观测向量怎么理解? +> +> A: 32 维 float32,包含位置、HP、背包、现金、市场价格相位、最近资源点/市场/算力中心相对位置等。详见 [公开接口](../interface/gym.md)。 + +> Q: 怎么写规则策略(不用 RL)? +> +> A: 读取 `info` 字典做 if-else 或状态机决策,直接调 `env.step()`。 diff --git a/docs/contests/RL9/pve/game/actions.md b/docs/contests/RL9/pve/game/actions.md new file mode 100644 index 00000000..037c4ede --- /dev/null +++ b/docs/contests/RL9/pve/game/actions.md @@ -0,0 +1,36 @@ +# 动作空间 + +PVE 使用 **8 个离散动作**: + +| 编号 | 动作 | 含义 | 有效性条件 | +|:----:|:----:|------|------------| +| 0 | `WAIT` | 等待一个 tick | 始终有效 | +| 1 | `MOVE_UP` | 向上移动 (x−1) | 目标格可通行,单位不 busy | +| 2 | `MOVE_DOWN` | 向下移动 (x+1) | 同上 | +| 3 | `MOVE_LEFT` | 向左移动 (y−1) | 同上 | +| 4 | `MOVE_RIGHT` | 向右移动 (y+1) | 同上 | +| 5 | `BUY` | 在相邻市场买最便宜的可负担商品 | Manhattan ≤1 有市场,背包有空间,现金充足 | +| 6 | `SELL` | 在相邻市场卖出背包内所有商品 | Manhattan ≤1 有市场,背包有商品 | +| 7 | `HARVEST` | 从附近资源点采集原材料 | Manhattan ≤2 有未耗尽资源,背包有空间 | + +- **BUY**:自动购买当前市场价格最低的可负担商品 +- **SELL**:一次性卖出背包中所有商品,获得当前市场价 +- **HARVEST**:采集范围 2 格(Manhattan 距离) +- 执行无效动作不会报错,但受到 **-0.05 分惩罚**并浪费步数 + +## 动作掩码 + +环境提供 `action_masks()` 方法,返回 `(8,)` 布尔数组,`True` 表示该动作当前有效。使用 `MaskablePPO` 可以自动过滤无效动作: + +```python +from sb3_contrib import MaskablePPO +from sb3_contrib.common.wrappers import ActionMasker + +def mask_fn(env): + return env.unwrapped.action_masks() + +masked_env = ActionMasker(env, mask_fn) +model = MaskablePPO("MlpPolicy", masked_env) +``` + +建议所有策略先查询 `action_masks()` 再决定动作。 diff --git a/docs/contests/RL9/pve/game/reward.md b/docs/contests/RL9/pve/game/reward.md new file mode 100644 index 00000000..83612c1c --- /dev/null +++ b/docs/contests/RL9/pve/game/reward.md @@ -0,0 +1,36 @@ +# 奖励与得分 + +## 得分(Score) + +最终排名依据的指标。只在 **SELL 动作成功**时增加: + +``` +score += revenue × score_factor(默认 × 10) +``` + +## 单步奖励(RL Reward) + +训练时的辅助信号,由以下部分叠加: + +| 来源 | 值 | 说明 | +|------|:--:|------| +| 现金变化 Δmoney | × 0.01 | 正负均有 | +| 得分变化 Δscore | × 0.01 | 仅卖出时为正 | +| 时间惩罚(每步) | −0.002 | 鼓励高效路径 | +| 采集奖励(每单位) | +0.001 | 采集塑形 | +| 算力中心解锁(一次性) | +0.5 | 进度奖励 | +| 无效动作惩罚 | −0.05 | 每步 | +| 破产惩罚(terminated 时) | −10.0 | 终端惩罚 | + +> 奖励是训练辅助信号。**最终排名以 `info["score"]` 为准**,不是累计奖励。 + +## `step()` 返回的 info 字典 + +| 字段 | 类型 | 含义 | +|------|------|------| +| `step` | `int` | 当前步数 | +| `time` | `float` | 游戏时间(秒) | +| `money` | `float` | 当前现金 | +| `score` | `float` | 当前累计得分 | +| `compute` | `float` | 当前算力 | +| `action_valid` | `bool` | 上一步动作是否有效 | diff --git a/docs/contests/RL9/pve/game/state.md b/docs/contests/RL9/pve/game/state.md new file mode 100644 index 00000000..8955d676 --- /dev/null +++ b/docs/contests/RL9/pve/game/state.md @@ -0,0 +1,37 @@ +# 游戏状态 + +## 单位属性 + +| 属性 | 值 | +|:----:|:--:| +| 血量 | 300 | +| 背包容量 | 30 | +| 采集速率 | 10/s | +| 算力中心占领时间 | 10s | + +- 背包分为**原材料**和**成品**两部分 +- `busy_ticks > 0` 时单位忙碌,忽略新指令 + +## 工厂 + +| 属性 | 值 | +|:----:|:--:| +| 位置 | (0, 0) | +| 仓储上限 | 300 | +| 初始生产线数 | 3 | + +## 算力产出 + +- 每个已占领算力中心:**1 算力/秒** +- 可花费算力招募新单位(Phase 2) + +## 资源再生 + +资源点库存会随时间缓慢再生: + +``` +regen(t) = rate × (1 + sin(2π·t / period)) / 2 +``` + +- 再生倍率:easy=2.0, medium=1.0, hard=0.5 +- 资源耗尽(`depleted=True`)后停止再生 diff --git a/docs/contests/RL9/pve/interface/gym.md b/docs/contests/RL9/pve/interface/gym.md new file mode 100644 index 00000000..6435ad12 --- /dev/null +++ b/docs/contests/RL9/pve/interface/gym.md @@ -0,0 +1,106 @@ +# 公开接口 + +PVE 算法**只能**通过标准 Gymnasium 接口与环境交互。不得直接访问内部对象。 + +## 环境初始化 + +```python +from GameLogic import GameEnvironment, GameConfig + +# 内置难度 +env = GameEnvironment(cfg=GameConfig.easy()) +env = GameEnvironment(cfg=GameConfig.medium()) +env = GameEnvironment(cfg=GameConfig.hard()) + +# 自定义配置 +env = GameEnvironment(cfg=GameConfig.from_dict({ + "map_width": 8, "map_height": 8, + "num_markets": 4, "initial_money": 100.0, +})) +``` + +## 接口方法 + +### `reset()` + +```python +obs, info = env.reset(seed=0) +``` + +重置环境,返回初始观测和 info。 + +### `step()` + +```python +obs, reward, terminated, truncated, info = env.step(action) +# action: int, 0-7 +# obs: np.ndarray, shape (32,), dtype float32 +# reward: float +# terminated: bool (money < 0,破产) +# truncated: bool (步数耗尽,正常结束) +# info: dict +``` + +### `action_masks()` + +```python +mask = env.action_masks() # np.ndarray[bool], shape (8,) +``` + +返回当前有效的动作掩码。 + +## 观测向量(32 维 float32) + +| 索引 | 含义 | 归一化 | +|:----:|------|--------| +| 0–1 | 单位位置 (x, y) | / (H, W) | +| 2 | 单位 HP | / max_hp | +| 3 | 原材料背包占比 | raw_inv / capacity | +| 4 | 成品背包占比 | prod_inv / capacity | +| 5 | busy 倒计时 | / 10,截断到 1 | +| 6 | 现金 | log10(money+1) / 5 | +| 7 | 算力 | / 100,截断到 2 | +| 8 | 游戏进度 | time / max_time | +| 9 | 价格相位 sin | sin(2π·t / period) | +| 10 | 价格相位 cos | cos(2π·t / period) | +| 11 | 工厂原料库存 | / storage_cap | +| 12 | 工厂成品库存 | / storage_cap | +| 13 | 生产队列长度 | / 10,截断到 1 | +| 14–16 | 资源点 0 | 相对位置 (dx/H, dy/W) + 库存比 | +| 17–19 | 资源点 1 | 同上 | +| 20–22 | 算力中心 0 | 相对位置 (dx/H, dy/W) + is_open | +| 23–25 | 算力中心 1 | 同上 | +| 26–28 | 市场 0 | 相对位置 (dx/H, dy/W) + best_price | +| 29–31 | 市场 1 | 同上 | + +> 如果实体数量不足(如只有 2 个市场),多余索引保持 0。 + +## 禁止访问的对象 + +以下为内部实现,选手算法**不得依赖**: + +- `env.unit`(Unit 内部字段) +- `env.factory`(Factory 内部字段) +- `env.board`(Board / 地图内部字段) +- `env.markets`(Market 列表) +- `env.money`、`env.compute`、`env.score`(通过 `info` 获取) +- 任何以下划线开头的方法或属性 + +> 评测机只会暴露 `reset`、`step`、`action_masks` 三个公开接口。 + +## 编写规则型策略 + +如果不需要 RL 训练,可以直接读取 `info` 字典做规则决策: + +```python +obs, info = env.reset() +while True: + # 规则策略基于 info 做决策 + if info["money"] > 50: + action = 5 # BUY + else: + action = 7 # HARVEST + obs, reward, terminated, truncated, info = env.step(action) + if terminated or truncated: + break +``` diff --git a/docs/contests/RL9/pve/intro/rule.md b/docs/contests/RL9/pve/intro/rule.md new file mode 100644 index 00000000..55b5afd2 --- /dev/null +++ b/docs/contests/RL9/pve/intro/rule.md @@ -0,0 +1,67 @@ +# PVE 赛制与规则 + +## 概述 + +PVE(PvE-RL)赛道是一个**强化学习**竞技环境。选手需要训练一个智能体,在有限时间内通过买卖商品和采集资源来最大化累计得分。 + +与 PVP 不同的是,PVE 选手**不直接连接游戏服务器**,而是通过标准 **Gymnasium** 接口与一个本地仿真环境交互。选手的算法通过 `reset()` / `step(action)` / `action_masks()` 三个接口控制一个单位,在地图上移动、买卖、采集。 + +## 核心目标 + +**最大化多 seed 下的平均总得分**。得分 = 所有卖出收入之和 × 10。 + +每局从初始资金开始,通过在市场低买高卖、采集资源、解锁算力中心来积累财富。资金归零(破产)或步数耗尽时游戏结束。 + +## 地图与实体 + +| 难度 | 地图 | 市场 | 资源点 | 算力中心 | 初始资金 | 初始算力 | 时长 | +|:----:|:----:|:----:|:------:|:--------:|:--------:|:--------:|:----:| +| **easy** | 5×5 | 3 | 2 | 1 | 200 | 60 | 300s | +| **medium** | 10×10 | 3 | 2 | 2 | 50 | 30 | 300s | +| **hard** | 15×15 | 4 | 4 | 3 | 30 | 20 | 500s | + +- **工厂**位于 `(0, 0)`,是智能体的出生点 +- **市场**:3~4 个,买卖商品 +- **资源点**:2~4 个,可采集原材料(有再生) +- **算力中心**:1~3 个,占领后提供算力加成 +- **障碍物**:地图中随机分布 + +## 商品 + +| ID | 名称 | 购买成本 | 市场价格范围 | 生产时间 | +|:--:|:----:|:--------:|:------------:|:--------:| +| 0 | 半导体 | 10 | 40–120 | 5.0s | +| 1 | 药品 | 5 | 20–60 | 4.0s | +| 2 | 小商品 | 1 | 4–12 | 2.0s | +| 3 | 服饰 | 8 | 32–96 | 6.0s | +| 4 | 食品 | 3 | 12–24 | 1.0s | + +## 市场价格 + +每个市场对每种商品有独立的正弦价格函数: + +``` +price(t) = base + amplitude × (1 + sin(2π·t / period + phase)) / 2 +``` + +- 不同市场的价格**相位随机不同步**,套利窗口随时间移动 +- `price_volatility` 控制波动幅度(easy=0.3, medium=1.0, hard=2.0) + +## 终止条件 + +| 条件 | 类型 | +|------|------| +| `money < 0`(破产) | `terminated = True` | +| `step >= max_steps`(时间耗尽) | `truncated = True` | + +## 与 PVP 的关键区别 + +| | PVP | PVE | +|:--|:---|:---| +| 交互方式 | gRPC 客户端连接服务器 | 本地 Gymnasium 环境 | +| 编程语言 | C++ | Python | +| 动作 | 连续移动 + 多种操作 | 8 个离散动作 | +| 市场定价 | 衰减机制 | 正弦周期波动 | +| 单位数 | 1 Team + 3 Character | 1 个可控单位 | +| 对手 | 其他人类队伍 | 无(纯经济环境) | +| 评测 | 单局得分 | 多 seed 平均得分 | diff --git a/docs/contests/RL9/pve/training/training.md b/docs/contests/RL9/pve/training/training.md new file mode 100644 index 00000000..8a7dfc5f --- /dev/null +++ b/docs/contests/RL9/pve/training/training.md @@ -0,0 +1,103 @@ +# 训练与评测 + +## 环境安装 + +```bash +pip install -r requirements.txt +``` + +## 单元测试 + +验证环境正常: + +```bash +python -m pytest tests/ -v +``` + +## 基础训练 + +```bash +# easy 难度,10 万步 +python TrainingDemo/train_basic.py --config easy --timesteps 100000 + +# medium 难度,20 万步 +python TrainingDemo/train_basic.py --config medium --timesteps 200000 + +# 自定义 YAML 配置 +python TrainingDemo/train_basic.py --config TrainingDemo/configs/medium.yaml --timesteps 200000 +``` + +## 评测模型 + +```bash +python TrainingDemo/evaluate.py --model models/ppo_thuai9_best --config medium --episodes 50 +``` + +输出多 seed 下的平均得分和方差。 + +## MaskablePPO + +使用 `sb3-contrib` 的 `MaskablePPO` 可以自动利用动作掩码: + +```python +from sb3_contrib import MaskablePPO +from sb3_contrib.common.wrappers import ActionMasker + +def mask_fn(env): + return env.unwrapped.action_masks() + +env = ActionMasker(env, mask_fn) +model = MaskablePPO("MlpPolicy", env, verbose=1) +model.learn(total_timesteps=100000) +model.save("ppo_thuai9") +``` + +## 难度对比 + +| 参数 | easy | medium | hard | +|------|:----:|:------:|:----:| +| 地图 | 5×5 | 10×10 | 15×15 | +| 市场数 | 3 | 3 | 4 | +| 资源点数 | 2 | 2 | 4 | +| 算力中心数 | 1 | 2 | 3 | +| 初始资金 | 200 | 50 | 30 | +| 初始算力 | 60 | 30 | 20 | +| 价格波动 | 0.3x | 1.0x | 2.0x | +| 资源再生 | 2.0x | 1.0x | 0.5x | +| 初始资源 | 200 | 100 | 50 | +| 游戏时长 | 300s | 300s | 500s | + +## 项目结构 + +``` +logic/pve/ +├── GameLogic/ # 游戏规则与状态管理(算法不可直接修改) +│ ├── config.py # 全局配置(难度参数化) +│ ├── board.py # 地图、资源点、算力中心 +│ ├── character.py # 单位(HP、背包、状态机) +│ ├── market.py # 市场动态价格函数 +│ ├── action_space.py # 动作空间与动作掩码 +│ ├── reward_calculator.py # 奖励计算 +│ └── game_env.py # 主环境(Gymnasium 接口) +├── RLInterfaces/ # RL 算法接口层 +│ ├── base_agent.py # 抽象基类 +│ ├── ppo_agent.py # PPO 实现(支持 MaskablePPO) +│ └── training_loop.py # 训练循环 +├── TrainingDemo/ # 训练与评测脚本 +│ ├── train_basic.py # 训练入口 +│ ├── evaluate.py # 评测脚本 +│ ├── visualization.py # ASCII 渲染 + 奖励曲线 +│ └── configs/ # YAML 配置 +├── tests/ # 单元测试 +└── docs/ # 选手/开发者文档 +``` + +## 建议方向 + +- 使用 `action_masks()` 过滤无效动作 +- BFS / A* 路径规划,找最优路线 +- 建模市场价格正弦周期(利用观测中的 sin/cos 相位) +- 选择高利润商品(半导体 40–120、服饰 32–96) +- 规则策略 + RL 混合(Hybrid Policy) +- 课程学习(easy → medium → hard) +- Recurrent Policy 识别价格周期 diff --git a/sidebars.js b/sidebars.js index 51ad7bec..d55936aa 100644 --- a/sidebars.js +++ b/sidebars.js @@ -254,6 +254,42 @@ module.exports = { }, ] }, + { + "RL9": [ + { + "PVE": [ + "contests/RL9/pve/README", + { + "引入": [ + "contests/RL9/pve/intro/rule", + ], + }, + { + "游戏": [ + "contests/RL9/pve/game/actions", + "contests/RL9/pve/game/state", + "contests/RL9/pve/game/reward", + ], + }, + { + "接口": [ + "contests/RL9/pve/interface/gym", + ], + }, + { + "训练": [ + "contests/RL9/pve/training/training", + ], + }, + { + "常见问题": [ + "contests/RL9/pve/faq/README", + ], + }, + ] + }, + ] + }, { "THUAI9": [ "contests/THUAI9/README", From ec43647c0ef4bd7f2b7212284aeca5ad4fb4581f Mon Sep 17 00:00:00 2001 From: lch24 Date: Tue, 12 May 2026 23:24:15 +0800 Subject: [PATCH 3/9] 1 --- docs/contests/THUAI9/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/contests/THUAI9/README.md b/docs/contests/THUAI9/README.md index e433e5da..21968c33 100644 --- a/docs/contests/THUAI9/README.md +++ b/docs/contests/THUAI9/README.md @@ -38,7 +38,7 @@ AI 工厂模拟(THUAI9) ### PVE 赛道(Python) -不熟悉强化学习的可以访问https://github.com/konpoku/THUAI9-RL,在小项目中学习一下 +不熟悉强化学习的可以访问https://github.com/konpoku/THUAI9-RL ,在小项目中学习一下 详细的使用说明参照选手包里的logic\pve\docs\CONTESTANT_GUIDE.md From ff39c62b8eb37882cd9947ac474a47047bfad485 Mon Sep 17 00:00:00 2001 From: lch Date: Thu, 14 May 2026 22:41:08 +0800 Subject: [PATCH 4/9] 10 --- docs/contests/THUAI9/README.md | 2 +- docs/contests/THUAI9/pvp/intro/rule.md | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/contests/THUAI9/README.md b/docs/contests/THUAI9/README.md index e433e5da..6f3ff89b 100644 --- a/docs/contests/THUAI9/README.md +++ b/docs/contests/THUAI9/README.md @@ -20,7 +20,7 @@ AI 工厂模拟(THUAI9) ## PVP 概要 -2~4 支队伍在同一地图对抗。每队拥有 1 座工厂和最多 3 个角色(Drone / Robot / AutonomousCar),通过采集资源、生产产品、占领算力中心、市场售卖、攻击敌方来获取分数。游戏时长 2 分钟,得分高者获胜。 +2~4 支队伍在同一地图对抗。每队拥有 1 座工厂和最多 3 个角色(Drone / Robot / AutonomousCar),通过采集资源、生产产品、占领算力中心、市场售卖、攻击敌方来获取分数。游戏时长 10 分钟,得分高者获胜。 ## PVE 概要 diff --git a/docs/contests/THUAI9/pvp/intro/rule.md b/docs/contests/THUAI9/pvp/intro/rule.md index 19e0f5ff..1510ace6 100644 --- a/docs/contests/THUAI9/pvp/intro/rule.md +++ b/docs/contests/THUAI9/pvp/intro/rule.md @@ -3,7 +3,7 @@ - PVP 模式支持 **2~4 支队伍**同时对抗,每支队伍由一名选手控制。 - 所有队伍起始条件完全相同:拥有一座**工厂**(位于地图角落),**100 点初始算力**,**0 点初始资源**。 - 双方需要在地图上采集资源、占领算力中心、生产产品、在市场售卖、攻击敌方单位,尽可能获取更高的分数。游戏结束时得分高者获胜。 -- 每局游戏时长为 **2 分钟(120 秒)**。 +- 每局游戏时长为 **10 分钟(600 秒)**。 - 游戏采用流式通信,服务器每 **50ms** 推送一帧数据,选手的 `play()` 函数每帧被调用一次。 - 选手需要为 **1 个 Team(PlayerID=0)** 和 **最多 3 个 Character(PlayerID=1,2,3)** 分别编写 AI 代码。 @@ -21,7 +21,7 @@ ## 游戏结束条件 -- 游戏时间达到 2 分钟(120 秒),游戏结束并结算最终得分。 +- 游戏时间达到 10 分钟(600 秒),游戏结束并结算最终得分。 - 工厂被摧毁的队伍无法再进行生产和招募,但已存在的角色仍可继续操作。 ## 关键时间参数 @@ -29,7 +29,7 @@ | 参数 | 值 | |:----:|:--:| | 每帧时长 | 50ms | -| 游戏总时长 | 120s | +| 游戏总时长 | 600s | | 工厂被攻击后停摆时间 | 10s | | 算力中心占领基础时间 | 10s | | AskAI 超时 | 60s | From ca61351255a9eb965d740a6925251198fd4ecf09 Mon Sep 17 00:00:00 2001 From: lch Date: Fri, 15 May 2026 22:12:32 +0800 Subject: [PATCH 5/9] md --- docs/contests/RL9/pve/game/actions.md | 70 ++++++++++++-- docs/contests/RL9/pve/game/state.md | 92 ++++++++++++++++-- docs/contests/RL9/pve/interface/gym.md | 112 +++++++++++++++++----- docs/contests/THUAI9/README.md | 4 +- docs/contests/THUAI9/pve/game/actions.md | 70 ++++++++++++-- docs/contests/THUAI9/pve/game/state.md | 92 ++++++++++++++++-- docs/contests/THUAI9/pve/interface/gym.md | 112 +++++++++++++++++----- 7 files changed, 472 insertions(+), 80 deletions(-) diff --git a/docs/contests/RL9/pve/game/actions.md b/docs/contests/RL9/pve/game/actions.md index 037c4ede..cf99d55b 100644 --- a/docs/contests/RL9/pve/game/actions.md +++ b/docs/contests/RL9/pve/game/actions.md @@ -1,6 +1,8 @@ # 动作空间 -PVE 使用 **8 个离散动作**: +PVE 使用 **28 个离散动作**(`N_ACTIONS = 28`),分为基础操作、商品交易、生产链、科技四大类。 + +## 基础动作(0–5) | 编号 | 动作 | 含义 | 有效性条件 | |:----:|:----:|------|------------| @@ -9,18 +11,68 @@ PVE 使用 **8 个离散动作**: | 2 | `MOVE_DOWN` | 向下移动 (x+1) | 同上 | | 3 | `MOVE_LEFT` | 向左移动 (y−1) | 同上 | | 4 | `MOVE_RIGHT` | 向右移动 (y+1) | 同上 | -| 5 | `BUY` | 在相邻市场买最便宜的可负担商品 | Manhattan ≤1 有市场,背包有空间,现金充足 | -| 6 | `SELL` | 在相邻市场卖出背包内所有商品 | Manhattan ≤1 有市场,背包有商品 | -| 7 | `HARVEST` | 从附近资源点采集原材料 | Manhattan ≤2 有未耗尽资源,背包有空间 | +| 5 | `BUY` | 在相邻市场购买利润最高的可负担商品 | Manhattan ≤1 有市场,背包有空间,现金充足 | + +## 卖出动作(6–10,按商品类型) + +| 编号 | 动作 | 含义 | 有效性条件 | +|:----:|:----:|------|------------| +| 6 | `SELL_0` | 卖出背包内所有**半导体** | Manhattan ≤1 有市场,背包有该商品 | +| 7 | `SELL_1` | 卖出背包内所有**药品** | 同上 | +| 8 | `SELL_2` | 卖出背包内所有**小商品** | 同上 | +| 9 | `SELL_3` | 卖出背包内所有**服饰** | 同上 | +| 10 | `SELL_4` | 卖出背包内所有**食品** | 同上 | + +- **SELL_pid**:卖出指定类型的所有商品,获得当前市场价 +- 卖出成功时:`score += revenue × score_factor`(默认 ×10),`money += revenue` + +## 生产链动作(11–18) + +| 编号 | 动作 | 含义 | 有效性条件 | +|:----:|:----:|------|------------| +| 11 | `HARVEST` | 从附近资源点采集原材料 | Manhattan ≤2 有未耗尽资源,背包有空间 | +| 12 | `DEPOSIT` | 将背包原材料存入工厂 | 在工厂格,背包 raw_inv > 0 | +| 13 | `PRODUCE_0` | 工厂开始生产**半导体**(消耗 5 原材料) | 在工厂格,工厂 raw_stock ≥ 5,生产队列未满 | +| 14 | `PRODUCE_1` | 工厂开始生产**药品**(消耗 3 原材料) | 在工厂格,工厂 raw_stock ≥ 3,生产队列未满 | +| 15 | `PRODUCE_2` | 工厂开始生产**小商品**(消耗 1 原材料) | 在工厂格,工厂 raw_stock ≥ 1,生产队列未满 | +| 16 | `PRODUCE_3` | 工厂开始生产**服饰**(消耗 4 原材料) | 在工厂格,工厂 raw_stock ≥ 4,生产队列未满 | +| 17 | `PRODUCE_4` | 工厂开始生产**食品**(消耗 2 原材料) | 在工厂格,工厂 raw_stock ≥ 2,生产队列未满 | +| 18 | `LOAD` | 从工厂仓库装载已完成成品到背包 | 在工厂格,工厂有成品,背包有空间 | + +- **HARVEST**:采集范围 2 格(Manhattan 距离),每次采集 `unit_harvest_rate × time_step` 单位 +- **DEPOSIT**:将背包中原材料存入工厂仓库,供后续生产使用 +- **PRODUCE_pid**:将工厂原材料转换为产品,加入生产队列 +- **LOAD**:从工厂仓库取出已完成的成品装入背包 + +## 算力与科技动作(19–27) + +| 编号 | 动作 | 含义 | 消耗 | 前置 | +|:----:|:----:|------|:----:|:----:| +| 19 | `OCCUPY` | 推进相邻算力中心的占领进度 | — | — | +| 20 | `TECH_0` | **降低成本**:商品购买成本 −2 | 50 算力 | — | +| 21 | `TECH_1` | **效率提升**:生产时间 ×0.5 | 40 算力 | — | +| 22 | `TECH_2` | **市场营销**:卖价 ×1.1 | 80 算力 | — | +| 23 | `TECH_3` | **耐久强化**:单位 max HP +50% | 30 算力 | — | +| 24 | `TECH_4` | **多产线**:工厂 +1 生产线 | 60 算力 | — | +| 25 | `TECH_5` | **路径优化**:移动不再产生 busy tick | 50 算力 | TECH_1 | +| 26 | `TECH_6` | **市场分析**:观测中标记已购买(可重复) | 40 算力 | — | +| 27 | `TECH_7` | **算力扩张**:算力积累速率 +30% | 70 算力 | — | + +- **OCCUPY**:需相邻有未开放的算力中心,每次 tick 推进 `time_step` 秒进度 +- **TECH_0~5, TECH_7**:持久科技,每种只能购买一次,在工厂格执行 +- **TECH_6**:非持久科技,可重复购买 +- 科技动作需要在工厂格、算力足够、前置科技满足时才有效 + +## 通用规则 -- **BUY**:自动购买当前市场价格最低的可负担商品 -- **SELL**:一次性卖出背包中所有商品,获得当前市场价 -- **HARVEST**:采集范围 2 格(Manhattan 距离) -- 执行无效动作不会报错,但受到 **-0.05 分惩罚**并浪费步数 +- `busy_ticks > 0` 时单位忙碌,**仅 WAIT 有效**,其他动作均被掩码屏蔽 +- 移动默认消耗 1 busy_tick(购买 TECH_5 后变为 0) +- BUY/SELL/LOAD 消耗 busy_ticks(`0.25 / time_step` 个 tick) +- 执行无效动作不会报错,但受到 **−0.05 奖励惩罚**并浪费步数 ## 动作掩码 -环境提供 `action_masks()` 方法,返回 `(8,)` 布尔数组,`True` 表示该动作当前有效。使用 `MaskablePPO` 可以自动过滤无效动作: +环境提供 `action_masks()` 方法,返回 `(28,)` 布尔数组,`True` 表示该动作当前有效。使用 `MaskablePPO` 可以自动过滤无效动作: ```python from sb3_contrib import MaskablePPO diff --git a/docs/contests/RL9/pve/game/state.md b/docs/contests/RL9/pve/game/state.md index 8955d676..553267df 100644 --- a/docs/contests/RL9/pve/game/state.md +++ b/docs/contests/RL9/pve/game/state.md @@ -1,5 +1,13 @@ # 游戏状态 +## 时间系统 + +| 参数 | 值 | +|:----:|:--:| +| 每 tick 时长 | 0.25s | +| easy/medium 总时长 | 300s(1200 ticks) | +| hard 总时长 | 500s(2000 ticks) | + ## 单位属性 | 属性 | 值 | @@ -9,8 +17,10 @@ | 采集速率 | 10/s | | 算力中心占领时间 | 10s | -- 背包分为**原材料**和**成品**两部分 -- `busy_ticks > 0` 时单位忙碌,忽略新指令 +- 背包分为**原材料**(`raw_inv`)和**成品**(`prod_inv[pid]`)两部分 +- `busy_ticks > 0` 时单位忙碌,仅 WAIT 有效 +- 移动默认消耗 1 busy_tick(购买 path_optimization 科技后变为 0) +- BUY/SELL/LOAD 消耗 `int(0.25 / time_step)` 个 busy_tick ## 工厂 @@ -20,10 +30,72 @@ | 仓储上限 | 300 | | 初始生产线数 | 3 | -## 算力产出 +### 生产系统 + +工厂将原材料加工为成品。生产队列最大长度 = `生产线数 × 5`。 + +生产流程: +1. 采集原材料(HARVEST) +2. 存入工厂(DEPOSIT) +3. 排队生产(PRODUCE_pid)——消耗原材料,加入生产队列 +4. 等待工厂 tick 推进完成 +5. 装载成品(LOAD) + +生产时间受 efficiency 科技影响(×0.5)。 -- 每个已占领算力中心:**1 算力/秒** -- 可花费算力招募新单位(Phase 2) +## 商品 + +| ID | 名称 | 购买成本 | 原材料消耗 | 市场价格范围 | 生产时间 | +|:--:|:----:|:--------:|:----------:|:------------:|:--------:| +| 0 | 半导体 | 10 | 5 | 40–120 | 5.0s | +| 1 | 药品 | 5 | 3 | 20–60 | 4.0s | +| 2 | 小商品 | 1 | 1 | 4–12 | 2.0s | +| 3 | 服饰 | 8 | 4 | 32–96 | 6.0s | +| 4 | 食品 | 3 | 2 | 12–24 | 1.0s | + +- 购买成本受 cost_reduction 科技影响(−2,最低为 0) +- 生产时间受 efficiency 科技影响(×0.5) + +## 市场定价 + +每个市场对每种商品有独立的正弦价格函数: + +``` +price(t) = base + amplitude × (1 + sin(2π·t / period + phase)) / 2 +``` + +- 不同市场的价格**相位随机不同步**,套利窗口随时间移动 +- `price_volatility` 控制波动幅度(easy=0.3, medium=1.0, hard=2.0) +- 卖价受 marketing 科技影响(×1.1) + +## 算力系统 + +- **基础产出**:每个已开放算力中心产出 **1 算力/秒** +- 购买 compute_expansion 科技后:+30% 算力速率 +- 可消耗算力招募新单位(40 算力/个,最多 5 个单位)——Phase 2 + +### 算力中心 + +- 占领方式:在相邻格执行 OCCUPY,每次 tick 推进 0.25s 进度 +- 进度达到 `unit_occupy_time`(10s)后开放 +- 开放后持续产出算力 + +## 科技树 + +| 键名 | 效果 | 消耗 | 前置 | 持久 | +|:-----|------|:----:|:----:|:----:| +| cost_reduction | 商品购买成本 −2 | 50 | — | ✓ | +| efficiency | 生产时间 ×0.5 | 40 | — | ✓ | +| marketing | 卖价 ×1.1 | 80 | — | ✓ | +| durability | 单位 max HP +50% | 30 | — | ✓ | +| multi_line | 工厂 +1 生产线 | 60 | — | ✓ | +| path_optimization | 移动不产生 busy_tick | 50 | efficiency | ✓ | +| market_analysis | 观测中标记已购买 | 40 | — | ✗ | +| compute_expansion | 算力速率 +30% | 70 | — | ✓ | + +- 持久科技每种只能购买一次 +- market_analysis 可重复购买 +- 科技在工厂格执行 ## 资源再生 @@ -34,4 +106,12 @@ regen(t) = rate × (1 + sin(2π·t / period)) / 2 ``` - 再生倍率:easy=2.0, medium=1.0, hard=0.5 -- 资源耗尽(`depleted=True`)后停止再生 +- 资源耗尽(累计采集量 ≥ max_stock)后停止再生 +- 最大库存为初始库存的 2 倍 + +## 得分与终止 + +- **得分**:`score += revenue × score_factor`(默认 ×10),仅在 SELL 成功时增加 +- **terminated**:`money < 0`(破产) +- **truncated**:`step >= max_steps`(时间耗尽) +- 最终排名以多 seed 下的 `info["score"]` 平均为准 diff --git a/docs/contests/RL9/pve/interface/gym.md b/docs/contests/RL9/pve/interface/gym.md index 6435ad12..9990a579 100644 --- a/docs/contests/RL9/pve/interface/gym.md +++ b/docs/contests/RL9/pve/interface/gym.md @@ -33,8 +33,8 @@ obs, info = env.reset(seed=0) ```python obs, reward, terminated, truncated, info = env.step(action) -# action: int, 0-7 -# obs: np.ndarray, shape (32,), dtype float32 +# action: int, 0-27 +# obs: np.ndarray, shape (58,), dtype float32 # reward: float # terminated: bool (money < 0,破产) # truncated: bool (步数耗尽,正常结束) @@ -44,36 +44,100 @@ obs, reward, terminated, truncated, info = env.step(action) ### `action_masks()` ```python -mask = env.action_masks() # np.ndarray[bool], shape (8,) +mask = env.action_masks() # np.ndarray[bool], shape (28,) ``` 返回当前有效的动作掩码。 -## 观测向量(32 维 float32) +## 观测向量(58 维 float32) + +### 单位状态([0–9]) | 索引 | 含义 | 归一化 | |:----:|------|--------| | 0–1 | 单位位置 (x, y) | / (H, W) | | 2 | 单位 HP | / max_hp | -| 3 | 原材料背包占比 | raw_inv / capacity | -| 4 | 成品背包占比 | prod_inv / capacity | -| 5 | busy 倒计时 | / 10,截断到 1 | -| 6 | 现金 | log10(money+1) / 5 | -| 7 | 算力 | / 100,截断到 2 | -| 8 | 游戏进度 | time / max_time | -| 9 | 价格相位 sin | sin(2π·t / period) | -| 10 | 价格相位 cos | cos(2π·t / period) | -| 11 | 工厂原料库存 | / storage_cap | -| 12 | 工厂成品库存 | / storage_cap | -| 13 | 生产队列长度 | / 10,截断到 1 | -| 14–16 | 资源点 0 | 相对位置 (dx/H, dy/W) + 库存比 | -| 17–19 | 资源点 1 | 同上 | -| 20–22 | 算力中心 0 | 相对位置 (dx/H, dy/W) + is_open | -| 23–25 | 算力中心 1 | 同上 | -| 26–28 | 市场 0 | 相对位置 (dx/H, dy/W) + best_price | -| 29–31 | 市场 1 | 同上 | - -> 如果实体数量不足(如只有 2 个市场),多余索引保持 0。 +| 3 | 原材料背包 | raw_inv / capacity | +| 4 | 半导体背包 | prod_inv[0] / capacity | +| 5 | 药品背包 | prod_inv[1] / capacity | +| 6 | 小商品背包 | prod_inv[2] / capacity | +| 7 | 服饰背包 | prod_inv[3] / capacity | +| 8 | 食品背包 | prod_inv[4] / capacity | +| 9 | busy 倒计时 | / 10,截断到 1 | + +### 经济状态([10–14]) + +| 索引 | 含义 | 归一化 | +|:----:|------|--------| +| 10 | 现金(对数) | log10(money+1) / 5 | +| 11 | 算力 | / 100,截断到 2 | +| 12 | 游戏进度 | time / max_game_time | +| 13 | 价格相位 sin | sin(2π·t / market_period) | +| 14 | 价格相位 cos | cos(2π·t / market_period) | + +### 工厂状态([15–21]) + +| 索引 | 含义 | 归一化 | +|:----:|------|--------| +| 15 | 工厂原材料库存 | / storage_cap | +| 16 | 工厂半导体库存 | / storage_cap | +| 17 | 工厂药品库存 | / storage_cap | +| 18 | 工厂小商品库存 | / storage_cap | +| 19 | 工厂服饰库存 | / storage_cap | +| 20 | 工厂食品库存 | / storage_cap | +| 21 | 生产队列长度 | / 10,截断到 1 | + +### 资源点([22–27]) + +| 索引 | 含义 | 归一化 | +|:----:|------|--------| +| 22–23 | 资源点 0 相对位置 (dx, dy) | / (H, W) | +| 24 | 资源点 0 库存比例 | stock / max_stock | +| 25–26 | 资源点 1 相对位置 (dx, dy) | / (H, W) | +| 27 | 资源点 1 库存比例 | stock / max_stock | + +### 算力中心([28–35]) + +| 索引 | 含义 | 归一化 | +|:----:|------|--------| +| 28–29 | 算力中心 0 相对位置 (dx, dy) | / (H, W) | +| 30 | 算力中心 0 是否开放 | 0 或 1 | +| 31 | 算力中心 0 占领进度 | / unit_occupy_time | +| 32–33 | 算力中心 1 相对位置 (dx, dy) | / (H, W) | +| 34 | 算力中心 1 是否开放 | 0 或 1 | +| 35 | 算力中心 1 占领进度 | / unit_occupy_time | + +### 市场([36–49]) + +| 索引 | 含义 | 归一化 | +|:----:|------|--------| +| 36–37 | 市场 0 相对位置 (dx, dy) | / (H, W) | +| 38 | 市场 0 半导体价格 | 按 val_range 归一化 | +| 39 | 市场 0 药品价格 | 同上 | +| 40 | 市场 0 小商品价格 | 同上 | +| 41 | 市场 0 服饰价格 | 同上 | +| 42 | 市场 0 食品价格 | 同上 | +| 43–44 | 市场 1 相对位置 (dx, dy) | / (H, W) | +| 45 | 市场 1 半导体价格 | 按 val_range 归一化 | +| 46 | 市场 1 药品价格 | 同上 | +| 47 | 市场 1 小商品价格 | 同上 | +| 48 | 市场 1 服饰价格 | 同上 | +| 49 | 市场 1 食品价格 | 同上 | + +### 科技状态([50–57]) + +| 索引 | 含义 | +|:----:|------| +| 50 | 是否已购买 cost_reduction | +| 51 | 是否已购买 efficiency | +| 52 | 是否已购买 marketing | +| 53 | 是否已购买 durability | +| 54 | 是否已购买 multi_line | +| 55 | 是否已购买 path_optimization | +| 56 | 是否已购买 market_analysis(可重复) | +| 57 | 是否已购买 compute_expansion | + +> 如果实体数量不足(如只有 1 个市场),多余索引保持 0。 ## 禁止访问的对象 @@ -99,7 +163,7 @@ while True: if info["money"] > 50: action = 5 # BUY else: - action = 7 # HARVEST + action = 11 # HARVEST obs, reward, terminated, truncated, info = env.step(action) if terminated or truncated: break diff --git a/docs/contests/THUAI9/README.md b/docs/contests/THUAI9/README.md index ca81b3e7..7ff67c81 100644 --- a/docs/contests/THUAI9/README.md +++ b/docs/contests/THUAI9/README.md @@ -14,8 +14,8 @@ AI 工厂模拟(THUAI9) | 编程语言 | C++ | Python | | 交互方式 | gRPC 连接服务器 | Gymnasium 本地环境 | | 控制对象 | 1 Team + 3 Character | 1 个单位 | -| 动作 | 连续移动 + 多种操作 | 8 个离散动作 | -| 得分机制 | 售卖 + 战斗 + 摧毁工厂 | 售卖 × 10 | +| 动作 | 连续移动 + 多种操作 | 28 个离散动作 | +| 得分机制 | 售卖 + 战斗 + 摧毁工厂 | 售卖 × 10(SELL成功时) | | 文档入口 | [`pvp/`](pvp/intro/rule.md) | [`pve/`](pve/intro/rule.md) | ## PVP 概要 diff --git a/docs/contests/THUAI9/pve/game/actions.md b/docs/contests/THUAI9/pve/game/actions.md index 037c4ede..cf99d55b 100644 --- a/docs/contests/THUAI9/pve/game/actions.md +++ b/docs/contests/THUAI9/pve/game/actions.md @@ -1,6 +1,8 @@ # 动作空间 -PVE 使用 **8 个离散动作**: +PVE 使用 **28 个离散动作**(`N_ACTIONS = 28`),分为基础操作、商品交易、生产链、科技四大类。 + +## 基础动作(0–5) | 编号 | 动作 | 含义 | 有效性条件 | |:----:|:----:|------|------------| @@ -9,18 +11,68 @@ PVE 使用 **8 个离散动作**: | 2 | `MOVE_DOWN` | 向下移动 (x+1) | 同上 | | 3 | `MOVE_LEFT` | 向左移动 (y−1) | 同上 | | 4 | `MOVE_RIGHT` | 向右移动 (y+1) | 同上 | -| 5 | `BUY` | 在相邻市场买最便宜的可负担商品 | Manhattan ≤1 有市场,背包有空间,现金充足 | -| 6 | `SELL` | 在相邻市场卖出背包内所有商品 | Manhattan ≤1 有市场,背包有商品 | -| 7 | `HARVEST` | 从附近资源点采集原材料 | Manhattan ≤2 有未耗尽资源,背包有空间 | +| 5 | `BUY` | 在相邻市场购买利润最高的可负担商品 | Manhattan ≤1 有市场,背包有空间,现金充足 | + +## 卖出动作(6–10,按商品类型) + +| 编号 | 动作 | 含义 | 有效性条件 | +|:----:|:----:|------|------------| +| 6 | `SELL_0` | 卖出背包内所有**半导体** | Manhattan ≤1 有市场,背包有该商品 | +| 7 | `SELL_1` | 卖出背包内所有**药品** | 同上 | +| 8 | `SELL_2` | 卖出背包内所有**小商品** | 同上 | +| 9 | `SELL_3` | 卖出背包内所有**服饰** | 同上 | +| 10 | `SELL_4` | 卖出背包内所有**食品** | 同上 | + +- **SELL_pid**:卖出指定类型的所有商品,获得当前市场价 +- 卖出成功时:`score += revenue × score_factor`(默认 ×10),`money += revenue` + +## 生产链动作(11–18) + +| 编号 | 动作 | 含义 | 有效性条件 | +|:----:|:----:|------|------------| +| 11 | `HARVEST` | 从附近资源点采集原材料 | Manhattan ≤2 有未耗尽资源,背包有空间 | +| 12 | `DEPOSIT` | 将背包原材料存入工厂 | 在工厂格,背包 raw_inv > 0 | +| 13 | `PRODUCE_0` | 工厂开始生产**半导体**(消耗 5 原材料) | 在工厂格,工厂 raw_stock ≥ 5,生产队列未满 | +| 14 | `PRODUCE_1` | 工厂开始生产**药品**(消耗 3 原材料) | 在工厂格,工厂 raw_stock ≥ 3,生产队列未满 | +| 15 | `PRODUCE_2` | 工厂开始生产**小商品**(消耗 1 原材料) | 在工厂格,工厂 raw_stock ≥ 1,生产队列未满 | +| 16 | `PRODUCE_3` | 工厂开始生产**服饰**(消耗 4 原材料) | 在工厂格,工厂 raw_stock ≥ 4,生产队列未满 | +| 17 | `PRODUCE_4` | 工厂开始生产**食品**(消耗 2 原材料) | 在工厂格,工厂 raw_stock ≥ 2,生产队列未满 | +| 18 | `LOAD` | 从工厂仓库装载已完成成品到背包 | 在工厂格,工厂有成品,背包有空间 | + +- **HARVEST**:采集范围 2 格(Manhattan 距离),每次采集 `unit_harvest_rate × time_step` 单位 +- **DEPOSIT**:将背包中原材料存入工厂仓库,供后续生产使用 +- **PRODUCE_pid**:将工厂原材料转换为产品,加入生产队列 +- **LOAD**:从工厂仓库取出已完成的成品装入背包 + +## 算力与科技动作(19–27) + +| 编号 | 动作 | 含义 | 消耗 | 前置 | +|:----:|:----:|------|:----:|:----:| +| 19 | `OCCUPY` | 推进相邻算力中心的占领进度 | — | — | +| 20 | `TECH_0` | **降低成本**:商品购买成本 −2 | 50 算力 | — | +| 21 | `TECH_1` | **效率提升**:生产时间 ×0.5 | 40 算力 | — | +| 22 | `TECH_2` | **市场营销**:卖价 ×1.1 | 80 算力 | — | +| 23 | `TECH_3` | **耐久强化**:单位 max HP +50% | 30 算力 | — | +| 24 | `TECH_4` | **多产线**:工厂 +1 生产线 | 60 算力 | — | +| 25 | `TECH_5` | **路径优化**:移动不再产生 busy tick | 50 算力 | TECH_1 | +| 26 | `TECH_6` | **市场分析**:观测中标记已购买(可重复) | 40 算力 | — | +| 27 | `TECH_7` | **算力扩张**:算力积累速率 +30% | 70 算力 | — | + +- **OCCUPY**:需相邻有未开放的算力中心,每次 tick 推进 `time_step` 秒进度 +- **TECH_0~5, TECH_7**:持久科技,每种只能购买一次,在工厂格执行 +- **TECH_6**:非持久科技,可重复购买 +- 科技动作需要在工厂格、算力足够、前置科技满足时才有效 + +## 通用规则 -- **BUY**:自动购买当前市场价格最低的可负担商品 -- **SELL**:一次性卖出背包中所有商品,获得当前市场价 -- **HARVEST**:采集范围 2 格(Manhattan 距离) -- 执行无效动作不会报错,但受到 **-0.05 分惩罚**并浪费步数 +- `busy_ticks > 0` 时单位忙碌,**仅 WAIT 有效**,其他动作均被掩码屏蔽 +- 移动默认消耗 1 busy_tick(购买 TECH_5 后变为 0) +- BUY/SELL/LOAD 消耗 busy_ticks(`0.25 / time_step` 个 tick) +- 执行无效动作不会报错,但受到 **−0.05 奖励惩罚**并浪费步数 ## 动作掩码 -环境提供 `action_masks()` 方法,返回 `(8,)` 布尔数组,`True` 表示该动作当前有效。使用 `MaskablePPO` 可以自动过滤无效动作: +环境提供 `action_masks()` 方法,返回 `(28,)` 布尔数组,`True` 表示该动作当前有效。使用 `MaskablePPO` 可以自动过滤无效动作: ```python from sb3_contrib import MaskablePPO diff --git a/docs/contests/THUAI9/pve/game/state.md b/docs/contests/THUAI9/pve/game/state.md index 8955d676..553267df 100644 --- a/docs/contests/THUAI9/pve/game/state.md +++ b/docs/contests/THUAI9/pve/game/state.md @@ -1,5 +1,13 @@ # 游戏状态 +## 时间系统 + +| 参数 | 值 | +|:----:|:--:| +| 每 tick 时长 | 0.25s | +| easy/medium 总时长 | 300s(1200 ticks) | +| hard 总时长 | 500s(2000 ticks) | + ## 单位属性 | 属性 | 值 | @@ -9,8 +17,10 @@ | 采集速率 | 10/s | | 算力中心占领时间 | 10s | -- 背包分为**原材料**和**成品**两部分 -- `busy_ticks > 0` 时单位忙碌,忽略新指令 +- 背包分为**原材料**(`raw_inv`)和**成品**(`prod_inv[pid]`)两部分 +- `busy_ticks > 0` 时单位忙碌,仅 WAIT 有效 +- 移动默认消耗 1 busy_tick(购买 path_optimization 科技后变为 0) +- BUY/SELL/LOAD 消耗 `int(0.25 / time_step)` 个 busy_tick ## 工厂 @@ -20,10 +30,72 @@ | 仓储上限 | 300 | | 初始生产线数 | 3 | -## 算力产出 +### 生产系统 + +工厂将原材料加工为成品。生产队列最大长度 = `生产线数 × 5`。 + +生产流程: +1. 采集原材料(HARVEST) +2. 存入工厂(DEPOSIT) +3. 排队生产(PRODUCE_pid)——消耗原材料,加入生产队列 +4. 等待工厂 tick 推进完成 +5. 装载成品(LOAD) + +生产时间受 efficiency 科技影响(×0.5)。 -- 每个已占领算力中心:**1 算力/秒** -- 可花费算力招募新单位(Phase 2) +## 商品 + +| ID | 名称 | 购买成本 | 原材料消耗 | 市场价格范围 | 生产时间 | +|:--:|:----:|:--------:|:----------:|:------------:|:--------:| +| 0 | 半导体 | 10 | 5 | 40–120 | 5.0s | +| 1 | 药品 | 5 | 3 | 20–60 | 4.0s | +| 2 | 小商品 | 1 | 1 | 4–12 | 2.0s | +| 3 | 服饰 | 8 | 4 | 32–96 | 6.0s | +| 4 | 食品 | 3 | 2 | 12–24 | 1.0s | + +- 购买成本受 cost_reduction 科技影响(−2,最低为 0) +- 生产时间受 efficiency 科技影响(×0.5) + +## 市场定价 + +每个市场对每种商品有独立的正弦价格函数: + +``` +price(t) = base + amplitude × (1 + sin(2π·t / period + phase)) / 2 +``` + +- 不同市场的价格**相位随机不同步**,套利窗口随时间移动 +- `price_volatility` 控制波动幅度(easy=0.3, medium=1.0, hard=2.0) +- 卖价受 marketing 科技影响(×1.1) + +## 算力系统 + +- **基础产出**:每个已开放算力中心产出 **1 算力/秒** +- 购买 compute_expansion 科技后:+30% 算力速率 +- 可消耗算力招募新单位(40 算力/个,最多 5 个单位)——Phase 2 + +### 算力中心 + +- 占领方式:在相邻格执行 OCCUPY,每次 tick 推进 0.25s 进度 +- 进度达到 `unit_occupy_time`(10s)后开放 +- 开放后持续产出算力 + +## 科技树 + +| 键名 | 效果 | 消耗 | 前置 | 持久 | +|:-----|------|:----:|:----:|:----:| +| cost_reduction | 商品购买成本 −2 | 50 | — | ✓ | +| efficiency | 生产时间 ×0.5 | 40 | — | ✓ | +| marketing | 卖价 ×1.1 | 80 | — | ✓ | +| durability | 单位 max HP +50% | 30 | — | ✓ | +| multi_line | 工厂 +1 生产线 | 60 | — | ✓ | +| path_optimization | 移动不产生 busy_tick | 50 | efficiency | ✓ | +| market_analysis | 观测中标记已购买 | 40 | — | ✗ | +| compute_expansion | 算力速率 +30% | 70 | — | ✓ | + +- 持久科技每种只能购买一次 +- market_analysis 可重复购买 +- 科技在工厂格执行 ## 资源再生 @@ -34,4 +106,12 @@ regen(t) = rate × (1 + sin(2π·t / period)) / 2 ``` - 再生倍率:easy=2.0, medium=1.0, hard=0.5 -- 资源耗尽(`depleted=True`)后停止再生 +- 资源耗尽(累计采集量 ≥ max_stock)后停止再生 +- 最大库存为初始库存的 2 倍 + +## 得分与终止 + +- **得分**:`score += revenue × score_factor`(默认 ×10),仅在 SELL 成功时增加 +- **terminated**:`money < 0`(破产) +- **truncated**:`step >= max_steps`(时间耗尽) +- 最终排名以多 seed 下的 `info["score"]` 平均为准 diff --git a/docs/contests/THUAI9/pve/interface/gym.md b/docs/contests/THUAI9/pve/interface/gym.md index 6435ad12..9990a579 100644 --- a/docs/contests/THUAI9/pve/interface/gym.md +++ b/docs/contests/THUAI9/pve/interface/gym.md @@ -33,8 +33,8 @@ obs, info = env.reset(seed=0) ```python obs, reward, terminated, truncated, info = env.step(action) -# action: int, 0-7 -# obs: np.ndarray, shape (32,), dtype float32 +# action: int, 0-27 +# obs: np.ndarray, shape (58,), dtype float32 # reward: float # terminated: bool (money < 0,破产) # truncated: bool (步数耗尽,正常结束) @@ -44,36 +44,100 @@ obs, reward, terminated, truncated, info = env.step(action) ### `action_masks()` ```python -mask = env.action_masks() # np.ndarray[bool], shape (8,) +mask = env.action_masks() # np.ndarray[bool], shape (28,) ``` 返回当前有效的动作掩码。 -## 观测向量(32 维 float32) +## 观测向量(58 维 float32) + +### 单位状态([0–9]) | 索引 | 含义 | 归一化 | |:----:|------|--------| | 0–1 | 单位位置 (x, y) | / (H, W) | | 2 | 单位 HP | / max_hp | -| 3 | 原材料背包占比 | raw_inv / capacity | -| 4 | 成品背包占比 | prod_inv / capacity | -| 5 | busy 倒计时 | / 10,截断到 1 | -| 6 | 现金 | log10(money+1) / 5 | -| 7 | 算力 | / 100,截断到 2 | -| 8 | 游戏进度 | time / max_time | -| 9 | 价格相位 sin | sin(2π·t / period) | -| 10 | 价格相位 cos | cos(2π·t / period) | -| 11 | 工厂原料库存 | / storage_cap | -| 12 | 工厂成品库存 | / storage_cap | -| 13 | 生产队列长度 | / 10,截断到 1 | -| 14–16 | 资源点 0 | 相对位置 (dx/H, dy/W) + 库存比 | -| 17–19 | 资源点 1 | 同上 | -| 20–22 | 算力中心 0 | 相对位置 (dx/H, dy/W) + is_open | -| 23–25 | 算力中心 1 | 同上 | -| 26–28 | 市场 0 | 相对位置 (dx/H, dy/W) + best_price | -| 29–31 | 市场 1 | 同上 | - -> 如果实体数量不足(如只有 2 个市场),多余索引保持 0。 +| 3 | 原材料背包 | raw_inv / capacity | +| 4 | 半导体背包 | prod_inv[0] / capacity | +| 5 | 药品背包 | prod_inv[1] / capacity | +| 6 | 小商品背包 | prod_inv[2] / capacity | +| 7 | 服饰背包 | prod_inv[3] / capacity | +| 8 | 食品背包 | prod_inv[4] / capacity | +| 9 | busy 倒计时 | / 10,截断到 1 | + +### 经济状态([10–14]) + +| 索引 | 含义 | 归一化 | +|:----:|------|--------| +| 10 | 现金(对数) | log10(money+1) / 5 | +| 11 | 算力 | / 100,截断到 2 | +| 12 | 游戏进度 | time / max_game_time | +| 13 | 价格相位 sin | sin(2π·t / market_period) | +| 14 | 价格相位 cos | cos(2π·t / market_period) | + +### 工厂状态([15–21]) + +| 索引 | 含义 | 归一化 | +|:----:|------|--------| +| 15 | 工厂原材料库存 | / storage_cap | +| 16 | 工厂半导体库存 | / storage_cap | +| 17 | 工厂药品库存 | / storage_cap | +| 18 | 工厂小商品库存 | / storage_cap | +| 19 | 工厂服饰库存 | / storage_cap | +| 20 | 工厂食品库存 | / storage_cap | +| 21 | 生产队列长度 | / 10,截断到 1 | + +### 资源点([22–27]) + +| 索引 | 含义 | 归一化 | +|:----:|------|--------| +| 22–23 | 资源点 0 相对位置 (dx, dy) | / (H, W) | +| 24 | 资源点 0 库存比例 | stock / max_stock | +| 25–26 | 资源点 1 相对位置 (dx, dy) | / (H, W) | +| 27 | 资源点 1 库存比例 | stock / max_stock | + +### 算力中心([28–35]) + +| 索引 | 含义 | 归一化 | +|:----:|------|--------| +| 28–29 | 算力中心 0 相对位置 (dx, dy) | / (H, W) | +| 30 | 算力中心 0 是否开放 | 0 或 1 | +| 31 | 算力中心 0 占领进度 | / unit_occupy_time | +| 32–33 | 算力中心 1 相对位置 (dx, dy) | / (H, W) | +| 34 | 算力中心 1 是否开放 | 0 或 1 | +| 35 | 算力中心 1 占领进度 | / unit_occupy_time | + +### 市场([36–49]) + +| 索引 | 含义 | 归一化 | +|:----:|------|--------| +| 36–37 | 市场 0 相对位置 (dx, dy) | / (H, W) | +| 38 | 市场 0 半导体价格 | 按 val_range 归一化 | +| 39 | 市场 0 药品价格 | 同上 | +| 40 | 市场 0 小商品价格 | 同上 | +| 41 | 市场 0 服饰价格 | 同上 | +| 42 | 市场 0 食品价格 | 同上 | +| 43–44 | 市场 1 相对位置 (dx, dy) | / (H, W) | +| 45 | 市场 1 半导体价格 | 按 val_range 归一化 | +| 46 | 市场 1 药品价格 | 同上 | +| 47 | 市场 1 小商品价格 | 同上 | +| 48 | 市场 1 服饰价格 | 同上 | +| 49 | 市场 1 食品价格 | 同上 | + +### 科技状态([50–57]) + +| 索引 | 含义 | +|:----:|------| +| 50 | 是否已购买 cost_reduction | +| 51 | 是否已购买 efficiency | +| 52 | 是否已购买 marketing | +| 53 | 是否已购买 durability | +| 54 | 是否已购买 multi_line | +| 55 | 是否已购买 path_optimization | +| 56 | 是否已购买 market_analysis(可重复) | +| 57 | 是否已购买 compute_expansion | + +> 如果实体数量不足(如只有 1 个市场),多余索引保持 0。 ## 禁止访问的对象 @@ -99,7 +163,7 @@ while True: if info["money"] > 50: action = 5 # BUY else: - action = 7 # HARVEST + action = 11 # HARVEST obs, reward, terminated, truncated, info = env.step(action) if terminated or truncated: break From bdd2573dc6348ae8f48a069b24fd6425d8daed24 Mon Sep 17 00:00:00 2001 From: lch Date: Mon, 18 May 2026 15:43:21 +0800 Subject: [PATCH 6/9] final rule --- docs/contests/RL9/pve/faq/README.md | 2 +- docs/contests/RL9/pve/game/actions.md | 108 ++++------ docs/contests/RL9/pve/game/reward.md | 25 ++- docs/contests/RL9/pve/game/state.md | 106 ++++------ docs/contests/RL9/pve/interface/gym.md | 189 ++++-------------- docs/contests/RL9/pve/intro/rule.md | 13 +- docs/contests/RL9/pve/training/training.md | 65 +----- docs/contests/THUAI9/README.md | 1 + docs/contests/THUAI9/pve/faq/README.md | 2 +- docs/contests/THUAI9/pve/game/actions.md | 108 ++++------ docs/contests/THUAI9/pve/game/reward.md | 25 ++- docs/contests/THUAI9/pve/game/state.md | 106 ++++------ docs/contests/THUAI9/pve/interface/gym.md | 189 ++++-------------- docs/contests/THUAI9/pve/intro/rule.md | 13 +- docs/contests/THUAI9/pve/training/training.md | 65 +----- 15 files changed, 305 insertions(+), 712 deletions(-) diff --git a/docs/contests/RL9/pve/faq/README.md b/docs/contests/RL9/pve/faq/README.md index 5e8d990d..a29d78a5 100644 --- a/docs/contests/RL9/pve/faq/README.md +++ b/docs/contests/RL9/pve/faq/README.md @@ -36,7 +36,7 @@ > Q: 观测向量怎么理解? > -> A: 32 维 float32,包含位置、HP、背包、现金、市场价格相位、最近资源点/市场/算力中心相对位置等。详见 [公开接口](../interface/gym.md)。 +> A: 58 维 float32,包含位置、HP、背包(按商品分)、现金、算力、市场 OU 价格、工厂库存、科技 one-hot、资源/算力中心相对位置等。详见 [公开接口](../interface/gym.md)。 > Q: 怎么写规则策略(不用 RL)? > diff --git a/docs/contests/RL9/pve/game/actions.md b/docs/contests/RL9/pve/game/actions.md index cf99d55b..05845904 100644 --- a/docs/contests/RL9/pve/game/actions.md +++ b/docs/contests/RL9/pve/game/actions.md @@ -1,88 +1,64 @@ # 动作空间 -PVE 使用 **28 个离散动作**(`N_ACTIONS = 28`),分为基础操作、商品交易、生产链、科技四大类。 +PVE 共 **28 个离散动作**(`N_ACTIONS = 28`)。 ## 基础动作(0–5) -| 编号 | 动作 | 含义 | 有效性条件 | -|:----:|:----:|------|------------| +| 编号 | 动作 | 含义 | 有效条件 | +|:----:|:----:|------|----------| | 0 | `WAIT` | 等待一个 tick | 始终有效 | -| 1 | `MOVE_UP` | 向上移动 (x−1) | 目标格可通行,单位不 busy | -| 2 | `MOVE_DOWN` | 向下移动 (x+1) | 同上 | -| 3 | `MOVE_LEFT` | 向左移动 (y−1) | 同上 | -| 4 | `MOVE_RIGHT` | 向右移动 (y+1) | 同上 | -| 5 | `BUY` | 在相邻市场购买利润最高的可负担商品 | Manhattan ≤1 有市场,背包有空间,现金充足 | +| 1 | `MOVE_UP` | x−1 | 目标格可通行,busy_ticks=0 | +| 2 | `MOVE_DOWN` | x+1 | 同上 | +| 3 | `MOVE_LEFT` | y−1 | 同上 | +| 4 | `MOVE_RIGHT` | y+1 | 同上 | +| 5 | `BUY` | 买入跨市场套利空间最大的可负担商品 | Manhattan≤1 有市场,背包有空间,现金足够 | -## 卖出动作(6–10,按商品类型) +## 卖出动作(6–10) -| 编号 | 动作 | 含义 | 有效性条件 | -|:----:|:----:|------|------------| -| 6 | `SELL_0` | 卖出背包内所有**半导体** | Manhattan ≤1 有市场,背包有该商品 | -| 7 | `SELL_1` | 卖出背包内所有**药品** | 同上 | -| 8 | `SELL_2` | 卖出背包内所有**小商品** | 同上 | -| 9 | `SELL_3` | 卖出背包内所有**服饰** | 同上 | -| 10 | `SELL_4` | 卖出背包内所有**食品** | 同上 | +| 编号 | 动作 | 有效条件 | +|:----:|:----:|----------| +| 6 | `SELL_0` | Manhattan≤1 有市场,背包有**半导体** | +| 7 | `SELL_1` | 同上,有**药品** | +| 8 | `SELL_2` | 同上,有**小商品** | +| 9 | `SELL_3` | 同上,有**服饰** | +| 10 | `SELL_4` | 同上,有**食品** | -- **SELL_pid**:卖出指定类型的所有商品,获得当前市场价 -- 卖出成功时:`score += revenue × score_factor`(默认 ×10),`money += revenue` +- SELL_pid 卖出非当前市场来源的商品(禁止同市场原地套利,商品追踪 `prod_origin`)。成功时 `score += revenue × 10`。 ## 生产链动作(11–18) -| 编号 | 动作 | 含义 | 有效性条件 | -|:----:|:----:|------|------------| -| 11 | `HARVEST` | 从附近资源点采集原材料 | Manhattan ≤2 有未耗尽资源,背包有空间 | -| 12 | `DEPOSIT` | 将背包原材料存入工厂 | 在工厂格,背包 raw_inv > 0 | -| 13 | `PRODUCE_0` | 工厂开始生产**半导体**(消耗 5 原材料) | 在工厂格,工厂 raw_stock ≥ 5,生产队列未满 | -| 14 | `PRODUCE_1` | 工厂开始生产**药品**(消耗 3 原材料) | 在工厂格,工厂 raw_stock ≥ 3,生产队列未满 | -| 15 | `PRODUCE_2` | 工厂开始生产**小商品**(消耗 1 原材料) | 在工厂格,工厂 raw_stock ≥ 1,生产队列未满 | -| 16 | `PRODUCE_3` | 工厂开始生产**服饰**(消耗 4 原材料) | 在工厂格,工厂 raw_stock ≥ 4,生产队列未满 | -| 17 | `PRODUCE_4` | 工厂开始生产**食品**(消耗 2 原材料) | 在工厂格,工厂 raw_stock ≥ 2,生产队列未满 | -| 18 | `LOAD` | 从工厂仓库装载已完成成品到背包 | 在工厂格,工厂有成品,背包有空间 | - -- **HARVEST**:采集范围 2 格(Manhattan 距离),每次采集 `unit_harvest_rate × time_step` 单位 -- **DEPOSIT**:将背包中原材料存入工厂仓库,供后续生产使用 -- **PRODUCE_pid**:将工厂原材料转换为产品,加入生产队列 -- **LOAD**:从工厂仓库取出已完成的成品装入背包 +| 编号 | 动作 | 消耗/条件 | +|:----:|:----:|-----------| +| 11 | `HARVEST` | Manhattan≤2 有未耗尽资源,背包有空间 | +| 12 | `DEPOSIT` | 在工厂格,背包 raw_inv > 0 | +| 13 | `PRODUCE_0` | 在工厂格,工厂 raw_stock≥5,队列未满 | +| 14 | `PRODUCE_1` | raw_stock≥3 | +| 15 | `PRODUCE_2` | raw_stock≥1 | +| 16 | `PRODUCE_3` | raw_stock≥4 | +| 17 | `PRODUCE_4` | raw_stock≥2 | +| 18 | `LOAD` | 在工厂格,工厂有成品,背包有空间 | ## 算力与科技动作(19–27) -| 编号 | 动作 | 含义 | 消耗 | 前置 | -|:----:|:----:|------|:----:|:----:| -| 19 | `OCCUPY` | 推进相邻算力中心的占领进度 | — | — | -| 20 | `TECH_0` | **降低成本**:商品购买成本 −2 | 50 算力 | — | -| 21 | `TECH_1` | **效率提升**:生产时间 ×0.5 | 40 算力 | — | -| 22 | `TECH_2` | **市场营销**:卖价 ×1.1 | 80 算力 | — | -| 23 | `TECH_3` | **耐久强化**:单位 max HP +50% | 30 算力 | — | -| 24 | `TECH_4` | **多产线**:工厂 +1 生产线 | 60 算力 | — | -| 25 | `TECH_5` | **路径优化**:移动不再产生 busy tick | 50 算力 | TECH_1 | -| 26 | `TECH_6` | **市场分析**:观测中标记已购买(可重复) | 40 算力 | — | -| 27 | `TECH_7` | **算力扩张**:算力积累速率 +30% | 70 算力 | — | +| 编号 | 动作 | 效果 | 消耗 | +|:----:|:----:|------|:----:| +| 19 | `OCCUPY` | 推进相邻算力中心占领进度 | — | +| 20 | `TECH_0` | 商品购买成本 −2 | 50 | +| 21 | `TECH_1` | 生产时间 ×0.5 | 40 | +| 22 | `TECH_2` | 卖价 ×1.1 | 80 | +| 23 | `TECH_3` | 单位 max HP +50% | 30 | +| 24 | `TECH_4` | 工厂 +1 生产线 | 60 | +| 25 | `TECH_5` | 移动不产生 busy_tick(需 TECH_1) | 50 | +| 26 | `TECH_6` | 观测中标记(可重复购买) | 40 | +| 27 | `TECH_7` | 算力速率 +30% | 70 | -- **OCCUPY**:需相邻有未开放的算力中心,每次 tick 推进 `time_step` 秒进度 -- **TECH_0~5, TECH_7**:持久科技,每种只能购买一次,在工厂格执行 -- **TECH_6**:非持久科技,可重复购买 -- 科技动作需要在工厂格、算力足够、前置科技满足时才有效 +科技在工厂格执行。持久科技(TECH_0~5,7)每种限一次。 ## 通用规则 -- `busy_ticks > 0` 时单位忙碌,**仅 WAIT 有效**,其他动作均被掩码屏蔽 -- 移动默认消耗 1 busy_tick(购买 TECH_5 后变为 0) -- BUY/SELL/LOAD 消耗 busy_ticks(`0.25 / time_step` 个 tick) -- 执行无效动作不会报错,但受到 **−0.05 奖励惩罚**并浪费步数 +- `busy_ticks > 0` 时仅 WAIT 有效;移动消耗 1 tick(TECH_5 后为 0) +- 无效动作不报错,但受 **−0.02 奖励惩罚** ## 动作掩码 -环境提供 `action_masks()` 方法,返回 `(28,)` 布尔数组,`True` 表示该动作当前有效。使用 `MaskablePPO` 可以自动过滤无效动作: - -```python -from sb3_contrib import MaskablePPO -from sb3_contrib.common.wrappers import ActionMasker - -def mask_fn(env): - return env.unwrapped.action_masks() - -masked_env = ActionMasker(env, mask_fn) -model = MaskablePPO("MlpPolicy", masked_env) -``` - -建议所有策略先查询 `action_masks()` 再决定动作。 +`action_masks()` 返回 `(28,)` bool 数组,True=有效。推荐 `MaskablePPO` + `ActionMasker` 自动过滤。 diff --git a/docs/contests/RL9/pve/game/reward.md b/docs/contests/RL9/pve/game/reward.md index 83612c1c..01e91f60 100644 --- a/docs/contests/RL9/pve/game/reward.md +++ b/docs/contests/RL9/pve/game/reward.md @@ -10,20 +10,29 @@ score += revenue × score_factor(默认 × 10) ## 单步奖励(RL Reward) -训练时的辅助信号,由以下部分叠加: +训练时的辅助信号,由以下部分叠加(`mode = "standard"`): -| 来源 | 值 | 说明 | +| 来源 | 默认值 | 说明 | |------|:--:|------| -| 现金变化 Δmoney | × 0.01 | 正负均有 | -| 得分变化 Δscore | × 0.01 | 仅卖出时为正 | -| 时间惩罚(每步) | −0.002 | 鼓励高效路径 | -| 采集奖励(每单位) | +0.001 | 采集塑形 | -| 算力中心解锁(一次性) | +0.5 | 进度奖励 | -| 无效动作惩罚 | −0.05 | 每步 | +| 现金变化 Δmoney | × 0.02 | 正负均有 | +| 卖出得分 Δscore | × 0.001 | 仅卖出时为正 | +| 时间惩罚(每步) | −0.001 | 鼓励高效路径 | +| 采集奖励(每单位) | +0.0 | 默认关闭,可通过 RewardConfig 开启 | +| 算力中心解锁(每个) | +1.0 | 进度奖励 | +| 科技购买(每个) | +1.0 | 进度奖励 | +| 无效动作惩罚 | −0.02 | 每步 | | 破产惩罚(terminated 时) | −10.0 | 终端惩罚 | > 奖励是训练辅助信号。**最终排名以 `info["score"]` 为准**,不是累计奖励。 +`RewardConfig` 支持两种模式: +- `"standard"`(默认):密集可学习信号,适用于 PPO/DQN 训练 +- `"adversarial"`:收割陷阱模式,仅使用采集奖励 + 终端得分 + +### 技术细节 + +`RewardCalculator.compute()` 在 `game_env.py` 的 `step()` 中调用,计算奖励时先评估终止条件,再结算奖励(确保 terminal bonus 正确触发)。 + ## `step()` 返回的 info 字典 | 字段 | 类型 | 含义 | diff --git a/docs/contests/RL9/pve/game/state.md b/docs/contests/RL9/pve/game/state.md index 553267df..66f4a730 100644 --- a/docs/contests/RL9/pve/game/state.md +++ b/docs/contests/RL9/pve/game/state.md @@ -2,116 +2,84 @@ ## 时间系统 -| 参数 | 值 | -|:----:|:--:| -| 每 tick 时长 | 0.25s | -| easy/medium 总时长 | 300s(1200 ticks) | -| hard 总时长 | 500s(2000 ticks) | +每 tick **0.25s**。easy/medium 300s(1200 tick),hard 500s(2000 tick)。 -## 单位属性 +## 单位 | 属性 | 值 | |:----:|:--:| -| 血量 | 300 | +| HP | 300 | | 背包容量 | 30 | | 采集速率 | 10/s | -| 算力中心占领时间 | 10s | +| 占领耗时 | 10s | -- 背包分为**原材料**(`raw_inv`)和**成品**(`prod_inv[pid]`)两部分 -- `busy_ticks > 0` 时单位忙碌,仅 WAIT 有效 -- 移动默认消耗 1 busy_tick(购买 path_optimization 科技后变为 0) -- BUY/SELL/LOAD 消耗 `int(0.25 / time_step)` 个 busy_tick +- 背包分原材料(`raw_inv`)和成品(`prod_inv[pid]`);商品追踪购买来源市场(`prod_origin`) +- `busy_ticks > 0` 时仅 WAIT 有效;移动消耗 1 tick(TECH_5 后为 0);BUY/SELL/LOAD 消耗 `0.25/dt` tick ## 工厂 | 属性 | 值 | |:----:|:--:| -| 位置 | (0, 0) | +| 位置 | (0,0) | | 仓储上限 | 300 | -| 初始生产线数 | 3 | - -### 生产系统 - -工厂将原材料加工为成品。生产队列最大长度 = `生产线数 × 5`。 +| 初始产线 | 3 | -生产流程: -1. 采集原材料(HARVEST) -2. 存入工厂(DEPOSIT) -3. 排队生产(PRODUCE_pid)——消耗原材料,加入生产队列 -4. 等待工厂 tick 推进完成 -5. 装载成品(LOAD) - -生产时间受 efficiency 科技影响(×0.5)。 +生产队列上限 = 产线数 × 5。流程:HARVEST → DEPOSIT → PRODUCE → 等待 tick 完成 → LOAD → SELL。 ## 商品 -| ID | 名称 | 购买成本 | 原材料消耗 | 市场价格范围 | 生产时间 | -|:--:|:----:|:--------:|:----------:|:------------:|:--------:| +| ID | 名称 | 买价 | 原料消耗 | 市价范围 | 生产时间 | +|:--:|:----:|:----:|:--------:|:--------:|:--------:| | 0 | 半导体 | 10 | 5 | 40–120 | 5.0s | | 1 | 药品 | 5 | 3 | 20–60 | 4.0s | | 2 | 小商品 | 1 | 1 | 4–12 | 2.0s | | 3 | 服饰 | 8 | 4 | 32–96 | 6.0s | | 4 | 食品 | 3 | 2 | 12–24 | 1.0s | -- 购买成本受 cost_reduction 科技影响(−2,最低为 0) -- 生产时间受 efficiency 科技影响(×0.5) +买价受 TECH_0(−2),生产时间受 TECH_1(×0.5) 影响。 -## 市场定价 +## 市场 -每个市场对每种商品有独立的正弦价格函数: +OU 随机游走价格,每 tick 更新: ``` -price(t) = base + amplitude × (1 + sin(2π·t / period + phase)) / 2 +dP = θ(μ−P)·dt + σ·√dt·N(0,1) ``` -- 不同市场的价格**相位随机不同步**,套利窗口随时间移动 -- `price_volatility` 控制波动幅度(easy=0.3, medium=1.0, hard=2.0) -- 卖价受 marketing 科技影响(×1.1) - -## 算力系统 +θ=0.05,σ=amplitude×0.12,价格夹在 [lo, lo+amplitude] 内。`price_volatility` 控制振幅(easy=0.3, medium=1.0, hard=2.0)。 -- **基础产出**:每个已开放算力中心产出 **1 算力/秒** -- 购买 compute_expansion 科技后:+30% 算力速率 -- 可消耗算力招募新单位(40 算力/个,最多 5 个单位)——Phase 2 +**套利规则**:BUY 选跨市场卖价最高的可负担商品。禁止同市场原地套利(商品追踪购买来源,卖出时排除当前市场来源部分)。卖价受 TECH_2(×1.1) 影响。 -### 算力中心 +## 算力 -- 占领方式:在相邻格执行 OCCUPY,每次 tick 推进 0.25s 进度 -- 进度达到 `unit_occupy_time`(10s)后开放 -- 开放后持续产出算力 +- 每个开放算力中心 +1 算力/秒;TECH_7 后 +30% +- OCCUPY 每 tick 推进 0.25s,10s 后开放 +- 招募新单位:40 算力/个,上限 5 -## 科技树 +## 科技 -| 键名 | 效果 | 消耗 | 前置 | 持久 | -|:-----|------|:----:|:----:|:----:| -| cost_reduction | 商品购买成本 −2 | 50 | — | ✓ | -| efficiency | 生产时间 ×0.5 | 40 | — | ✓ | -| marketing | 卖价 ×1.1 | 80 | — | ✓ | -| durability | 单位 max HP +50% | 30 | — | ✓ | -| multi_line | 工厂 +1 生产线 | 60 | — | ✓ | -| path_optimization | 移动不产生 busy_tick | 50 | efficiency | ✓ | -| market_analysis | 观测中标记已购买 | 40 | — | ✗ | -| compute_expansion | 算力速率 +30% | 70 | — | ✓ | +| 键名 | 效果 | 消耗 | 前置 | +|:-----|------|:----:|:----:| +| cost_reduction | 买价 −2 | 50 | — | +| efficiency | 生产时间 ×0.5 | 40 | — | +| marketing | 卖价 ×1.1 | 80 | — | +| durability | HP +50% | 30 | — | +| multi_line | 产线 +1 | 60 | — | +| path_optimization | 移动无 busy_tick | 50 | efficiency | +| market_analysis | obs标记(可重复) | 40 | — | +| compute_expansion | 算力 +30% | 70 | — | -- 持久科技每种只能购买一次 -- market_analysis 可重复购买 -- 科技在工厂格执行 +持久科技(除 market_analysis)限购一次。在工厂格执行。 ## 资源再生 -资源点库存会随时间缓慢再生: - ``` -regen(t) = rate × (1 + sin(2π·t / period)) / 2 +regen(t) = rate × (1 + sin(2π·t/period)) / 2 ``` -- 再生倍率:easy=2.0, medium=1.0, hard=0.5 -- 资源耗尽(累计采集量 ≥ max_stock)后停止再生 -- 最大库存为初始库存的 2 倍 +倍率:easy=2.0, medium=1.0, hard=0.5。耗尽(累计≥max_stock=2×初始)后停止。 ## 得分与终止 -- **得分**:`score += revenue × score_factor`(默认 ×10),仅在 SELL 成功时增加 -- **terminated**:`money < 0`(破产) -- **truncated**:`step >= max_steps`(时间耗尽) -- 最终排名以多 seed 下的 `info["score"]` 平均为准 +- `score += revenue × 10`(仅 SELL 成功时) +- `money < 0` → terminated;`step ≥ max_steps` → truncated diff --git a/docs/contests/RL9/pve/interface/gym.md b/docs/contests/RL9/pve/interface/gym.md index 9990a579..a96a41fb 100644 --- a/docs/contests/RL9/pve/interface/gym.md +++ b/docs/contests/RL9/pve/interface/gym.md @@ -1,170 +1,57 @@ # 公开接口 -PVE 算法**只能**通过标准 Gymnasium 接口与环境交互。不得直接访问内部对象。 +PVE 算法只通过标准 Gymnasium 接口与环境交互,禁止访问内部对象。 ## 环境初始化 ```python from GameLogic import GameEnvironment, GameConfig -# 内置难度 -env = GameEnvironment(cfg=GameConfig.easy()) -env = GameEnvironment(cfg=GameConfig.medium()) -env = GameEnvironment(cfg=GameConfig.hard()) - -# 自定义配置 -env = GameEnvironment(cfg=GameConfig.from_dict({ - "map_width": 8, "map_height": 8, - "num_markets": 4, "initial_money": 100.0, -})) +env = GameEnvironment(cfg=GameConfig.easy()) # medium / hard / from_dict({...}) ``` -## 接口方法 - -### `reset()` +## 接口 ```python obs, info = env.reset(seed=0) +obs, reward, terminated, truncated, info = env.step(action) # action: int 0–27 +mask = env.action_masks() # (28,) bool, True=有效 ``` -重置环境,返回初始观测和 info。 - -### `step()` - -```python -obs, reward, terminated, truncated, info = env.step(action) -# action: int, 0-27 -# obs: np.ndarray, shape (58,), dtype float32 -# reward: float -# terminated: bool (money < 0,破产) -# truncated: bool (步数耗尽,正常结束) -# info: dict -``` - -### `action_masks()` - -```python -mask = env.action_masks() # np.ndarray[bool], shape (28,) -``` - -返回当前有效的动作掩码。 +| 返回 | 类型 | 含义 | +|------|------|------| +| `obs` | `(58,) float32` | 观测向量 | +| `reward` | `float` | 单步奖励 | +| `terminated` | `bool` | money<0 破产 | +| `truncated` | `bool` | 步数耗尽 | +| `info` | `dict` | step/time/money/score/compute/action_valid | ## 观测向量(58 维 float32) -### 单位状态([0–9]) - -| 索引 | 含义 | 归一化 | -|:----:|------|--------| -| 0–1 | 单位位置 (x, y) | / (H, W) | -| 2 | 单位 HP | / max_hp | -| 3 | 原材料背包 | raw_inv / capacity | -| 4 | 半导体背包 | prod_inv[0] / capacity | -| 5 | 药品背包 | prod_inv[1] / capacity | -| 6 | 小商品背包 | prod_inv[2] / capacity | -| 7 | 服饰背包 | prod_inv[3] / capacity | -| 8 | 食品背包 | prod_inv[4] / capacity | -| 9 | busy 倒计时 | / 10,截断到 1 | - -### 经济状态([10–14]) - -| 索引 | 含义 | 归一化 | -|:----:|------|--------| -| 10 | 现金(对数) | log10(money+1) / 5 | -| 11 | 算力 | / 100,截断到 2 | -| 12 | 游戏进度 | time / max_game_time | -| 13 | 价格相位 sin | sin(2π·t / market_period) | -| 14 | 价格相位 cos | cos(2π·t / market_period) | - -### 工厂状态([15–21]) - -| 索引 | 含义 | 归一化 | -|:----:|------|--------| -| 15 | 工厂原材料库存 | / storage_cap | -| 16 | 工厂半导体库存 | / storage_cap | -| 17 | 工厂药品库存 | / storage_cap | -| 18 | 工厂小商品库存 | / storage_cap | -| 19 | 工厂服饰库存 | / storage_cap | -| 20 | 工厂食品库存 | / storage_cap | -| 21 | 生产队列长度 | / 10,截断到 1 | - -### 资源点([22–27]) - -| 索引 | 含义 | 归一化 | -|:----:|------|--------| -| 22–23 | 资源点 0 相对位置 (dx, dy) | / (H, W) | -| 24 | 资源点 0 库存比例 | stock / max_stock | -| 25–26 | 资源点 1 相对位置 (dx, dy) | / (H, W) | -| 27 | 资源点 1 库存比例 | stock / max_stock | - -### 算力中心([28–35]) - -| 索引 | 含义 | 归一化 | -|:----:|------|--------| -| 28–29 | 算力中心 0 相对位置 (dx, dy) | / (H, W) | -| 30 | 算力中心 0 是否开放 | 0 或 1 | -| 31 | 算力中心 0 占领进度 | / unit_occupy_time | -| 32–33 | 算力中心 1 相对位置 (dx, dy) | / (H, W) | -| 34 | 算力中心 1 是否开放 | 0 或 1 | -| 35 | 算力中心 1 占领进度 | / unit_occupy_time | - -### 市场([36–49]) - -| 索引 | 含义 | 归一化 | -|:----:|------|--------| -| 36–37 | 市场 0 相对位置 (dx, dy) | / (H, W) | -| 38 | 市场 0 半导体价格 | 按 val_range 归一化 | -| 39 | 市场 0 药品价格 | 同上 | -| 40 | 市场 0 小商品价格 | 同上 | -| 41 | 市场 0 服饰价格 | 同上 | -| 42 | 市场 0 食品价格 | 同上 | -| 43–44 | 市场 1 相对位置 (dx, dy) | / (H, W) | -| 45 | 市场 1 半导体价格 | 按 val_range 归一化 | -| 46 | 市场 1 药品价格 | 同上 | -| 47 | 市场 1 小商品价格 | 同上 | -| 48 | 市场 1 服饰价格 | 同上 | -| 49 | 市场 1 食品价格 | 同上 | - -### 科技状态([50–57]) - -| 索引 | 含义 | +| 索引 | 内容 | |:----:|------| -| 50 | 是否已购买 cost_reduction | -| 51 | 是否已购买 efficiency | -| 52 | 是否已购买 marketing | -| 53 | 是否已购买 durability | -| 54 | 是否已购买 multi_line | -| 55 | 是否已购买 path_optimization | -| 56 | 是否已购买 market_analysis(可重复) | -| 57 | 是否已购买 compute_expansion | - -> 如果实体数量不足(如只有 1 个市场),多余索引保持 0。 - -## 禁止访问的对象 - -以下为内部实现,选手算法**不得依赖**: - -- `env.unit`(Unit 内部字段) -- `env.factory`(Factory 内部字段) -- `env.board`(Board / 地图内部字段) -- `env.markets`(Market 列表) -- `env.money`、`env.compute`、`env.score`(通过 `info` 获取) -- 任何以下划线开头的方法或属性 - -> 评测机只会暴露 `reset`、`step`、`action_masks` 三个公开接口。 - -## 编写规则型策略 - -如果不需要 RL 训练,可以直接读取 `info` 字典做规则决策: - -```python -obs, info = env.reset() -while True: - # 规则策略基于 info 做决策 - if info["money"] > 50: - action = 5 # BUY - else: - action = 11 # HARVEST - obs, reward, terminated, truncated, info = env.step(action) - if terminated or truncated: - break -``` +| 0–1 | 单位坐标 / (H,W) | +| 2 | HP / max_hp | +| 3 | raw_inv / capacity | +| 4–8 | prod_inv[0–4] / capacity | +| 9 | busy_ticks / 10 | +| 10 | log10(money+1) / 5 | +| 11 | compute / 100 | +| 12 | time / max_time | +| 13–14 | sin/cos(2π·t / market_period) | +| 15 | 工厂 raw_stock / storage_cap | +| 16–20 | 工厂 products[0–4] / storage_cap | +| 21 | 生产队列长度 / 10 | +| 22–24 | 资源0: dx/H, dy/W, stock/max | +| 25–27 | 资源1: 同上 | +| 28–31 | 算力中心0: dx/H, dy/W, is_open, progress | +| 32–35 | 算力中心1: 同上 | +| 36–42 | 市场0: dx/H, dy/W, 5种商品价格(val_range归一化) | +| 43–49 | 市场1: 同上 | +| 50–57 | 科技 one-hot(8槽对应 TECH_0~7 顺序) | + +不足实体数量的索引保持 0。 + +## 禁止访问 + +`env.unit`、`env.factory`、`env.board`、`env.markets`、`env.money` 等内部对象均禁止。评测机只暴露 `reset/step/action_masks`。 diff --git a/docs/contests/RL9/pve/intro/rule.md b/docs/contests/RL9/pve/intro/rule.md index 55b5afd2..b411f9d6 100644 --- a/docs/contests/RL9/pve/intro/rule.md +++ b/docs/contests/RL9/pve/intro/rule.md @@ -38,13 +38,16 @@ PVE(PvE-RL)赛道是一个**强化学习**竞技环境。选手需要训练 ## 市场价格 -每个市场对每种商品有独立的正弦价格函数: +每个市场对每种商品有独立的 **Ornstein-Uhlenbeck 随机游走**价格过程: ``` -price(t) = base + amplitude × (1 + sin(2π·t / period + phase)) / 2 +dP = θ(μ − P)·dt + σ·√dt · N(0,1) ``` -- 不同市场的价格**相位随机不同步**,套利窗口随时间移动 +- θ=0.05 均值回归,σ=12% 振幅波动 +- 每个市场独立初始化随机起点,每 tick(0.25s)更新 +- 价格被夹在 [lo, lo+amplitude] 之间 +- 跨市场套利禁止原地买卖——不能在同一市场卖出从该市场购买的商品 - `price_volatility` 控制波动幅度(easy=0.3, medium=1.0, hard=2.0) ## 终止条件 @@ -60,8 +63,8 @@ price(t) = base + amplitude × (1 + sin(2π·t / period + phase)) / 2 |:--|:---|:---| | 交互方式 | gRPC 客户端连接服务器 | 本地 Gymnasium 环境 | | 编程语言 | C++ | Python | -| 动作 | 连续移动 + 多种操作 | 8 个离散动作 | -| 市场定价 | 衰减机制 | 正弦周期波动 | +| 动作 | 连续移动 + 多种操作 | 28 个离散动作 | +| 市场定价 | 衰减机制 | OU 随机游走 | | 单位数 | 1 Team + 3 Character | 1 个可控单位 | | 对手 | 其他人类队伍 | 无(纯经济环境) | | 评测 | 单局得分 | 多 seed 平均得分 | diff --git a/docs/contests/RL9/pve/training/training.md b/docs/contests/RL9/pve/training/training.md index 8a7dfc5f..8b8030ac 100644 --- a/docs/contests/RL9/pve/training/training.md +++ b/docs/contests/RL9/pve/training/training.md @@ -4,52 +4,32 @@ ```bash pip install -r requirements.txt +python -m pytest tests/ -v # 验证环境 ``` -## 单元测试 - -验证环境正常: +## 训练 ```bash -python -m pytest tests/ -v -``` - -## 基础训练 - -```bash -# easy 难度,10 万步 python TrainingDemo/train_basic.py --config easy --timesteps 100000 - -# medium 难度,20 万步 python TrainingDemo/train_basic.py --config medium --timesteps 200000 - -# 自定义 YAML 配置 python TrainingDemo/train_basic.py --config TrainingDemo/configs/medium.yaml --timesteps 200000 ``` -## 评测模型 +## 评测 ```bash python TrainingDemo/evaluate.py --model models/ppo_thuai9_best --config medium --episodes 50 ``` -输出多 seed 下的平均得分和方差。 - ## MaskablePPO -使用 `sb3-contrib` 的 `MaskablePPO` 可以自动利用动作掩码: - ```python from sb3_contrib import MaskablePPO from sb3_contrib.common.wrappers import ActionMasker -def mask_fn(env): - return env.unwrapped.action_masks() - -env = ActionMasker(env, mask_fn) +env = ActionMasker(env, lambda e: e.unwrapped.action_masks()) model = MaskablePPO("MlpPolicy", env, verbose=1) model.learn(total_timesteps=100000) -model.save("ppo_thuai9") ``` ## 难度对比 @@ -67,37 +47,10 @@ model.save("ppo_thuai9") | 初始资源 | 200 | 100 | 50 | | 游戏时长 | 300s | 300s | 500s | -## 项目结构 - -``` -logic/pve/ -├── GameLogic/ # 游戏规则与状态管理(算法不可直接修改) -│ ├── config.py # 全局配置(难度参数化) -│ ├── board.py # 地图、资源点、算力中心 -│ ├── character.py # 单位(HP、背包、状态机) -│ ├── market.py # 市场动态价格函数 -│ ├── action_space.py # 动作空间与动作掩码 -│ ├── reward_calculator.py # 奖励计算 -│ └── game_env.py # 主环境(Gymnasium 接口) -├── RLInterfaces/ # RL 算法接口层 -│ ├── base_agent.py # 抽象基类 -│ ├── ppo_agent.py # PPO 实现(支持 MaskablePPO) -│ └── training_loop.py # 训练循环 -├── TrainingDemo/ # 训练与评测脚本 -│ ├── train_basic.py # 训练入口 -│ ├── evaluate.py # 评测脚本 -│ ├── visualization.py # ASCII 渲染 + 奖励曲线 -│ └── configs/ # YAML 配置 -├── tests/ # 单元测试 -└── docs/ # 选手/开发者文档 -``` - ## 建议方向 -- 使用 `action_masks()` 过滤无效动作 -- BFS / A* 路径规划,找最优路线 -- 建模市场价格正弦周期(利用观测中的 sin/cos 相位) -- 选择高利润商品(半导体 40–120、服饰 32–96) -- 规则策略 + RL 混合(Hybrid Policy) -- 课程学习(easy → medium → hard) -- Recurrent Policy 识别价格周期 +- `action_masks()` 过滤无效动作;BFS/A* 路径规划 +- 建模 OU 价格随机游走,利用观测 sin/cos 相位辅助时序判断 +- 生产链:食品(1s)快周转,半导体(40–120)高利润 +- 优先占领算力中心 → efficiency(×0.5) 或 marketing(×1.1) +- 课程学习 easy→medium→hard;Recurrent Policy 捕捉价格趋势 diff --git a/docs/contests/THUAI9/README.md b/docs/contests/THUAI9/README.md index 7ff67c81..b9f8b5fc 100644 --- a/docs/contests/THUAI9/README.md +++ b/docs/contests/THUAI9/README.md @@ -16,6 +16,7 @@ AI 工厂模拟(THUAI9) | 控制对象 | 1 Team + 3 Character | 1 个单位 | | 动作 | 连续移动 + 多种操作 | 28 个离散动作 | | 得分机制 | 售卖 + 战斗 + 摧毁工厂 | 售卖 × 10(SELL成功时) | +| 市场定价 | 乘数 + 衰减 | OU 随机游走,禁止原地套利 | | 文档入口 | [`pvp/`](pvp/intro/rule.md) | [`pve/`](pve/intro/rule.md) | ## PVP 概要 diff --git a/docs/contests/THUAI9/pve/faq/README.md b/docs/contests/THUAI9/pve/faq/README.md index 5e8d990d..a29d78a5 100644 --- a/docs/contests/THUAI9/pve/faq/README.md +++ b/docs/contests/THUAI9/pve/faq/README.md @@ -36,7 +36,7 @@ > Q: 观测向量怎么理解? > -> A: 32 维 float32,包含位置、HP、背包、现金、市场价格相位、最近资源点/市场/算力中心相对位置等。详见 [公开接口](../interface/gym.md)。 +> A: 58 维 float32,包含位置、HP、背包(按商品分)、现金、算力、市场 OU 价格、工厂库存、科技 one-hot、资源/算力中心相对位置等。详见 [公开接口](../interface/gym.md)。 > Q: 怎么写规则策略(不用 RL)? > diff --git a/docs/contests/THUAI9/pve/game/actions.md b/docs/contests/THUAI9/pve/game/actions.md index cf99d55b..05845904 100644 --- a/docs/contests/THUAI9/pve/game/actions.md +++ b/docs/contests/THUAI9/pve/game/actions.md @@ -1,88 +1,64 @@ # 动作空间 -PVE 使用 **28 个离散动作**(`N_ACTIONS = 28`),分为基础操作、商品交易、生产链、科技四大类。 +PVE 共 **28 个离散动作**(`N_ACTIONS = 28`)。 ## 基础动作(0–5) -| 编号 | 动作 | 含义 | 有效性条件 | -|:----:|:----:|------|------------| +| 编号 | 动作 | 含义 | 有效条件 | +|:----:|:----:|------|----------| | 0 | `WAIT` | 等待一个 tick | 始终有效 | -| 1 | `MOVE_UP` | 向上移动 (x−1) | 目标格可通行,单位不 busy | -| 2 | `MOVE_DOWN` | 向下移动 (x+1) | 同上 | -| 3 | `MOVE_LEFT` | 向左移动 (y−1) | 同上 | -| 4 | `MOVE_RIGHT` | 向右移动 (y+1) | 同上 | -| 5 | `BUY` | 在相邻市场购买利润最高的可负担商品 | Manhattan ≤1 有市场,背包有空间,现金充足 | +| 1 | `MOVE_UP` | x−1 | 目标格可通行,busy_ticks=0 | +| 2 | `MOVE_DOWN` | x+1 | 同上 | +| 3 | `MOVE_LEFT` | y−1 | 同上 | +| 4 | `MOVE_RIGHT` | y+1 | 同上 | +| 5 | `BUY` | 买入跨市场套利空间最大的可负担商品 | Manhattan≤1 有市场,背包有空间,现金足够 | -## 卖出动作(6–10,按商品类型) +## 卖出动作(6–10) -| 编号 | 动作 | 含义 | 有效性条件 | -|:----:|:----:|------|------------| -| 6 | `SELL_0` | 卖出背包内所有**半导体** | Manhattan ≤1 有市场,背包有该商品 | -| 7 | `SELL_1` | 卖出背包内所有**药品** | 同上 | -| 8 | `SELL_2` | 卖出背包内所有**小商品** | 同上 | -| 9 | `SELL_3` | 卖出背包内所有**服饰** | 同上 | -| 10 | `SELL_4` | 卖出背包内所有**食品** | 同上 | +| 编号 | 动作 | 有效条件 | +|:----:|:----:|----------| +| 6 | `SELL_0` | Manhattan≤1 有市场,背包有**半导体** | +| 7 | `SELL_1` | 同上,有**药品** | +| 8 | `SELL_2` | 同上,有**小商品** | +| 9 | `SELL_3` | 同上,有**服饰** | +| 10 | `SELL_4` | 同上,有**食品** | -- **SELL_pid**:卖出指定类型的所有商品,获得当前市场价 -- 卖出成功时:`score += revenue × score_factor`(默认 ×10),`money += revenue` +- SELL_pid 卖出非当前市场来源的商品(禁止同市场原地套利,商品追踪 `prod_origin`)。成功时 `score += revenue × 10`。 ## 生产链动作(11–18) -| 编号 | 动作 | 含义 | 有效性条件 | -|:----:|:----:|------|------------| -| 11 | `HARVEST` | 从附近资源点采集原材料 | Manhattan ≤2 有未耗尽资源,背包有空间 | -| 12 | `DEPOSIT` | 将背包原材料存入工厂 | 在工厂格,背包 raw_inv > 0 | -| 13 | `PRODUCE_0` | 工厂开始生产**半导体**(消耗 5 原材料) | 在工厂格,工厂 raw_stock ≥ 5,生产队列未满 | -| 14 | `PRODUCE_1` | 工厂开始生产**药品**(消耗 3 原材料) | 在工厂格,工厂 raw_stock ≥ 3,生产队列未满 | -| 15 | `PRODUCE_2` | 工厂开始生产**小商品**(消耗 1 原材料) | 在工厂格,工厂 raw_stock ≥ 1,生产队列未满 | -| 16 | `PRODUCE_3` | 工厂开始生产**服饰**(消耗 4 原材料) | 在工厂格,工厂 raw_stock ≥ 4,生产队列未满 | -| 17 | `PRODUCE_4` | 工厂开始生产**食品**(消耗 2 原材料) | 在工厂格,工厂 raw_stock ≥ 2,生产队列未满 | -| 18 | `LOAD` | 从工厂仓库装载已完成成品到背包 | 在工厂格,工厂有成品,背包有空间 | - -- **HARVEST**:采集范围 2 格(Manhattan 距离),每次采集 `unit_harvest_rate × time_step` 单位 -- **DEPOSIT**:将背包中原材料存入工厂仓库,供后续生产使用 -- **PRODUCE_pid**:将工厂原材料转换为产品,加入生产队列 -- **LOAD**:从工厂仓库取出已完成的成品装入背包 +| 编号 | 动作 | 消耗/条件 | +|:----:|:----:|-----------| +| 11 | `HARVEST` | Manhattan≤2 有未耗尽资源,背包有空间 | +| 12 | `DEPOSIT` | 在工厂格,背包 raw_inv > 0 | +| 13 | `PRODUCE_0` | 在工厂格,工厂 raw_stock≥5,队列未满 | +| 14 | `PRODUCE_1` | raw_stock≥3 | +| 15 | `PRODUCE_2` | raw_stock≥1 | +| 16 | `PRODUCE_3` | raw_stock≥4 | +| 17 | `PRODUCE_4` | raw_stock≥2 | +| 18 | `LOAD` | 在工厂格,工厂有成品,背包有空间 | ## 算力与科技动作(19–27) -| 编号 | 动作 | 含义 | 消耗 | 前置 | -|:----:|:----:|------|:----:|:----:| -| 19 | `OCCUPY` | 推进相邻算力中心的占领进度 | — | — | -| 20 | `TECH_0` | **降低成本**:商品购买成本 −2 | 50 算力 | — | -| 21 | `TECH_1` | **效率提升**:生产时间 ×0.5 | 40 算力 | — | -| 22 | `TECH_2` | **市场营销**:卖价 ×1.1 | 80 算力 | — | -| 23 | `TECH_3` | **耐久强化**:单位 max HP +50% | 30 算力 | — | -| 24 | `TECH_4` | **多产线**:工厂 +1 生产线 | 60 算力 | — | -| 25 | `TECH_5` | **路径优化**:移动不再产生 busy tick | 50 算力 | TECH_1 | -| 26 | `TECH_6` | **市场分析**:观测中标记已购买(可重复) | 40 算力 | — | -| 27 | `TECH_7` | **算力扩张**:算力积累速率 +30% | 70 算力 | — | +| 编号 | 动作 | 效果 | 消耗 | +|:----:|:----:|------|:----:| +| 19 | `OCCUPY` | 推进相邻算力中心占领进度 | — | +| 20 | `TECH_0` | 商品购买成本 −2 | 50 | +| 21 | `TECH_1` | 生产时间 ×0.5 | 40 | +| 22 | `TECH_2` | 卖价 ×1.1 | 80 | +| 23 | `TECH_3` | 单位 max HP +50% | 30 | +| 24 | `TECH_4` | 工厂 +1 生产线 | 60 | +| 25 | `TECH_5` | 移动不产生 busy_tick(需 TECH_1) | 50 | +| 26 | `TECH_6` | 观测中标记(可重复购买) | 40 | +| 27 | `TECH_7` | 算力速率 +30% | 70 | -- **OCCUPY**:需相邻有未开放的算力中心,每次 tick 推进 `time_step` 秒进度 -- **TECH_0~5, TECH_7**:持久科技,每种只能购买一次,在工厂格执行 -- **TECH_6**:非持久科技,可重复购买 -- 科技动作需要在工厂格、算力足够、前置科技满足时才有效 +科技在工厂格执行。持久科技(TECH_0~5,7)每种限一次。 ## 通用规则 -- `busy_ticks > 0` 时单位忙碌,**仅 WAIT 有效**,其他动作均被掩码屏蔽 -- 移动默认消耗 1 busy_tick(购买 TECH_5 后变为 0) -- BUY/SELL/LOAD 消耗 busy_ticks(`0.25 / time_step` 个 tick) -- 执行无效动作不会报错,但受到 **−0.05 奖励惩罚**并浪费步数 +- `busy_ticks > 0` 时仅 WAIT 有效;移动消耗 1 tick(TECH_5 后为 0) +- 无效动作不报错,但受 **−0.02 奖励惩罚** ## 动作掩码 -环境提供 `action_masks()` 方法,返回 `(28,)` 布尔数组,`True` 表示该动作当前有效。使用 `MaskablePPO` 可以自动过滤无效动作: - -```python -from sb3_contrib import MaskablePPO -from sb3_contrib.common.wrappers import ActionMasker - -def mask_fn(env): - return env.unwrapped.action_masks() - -masked_env = ActionMasker(env, mask_fn) -model = MaskablePPO("MlpPolicy", masked_env) -``` - -建议所有策略先查询 `action_masks()` 再决定动作。 +`action_masks()` 返回 `(28,)` bool 数组,True=有效。推荐 `MaskablePPO` + `ActionMasker` 自动过滤。 diff --git a/docs/contests/THUAI9/pve/game/reward.md b/docs/contests/THUAI9/pve/game/reward.md index 83612c1c..01e91f60 100644 --- a/docs/contests/THUAI9/pve/game/reward.md +++ b/docs/contests/THUAI9/pve/game/reward.md @@ -10,20 +10,29 @@ score += revenue × score_factor(默认 × 10) ## 单步奖励(RL Reward) -训练时的辅助信号,由以下部分叠加: +训练时的辅助信号,由以下部分叠加(`mode = "standard"`): -| 来源 | 值 | 说明 | +| 来源 | 默认值 | 说明 | |------|:--:|------| -| 现金变化 Δmoney | × 0.01 | 正负均有 | -| 得分变化 Δscore | × 0.01 | 仅卖出时为正 | -| 时间惩罚(每步) | −0.002 | 鼓励高效路径 | -| 采集奖励(每单位) | +0.001 | 采集塑形 | -| 算力中心解锁(一次性) | +0.5 | 进度奖励 | -| 无效动作惩罚 | −0.05 | 每步 | +| 现金变化 Δmoney | × 0.02 | 正负均有 | +| 卖出得分 Δscore | × 0.001 | 仅卖出时为正 | +| 时间惩罚(每步) | −0.001 | 鼓励高效路径 | +| 采集奖励(每单位) | +0.0 | 默认关闭,可通过 RewardConfig 开启 | +| 算力中心解锁(每个) | +1.0 | 进度奖励 | +| 科技购买(每个) | +1.0 | 进度奖励 | +| 无效动作惩罚 | −0.02 | 每步 | | 破产惩罚(terminated 时) | −10.0 | 终端惩罚 | > 奖励是训练辅助信号。**最终排名以 `info["score"]` 为准**,不是累计奖励。 +`RewardConfig` 支持两种模式: +- `"standard"`(默认):密集可学习信号,适用于 PPO/DQN 训练 +- `"adversarial"`:收割陷阱模式,仅使用采集奖励 + 终端得分 + +### 技术细节 + +`RewardCalculator.compute()` 在 `game_env.py` 的 `step()` 中调用,计算奖励时先评估终止条件,再结算奖励(确保 terminal bonus 正确触发)。 + ## `step()` 返回的 info 字典 | 字段 | 类型 | 含义 | diff --git a/docs/contests/THUAI9/pve/game/state.md b/docs/contests/THUAI9/pve/game/state.md index 553267df..66f4a730 100644 --- a/docs/contests/THUAI9/pve/game/state.md +++ b/docs/contests/THUAI9/pve/game/state.md @@ -2,116 +2,84 @@ ## 时间系统 -| 参数 | 值 | -|:----:|:--:| -| 每 tick 时长 | 0.25s | -| easy/medium 总时长 | 300s(1200 ticks) | -| hard 总时长 | 500s(2000 ticks) | +每 tick **0.25s**。easy/medium 300s(1200 tick),hard 500s(2000 tick)。 -## 单位属性 +## 单位 | 属性 | 值 | |:----:|:--:| -| 血量 | 300 | +| HP | 300 | | 背包容量 | 30 | | 采集速率 | 10/s | -| 算力中心占领时间 | 10s | +| 占领耗时 | 10s | -- 背包分为**原材料**(`raw_inv`)和**成品**(`prod_inv[pid]`)两部分 -- `busy_ticks > 0` 时单位忙碌,仅 WAIT 有效 -- 移动默认消耗 1 busy_tick(购买 path_optimization 科技后变为 0) -- BUY/SELL/LOAD 消耗 `int(0.25 / time_step)` 个 busy_tick +- 背包分原材料(`raw_inv`)和成品(`prod_inv[pid]`);商品追踪购买来源市场(`prod_origin`) +- `busy_ticks > 0` 时仅 WAIT 有效;移动消耗 1 tick(TECH_5 后为 0);BUY/SELL/LOAD 消耗 `0.25/dt` tick ## 工厂 | 属性 | 值 | |:----:|:--:| -| 位置 | (0, 0) | +| 位置 | (0,0) | | 仓储上限 | 300 | -| 初始生产线数 | 3 | - -### 生产系统 - -工厂将原材料加工为成品。生产队列最大长度 = `生产线数 × 5`。 +| 初始产线 | 3 | -生产流程: -1. 采集原材料(HARVEST) -2. 存入工厂(DEPOSIT) -3. 排队生产(PRODUCE_pid)——消耗原材料,加入生产队列 -4. 等待工厂 tick 推进完成 -5. 装载成品(LOAD) - -生产时间受 efficiency 科技影响(×0.5)。 +生产队列上限 = 产线数 × 5。流程:HARVEST → DEPOSIT → PRODUCE → 等待 tick 完成 → LOAD → SELL。 ## 商品 -| ID | 名称 | 购买成本 | 原材料消耗 | 市场价格范围 | 生产时间 | -|:--:|:----:|:--------:|:----------:|:------------:|:--------:| +| ID | 名称 | 买价 | 原料消耗 | 市价范围 | 生产时间 | +|:--:|:----:|:----:|:--------:|:--------:|:--------:| | 0 | 半导体 | 10 | 5 | 40–120 | 5.0s | | 1 | 药品 | 5 | 3 | 20–60 | 4.0s | | 2 | 小商品 | 1 | 1 | 4–12 | 2.0s | | 3 | 服饰 | 8 | 4 | 32–96 | 6.0s | | 4 | 食品 | 3 | 2 | 12–24 | 1.0s | -- 购买成本受 cost_reduction 科技影响(−2,最低为 0) -- 生产时间受 efficiency 科技影响(×0.5) +买价受 TECH_0(−2),生产时间受 TECH_1(×0.5) 影响。 -## 市场定价 +## 市场 -每个市场对每种商品有独立的正弦价格函数: +OU 随机游走价格,每 tick 更新: ``` -price(t) = base + amplitude × (1 + sin(2π·t / period + phase)) / 2 +dP = θ(μ−P)·dt + σ·√dt·N(0,1) ``` -- 不同市场的价格**相位随机不同步**,套利窗口随时间移动 -- `price_volatility` 控制波动幅度(easy=0.3, medium=1.0, hard=2.0) -- 卖价受 marketing 科技影响(×1.1) - -## 算力系统 +θ=0.05,σ=amplitude×0.12,价格夹在 [lo, lo+amplitude] 内。`price_volatility` 控制振幅(easy=0.3, medium=1.0, hard=2.0)。 -- **基础产出**:每个已开放算力中心产出 **1 算力/秒** -- 购买 compute_expansion 科技后:+30% 算力速率 -- 可消耗算力招募新单位(40 算力/个,最多 5 个单位)——Phase 2 +**套利规则**:BUY 选跨市场卖价最高的可负担商品。禁止同市场原地套利(商品追踪购买来源,卖出时排除当前市场来源部分)。卖价受 TECH_2(×1.1) 影响。 -### 算力中心 +## 算力 -- 占领方式:在相邻格执行 OCCUPY,每次 tick 推进 0.25s 进度 -- 进度达到 `unit_occupy_time`(10s)后开放 -- 开放后持续产出算力 +- 每个开放算力中心 +1 算力/秒;TECH_7 后 +30% +- OCCUPY 每 tick 推进 0.25s,10s 后开放 +- 招募新单位:40 算力/个,上限 5 -## 科技树 +## 科技 -| 键名 | 效果 | 消耗 | 前置 | 持久 | -|:-----|------|:----:|:----:|:----:| -| cost_reduction | 商品购买成本 −2 | 50 | — | ✓ | -| efficiency | 生产时间 ×0.5 | 40 | — | ✓ | -| marketing | 卖价 ×1.1 | 80 | — | ✓ | -| durability | 单位 max HP +50% | 30 | — | ✓ | -| multi_line | 工厂 +1 生产线 | 60 | — | ✓ | -| path_optimization | 移动不产生 busy_tick | 50 | efficiency | ✓ | -| market_analysis | 观测中标记已购买 | 40 | — | ✗ | -| compute_expansion | 算力速率 +30% | 70 | — | ✓ | +| 键名 | 效果 | 消耗 | 前置 | +|:-----|------|:----:|:----:| +| cost_reduction | 买价 −2 | 50 | — | +| efficiency | 生产时间 ×0.5 | 40 | — | +| marketing | 卖价 ×1.1 | 80 | — | +| durability | HP +50% | 30 | — | +| multi_line | 产线 +1 | 60 | — | +| path_optimization | 移动无 busy_tick | 50 | efficiency | +| market_analysis | obs标记(可重复) | 40 | — | +| compute_expansion | 算力 +30% | 70 | — | -- 持久科技每种只能购买一次 -- market_analysis 可重复购买 -- 科技在工厂格执行 +持久科技(除 market_analysis)限购一次。在工厂格执行。 ## 资源再生 -资源点库存会随时间缓慢再生: - ``` -regen(t) = rate × (1 + sin(2π·t / period)) / 2 +regen(t) = rate × (1 + sin(2π·t/period)) / 2 ``` -- 再生倍率:easy=2.0, medium=1.0, hard=0.5 -- 资源耗尽(累计采集量 ≥ max_stock)后停止再生 -- 最大库存为初始库存的 2 倍 +倍率:easy=2.0, medium=1.0, hard=0.5。耗尽(累计≥max_stock=2×初始)后停止。 ## 得分与终止 -- **得分**:`score += revenue × score_factor`(默认 ×10),仅在 SELL 成功时增加 -- **terminated**:`money < 0`(破产) -- **truncated**:`step >= max_steps`(时间耗尽) -- 最终排名以多 seed 下的 `info["score"]` 平均为准 +- `score += revenue × 10`(仅 SELL 成功时) +- `money < 0` → terminated;`step ≥ max_steps` → truncated diff --git a/docs/contests/THUAI9/pve/interface/gym.md b/docs/contests/THUAI9/pve/interface/gym.md index 9990a579..a96a41fb 100644 --- a/docs/contests/THUAI9/pve/interface/gym.md +++ b/docs/contests/THUAI9/pve/interface/gym.md @@ -1,170 +1,57 @@ # 公开接口 -PVE 算法**只能**通过标准 Gymnasium 接口与环境交互。不得直接访问内部对象。 +PVE 算法只通过标准 Gymnasium 接口与环境交互,禁止访问内部对象。 ## 环境初始化 ```python from GameLogic import GameEnvironment, GameConfig -# 内置难度 -env = GameEnvironment(cfg=GameConfig.easy()) -env = GameEnvironment(cfg=GameConfig.medium()) -env = GameEnvironment(cfg=GameConfig.hard()) - -# 自定义配置 -env = GameEnvironment(cfg=GameConfig.from_dict({ - "map_width": 8, "map_height": 8, - "num_markets": 4, "initial_money": 100.0, -})) +env = GameEnvironment(cfg=GameConfig.easy()) # medium / hard / from_dict({...}) ``` -## 接口方法 - -### `reset()` +## 接口 ```python obs, info = env.reset(seed=0) +obs, reward, terminated, truncated, info = env.step(action) # action: int 0–27 +mask = env.action_masks() # (28,) bool, True=有效 ``` -重置环境,返回初始观测和 info。 - -### `step()` - -```python -obs, reward, terminated, truncated, info = env.step(action) -# action: int, 0-27 -# obs: np.ndarray, shape (58,), dtype float32 -# reward: float -# terminated: bool (money < 0,破产) -# truncated: bool (步数耗尽,正常结束) -# info: dict -``` - -### `action_masks()` - -```python -mask = env.action_masks() # np.ndarray[bool], shape (28,) -``` - -返回当前有效的动作掩码。 +| 返回 | 类型 | 含义 | +|------|------|------| +| `obs` | `(58,) float32` | 观测向量 | +| `reward` | `float` | 单步奖励 | +| `terminated` | `bool` | money<0 破产 | +| `truncated` | `bool` | 步数耗尽 | +| `info` | `dict` | step/time/money/score/compute/action_valid | ## 观测向量(58 维 float32) -### 单位状态([0–9]) - -| 索引 | 含义 | 归一化 | -|:----:|------|--------| -| 0–1 | 单位位置 (x, y) | / (H, W) | -| 2 | 单位 HP | / max_hp | -| 3 | 原材料背包 | raw_inv / capacity | -| 4 | 半导体背包 | prod_inv[0] / capacity | -| 5 | 药品背包 | prod_inv[1] / capacity | -| 6 | 小商品背包 | prod_inv[2] / capacity | -| 7 | 服饰背包 | prod_inv[3] / capacity | -| 8 | 食品背包 | prod_inv[4] / capacity | -| 9 | busy 倒计时 | / 10,截断到 1 | - -### 经济状态([10–14]) - -| 索引 | 含义 | 归一化 | -|:----:|------|--------| -| 10 | 现金(对数) | log10(money+1) / 5 | -| 11 | 算力 | / 100,截断到 2 | -| 12 | 游戏进度 | time / max_game_time | -| 13 | 价格相位 sin | sin(2π·t / market_period) | -| 14 | 价格相位 cos | cos(2π·t / market_period) | - -### 工厂状态([15–21]) - -| 索引 | 含义 | 归一化 | -|:----:|------|--------| -| 15 | 工厂原材料库存 | / storage_cap | -| 16 | 工厂半导体库存 | / storage_cap | -| 17 | 工厂药品库存 | / storage_cap | -| 18 | 工厂小商品库存 | / storage_cap | -| 19 | 工厂服饰库存 | / storage_cap | -| 20 | 工厂食品库存 | / storage_cap | -| 21 | 生产队列长度 | / 10,截断到 1 | - -### 资源点([22–27]) - -| 索引 | 含义 | 归一化 | -|:----:|------|--------| -| 22–23 | 资源点 0 相对位置 (dx, dy) | / (H, W) | -| 24 | 资源点 0 库存比例 | stock / max_stock | -| 25–26 | 资源点 1 相对位置 (dx, dy) | / (H, W) | -| 27 | 资源点 1 库存比例 | stock / max_stock | - -### 算力中心([28–35]) - -| 索引 | 含义 | 归一化 | -|:----:|------|--------| -| 28–29 | 算力中心 0 相对位置 (dx, dy) | / (H, W) | -| 30 | 算力中心 0 是否开放 | 0 或 1 | -| 31 | 算力中心 0 占领进度 | / unit_occupy_time | -| 32–33 | 算力中心 1 相对位置 (dx, dy) | / (H, W) | -| 34 | 算力中心 1 是否开放 | 0 或 1 | -| 35 | 算力中心 1 占领进度 | / unit_occupy_time | - -### 市场([36–49]) - -| 索引 | 含义 | 归一化 | -|:----:|------|--------| -| 36–37 | 市场 0 相对位置 (dx, dy) | / (H, W) | -| 38 | 市场 0 半导体价格 | 按 val_range 归一化 | -| 39 | 市场 0 药品价格 | 同上 | -| 40 | 市场 0 小商品价格 | 同上 | -| 41 | 市场 0 服饰价格 | 同上 | -| 42 | 市场 0 食品价格 | 同上 | -| 43–44 | 市场 1 相对位置 (dx, dy) | / (H, W) | -| 45 | 市场 1 半导体价格 | 按 val_range 归一化 | -| 46 | 市场 1 药品价格 | 同上 | -| 47 | 市场 1 小商品价格 | 同上 | -| 48 | 市场 1 服饰价格 | 同上 | -| 49 | 市场 1 食品价格 | 同上 | - -### 科技状态([50–57]) - -| 索引 | 含义 | +| 索引 | 内容 | |:----:|------| -| 50 | 是否已购买 cost_reduction | -| 51 | 是否已购买 efficiency | -| 52 | 是否已购买 marketing | -| 53 | 是否已购买 durability | -| 54 | 是否已购买 multi_line | -| 55 | 是否已购买 path_optimization | -| 56 | 是否已购买 market_analysis(可重复) | -| 57 | 是否已购买 compute_expansion | - -> 如果实体数量不足(如只有 1 个市场),多余索引保持 0。 - -## 禁止访问的对象 - -以下为内部实现,选手算法**不得依赖**: - -- `env.unit`(Unit 内部字段) -- `env.factory`(Factory 内部字段) -- `env.board`(Board / 地图内部字段) -- `env.markets`(Market 列表) -- `env.money`、`env.compute`、`env.score`(通过 `info` 获取) -- 任何以下划线开头的方法或属性 - -> 评测机只会暴露 `reset`、`step`、`action_masks` 三个公开接口。 - -## 编写规则型策略 - -如果不需要 RL 训练,可以直接读取 `info` 字典做规则决策: - -```python -obs, info = env.reset() -while True: - # 规则策略基于 info 做决策 - if info["money"] > 50: - action = 5 # BUY - else: - action = 11 # HARVEST - obs, reward, terminated, truncated, info = env.step(action) - if terminated or truncated: - break -``` +| 0–1 | 单位坐标 / (H,W) | +| 2 | HP / max_hp | +| 3 | raw_inv / capacity | +| 4–8 | prod_inv[0–4] / capacity | +| 9 | busy_ticks / 10 | +| 10 | log10(money+1) / 5 | +| 11 | compute / 100 | +| 12 | time / max_time | +| 13–14 | sin/cos(2π·t / market_period) | +| 15 | 工厂 raw_stock / storage_cap | +| 16–20 | 工厂 products[0–4] / storage_cap | +| 21 | 生产队列长度 / 10 | +| 22–24 | 资源0: dx/H, dy/W, stock/max | +| 25–27 | 资源1: 同上 | +| 28–31 | 算力中心0: dx/H, dy/W, is_open, progress | +| 32–35 | 算力中心1: 同上 | +| 36–42 | 市场0: dx/H, dy/W, 5种商品价格(val_range归一化) | +| 43–49 | 市场1: 同上 | +| 50–57 | 科技 one-hot(8槽对应 TECH_0~7 顺序) | + +不足实体数量的索引保持 0。 + +## 禁止访问 + +`env.unit`、`env.factory`、`env.board`、`env.markets`、`env.money` 等内部对象均禁止。评测机只暴露 `reset/step/action_masks`。 diff --git a/docs/contests/THUAI9/pve/intro/rule.md b/docs/contests/THUAI9/pve/intro/rule.md index 55b5afd2..b411f9d6 100644 --- a/docs/contests/THUAI9/pve/intro/rule.md +++ b/docs/contests/THUAI9/pve/intro/rule.md @@ -38,13 +38,16 @@ PVE(PvE-RL)赛道是一个**强化学习**竞技环境。选手需要训练 ## 市场价格 -每个市场对每种商品有独立的正弦价格函数: +每个市场对每种商品有独立的 **Ornstein-Uhlenbeck 随机游走**价格过程: ``` -price(t) = base + amplitude × (1 + sin(2π·t / period + phase)) / 2 +dP = θ(μ − P)·dt + σ·√dt · N(0,1) ``` -- 不同市场的价格**相位随机不同步**,套利窗口随时间移动 +- θ=0.05 均值回归,σ=12% 振幅波动 +- 每个市场独立初始化随机起点,每 tick(0.25s)更新 +- 价格被夹在 [lo, lo+amplitude] 之间 +- 跨市场套利禁止原地买卖——不能在同一市场卖出从该市场购买的商品 - `price_volatility` 控制波动幅度(easy=0.3, medium=1.0, hard=2.0) ## 终止条件 @@ -60,8 +63,8 @@ price(t) = base + amplitude × (1 + sin(2π·t / period + phase)) / 2 |:--|:---|:---| | 交互方式 | gRPC 客户端连接服务器 | 本地 Gymnasium 环境 | | 编程语言 | C++ | Python | -| 动作 | 连续移动 + 多种操作 | 8 个离散动作 | -| 市场定价 | 衰减机制 | 正弦周期波动 | +| 动作 | 连续移动 + 多种操作 | 28 个离散动作 | +| 市场定价 | 衰减机制 | OU 随机游走 | | 单位数 | 1 Team + 3 Character | 1 个可控单位 | | 对手 | 其他人类队伍 | 无(纯经济环境) | | 评测 | 单局得分 | 多 seed 平均得分 | diff --git a/docs/contests/THUAI9/pve/training/training.md b/docs/contests/THUAI9/pve/training/training.md index 8a7dfc5f..8b8030ac 100644 --- a/docs/contests/THUAI9/pve/training/training.md +++ b/docs/contests/THUAI9/pve/training/training.md @@ -4,52 +4,32 @@ ```bash pip install -r requirements.txt +python -m pytest tests/ -v # 验证环境 ``` -## 单元测试 - -验证环境正常: +## 训练 ```bash -python -m pytest tests/ -v -``` - -## 基础训练 - -```bash -# easy 难度,10 万步 python TrainingDemo/train_basic.py --config easy --timesteps 100000 - -# medium 难度,20 万步 python TrainingDemo/train_basic.py --config medium --timesteps 200000 - -# 自定义 YAML 配置 python TrainingDemo/train_basic.py --config TrainingDemo/configs/medium.yaml --timesteps 200000 ``` -## 评测模型 +## 评测 ```bash python TrainingDemo/evaluate.py --model models/ppo_thuai9_best --config medium --episodes 50 ``` -输出多 seed 下的平均得分和方差。 - ## MaskablePPO -使用 `sb3-contrib` 的 `MaskablePPO` 可以自动利用动作掩码: - ```python from sb3_contrib import MaskablePPO from sb3_contrib.common.wrappers import ActionMasker -def mask_fn(env): - return env.unwrapped.action_masks() - -env = ActionMasker(env, mask_fn) +env = ActionMasker(env, lambda e: e.unwrapped.action_masks()) model = MaskablePPO("MlpPolicy", env, verbose=1) model.learn(total_timesteps=100000) -model.save("ppo_thuai9") ``` ## 难度对比 @@ -67,37 +47,10 @@ model.save("ppo_thuai9") | 初始资源 | 200 | 100 | 50 | | 游戏时长 | 300s | 300s | 500s | -## 项目结构 - -``` -logic/pve/ -├── GameLogic/ # 游戏规则与状态管理(算法不可直接修改) -│ ├── config.py # 全局配置(难度参数化) -│ ├── board.py # 地图、资源点、算力中心 -│ ├── character.py # 单位(HP、背包、状态机) -│ ├── market.py # 市场动态价格函数 -│ ├── action_space.py # 动作空间与动作掩码 -│ ├── reward_calculator.py # 奖励计算 -│ └── game_env.py # 主环境(Gymnasium 接口) -├── RLInterfaces/ # RL 算法接口层 -│ ├── base_agent.py # 抽象基类 -│ ├── ppo_agent.py # PPO 实现(支持 MaskablePPO) -│ └── training_loop.py # 训练循环 -├── TrainingDemo/ # 训练与评测脚本 -│ ├── train_basic.py # 训练入口 -│ ├── evaluate.py # 评测脚本 -│ ├── visualization.py # ASCII 渲染 + 奖励曲线 -│ └── configs/ # YAML 配置 -├── tests/ # 单元测试 -└── docs/ # 选手/开发者文档 -``` - ## 建议方向 -- 使用 `action_masks()` 过滤无效动作 -- BFS / A* 路径规划,找最优路线 -- 建模市场价格正弦周期(利用观测中的 sin/cos 相位) -- 选择高利润商品(半导体 40–120、服饰 32–96) -- 规则策略 + RL 混合(Hybrid Policy) -- 课程学习(easy → medium → hard) -- Recurrent Policy 识别价格周期 +- `action_masks()` 过滤无效动作;BFS/A* 路径规划 +- 建模 OU 价格随机游走,利用观测 sin/cos 相位辅助时序判断 +- 生产链:食品(1s)快周转,半导体(40–120)高利润 +- 优先占领算力中心 → efficiency(×0.5) 或 marketing(×1.1) +- 课程学习 easy→medium→hard;Recurrent Policy 捕捉价格趋势 From cb134a0b1ea5e23b64bc4462053a2c638fcf7edc Mon Sep 17 00:00:00 2001 From: lch Date: Mon, 18 May 2026 16:34:08 +0800 Subject: [PATCH 7/9] 2 --- docs/contests/THUAI9/pvp/character/character.md | 2 +- docs/contests/THUAI9/pvp/intro/rule.md | 2 +- docs/contests/THUAI9/pvp/map/factory.md | 2 +- docs/contests/THUAI9/pvp/map/resource.md | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/contests/THUAI9/pvp/character/character.md b/docs/contests/THUAI9/pvp/character/character.md index 568a0847..23d85aaa 100644 --- a/docs/contests/THUAI9/pvp/character/character.md +++ b/docs/contests/THUAI9/pvp/character/character.md @@ -43,7 +43,7 @@ ## 效率的影响 -- **采集速度**:实际采集 = 200 × (1 + 效率等级 × 0.2) 单位/秒 +- **采集速度**:实际采集 = 80 × (1 + 效率等级 × 0.2) 单位/秒(每帧 `200/50 = 4` 单位) - **占领速度**:实际占领时间 = 10000 / (1 + 效率等级 × 0.2) ms ## 视野 diff --git a/docs/contests/THUAI9/pvp/intro/rule.md b/docs/contests/THUAI9/pvp/intro/rule.md index 1510ace6..d98c1704 100644 --- a/docs/contests/THUAI9/pvp/intro/rule.md +++ b/docs/contests/THUAI9/pvp/intro/rule.md @@ -30,7 +30,7 @@ |:----:|:--:| | 每帧时长 | 50ms | | 游戏总时长 | 600s | -| 工厂被攻击后停摆时间 | 10s | +| 工厂被攻击后停摆时间 | 1s | | 算力中心占领基础时间 | 10s | | AskAI 超时 | 60s | | 市场事件间隔 | 30s | diff --git a/docs/contests/THUAI9/pvp/map/factory.md b/docs/contests/THUAI9/pvp/map/factory.md index b4f5aed1..94a69508 100644 --- a/docs/contests/THUAI9/pvp/map/factory.md +++ b/docs/contests/THUAI9/pvp/map/factory.md @@ -38,7 +38,7 @@ - 受到攻击时,实际伤害 = `Max(ATK - Robust, 1)` - 攻击方获得 `伤害量 × 20` 分 -- 工厂被攻击后进入 **10 秒停摆**(`CanProduce` 和 `CanRecruit` 均为 false) +- 工厂被攻击后进入 **1s 停摆**(`CanProduce` 和 `CanRecruit` 均为 false) - 工厂被摧毁(HP=0):攻击方获得 **20000 分**,工厂所属队伍无法再生产/招募 ## 商品生产成本 diff --git a/docs/contests/THUAI9/pvp/map/resource.md b/docs/contests/THUAI9/pvp/map/resource.md index 8c481e51..cd34a908 100644 --- a/docs/contests/THUAI9/pvp/map/resource.md +++ b/docs/contests/THUAI9/pvp/map/resource.md @@ -8,12 +8,12 @@ |:----:|:--:| | 血量 (HP) | 500 | | 半径 | 500 | -| 基础采集速度 | 200/秒 | +| 基础采集速度 | 80/秒(4/帧) | ## 采集 - 条件:角色位于资源格子的**九宫格内** -- 速度:`200 × (1 + 角色效率 × 0.2)` 单位/秒 +- 速度:`80 × (1 + 角色效率 × 0.2)` 单位/秒(每帧 `200/50 = 4` 单位) - 采集的资源(Source)存入己方工厂 - 资源血量归零后变为 `HARVESTED`,不可再采集 - 不同队伍的角色可同时采集同一资源 From 94917fa74cf48c348d431e4f7b473307dd84421e Mon Sep 17 00:00:00 2001 From: lch Date: Mon, 25 May 2026 09:35:56 +0800 Subject: [PATCH 8/9] shuzhi --- docs/contests/THUAI9/pvp/intro/score.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/contests/THUAI9/pvp/intro/score.md b/docs/contests/THUAI9/pvp/intro/score.md index 79d17fca..56fb2576 100644 --- a/docs/contests/THUAI9/pvp/intro/score.md +++ b/docs/contests/THUAI9/pvp/intro/score.md @@ -14,11 +14,11 @@ | 商品 | 基础价格 | 生产成本(Source) | 生产时间 | |:----:|:--------:|:----------------:|:--------:| -| Semiconductor(半导体) | 80 | 10 | 5s | -| Medicine(药品) | 50 | 5 | 4s | -| Clothes(服装) | 32 | 8 | 6s | -| Toys(玩具) | 8 | 1 | 2s | -| Food(食品) | 6 | 3 | 1s | +| Semiconductor(半导体) | 180 | 10 | 5s | +| Medicine(药品) | 110 | 5 | 4s | +| Clothes(服装) | 70 | 8 | 6s | +| Toys(玩具) | 25 | 1 | 2s | +| Food(食品) | 20 | 3 | 1s | > 市场价格 = 基础价格 × 市场倍率 × 衰减系数 > 售价可受"Price"科技加成(每级 +10%,最高 +20%) From 50ddb21a6d555e4fc4adb3f81cae7bf5e6b23dec Mon Sep 17 00:00:00 2001 From: lch Date: Wed, 27 May 2026 19:28:25 +0800 Subject: [PATCH 9/9] balence --- docs/contests/THUAI9/pvp/intro/rule.md | 1 + docs/contests/THUAI9/pvp/intro/score.md | 4 ++-- docs/contests/THUAI9/pvp/map/factory.md | 5 +++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/contests/THUAI9/pvp/intro/rule.md b/docs/contests/THUAI9/pvp/intro/rule.md index d98c1704..9e76493d 100644 --- a/docs/contests/THUAI9/pvp/intro/rule.md +++ b/docs/contests/THUAI9/pvp/intro/rule.md @@ -30,6 +30,7 @@ |:----:|:--:| | 每帧时长 | 50ms | | 游戏总时长 | 600s | +| 工厂无敌时间 | 7min(前7分钟工厂不受伤害) | | 工厂被攻击后停摆时间 | 1s | | 算力中心占领基础时间 | 10s | | AskAI 超时 | 60s | diff --git a/docs/contests/THUAI9/pvp/intro/score.md b/docs/contests/THUAI9/pvp/intro/score.md index 56fb2576..fb23dfc3 100644 --- a/docs/contests/THUAI9/pvp/intro/score.md +++ b/docs/contests/THUAI9/pvp/intro/score.md @@ -7,8 +7,8 @@ | **市场售卖产品** | 商品当前市场价 × 售卖数量 | | **对敌方角色造成伤害** | 伤害量 × 20 | | **击杀敌方角色** | `角色造价 × 40`(Drone=2000, Robot=2000, Car=2000) | -| **对敌方工厂造成伤害** | 伤害量 × 20 | -| **摧毁敌方工厂** | 20000 | +| **对敌方工厂造成伤害** | 伤害量(工厂实际损失血量) | +| **摧毁敌方工厂** | 1000 | ## 商品基础价格 diff --git a/docs/contests/THUAI9/pvp/map/factory.md b/docs/contests/THUAI9/pvp/map/factory.md index 94a69508..b5cac949 100644 --- a/docs/contests/THUAI9/pvp/map/factory.md +++ b/docs/contests/THUAI9/pvp/map/factory.md @@ -36,10 +36,11 @@ ## 被攻击 +- 前 **7 分钟**工厂处于**无敌状态**,不受任何伤害 - 受到攻击时,实际伤害 = `Max(ATK - Robust, 1)` -- 攻击方获得 `伤害量 × 20` 分 +- 攻击方获得等同于工厂实际损失血量的分数 - 工厂被攻击后进入 **1s 停摆**(`CanProduce` 和 `CanRecruit` 均为 false) -- 工厂被摧毁(HP=0):攻击方获得 **20000 分**,工厂所属队伍无法再生产/招募 +- 工厂被摧毁(HP=0):攻击方获得 **1000 分**,工厂所属队伍无法再生产/招募 ## 商品生产成本