メインコンテンツへスキップ
記事一覧に戻る
programmingリバーシ開発の舞台裏 (2/3)

Cloudflare Durable Objectsでリバーシのリアルタイム対戦を実装する

Cloudflare Workers の Durable Objects と WebSocket を使ってリバーシ(オセロ)のリアルタイムオンライン対戦を実装する方法を解説。サーバー権威型の設計、パス自動判定、ターン管理など、ボードゲーム特有の課題と解決策を紹介します。

2026-04-0617 min readAdabana Saki
-
リバーシオセロCloudflare WorkersDurable ObjectsWebSocketリアルタイム通信TypeScriptゲーム開発オンライン対戦
シリーズ中級者〜上級者

リバーシ開発の舞台裏

リバーシ(オセロ)ゲームの開発で使ったAIアルゴリズム、オンライン対戦の実装、評価関数の設計を解説

進捗2 / 3

Cloudflare Durable Objectsでリバーシのリアルタイム対戦を実装する

前回の記事では、リバーシAIを3段階の難易度で実装しました。今回は、そのリバーシをオンライン対戦に対応させます。

「リアルタイム対戦」と聞くと複雑そうですが、Cloudflare Durable Objectsを使えば、サーバーレスなのにステートフルなゲームサーバーを簡単に作れます。

なぜDurable Objectsなのか?

ボードゲームのオンライン対戦には3つの要件があります:

Text

Durable Objectsはこの3つをすべて満たします:

Text

各「対戦部屋」が1つのDurable Objectインスタンスになります。部屋ごとに独立した状態を持ち、WebSocket接続も直接管理できます。

アーキテクチャ全体像

Text

OthelloRoom クラスが DurableObject を継承し、盤面状態・プレイヤー情報・WebSocket接続の3つをまとめて管理します。

部屋のライフサイクル

対戦部屋は4つの状態を遷移します:

Text

1. waiting(待機中)

プレイヤーが部屋に参加し、準備完了を待つフェーズです。

TypeScript

色の割り当てはシンプルに**先に入った人が黒(先手)**です。

2. countdown(カウントダウン)

2人ともReadyになると、3秒のカウントダウンが始まります:

TypeScript

カウントダウン中にReadyを解除すると、待機状態に戻ります。これにより「準備ができていないのにゲームが始まる」事態を防ぎます。

3. playing(対戦中)

ゲーム開始時に盤面をリセットし、10分のタイマーを開始します:

TypeScript

タイマーの通知は残り60秒以下で毎秒、それ以外は30秒ごとです。これでクライアントの負荷を抑えつつ、終盤の緊迫感を演出します。

4. finished(終了)→ リマッチ

ゲーム終了後、プレイヤーの状態をリセットして待機状態に戻します:

TypeScript

部屋自体は破棄せず、再びReadyすれば新しいゲームを始められます。

サーバー権威型の設計

オンライン対戦で最も重要な設計判断が「誰がゲームロジックを実行するか」です。

Text

OthelloRoomでは、クライアントから受け取るのは座標だけです。裏返しの計算、合法手の判定、勝敗判定はすべてサーバー側で行います:

TypeScript

3段階のバリデーション(ターン → 範囲 → 合法手)を通過した手だけが盤面に反映されます。

リバーシ特有の課題:パスの自動判定

リバーシには「打てる場所がないときはパス」というルールがあります。これはチェスや将棋にはない、リバーシ特有の仕組みです。

オンライン対戦では、パスの判定をサーバーが自動で行う必要があります。プレイヤーに「パス」ボタンを押させると、打てる場所があるのにパスする不正が可能になるためです。

3つのケース

手を適用した後、次にどうなるかは3パターンあります:

Text

実装コードを見てみましょう:

TypeScript

Case 2のパス処理を詳しく見る

パスが発生したとき、サーバーは2つのメッセージを送信します:

Text
TypeScript

具体例で見てみましょう:

Text

情報の隠蔽:プレイヤーごとの有効手

セキュリティ上重要なのが、各プレイヤーに自分の有効手だけを送ることです:

TypeScript
Text

なぜこれが重要なのか?もし相手の有効手が分かると、「相手はここしか打てないから、その裏をかこう」という不公平な戦略が可能になります。リバーシでは有効手の数が戦略に直結するため、この隠蔽は公平性を保つために必要です。

切断への対応

オンライン対戦では、プレイヤーが突然切断する可能性を常に考慮する必要があります:

TypeScript
Text

WebSocketのcloseイベントとerrorイベントの両方で handlePlayerLeave を呼ぶことで、正常切断・異常切断の両方に対応しています。

メッセージプロトコル

クライアントとサーバー間のメッセージは、すべてJSON形式で型安全に定義されています:

Text

クライアントが送るメッセージは最小限(意図だけ)で、サーバーが返すメッセージには計算結果がすべて含まれています。これがサーバー権威型の特徴です。

ゲーム終了の3パターン

ゲームが終了する条件は3つあります:

Text

終了時には、両プレイヤーに詳細な結果を送信します:

TypeScript

まとめ

リバーシのオンライン対戦をDurable Objectsで実装するポイントをまとめます:

Text

Durable Objectsの最大の利点は、WebSocketの接続管理とゲーム状態の保持を1つのクラスにまとめられることです。従来のようにRedisやデータベースで状態を外部管理する必要がなく、コードの見通しが良くなります。

次回の記事では、AIの「強さ」を決定する評価関数の設計に焦点を当て、位置評価・角の価値・モビリティ・終盤戦略の4要素を深掘りします。

実際のオンライン対戦は ADA Labのリバーシゲーム で体験できます。