はじめに
「三目並べや大富豪みたいなターン制ゲームをWebで作りたい」と思ったとき、真っ先にぶつかる壁がある。ゲームのルールそのものよりも先に、状態管理・通信・同期処理を書く羽目になることだ。
誰のターンか、手番は正しいか、勝敗判定はどうするか、そして複数プレイヤー間で状態をどう同期させるか。ゲーム本体のロジックを書き始める前に、この手の「土台」だけでかなりの時間を溶かしてしまう。
boardgame.ioは、この土台部分をまるごと肩代わりしてくれるJavaScriptエンジンです。開発者は「ゲームの状態がどう変化するか」を関数として書くだけで、ネットワーク同期やストレージのコードを一切書かずにマルチプレイヤー対応のゲームが完成します。
boardgame.ioとは
boardgame.ioは、ターン制ゲームを構築するためのステート管理・マルチプレイヤーネットワーキングエンジンです。三目並べのような単純なゲームから、フェーズや役割が複雑に絡み合うカードゲームまで、幅広いジャンルのゲームロジックを表現できます。
ビューレイヤーには依存しない設計になっており、React向けのバインディングが公式に用意されているほか、Vanilla JSでも組み込めます。ロジックとUIがきれいに分離されているため、「まずルールだけ作って、あとから見た目を整える」という開発の進め方がしやすいのも特徴です。
主な特徴
- 宣言的なゲーム定義 -
setupとmovesを書くだけで、状態遷移のロジックを表現できる - 自動状態同期 - クライアント・サーバー間の状態同期を自前で実装する必要がない
- フェーズ管理とAI対応 - ゲームの局面(フェーズ)ごとにルールを切り替えたり、自動対戦用のボットを組み込んだりできる
- タイムトラベル機能 - ゲームログから任意の時点の盤面を再現できるため、デバッグや観戦機能の実装が容易
インストール
npm install boardgame.io
Yarnを使っている場合は次のようにインストールします。
yarn add boardgame.io
Reactと組み合わせて使う場合は、boardgame.io/reactからClientをインポートするだけで済むため、追加のパッケージは不要です。
基本的な使い方
まずはボードゲームの定番、三目並べ(Tic-Tac-Toe)を例にゲームロジックを定義してみます。
// game.js
export const TicTacToe = {
setup: () => ({
cells: Array(9).fill(null),
}),
moves: {
clickCell: ({ G, ctx }, id) => {
if (G.cells[id] !== null) {
return; // 無効な手は何もしない
}
G.cells[id] = ctx.currentPlayer;
},
},
turn: {
minMoves: 1,
maxMoves: 1,
},
endIf: ({ G, ctx }) => {
if (isVictory(G.cells)) {
return { winner: ctx.currentPlayer };
}
if (G.cells.every((cell) => cell !== null)) {
return { draw: true };
}
},
};
function isVictory(cells) {
const lines = [
[0, 1, 2], [3, 4, 5], [6, 7, 8],
[0, 3, 6], [1, 4, 7], [2, 5, 8],
[0, 4, 8], [2, 4, 6],
];
return lines.some((line) => {
const [a, b, c] = line;
return cells[a] !== null && cells[a] === cells[b] && cells[a] === cells[c];
});
}
movesに定義した関数は、G(ゲームの状態)を直接書き換えるだけで反映されます。内部でImmerが使われているため、イミュータブルな更新を意識せずに書けるのがうれしいポイントです。
次に、このゲームロジックをReactコンポーネントに接続します。
// App.jsx
import { Client } from "boardgame.io/react";
import { TicTacToe } from "./game";
function Board({ G, ctx, moves }) {
const onClick = (id) => {
if (G.cells[id] !== null) return;
moves.clickCell(id);
};
const winner = ctx.gameover?.winner;
const status = winner !== undefined
? `プレイヤー ${winner} の勝利です`
: `現在の手番: プレイヤー ${ctx.currentPlayer}`;
return (
<div>
<p>{status}</p>
<div style={{ display: "grid", gridTemplateColumns: "repeat(3, 50px)" }}>
{G.cells.map((cell, id) => (
<button key={id} onClick={() => onClick(id)}>
{cell !== null ? cell : ""}
</button>
))}
</div>
</div>
);
}
const App = Client({ game: TicTacToe, board: Board });
export default App;
Clientにゲーム定義とボードコンポーネントを渡すだけで、状態管理と描画がひも付いた1つのReactコンポーネントが出来上がります。ここまでの時点で、まだサーバーやネットワーク処理は一切書いていません。
実践的なユースケース
マルチプレイヤー対応にする
ローカルで動くゲームができたら、次はオンライン対戦への拡張です。boardgame.io/multiplayerのトランスポートを指定するだけで、複数ブラウザ間の状態同期が有効になります。
import { Client } from "boardgame.io/react";
import { SocketIO } from "boardgame.io/multiplayer";
import { TicTacToe } from "./game";
import { Board } from "./board";
const App = Client({
game: TicTacToe,
board: Board,
multiplayer: SocketIO({ server: "localhost:8000" }),
});
export default App;
サーバー側はboardgame.io/serverが提供するServerを使って、数行で立ち上げられます。
// server.js
const { Server, Origins } = require("boardgame.io/server");
const { TicTacToe } = require("./game");
const server = Server({
games: [TicTacToe],
origins: [Origins.LOCALHOST],
});
server.run(8000);
これだけで、複数のクライアントが同じゲームサーバーに接続し、リアルタイムに手番を同期しながら対戦できる環境が整います。ゲームロジック(game.js)は一切変更していない点がポイントで、ロジックとネットワーク層が完全に分離されていることがわかります。
フェーズを使った複雑なゲームの表現
カードゲームのように「配札フェーズ」「入札フェーズ」「プレイフェーズ」のような局面ごとのルールが必要な場合は、phasesを使って表現できます。
export const CardGame = {
setup: () => ({ hands: {}, bids: {} }),
phases: {
deal: {
start: true,
moves: { drawCard: ({ G, ctx }) => {/* 配札処理 */} },
endIf: ({ G }) => Object.keys(G.hands).length > 0,
next: "bidding",
},
bidding: {
moves: { placeBid: ({ G, ctx }, amount) => {
G.bids[ctx.currentPlayer] = amount;
}},
next: "playing",
},
playing: {
moves: { playCard: ({ G, ctx }, card) => {/* プレイ処理 */} },
},
},
};
フェーズごとに使えるmovesやルールを切り替えられるため、「入札が終わるまではカードをプレイできない」といった制約を自然に表現できます。
まとめ
boardgame.ioを使うと、ターン制ゲームの開発で本来一番大変なはずの「状態管理」「マルチプレイヤー同期」「フェーズ管理」を、フレームワーク側にほぼ任せられます。開発者がやることは、setupとmovesを中心にゲームルールを宣言的に書くことだけです。
三目並べのような小さな題材から始めて、慣れてきたらフェーズやAI対応、ロビー機能なども試してみると、boardgame.ioが持つ本来の強みがより実感できるはずです。まずは公式ドキュメントのチュートリアルを片手に、手元で小さなゲームを1つ動かしてみるところから始めてみてください。