docs: sync updated boardgame-core

This commit is contained in:
hypercross 2026-04-03 17:45:56 +08:00
parent 4d4889f825
commit cbee709a27
2 changed files with 51 additions and 36 deletions

View File

@ -57,6 +57,9 @@ const hand = createRegion('hand', [
**区域操作:**
```ts
// 注意parts 现在是 Record<string, Part> 格式
const parts: Record<string, Part> = { ... };
// 对齐/紧凑排列(根据 axis.align 自动调整位置)
applyAlign(hand, parts);
@ -78,7 +81,7 @@ removeFromRegion(part, region);
`Part<TMeta>` 表示游戏中的一个部件(棋子、卡牌等)。
```ts
import { createPart, createParts, createPartPool, flip, flipTo, roll } from 'boardgame-core';
import { createPart, createParts, createPartPool, flip, flipTo, roll, mergePartPools } from 'boardgame-core';
// 创建单个部件
const piece = createPart(
@ -86,7 +89,7 @@ const piece = createPart(
'piece-1'
);
// 批量创建(生成 piece-pawn-0, piece-pawn-1, ...
// 批量创建(生成 piece-pawn-1, piece-pawn-2, ...
const pawns = createParts(
{ regionId: 'supply', position: [0, 0] },
8,
@ -141,6 +144,12 @@ roll(dice, rng);
```ts
import { findPartById, isCellOccupied, getPartAtPosition } from 'boardgame-core';
// Parts 现在是 Record<string, Part> 格式
const parts: Record<string, Part> = {
'piece-1': piece1,
'piece-2': piece2,
};
// 按 ID 查找
const piece = findPartById(parts, 'piece-1');
@ -358,7 +367,7 @@ export function createInitialState() {
{ name: 'x', min: 0, max: BOARD_SIZE - 1 },
{ name: 'y', min: 0, max: BOARD_SIZE - 1 },
]),
parts: [] as TicTacToePart[],
parts: {} as Record<string, TicTacToePart>,
currentPlayer: 'X' as PlayerType,
winner: null as PlayerType | 'draw' | null,
turn: 0,
@ -411,7 +420,7 @@ registration.add('turn <player> <turn:number>', async function(cmd) {
if (row < 0 || row >= BOARD_SIZE || col < 0 || col >= BOARD_SIZE) {
return `Invalid position: (${row}, ${col})`;
}
if (isCellOccupied(this.context, row, col)) {
if (isCellOccupied(this.context.value.parts, 'board', [row, col])) {
return `Cell (${row}, ${col}) is occupied`;
}
return null;
@ -426,7 +435,7 @@ registration.add('turn <player> <turn:number>', async function(cmd) {
`piece-${player}-${turnNumber}`
);
this.context.produce(state => {
state.parts.push(piece);
state.parts[piece.id] = piece;
state.board.childIds.push(piece.id);
state.board.partMap[`${row},${col}`] = piece.id;
});
@ -486,12 +495,12 @@ const game = createGameContextFromModule(ticTacToe);
| `PartTemplate<TMeta>` | 部件模板(创建时排除 id |
| `PartPool<TMeta>` | 部件池draw/return/remaining |
| `createPart(template, id)` | 创建单个部件 |
| `createParts(template, count, idPrefix)` | 批量创建部件 |
| `createParts(template, count, idPrefix)` | 批量创建部件ID 从 1 开始:`prefix-1`, `prefix-2`, ... |
| `createPartPool(template, count, idPrefix)` | 创建部件池 |
| `mergePartPools(...pools)` | 合并部件池 |
| `findPartById(parts, id)` | 按 ID 查找 |
| `isCellOccupied(parts, regionId, position)` | 检查位置占用 |
| `getPartAtPosition(parts, regionId, position)` | 获取位置上的部件 |
| `findPartById(parts, id)` | 按 ID 查找`parts` 为 `Record<string, Part>` |
| `isCellOccupied(parts, regionId, position)` | 检查位置占用`parts` 为 `Record<string, Part>` |
| `getPartAtPosition(parts, regionId, position)` | 获取位置上的部件`parts` 为 `Record<string, Part>` |
| `flip(part)` | 翻面 |
| `flipTo(part, side)` | 翻到指定面 |
| `roll(part, rng)` | 随机面 |
@ -503,10 +512,10 @@ const game = createGameContextFromModule(ticTacToe);
| `Region` | 区域类型 |
| `RegionAxis` | 坐标轴定义 |
| `createRegion(id, axes)` | 创建区域 |
| `applyAlign(region, parts)` | 对齐/紧凑排列 |
| `shuffle(region, parts, rng)` | 随机打乱位置 |
| `moveToRegion(part, sourceRegion?, targetRegion, position?)` | 移动部件 |
| `moveToRegionAll(parts, sourceRegion?, targetRegion, positions?)` | 批量移动 |
| `applyAlign(region, parts)` | 对齐/紧凑排列`parts` 为 `Record<string, Part>` |
| `shuffle(region, parts, rng)` | 随机打乱位置`parts` 为 `Record<string, Part>` |
| `moveToRegion(part, sourceRegion?, targetRegion, position?)` | 移动部件`sourceRegion` 可选) |
| `moveToRegionAll(parts, sourceRegion?, targetRegion, positions?)` | 批量移动`parts` 为 `Record<string, Part>``sourceRegion` 可选) |
| `removeFromRegion(part, region)` | 移除部件 |
### Command
@ -520,12 +529,18 @@ const game = createGameContextFromModule(ticTacToe);
| `parseCommandSchema(schema)` | 解析模式字符串 |
| `validateCommand(cmd, schema)` | 验证命令 |
| `parseCommandWithSchema(cmd, schema)` | 解析并验证 |
| `applyCommandSchema(cmd, schema)` | 应用模式验证并返回验证后的命令 |
| `createCommandRegistry<TContext>()` | 创建命令注册表 |
| `registerCommand(registry, runner)` | 注册命令 |
| `unregisterCommand(registry, name)` | 取消注册命令 |
| `hasCommand(registry, name)` | 检查命令是否存在 |
| `getCommand(registry, name)` | 获取命令处理器 |
| `runCommand(registry, context, input)` | 解析并运行命令 |
| `runCommandParsed(registry, context, command)` | 运行已解析的命令 |
| `createCommandRunnerContext(registry, context)` | 创建命令运行器上下文 |
| `PromptEvent` | Prompt 事件tryCommit/cancel |
| `CommandRunnerContext<TContext>` | 命令运行器上下文 |
| `CommandRunnerContextExport<TContext>` | 导出的命令运行器上下文(含 `promptQueue` |
### Utilities
@ -541,16 +556,20 @@ const game = createGameContextFromModule(ticTacToe);
- **总是使用 `produce()`** 更新状态,不要直接修改 `.value`
- `produce()` 内部是 draft 模式,可以直接修改属性
- **Parts 使用 Record 格式**,通过 ID 作为键访问
```ts
// ✅ 正确
state.produce(draft => {
draft.score += 1;
draft.parts.push(newPart);
draft.parts[newPart.id] = newPart;
});
// ❌ 错误 — 会破坏响应式
state.value.score = 10;
// ❌ 错误 — parts 是 Record 不是数组
state.parts.push(newPart);
```
### 命令设计

View File

@ -39,11 +39,10 @@ export function bindRegion<TState, TMeta>(
container: Phaser.GameObjects.Container,
): { cleanup: () => void; objects: Map<string, Phaser.GameObjects.GameObject> } {
const objects = new Map<string, Phaser.GameObjects.GameObject>();
const effects: DisposeFn[] = [];
const offset = options.offset ?? { x: 0, y: 0 };
function syncParts() {
const dispose = effect(function(this: { dispose: () => void }) {
const parts = partsGetter(state.value);
const currentIds = new Set(region.childIds);
for (const [id, obj] of objects) {
@ -73,14 +72,11 @@ export function bindRegion<TState, TMeta>(
}
}
}
}
const e = effect(syncParts);
effects.push(e);
});
return {
cleanup: () => {
for (const e of effects) e();
dispose();
for (const [, obj] of objects) obj.destroy();
objects.clear();
},