誰かの役に立てばと思って、実装の際の技術的トピックについて思い出深い部分をメモしておく。
大まかな構成
- EC2 small のリザーブドインスタンスにアプリ+DBその他すべて乗せてた。リザーブ3年前払いで360$、ランニングコストが10~20$程度、平均すると30$/月くらいで動いてた。
- NodeJS(Vanilla JS) + Express
- TSはまだ今ほど覇権ではなかった
- Socket.IO
- 今は廃れているかもしれないが、WebSocketが使えない環境のfallbackをlong pollingで補ってくれる双方向通信ライブラリ。今なら裸のwsでやると思う。
- ゲーム中の通信はすべて双方向通信で、新規接続時には一度だけ過去イベントをすべて送りつける、その後はチャットアプリのブロードキャスト風に新しい会話などは垂れ流すような構成だったはず。
- MongoDB
- 当時はイケてた
- ユーザーデータ、認証、過去ログなどをすべて保存。複雑なトランザクションなども無かったので、ざっくばらんに書き込んでた。
- ちょっと複雑な統計取ろうとすると期待したindexを使ってくれなかったりで、かなり嫌な思いをした。
- Redis
- ところどころで使った気がする
実行中の村はすべてオンメモリ
なぜかいけると思った。人狼ゲームは所詮テキストデータなので、1ゲームが肥大化してもせいぜいキロバイト単位。へんなメモリリークさえ踏まなければ(確か一回だけ踏み抜いたと思う)いけると思った。ゲーム終了時と感想戦終了の二回にわけてMongoDBにまとめて書き込むようにしてたと思う。 このやり方はOOMでクラッシュするとゲーム中のデータがすべて飛んでしまうリスクはあったが、結局最後までOOMで苦労することはほぼ無かった。 でも、これがリスクに見合ったベネフィットがあったかは正直わからん。今なら多分やらないとおもう。
デプロイ戦略(バージョン管理)
ここは結構気が利いたことをやった。 ゲーム画面のURLにはデプロイごとにv0からv15までをループするバージョン番号が追加されていて、ゲーム実行中にデプロイが行われたときにもゲーム開始時のバージョンで起動したプロセスに接続し続ける仕組みだった。ループするのはnginxか何かの設定ファイルで結局ポート番号をハードコードする必要があったから。サーバー1台の中でポートとプロセスを使ってやりくりするため。
アプリの起動時には数字が書いてあるgenerationファイルを読んで、自分があるべきバージョン番号を確認してport5000+vをリッスンする。上限16ってのは確かnginxも使ってたか?そこでポート番号をハードコードしたかったからだと思う。
アプリの前には小さいNodeJS製のリバースプロキシが設置していてあって、URLをパースし、ゲーム画面ならv0~v15をport5000+vに書き換えてリバースプロキシする。それ以外(ロビーやその他画面)は最新へ。
デプロイが走ると、以下の手順でアプリを更新する
generationファイルのバージョン番号をv=((v+1) mod 16)する。- proxyは、ゲーム以外のリクエストは最新のgenerationのポートにリクエストを流す
- 更新前のバージョンのアプリに、SIGTERM(多分。HUPだったかも)を送信
- JSのメインループ側は、SIGTERMを受け取ったら終了準備状態に入る。新規ゲームの開始を拒否、実行中のゲームがすべて終了したらプロセスをexitする
- 新規プロセス起動時には、v.pid(例:1.pid)みたいに自分のジェネレーションのpidを追跡できるようにファイルを書き出してから起動する
こんなかんじの仕組みで、走り始めた村に悪影響などがでないよう保護しつつ、新しい実装をどんどんデプロイしていけた。
UI
ベタ書きのjQueryでよく頑張ったと思う。10年前(!!)まだShadowDOMは発見されてなかった気がする。 チャットのスクロール追随と発言者フィルタには結構な時間かけてパフォーマンスを最適化してた記憶がある。詳細はうろ覚えだが、スクロール位置が末端(最新)かどうか地道に場合分けしつつ、挙動を切り替えてた気がする。フィルタは確か、再レンダリングをしないように全部CSSでまとめたらいきなりスムーズになった。。。んだったかな。よく覚えてないや。
ゲームロジック
人狼ゲーム自体は相対的にはシンプルなルールではあるので、実装には苦労しなかったが、テストが難しかった。結局今でもどういうテストが必要だったか可能だったか答えはわからない。役職を追加するたびに、プレイヤーの皆様になんだかんだ色々迷惑を掛けていた気がする。この辺は将来の課題ですね。
リプレイ機能
えけけ鯖ではゲーム内のすべての記録にタイムスタンプを保存してあって、過去ログをプレイ中と同じUI、同じ速度で再生するという機能があって、これはCOのタイミングや空気感が重要になるチャット人狼において良いチャレンジだったと思う。
その他
- ゲーム終了時のBGM(自作)とアニメーション演出の同期なんかもちょっとした工夫があって気に入っていた。
- mobileは一切対応しなかった。にも関わらずずっとスマホでプレイしているという猛者もいて、人間って凄いって思った
- 一個だけめっちゃ悔しかったのは、デスノート村を実装して大人数集めて開始したのに、いざ始まってみたらUIが参加人数分のアイコンを表示しきれずポシャったやつ。あれは落ち込んだ。
などなど昔語りでした。