Compare commits
2 Commits
2f0fb2bca8
...
0e7aaea7da
| Author | SHA1 | Date |
|---|---|---|
|
|
0e7aaea7da | |
|
|
acb2fc82ba |
|
|
@ -0,0 +1 @@
|
||||||
|
!node_modules
|
||||||
19
AGENTS.md
19
AGENTS.md
|
|
@ -9,35 +9,34 @@ A Phaser 3 framework for building web board games, built on top of `boardgame-co
|
||||||
|
|
||||||
## boardgame-core Usage
|
## boardgame-core Usage
|
||||||
|
|
||||||
For detailed boardgame-core API documentation and examples, see **[boardgame-core Guide](docs/boardgame-core-guide.md)**.
|
For detailed boardgame-core API documentation and examples, see `packages/framework/node_modules/boardgame-core/`
|
||||||
|
|
||||||
Key concepts:
|
Key concepts:
|
||||||
- **MutableSignal** — Reactive state container with `.value` and `.produce()`
|
- **MutableSignal** — Reactive state container with `.value` and `.produce()`
|
||||||
- **Command System** — CLI-style parsing with schema validation and prompt support
|
- **Command System** — CLI-style parsing with schema validation and prompt support
|
||||||
- **Region System** — Spatial management with `createRegion()`, `applyAlign()`, `shuffle()`, `moveToRegion()`
|
- **Region System** — Spatial management with `createRegion()`, `applyAlign()`, `shuffle()`, `moveToRegion()`
|
||||||
- **Part System** — Game pieces with `createPart()`, `createPartPool()`, `flip()`, `roll()`
|
- **Part System** — Game pieces with `createPartsFromTable()`, `flip()`, `roll()`
|
||||||
- **RNG** — Deterministic PRNG via `createRNG(seed)` for reproducible game states
|
- **RNG** — Deterministic PRNG via `createRNG(seed)` for reproducible game states
|
||||||
|
|
||||||
### Quick Example
|
### Quick Example
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { createGameCommandRegistry, createRegion, MutableSignal } from 'boardgame-core';
|
import { createGameCommandRegistry, createRegion, IGameContext } from 'boardgame-core';
|
||||||
|
|
||||||
type GameState = {
|
type GameState = {
|
||||||
board: Region;
|
board: Region;
|
||||||
parts: Part<{ player: 'X' | 'O' }>[];
|
parts: Part<{ player: 'X' | 'O' }>[];
|
||||||
currentPlayer: 'X' | 'O';
|
currentPlayer: 'X' | 'O';
|
||||||
};
|
};
|
||||||
|
type Game = IGameContext<GameState>;
|
||||||
|
|
||||||
const registration = createGameCommandRegistry<GameState>();
|
const registry = createGameCommandRegistry<GameState>();
|
||||||
export const registry = registration.registry;
|
|
||||||
|
|
||||||
registration.add('place <row:number> <col:number>', async function(cmd) {
|
registry.register('place <row:number> <col:number>', async function(game: Game, row: number, col: number) {
|
||||||
const [row, col] = cmd.params as [number, number];
|
await game.produceAsync(state => {
|
||||||
this.context.produce(state => {
|
|
||||||
state.parts.push({ id: `p-${row}-${col}`, regionId: 'board', position: [row, col], player: state.currentPlayer });
|
state.parts.push({ id: `p-${row}-${col}`, regionId: 'board', position: [row, col], player: state.currentPlayer });
|
||||||
});
|
});
|
||||||
return { success: true };
|
return true;
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -71,7 +70,7 @@ pnpm --filter sample-game typecheck # tsc --noEmit (add to scripts first)
|
||||||
```bash
|
```bash
|
||||||
# boardgame-core is a local dependency via symlink (link:../../../boardgame-core)
|
# boardgame-core is a local dependency via symlink (link:../../../boardgame-core)
|
||||||
# After changes to boardgame-core, simply rebuild it:
|
# After changes to boardgame-core, simply rebuild it:
|
||||||
cd ../boardgame-core && pnpm build
|
cd ../boardgame-core && npm build
|
||||||
# The symlink automatically resolves to the updated dist/
|
# The symlink automatically resolves to the updated dist/
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,7 @@ const placeCommand = registry.register( 'place <row:number> <col:number> <player
|
||||||
async function boop(game: BoopGame, row: number, col: number, type: PieceType) {
|
async function boop(game: BoopGame, row: number, col: number, type: PieceType) {
|
||||||
const booped: string[] = [];
|
const booped: string[] = [];
|
||||||
|
|
||||||
|
const toRemove = new Set<string>();
|
||||||
await game.produceAsync((state: BoopState) => {
|
await game.produceAsync((state: BoopState) => {
|
||||||
// 按照远离放置位置的方向推动
|
// 按照远离放置位置的方向推动
|
||||||
for (const [dr, dc] of getNeighborPositions()) {
|
for (const [dr, dc] of getNeighborPositions()) {
|
||||||
|
|
@ -73,8 +74,9 @@ async function boop(game: BoopGame, row: number, col: number, type: PieceType) {
|
||||||
// 检查新位置是否为空或在棋盘外
|
// 检查新位置是否为空或在棋盘外
|
||||||
if (!isInBounds(newRow, newCol)) {
|
if (!isInBounds(newRow, newCol)) {
|
||||||
// 棋子被推出棋盘,返回玩家supply
|
// 棋子被推出棋盘,返回玩家supply
|
||||||
|
toRemove.add(part.id);
|
||||||
booped.push(part.id);
|
booped.push(part.id);
|
||||||
moveToRegion(part, state.regions.board, state.regions[part.player]);
|
moveToRegion(part, state.regions.board, state.regions.board, [newRow, newCol]);
|
||||||
} else if (!isCellOccupied(state, newRow, newCol)) {
|
} else if (!isCellOccupied(state, newRow, newCol)) {
|
||||||
// 新位置为空,移动过去
|
// 新位置为空,移动过去
|
||||||
booped.push(part.id);
|
booped.push(part.id);
|
||||||
|
|
@ -83,6 +85,13 @@ async function boop(game: BoopGame, row: number, col: number, type: PieceType) {
|
||||||
// 如果新位置被占用,则不移动(两个棋子都保持原位)
|
// 如果新位置被占用,则不移动(两个棋子都保持原位)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
await game.produceAsync((state: BoopState) => {
|
||||||
|
// 移除被吃掉的棋子
|
||||||
|
for (const partId of toRemove) {
|
||||||
|
const part = state.pieces[partId];
|
||||||
|
moveToRegion(part, state.regions.board, state.regions[part.player]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return { booped };
|
return { booped };
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,13 +22,14 @@ class BoopPartSpawner implements Spawner<BoopPart, Phaser.GameObjects.Container>
|
||||||
const x = BOARD_OFFSET.x + col * CELL_SIZE + CELL_SIZE / 2;
|
const x = BOARD_OFFSET.x + col * CELL_SIZE + CELL_SIZE / 2;
|
||||||
const y = BOARD_OFFSET.y + row * CELL_SIZE + CELL_SIZE / 2;
|
const y = BOARD_OFFSET.y + row * CELL_SIZE + CELL_SIZE / 2;
|
||||||
|
|
||||||
this.scene.tweens.add({
|
const tween = this.scene.tweens.add({
|
||||||
targets: obj,
|
targets: obj,
|
||||||
x: x,
|
x: x,
|
||||||
y: y,
|
y: y,
|
||||||
duration: 200,
|
duration: 200,
|
||||||
ease: 'Power2',
|
ease: 'Power2',
|
||||||
});
|
});
|
||||||
|
this.scene.addTweenInterruption(tween);
|
||||||
}
|
}
|
||||||
|
|
||||||
onSpawn(part: BoopPart) {
|
onSpawn(part: BoopPart) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue