dwgx@blog:~$dwgx
> cd ../posts

EmperorGame: 単一の Java プロセスで動くオンライン対戦カードゲーム

// created

EmperorGame はブラウザで遊ぶオンライン対戦カードゲームだ。遊び方は皇帝・市民・奴隷のじゃんけん系で、ルールは単純だが心理戦は十分にある。バックエンドはまるごと一つの Java プロセスに詰め込んであり、リアルタイム通信の練習として作った。

小さなゲームのためにサービスを大量に立てたくなかったので、バックエンドは単一プロセスにした。対戦のリアルタイム通信は Java-WebSocket が担当し、JDK 付属の HttpServer がついでにフロントエンドの静的ページといくつかの JSON API をホストする。Spring もサーブレットコンテナもなし。mvn clean package で fat jar を作り、java -jar でそのまま起動する。Vue 3 + Vite のフロントエンドのビルド成果物を内蔵 HTTP に放り込む。本番では前段に Nginx を同一ドメインのリバースプロキシとして置き、/ は静的、/api/ は HTTP、/ws/ は WebSocket に振り分け、HTTPS 下では自然に WSS に切り替わる。デフォルトポートは WebSocket が 13337、HTTP が 13338

対戦フローは素朴だ。クライアントが match_online を送ってキューに入り、人数が揃うと一つの GameRoom にマッチングされる。毎ターン双方が play_online でカードをロックし、両者が ready になるとサーバーが判定してブロードキャストする。判定はあの相克の輪、皇帝 > 市民 > 奴隷 > 皇帝。裏切り者はズルをして皇帝にだけ勝ち、他には全敗する。狂人はさらに滅茶苦茶で、強制的に引き分けにして上にランダムイベントを発動させる。皇帝は負け手を一つ出した瞬間に終わり、奴隷が皇帝を倒すと即勝ち、山札が尽きたら終了。裏切り者か狂人が登場したターンはカードが強制的に表向きになり、駆け引きが一気に緊張する。山札には standardrandom の二モードがあり、コンソールで /set randomcard on|off を叩くか DECK_MODE 環境変数で切り替える。オンラインで遊びたくない場合は、コンピュータと対戦する AiGame シングルプレイモードもある。

オンライン対戦では相手が誰かを認識する必要がある。登録とログインは POST /api/register/api/login を通し、accountId と署名付きの token を返す。token は HMAC-SHA256 で署名され、24 時間有効で、session_tokens テーブルに保存される。ユーザーとログは MySQL に utf8mb4 で格納され、起動時にテーブルが自動作成される。フロントエンドは accountIdtoken を localStorage にキャッシュし、WebSocket 接続時に身元を持たせる。ほかに署名鍵用の AUTH_SECRET、CORS を制御する ALLOWED_API_ORIGINS、WSS を有効化する一組の keystore 変数も用意してある。

いくつか落とし穴も踏んだ。shade は jar を二つ吐くので、-shaded サフィックス付きの方を実行しないと “no main manifest” が出る。CORS で Origin をエコーバックする件や WSS の keystore といった細かい部分は、実際に本番に載せて初めて詳細が多いと分かった。ルールと Nginx の例はリポジトリの gamelogic.md にすべて書いてある。