boardgame-core/skills/game-module.md

3.0 KiB
Raw Blame History

如何编写游戏模组

要求

游戏模组需要以下接口导出:

import {createGameCommandRegistry, IGameContext} from "boardgame-core";

// 定义类型
export type GameState = {
    //...
}
export type Game = IGameContext<GameState>;

// 创建mutative游戏初始状态
export function createInitialState(): GameState {
    //...
}

// 运行游戏
export async function start(game: Game) {
    // ...
}

流程

  1. 确认规则。

规则应当存放在rule.md

描述一个桌面游戏的以下要素:

  • 主题
  • 配件
  • 游戏布置形式
  • 游戏流程
  • 玩家行动
  • 胜利条件与终局结算
  1. 创建类型:创建types.ts并导出游戏所用的类型。
  • 为游戏概念创建字符串枚举类型。
  • 使用@/core/part.ts为游戏配件创建对象类型。
  • 使用@/core/region.ts为游戏区域创建容器类型。
  • 设计游戏的全局状态类型。

游戏使用mutative不可变类型驱动。

  1. 创建prompts

使用prompt来描述需要玩家进行的行动命令schema。

  • prompt包含一个id和若干params。
  • 每个param通常指定某个配件id、某个枚举字符串、或者某个数字。
export const prompts = {
    play: createPromptDef<[PlayerType, number, number]>(
        'play <player> <row:number> <col:number>',
        '选择下子位置')
}
  1. 创建游戏流程:
export async function start(game: TicTacToeGame) {
    while (true) {
        // game.value可获取当前的全局状态
        const currentPlayer = game.value.currentPlayer;
        const turnNumber = game.value.turn + 1;
        const turnOutput = await turn(game, currentPlayer, turnNumber);

        // 更新状态
        await game.produceAsync(state => {
            state.winner = turnOutput.winner;
            if (!state.winner) {
                state.currentPlayer = state.currentPlayer === 'X' ? 'O' : 'X';
                state.turn = turnNumber;
            }
        });
        if (game.value.winner) break;
    }

    return game.value;
}
async function run(game: TicTacToeGame, turnPlayer: PlayerType, turnNumber: number) {
    // 获取玩家输入
    const {player, row, col} = await game.prompt(
        prompts.play,
        (player, row, col) => {
            if (player !== turnPlayer) {
                throw `Invalid player: ${player}. Expected ${turnPlayer}.`;
            } else if (!isValidMove(row, col)) {
                throw `Invalid position: (${row}, ${col}). Must be between 0 and ${BOARD_SIZE - 1}.`;
            } else if (isCellOccupied(game, row, col)) {
                throw `Cell (${row}, ${col}) is already occupied.`;
            } else {
                return {player, row, col};
            }
        },
        game.value.currentPlayer
    );

    placePiece(game, row, col, turnPlayer);
}
  1. 创建测试

基于@/core/game.ts的createGameContext来测试。

为每种游戏结束条件准备至少一条测试。 为每种玩家行动至少准备一条测试。