学園祭で謎解きゲームを作ったら死ぬかと思った話
軽い自己紹介
初めましての方は初めまして。会津大学で学部一年生をやっています。しゃけのきりみ。と申します。
「。」までを含めて私の名前となっておりますので、お見知りおきください。
現在は、Google Developer Groups on Campus AizuのOrganizerをやらせていただいています。
では、本題です。
何をした?
表題の通り、と言ってしまえばそのままですが、弊学の学園祭である「蒼翔祭」で、謎解きゲームを行い、そこで使用したデジタルシステムを1人で開発しました。
企画名は「Save the UoA」、コンセプトは「会津大学に何者かがハッキングを行った。15分以内に謎を解き切らなければ、会津大学のデータは削除されてしまう。君の手で、会津大学を救え。」でした。
技術スタック
まずは技術スタック、もとい選定の過程も含めて書いていきます。
使用したのは以下です。
- Electron + Vite
- React + Chakra UI(V2)
- Express.js
- SQLite
お気づきの通り、Googleにほとんど、いや、なんら関係がありません。許して。
まず、このシステムはクラウドサービス始めとした、インターネットへの公開物はありません。
なぜか。インターネットに公開すると弊学の某サークルに問題が流出する可能性があったからです。
弊学には、コードを書くことが三度の飯よりも好きなのではないかと思うほど技術が好きな方々が集まったサークルがあります。彼らは誰かがサービスを公開すると、すぐにCTFしてくる習性があるようで、私も1度被害に遭いました。
そんな経緯から、その穴を塞ぐ努力をするよりもローカルで完結させた方がいわゆる「タイパ」が良いと判断しました。
そんなわけで、当日は Ubuntu Server 22.04をインストールしたFujitsu Primergy TX1310 M1を会場内に設置し、運営用のWi-Fiを発射、という方法を取りました。
技術構成
今回作成したのは、主に4つです。
- サーバー(stuoa-server)
技術的にはexpress.jsを使用し、その上にsqlite3のDBと後述するPDF生成にhummusを使用しました。後ろ2つのUIが載っているのと、APIサーバーを兼ねています。 - クライアント(stuoa-client)
プレイヤーが主に使用する、出題・解答アプリケーションです。
技術的にはElectron+Vite+Reactで動いています。
運用自体はWindows PCにモニターを3枚接続しました。 - 管理UI(stuoa-adminui)
スタッフが、開始登録や成功証明書の発行に使用するためのWebページです。
React(create-react-app)で作成、stuoa-serverにビルドしたものを置きました。 - モバイルUI(stuoa-mobileui)
後述する専用のデバイスで、スムーズな受付を実現するために作成したモバイル用受付UIです。
React+Viteで作成、stuoa-serverにビルドしたものを置きました。
運営形態
Save the UoAは、クライアントとサーバーに分けられたシステムで、サーバー側にオープニング動画や謎の問題画像などを保存し、それをAPI経由でクライアントがfetchする、という形を取りました。
クライアントの画面がこんな感じです。
上の画像はスタンバイ中の画面ですが、実際は
- 右側に残り時間や正解・不正解などの情報を表示
- 左側に問題を表示
- 中央のタッチディスプレイで回答を送信
こんな感じです。
開発
まずはサーバーから
本システムの作成当初から、その場での問題変更や難易度変更などの可能性があることから、サーバー依存のアプリケーションにすることは決めていたので、サーバーから開発を始めました。
APIを書くに当たり、以下の点が必要となったのですが....
- ランダムな出題ができ、1度出た問題は二度と出ないこと
- ルームを3つ用意し、それらは別々で同時に動作できること
が必要でした。
当然SQLを使うわけですが、行数がとんでもない(当社比)ので大変でした。
UUID生成をcrypto.randomUUID()
でやれるというのもここで得た知見。(逆になんで知らなかったんだ)
早速挑戦者の識別に使用しました。
そもそもいつもCloudflare Workersで生活している人間なので、express.jsを書くこと自体が初めて。生成AIや優秀なデバッガーの友達に助けてもらいながらなんとか書きました。
C#を作ってそれをかなぐり捨てる
問題(questionではなくproblemの方)のクライアントです。
私は今でこそWebの人間ですが、昔はC#でWinFormsやWPFを開発していた時期がありました。
んじゃあGUI作るならそっちがいいよね、という安直な思考でC# WPFを選択。
これが最悪の選択でした。
①そもそもC#を書くことが1年ぶり
あまりにもwebの海に浸かりすぎてC#自体が久しぶり。
さらにjsの人間なので型に縛られまくるスタイルも久々で、開発が進むのが急激に遅くなりました。
②APIリクエストがだるい
jsonとってくるのこんなに大変だと思ってなかった。
いちいちclass定義しなきゃなのね。これのために20行近く毎回消費するのもやだな。とおもいC#で作ったものはかなぐり捨てることを選択。
Electronとの出会い
ということでElectronに出会いました。出会ったというよりかは先程の友達のデバッガーに教えてもらいました。
名前は知っていましたが、触れ合う機会はなかったのでいっそやってしまえということで選択。
結果的にはC#より簡単に実装できた(一部をのぞいて)のでまぁ満足です。
ちなみに除いた一部は、複数画面連携の部分。
複数画面を使用するこのアプリケーション、複数画面でデータを同期しなければならないわけですが、Electronには全てに共通の変数を定義することができないらしい。
これを解決するためにipc通信を使うわけですが、いやー辛かった。
本当に骨が折れました。なにせ1回のゲームで30回近く通信する(出題開始、制限時間検知、正誤判定などなど)わけですから。
もう複数画面のアプリケーションは作りたくない、と思うほどに大変でした。まぁいい経験。
管理UIと受付システムの構築
お客さんに勝手に入られてゲームされては困るので、当然受付用の管理画面が必要でした。
この管理画面のロジックがおかしくて後々困るわけですが。
この受付は、チーム名と人数と難易度を聞いてゲーム開始登録をするものでした。
React+Chakra UIで構築。大体2週間かからずに実装したはず。よく考えるとやばいな。
システム外の構築
超優秀動画担当
まさかcssアニメーションでオープニングやエンディング、正誤の動画を流すなんてことはしたくない。なお、前述の友達のデバッガーはこれを提案してきた(冗談で)ので闇に葬り去りました(嘘)。
ということでゲームの演出周りは外部委託。AviUtlを使ったことがないゆるりに動画制作を依頼しました。このプロジェクトで彼女はAviUtlに強くなれたみたい。よかったよかった。(割と無茶な依頼をした自覚はあります。ごめん。)
結果的には彼女の動画なしでは成り立たないものだったので、本当に優秀な動画担当でした。心から感謝。
超優秀作問担当
勘のいい皆様ならお気づきかもしれませんが、この謎解きシステム、同一の人間が複数回挑戦した際に別の問題を出す必要がありました。
そう。作問数がとんでもない。
でも大丈夫。謎作成部分は私の友達であるいかやきが全て担当してくれたので。
といっても100問近く依頼するのは流石に...と思いましたが問題がなければシステムを作る意味もないので依頼しました。
結果的に2ヶ月くらいで80問近く作問してもらいました。ありがとう本当に。
運用
結果的には「成功」と位置付けられるものだったと思います。
想定外のシステムダウンは2日間で2回ほど。2ヶ月で1人で作ったものにしては良いんじゃないでしょうか。まぁただ、問題点はあったわけですが。
受付のアルゴリズムがおかしかった
受付時に行う操作は以下のようにしていました。
- チーム名を聞く
- 人数を聞く
- 難易度を聞く
- 整理券番号を記入する
- 登録する
上記の流れで、ゲーム開始までの並び列では整理券を持ってもらい、順番が来た際に呼び出せる実装を行なっていました。
しかし、問題だったのが登録の方法。
登録の際に、「あらかじめ空いているルームに割り当てる」ようになっていたのです。
段階としては、
①3つのルームのうち最も空いているルームに割り当てる
②現在ゲームを行なっているチームが終わった時、ルームごとに次の人を呼び出す
何がまずかったかというと、
例えば
A、B、Cに2チーム並んでいるとき、システムは次の人を自動的にAに割り当てます。
このとき、もしBのチームが両方とも最も簡単な難易度に調整し、2チームで10分程度で解き切ってしまったものとします。しかし、Aの現在プレイ中のチームは最高難易度で、15分いっぱい悩んで結局クリアできませんでした。
この時点でAには3チーム、Bには待機列なし、Cには2チームの待機が発生しています。
しかし、廊下で待っている人たちはBルームに割り当てられることはありません。
なぜなら、すでに割り当てられたルームがあるから。
つまり、空きルームがあるのにも関わらずまだ待つという状態が発生してしまうのです。
さらに酷いのが、この後新規で受付を行うとどうなるか。
「最も遅く来た人がBルームに割り当てられ、待ち時間なしでゲームが開始する。」
最悪ですね。
当日はDBを直接いじるという怖すぎることをしてしまったわけですが、もっと早く気づくべきだったなと猛省しております。マジでなんでこんなアホ設計なのか。作ったの誰だよ(私)。
その他
最後にもう一度写真をご覧いただきたいのですが、
背景、何かおかしくないですか...?
そう。コンパネが並んでおります。
このコンパネ、元々教室にあったものではありません。そう。この学園祭の2日間のためだけに35枚のコンパネをビス留めして壁を錬成しています。
まじでやばかった。こんなアホな計画に付き合ってくれたStUoAメンバーには本当に感謝しかありません。
最後に
個人的には技術的にできることはほぼやり切ったと思います。
失敗は多くあれど、自分の持てるものは出し切ったかなと。
色々と学ぶものが多かった企画でした。
改めて、動画を作ってくれたゆるり、策問してくれたいかやき、節々のトラブルシュートをしてくれた優秀なデバッガーの友達ことulong32、企画運営に付き合ってくれた StUoAチームのみんな、そして学園祭実行委員会の先輩方、本当にありがとうございました。
反省は今後に活かし、より良いものを作っていきます!
ここまで読んでくださった皆様も、本当にありがとうございました。
今後とも何卒よろしくお願いします。
それでは。
Discussion