概要
チェスや将棋ほどのガッツリ頭を使って心理戦を行うものとそんなに頭を使わずにすらすらできるゲームのちょうど中間に当たるようなゲームを今回のハッカソンのお題である妄想を題材として制作を行いました。
ルール
同時選択ではなく、攻撃側と防御側が交互に手番を持つターン制を採用している。
- 攻撃側は、現実カード12種類の中からそのターン用にランダムで配られた3種類のいずれか、または妄想カードを選ぶ(妄想の場合はその場で申告ダメージ量も入力する)。配られる3種類は攻守交代のたびに毎回引き直される

- 防御側は攻撃側が確定させた申告ダメージor回復量を確認したうえで、それが「現実」か「妄想」かを予想する(現実カードのどれかまでは予想しない)

- 判定して結果を防御側に通知し、次のターンは攻守交代する(先攻はランダムに決定)

- 防御側が攻撃側のカードの種類を見破った場合:カードの効果は攻撃側にそのまま返らず、ダメージ系カードは攻撃側への反動ダメージ、回復系カードは見破った防御側自身がそのライフを回復するという形で処理される
- 見破られたのが妄想カードだった場合:ダメージ申告なら申告量が攻撃側への反動ダメージになり、回復申告なら見破った防御側が申告量だけ回復する。いずれの場合も攻撃側の妄想ゲージが申告量だけ上がり、さらに見破られる直前の妄想ゲージ%を確率とした抽選があり、外れると即座に妄想ゲージが100%になる(=即敗北扱い)
- 見破られたのが現実カードだった場合:ダメージ系カードは反動ダメージのみで妄想ゲージは変動しない(「疼く傷跡」は継続ダメージも含めて攻撃側自身が受ける)。回復系カード(休息・無理な回復・緩やかな回復)は見破った防御側がそのライフを回復し、攻撃側は回復できない。瞑想(ゲージ系)のみ例外的にゲージが反転して上がる
- 見破られなかった場合:申告・計算どおりの効果が発生する
勝利条件
- 相手プレイヤーの残りライフを0にする
- 相手プレイヤーの妄想ゲージを爆発させる
- 妄想カードを5回見破られずに使用する
技術構成
フロントエンド
- Nuxt.js(App Router)
- React
- TypeScript
役割:画面表示・ユーザー操作の受付。Socket.ioクライアントでサーバーと通信する
スタイリング
役割:UIのスタイリング全般
リアルタイム通信(クライアント)
役割:WSサーバーとの双方向通信(ルーム作成・攻撃/防御の送受信)
リアルタイム通信(サーバー)
- Socket.io(Node.js + Express)
役割:ルーム管理・イベントの送受信の基盤
ゲームロジック・共通型
@battle/shared(TypeScriptパッケージ)
役割: フロントエンド・サーバー双方から参照する型定義とゲーム判定の純粋関数(resolveTurn・evaluateMatchOutcomeなど)。ロジックの二重実装を防ぐ
ゲーム状態管理
RoomManager(サーバー側、in-memoryクラス)
役割:ルームごとのプレイヤー・ライフ・妄想ゲージ・攻守などの状態を保持。永続化はせず、サーバー再起動で消える
認証
役割:メール・パスワードによる任意ログイン。未ログインでもゲストとしてプレイ可能
データ永続化
役割: ログインユーザーのプロフィール・対戦履歴の保存(supabase/migrations/でスキーマ管理)。未設定でも動作する(ログイン関連機能のみ無効化)
フロントエンドホスティング
役割: Next.jsアプリの本番・プレビューデプロイ。
WSサーバーホスティング
役割: Socket.ioサーバーの常時稼働ホスティング。
VercelはWebSocketの常時接続ホスティングに不向きなため分離
CI
- GitHub Actions(
.github/workflows/ci.yml)
役割: 後述
Github Actions
今回の開発ではPush -> marge の間にGithub Actionsを採用しました。
Github Actionsで
dev・main宛PRで自動実行。sharedのビルド
- server・frontendの型チェック
- frontendのLint
- serverのユニットテスト
- frontendの本番ビルドを検証
を自動で全部行ってくれました。今回の開発のMVPは間違いなくGithub Actionsです。
詰まったところ
今回もともと考えていたものと方向性を変更せざるを得ないことになったのでチームのみんなで1から構想を考えて一気に実装まで持っていきました。もともと使用していた技術スタックはそのまま使用できて実装することができたのでなんとかなりました。
感想
- いきなりの方向変換でデプロイまでできるかどうかわからなかったが無事にデプロイまでできてよかった。
- 1からゲーム性を練って形にすることができてよかった
- Githubの使い方がぐだぐだすぎてフロントエンドとして機能できなくなって悔しかった。次の機会は絶対にフロントエンドを書く。