聚场 PlayHub插件开发

用 GamePlugin 契约接入新玩法

平台 Shell 负责房间、身份、实时同步、计时与错误边界;玩法全部封装在插件里。新增一款游戏 = 实现下面的契约 + 在 registry 注册,无需改动任何页面。

已注册插件

🎲 骰王争霸

五颗骰子,全场比大小

ID:dice-king
版本:1.0.0
人数:1-50
作者:聚场 PlayHub

阶段:LOBBY → COUNTDOWN → ROLLING → ROUND_RESULT → FINAL_RESULT

第一步:实现契约

export interface GamePlugin<TState = Record<string, unknown>> {
  metadata: PluginMetadata;          // id / 名称 / 人数范围 / 版本
  defaultConfig: PluginConfig;       // totalRounds、roundSeconds 等
  validateConfig(c): ValidationResult;
  initialState: TState;
  phases: Phase[];                   // LOBBY / COUNTDOWN / ROLLING / ...
  handleCommand(cmd, room): Promise<void>;   // 只发意图,裁决在服务端
  reduceEvent(state, event): TState;         // 事件信封 -> 本地视图状态
  score(snapshot): LeaderboardEntry[];       // 从权威快照派生排行榜
  ScreenComponent: ComponentType<PluginViewProps>;   // 大屏
  PlayerComponent: ComponentType<PluginViewProps>;   // 手机
  HostControlPanel: ComponentType<PluginViewProps>;  // 主持
  RulesComponent: ComponentType;                     // 规则
}

第二步:最小插件示例并注册

// src/lib/game/quick-tap/index.tsx
import type { GamePlugin } from "@/lib/game/contract";

export const quickTapPlugin: GamePlugin = {
  metadata: {
    id: "quick-tap", name: "极速点击", tagline: "10 秒手速对决",
    description: "限时内点击次数最多者胜。", icon: "⚡", version: "1.0.0",
    minPlayers: 2, maxPlayers: 50, author: "PlayHub",
  },
  defaultConfig: { totalRounds: 3, roundSeconds: 10 },
  validateConfig: (c) => ({
    ok: (c.totalRounds ?? 3) >= 1 && (c.roundSeconds ?? 10) >= 10,
    errors: [],
  }),
  initialState: {},
  phases: ["LOBBY", "COUNTDOWN", "ROLLING", "ROUND_RESULT", "FINAL_RESULT"],
  async handleCommand(cmd, room) {
    if (cmd.type === "TAP") await room.actions.roll();
  },
  reduceEvent: (s) => s,
  score: () => [],
  ScreenComponent: () => <div>大屏视图</div>,
  PlayerComponent: () => <div>手机视图</div>,
  HostControlPanel: () => <div>主持面板</div>,
  RulesComponent: () => <div>规则</div>,
};

// src/lib/game/registry.ts
registerPlugin(quickTapPlugin);

约定