アルバムバトラー

https://github.com/ABfry/album-battler

Next.js

Go

AWS

MySQL

Terraform

アルバムの写真で戦う!AI×リアルタイム即興フォトバトル

IH

guu

RAIT-09

moti

推しアイデア

スマホの写真フォルダがそのまま武器になる!AIが出す予測不能なお題に合わせて手持ちの画像を提出 埋もれた「奇跡の一枚」や「黒歴史」で笑い合える新感覚パーティーゲーム!

作った背景

ハッカソンのテーマ「Spark」から「火花散るバトル」を連想し、そこに「昔のアルバムを見返す楽しさ」を掛け合わせました

推し技術

・ Context APIで快適なUX ・ 保守性の高い設計 ・ TerraformとGitHub ActionsでCI/CD

プロジェクト詳細

遊び方

image

  1. ルームを作る image
  2. ルームナンバーを共有して、対戦相手を募集 image
  3. メンバーが揃ったら、ゲームを開始 image
  4. お題が出題されるので、自身のアルバムから探索 image
  5. いい写真が見つかったら、確定 image
  6. 他のユーザの写真を拍手で評価 image
  7. 結果発表 image
  8. 点数の詳細や AI の評価が見れる image

image

技術スタック

フロントエンド

  • Next.js
  • TailwindCSS

バックエンド

  • GO
  • Gemini API
  • Terraform
  • AWS ECS, S3, RDB など

アーキテクチャ図

image

技術的工夫点

フロントエンド

WebSocket 接続の共有

リアルタイムでの通信に使用する 1 つの WebSocket 接続を、アプリ全体で共有できるようにしました。Context API を用いて WebSocket コンテキストとプロバイダーを設計し、ページ遷移における接続の維持を実現しています。

// WebSocketContext - 接続状態と操作を提供 type WebSocketContextType = { status: "idle" | "connecting" | "connected" | "disconnected" | "error"; sendMessage: (data: string) => void; connect: () => void; disconnect: () => void; isReconnecting: boolean; // ... };

WebSocketProvider がルートレイアウトでラップするので、ルーム画面からバトル画面へ遷移しても接続が維持されます。

用途別のカスタムフック

1 つの WebSocket 接続を複数の用途で容易に使えるよう、専用のフックを用意しました。

  • useWebSocketEvents: ゲーム進行のイベント購読
  • useWebSocketClap: 拍手メッセージの送信

バトル画面のフェーズ管理

バトルの進行状態を「フェーズ」として管理し、各フェーズの開始・終了・タイムアップ時にコールバックを発火させる設計にしました。

type BattlePhase = | "waiting" | "selecting" | "clap_time_1" | "clap_time_2" | "clap_time_3" | "clap_time_4" | "clap_time_5" | "result" | "finished"; // フェーズごとのイベントハンドラ onPhaseStart?: (phase) => void; // フェーズ開始時(演出開始など) onPhaseEnd?: (phase) => void; // フェーズ終了時 onTimeUp?: (phase) => void; // 制限時間終了時

これにより、フェーズ遷移に伴う演出(タイマーリセット、画像切り替えなど)のトリガーが容易になりました。

API テストページによるデバッグ支援

開発効率を上げるため、WebSocket イベントと REST API を個別にテストできるページを用意しました。ルーム作成からゲーム開始,拍手送信まで一通りの操作をテストでき、WebSocket メッセージや API レスポンスをリアルタイムで確認できます。 これにより、バトル途中の状態からのデバッグや、特定のイベントの動作確認が容易になりました。 image

バックエンド

通信は用途に応じて WebSocket と REST API を使い分けました。 イベント通知などリアルタイム性が必要なデータ送受信は WebSocket を使用し、 その通知を受けてクライアントは REST API を呼び出してデータを取得することで、リアルタイム性とシンプルさの両立を目指しました。 もう少し具体的に言うと、WebSocket はイベントタイプのみの「何かあったよ」という通知のみを送信し、実際のデータはこのイベントを受けてクライアントが REST によってデータの取得を行うといった感じです。

また、イベント通知などで少し複雑になりそうだったため、シーケンス図やクラス図を作成・設計によって認識の共有を行ってから実装にとりかかりました。

ルーム作成

image

バトル

image

リザルト

image

イベント駆動 + DDD

クリーンアーキテクチャに沿って、ドメイン層を中心に据えた設計にしました。 ビジネスルールをドメインに閉じ込めることで、他の層に振り回されない構造を目指しました。

WebSocket のイベント通知周りはイベント駆動で設計しました。 EventDispatcher であるイベントタイプに関連したハンドラーを登録しておき、イベントの発火を検知してハンドラーを呼び出すようにしています。

クラス図(一部抜粋)

image

インフラ

0 ベースで書くのは初めてでしたが、Terraform にチャレンジしてみました。 AWS のリソースを定義して、コンテナのビルドとデプロイのみは Github Actions での自動化を行いました。

自動レビュー

プルリクエストを出すと AI が自動でレビューしてくれるシステムを構築しました。 見落としがちなエラーにも気づけてとても助かりました。(↓寝ぼけてた中助かりました image

自動デプロイ

main ブランチに変更がマージされると、AWS へ自動でビルド・デプロイされます。

image

IH

@ih