Webエンジニア向けプログラミング解説動画をYouTubeで配信中!
▶ チャンネル登録はこちら

【ITニュース解説】What Building Four Browser Card Games Taught Me About State

2026年10月22日に「Dev.to」が公開したITニュース「What Building Four Browser Card Games Taught Me About State」について初心者にもわかりやすく解説しています。

作成日: 更新日:

ITニュース概要

複数のカードゲーム開発を通じ、ゲームのルール処理と画面表示の連携に苦労した。問題を解決するには、ゲームの「状態」を変えるのはエンジンのみとし、UIは結果を表示するに徹するべきだ。アニメーションは表示中の状態を固定して使い、複雑な動作は一つの処理として完結させると、バグが減りテストも容易になる。

ITニュース解説

ウェブブラウザで複数のトランプゲーム、具体的にはスペード、ハーツ、ユーカレ、ブリッジを開発する過程で、開発者はゲームのルールそのものではなく、「ゲームの現在の状況(状態)」と「それをプレイヤーにどう見せるか(表示)」の境界で多くの難しい問題に直面した。例えば、カードを移動させている途中でそのカードの絵柄が変わってしまったり、ラウンドの最後のカード処理が二重に行われたり、ゲームに参加していないはずのプレイヤーがまるでアクティブであるかのように見えたり、操作権限が期待通りでなかったりといった現象が発生した。これらは、ゲームのルールを誤解していたからではなく、システム内部の「状態」と画面上の「表示」の間にずれが生じたことが原因だった。

この経験から得られた重要な教訓の一つは、システムのどの部分を「共有」し、どの部分を「分離」すべきかという判断だった。当初、開発者は「カードゲーム」という一つの大きな枠組みを作り、すべてのゲームで共通のルールエンジンを使おうと考えた。しかし、スペード、ハーツ、ユーカレ、ブリッジはそれぞれ、入札の仕方、得点計算、トランプの決め方、プレイヤー間の役割分担など、基本的なルールが大きく異なる。そのため、一つの共通エンジンでこれらすべてをまかなおうとすると、「このゲームはカードを配るのか?」や「このゲームはダミープレイヤーがいるのか?」といった無数の条件分岐(フラグ)でいっぱいになり、かえって複雑で扱いにくいシステムになってしまった。

最終的に、開発者は「プレイヤーが常に一貫性を期待する部分」だけを共有し、「ゲームのルールや言葉遣いが異なる部分」は個別に保持するという方針に変更した。具体的には、カードの見た目、手札の並べ方、カードの配り方や集め方のアニメーション、テーブルのデザイン、メニュー操作、画面レイアウトといった「見た目や操作感」に関する部分は共通化した。一方で、各ゲームの「ルールエンジン」は完全に分離して個別に開発した。これにより、無駄な共通化による複雑さを避け、それぞれのゲームの特性に合わせた柔軟な設計が可能になった。

次に、ゲームの状態を管理する上で極めて重要だと分かったのは、「何が本当か」を決定する中心となる場所を一つに定めることだった。以前は、画面表示を担当する部分(UI)が「このカードは出せるか?」「ゲームは終わったか?」といったルールの判断を独自に行うことがあった。しかし、UIとゲームの核心部分(エンジン)が同じ質問に対して異なる答えを出すようになると、一貫性のない動作やバグが発生した。この問題を解決するため、開発者は「UIは何か行動をしたい時に、それをエンジンに伝え、エンジンの判断の結果を待つだけ」という厳格なルールを設けた。つまり、カードを出す、手を引くといったプレイヤーの行動は、UIからエンジンに「アクション」として送られ、エンジンがそのアクションがルール上適切かを判断し、新しいゲームの状態を計算してUIに返す。これにより、ボタン操作、キーボード入力、コンピュータプレイヤーの行動、アニメーションの実行といった様々なきっかけからゲームが進行しようとする場合でも、エンジンだけが唯一の「真実を語る」場所となり、状態の一貫性が保たれるようになった。このアプローチは、ゲームのテストを劇的に容易にする効果もあった。エンジン部分は画面表示などの複雑な要素から独立しているため、純粋なルールの正しさを非常に高速に検証できるようになった。

アニメーションの扱方も重要な改善点だった。前述の「カードの絵柄が途中で変わる」バグは、アニメーション中に元のデータが変更されてしまったために発生した。この解決策は、アニメーションが始まる時点で、動かすカードの情報を「スナップショット(静的な複製)」として取得することだった。アニメーションはこのスナップショットの情報を参照するため、アニメーション中に元のデータ(手札)が変化しても、表示されるカードの絵柄や状態が変わることはなくなった。これにより、UIのレンダリングも効率化され、例えば相手の手札からカードが減った場合でも、手札全体を再描画するのではなく、減ったカードだけを更新するといったきめ細やかな表示が可能になり、よりスムーズで安定したゲーム体験を提供できるようになった。

