API Gateway + WebSocket なオンライン対戦ゲームを作った
Twitter で #web1week という お題に沿ったサービスを1週間で作成、公開してみよう!という趣旨のハッシュタグがある。
9/7 ~ 9/13 に「2」というお題で開催されていて、友人に誘われて参加することにした。
どうせだから新しい技術に触れつつ何か作りたいなーと思い、 前から気になっていた Golang と WebSocket を使って簡単なオンライン対戦ゲームを作ることにした。
どんなアプリ?
相手よりも早く答えが2になる数式を作ることを目指す、一対一のオンライン対戦アプリ。 ソースコードはこちら。
ゲーム画面のイメージ。
システム構成
システム構成図。
API Gateway + Lambda で WebSocket 接続を受け付ける。 対戦相手のマッチングには SQS、部屋情報とユーザー情報の管理には DynamoDB を用いている。
処理の流れ
部屋は以下の状態遷移をする。
- WAITING: 対戦相手のマッチング中の状態
- PREPARING: 問題文をユーザーに通知し、その応答を待っている状態
- PLAYING: 対戦中の状態
処理の大まかな流れは以下の通り。
クライアントから join にリクエスト(new WebSocket())
- SQS を確認してタスクがない = マッチング中の対戦相手がいない場合は、状態が WAITING の部屋を新規作成して、現在のユーザを部屋に追加し SQS にタスクを登録。
- SQS を確認してタスクがある = マッチング中の対戦相手がいる場合は、部屋にユーザーを追加して状態を PREPARING に変更。
クライアントから problem にリクエスト(WebSocket の onopen ハンドラ内)
- 部屋の状態が PREPARING の場合は、部屋に所属するユーザーに問題文を通知して、部屋の状態を PLAYING に変更。
- 部屋の状態が PREPARING 以外の場合は、マッチング中なので待つ旨を通知。
クライアントは問題文を受け取り次第、画面に表示。
ユーザーが回答ボタンを押下したとき、回答内容を solve にリクエスト
- 正解の場合、勝敗結果を部屋に所属するユーザーに通知
- 不正解の場合、その旨をリクエストユーザーに通知
クライアントは勝敗結果を受け取り次第、画面に表示。leave にリクエストして接続断。
詰まったところと解決策
当初は DB から 状態が WAITING の任意の一部屋を検索し、 マッチングしようとしたが、DynamoDB ではレコードの全件取得をしないと無理そうだったので断念。 代わりに SQS を利用することに。
が後に、SQS の場合、ユーザーがマッチングを待たず離脱して接続断が発生した場合などに、 SQS に残ったゴミをケアする必要があることに気づいた。 エラーにすることで対応したが、UX 的にはあまり良くなさそう。
感想
Go 書くの楽しい!スクリプト言語とコンパイル言語のいいとこ取り感がある。 リンタなどのツールが公式で用意されているのも嬉しい。モジュールの扱いなど一通り触れてよかった。
DynamoDB の使いどころが難しい。検索が絡むと途端に DynamoDB だけで解決するのが困難になりそう。 個人開発だとお金もかかるから別に DB とかは使いたくないけれど、何か良い設計パターンがあるのだろうか。
マッチング系アプリの UX 設計の難しさ。待ちや通知のタイミングをこだわりだすと時間がどんどん溶ける。 今回は時間も限られていたから適当に決めたけれど、一度きちんと向き合って設計してみたい。