Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions docs/contests/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@
title: Contests
slug: ./
---

## 简介


科协赛事文档合集。

## List
Expand All @@ -16,4 +14,5 @@ slug: ./
+ [THUAI5](THUAI5/README.md):清华大学第五届人工智能挑战赛电子系赛道(原电子系第 23 届队式程序设计大赛 teamstyle23)
+ [THUAI6](THUAI6/README.md):清华大学第六届人工智能挑战赛电子系赛道(原电子系第 24 届队式程序设计大赛 teamstyle24)
+ [THUAI7](THUAI7/README.md):清华大学第七届人工智能挑战赛电子系赛道(原电子系第 25 届队式程序设计大赛 teamstyle25)
+ [THUAI8](THUAI8/README.md):清华大学第七届人工智能挑战赛电子系赛道(原电子系第 25 届队式程序设计大赛 teamstyle25)
+ [THUAI8](THUAI8/README.md):清华大学第八届人工智能挑战赛电子系赛道(原电子系第 26 届队式程序设计大赛 teamstyle26)
+ [THUAI9](THUAI8/README.md):清华大学第九届人工智能挑战赛电子系赛道(原电子系第 27 届队式程序设计大赛 teamstyle27)
49 changes: 49 additions & 0 deletions docs/contests/THUAI9/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
---
title: THUAI9
slug: ./
---
## 赛事名称

AI 工厂模拟(THUAI9)

## 赛道

| | PVP | PVE |
| :------- | :------------------------- | :------------------------- |
| 模式 | 多队伍实时对抗 | 单人经济 RL 环境 |
| 编程语言 | C++ | Python |
| 交互方式 | gRPC 连接服务器 | Gymnasium 本地环境 |
| 控制对象 | 1 Team + 3 Character | 1 个单位 |
| 动作 | 连续移动 + 多种操作 | 8 个离散动作 |
| 得分机制 | 售卖 + 战斗 + 摧毁工厂 | 售卖 × 10 |
| 文档入口 | [`pvp/`](pvp/intro/rule.md) | [`pve/`](pve/intro/rule.md) |

## PVP 概要

2~4 支队伍在同一地图对抗。每队拥有 1 座工厂和最多 3 个角色(Drone / Robot / AutonomousCar),通过采集资源、生产产品、占领算力中心、市场售卖、攻击敌方来获取分数。游戏时长 2 分钟,得分高者获胜。

## PVE 概要

强化学习竞技环境。选手训练智能体在地图上移动、低买高卖、采集资源,在有限时间内最大化累计得分。提供 easy / medium / hard 三级难度,支持 MaskablePPO 训练。比赛以多 seed 平均得分排名。

---

## 选手包使用说明

### PVP 赛道(C++)

详细的使用说明参照选手包里的 docs\选手使用说明书.md

---

### PVE 赛道(Python)

不熟悉强化学习的可以访问https://github.com/konpoku/THUAI9-RL,在小项目中学习一下

详细的使用说明参照选手包里的logic\pve\docs\CONTESTANT_GUIDE.md

---

## 相关链接

+ THUAI9 GitHub 仓库:[https://github.com/lch24/THUAI9](https://github.com/lch24/THUAI9)
43 changes: 43 additions & 0 deletions docs/contests/THUAI9/pve/faq/README.md
Original file line number Diff line number Diff line change
@@ -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()`。
36 changes: 36 additions & 0 deletions docs/contests/THUAI9/pve/game/actions.md
Original file line number Diff line number Diff line change
@@ -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()` 再决定动作。
36 changes: 36 additions & 0 deletions docs/contests/THUAI9/pve/game/reward.md
Original file line number Diff line number Diff line change
@@ -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` | 上一步动作是否有效 |
37 changes: 37 additions & 0 deletions docs/contests/THUAI9/pve/game/state.md
Original file line number Diff line number Diff line change
@@ -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`)后停止再生
106 changes: 106 additions & 0 deletions docs/contests/THUAI9/pve/interface/gym.md
Original file line number Diff line number Diff line change
@@ -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
```
67 changes: 67 additions & 0 deletions docs/contests/THUAI9/pve/intro/rule.md
Original file line number Diff line number Diff line change
@@ -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 平均得分 |
Loading