さらに、一連のゲーム操作を「トランザクション」として扱う考え方も導入された。例えば、トリック(全員がカードを出し終える一連の行為)が完了する際、最後のカードが置かれ、勝者が決定し、カードが集められ、次のラウンドに進むか得点表示に切り替わる、という一連のステップがある。以前は、この一連の処理が複数の場所で並行して実行されようとし、結果としてトリック完了の表示が二重になったりするバグが発生した。これを防ぐため、開発者はトリック完了を一つのまとまった処理(トランザクション)として扱い、その処理が完了するまでは他の処理が割り込まないようにロックする仕組みを導入した。これにより、「最後のカードが置かれた瞬間」と「次の行動の準備が整った瞬間」を明確に区別し、一貫したゲーム進行を実現した。

プレイヤーの操作権限についても、表面的な判断ではなくより深い分析が必要であることが分かった。ブリッジゲームでは、特定の状況でプレイヤーが自分だけでなく「ダミー」と呼ばれる相方の手札も操作することがある。また、ユーカレでは、パートナーがゲームから一時的に外れる「単独プレイ」が存在する。これらのケースでは、「現在のターンが自分の席なのか」という単純な判断だけでは不十分で、「現在の契約において、この席の操作権限が誰にあるのか」という、より複雑な判断ロジックが必要だった。この「操作権限」を明確な関数として定義することで、UI上のヒント表示、出せるカードのハイライト、コンピュータプレイヤーの行動判断といった全てが、この共通のロジックに基づいて一貫して動作するようになった。

コンピュータプレイヤーの実装においても、重要な配慮があった。ブラウザゲームでは、全てのプレイヤーの手札がメモリ上に存在するため、誤ってコンピュータプレイヤーに「相手の手札」といった、本来知るべきではない情報が渡されてしまう可能性がある。これは意図せずコンピュータが「チート」してしまう状況を生み出す。開発者はこれを防ぐため、コンピュータプレイヤーに渡す情報を「そのプレイヤーがルール上知っているべき情報のみ」に厳しく制限した。例えば、そのコンピュータプレイヤーの手札、現在のトリック、これまでのトリック、契約内容、公開された入札状況などだ。これにより、実装ミスによる不公平を防ぎ、コンピュータプレイヤーが「正直な」判断をするように強制することができた。

テスト戦略においても、完全なゲームシミュレーションテストの価値が強調されている。個々のルールが正しく機能するかを確認する「単体テスト」はもちろん重要だが、それだけでは見つけられないバグが多く存在した。例えば、ゲーム全体を通して「常に正しい数のユニークなカードが配られているか」「プレイヤーは決してルールに反してカードを出さないか」「各トリックで全ての手札からカードが1枚ずつ減っているか」「ラウンドが途中で止まらずに得点計算まで到達するか」といった、ゲーム全体を何百回、何千回とシミュレーションして確認する「全ゲームシミュレーションテスト」が非常に有効だった。これは、ターン進行の複雑な相互作用によって発生するバグを見つけるのに特に役立った。

また、ウェブサイトの構成においても工夫が凝らされた。ゲームのルール解説やFAQといった静的な情報は「Astro」というツールで生成されたHTMLページとして提供され、実際にゲームがプレイできるテーブルの部分だけはTypeScriptで書かれた動的なプログラムが読み込まれるようにした。この分離により、ゲームのパフォーマンスが向上するだけでなく、ルール解説ページは安定したURLを持ち、JavaScriptが読み込まれる前でも内容が参照できるといった利点が得られた。これにより、プレイヤーはゲームをプレイしながら、同じページ内でルールを確認することも可能になり、ユーザー体験が向上した。

この開発経験を通じて、開発者は今後のプロジェクトで特に三つの境界線を明確にすることが重要だと学んだ。一つは、ゲームの状態変化は必ずエンジンだけが唯一の所有者として管理すること。二つ目は、アニメーションはデータの「スナップショット」を利用し、動的に変化する配列のインデックスなどからカードの情報を推測しないこと。三つ目は、非同期で行われる一連の処理(例:カードを出すアニメーションから次のターンへの移行)は、単一の担当者が責任を持ち、他の処理が割り込まないようにロックし、途中でキャンセルされる経路も明確にすること。

カードゲームは画面に収まるため、一見するとシンプルなように見えるかもしれない。しかし、隠された情報、厳密なターン順序、遅延を伴うアニメーション、パートナーシップ、ラウンド間の状態遷移といった要素が組み合わさることで、その内部状態は非常に複雑になる。開発者が最も苦労したバグの多くは、この「二つの異なるシステム(エンジンとUI)が、それぞれは正しいと思っているのに、実際には食い違っている」という状況から生まれていた。この「状態」の境界を明確に定義し、厳格に管理することが、洗練されたゲーム体験を提供するために何よりも重要であるという教訓が、このプロジェクトから得られた最も大きな成果と言えるだろう。

関連コンテンツ

関連IT用語

関連ITニュース