DEVELOPMENT
オンライン対戦を実装して大変だったこと
画面を同期するだけでは対戦になりません。公開情報と秘密情報を分け、重複・欠落・再接続を同じ試合へ戻すことが最大の課題でした。
コードとデータベースschemaは同時に進化する
オンライン機能の拡張では、クライアントが期待する列とSupabase schemaのずれが接続失敗につながりました。experience_bandのような参加条件用の値も、コードだけ先に増やしてはいけません。
SQLは再実行できる形で管理し、Route HandlerやRPCが未設定・旧schemaでも意図しない秘密情報を返さないよう確認します。公開環境でキーが未設定の時は、CPU戦などオンライン以外を壊さないフォールバックも必要です。
公開スナップショットと秘密状態を分ける
HP、手札枚数、山札枚数、状態、ターン、確定したカード、ログは対戦相手や観戦者へ公開できます。しかし手札内容、山札順、次に引くカード、非公開の対象選択は本人以外へ送れません。
そこでサーバーの正本から、参加者本人向けのprivate viewと、観戦を含むpublic viewを分けています。開発記事でもtoken、秘密手札、山札順など悪用可能な内部情報は公開しません。
sequenceNumberとstateVersionで順番を守る
Realtimeイベントは常に理想の順番で届くとは限らず、再接続時に重複して見えることもあります。各アクションへactionIdを持たせ、stateVersionとsequenceNumberで古い更新、重複、欠落を判断します。
欠落を検知した場合は、曖昧なローカル状態のまま続けず、最新の公開または本人用スナップショットを取得して差分へ追いつきます。この時、入力を一時無効化して同じカードの二重使用を防ぎます。
切断は試合終了ではなく、復帰待ちの状態
モバイルではアプリ切り替えや回線変更が起きます。一定時間内の再接続を想定し、参加者IDと安全な復帰情報から同じ席へ戻し、最新状態を復元します。
復帰できない場合の自動処理は既存モードのルールへ合わせます。1対1、2対2、FFAで別々のオンライン基盤を作らず、同じサーバー正本とイベント順序を拡張しました。
FFAでは一人増えるだけで公開状態が増える
3〜10人戦では生存者、脱落順、順位、対象、次の行動者、集中攻撃軽減、全体攻撃補正を共有します。脱落者は操作権を失い、同じ試合の公開情報を見る観戦へ移ります。
人数が増えても手札内容は漏らさず、観戦者へは公開イベントだけを送ります。同期の派手さより、『違う端末で同じ勝敗へ到達する』ことを優先した実装です。