大人数リモート飲み会用クイズwebアプリつくってみた（Nuxt×GCP）

# 0. はじまり
昨年12月、大学の学科コンパの幹事である我々は頭を悩ませていた。
毎年この時期にあるコンパは、現役の大学生のみならず社会人の先輩方・OB教職員の先生方を含め100人規模で開催される。
ステイホームが叫ばれるこのご時世。とはいえ、多くの（元）学科民にとって縦・横のつながりを持てる有益なこの会を開かないわけにはいかない。

オンライン飲みの形式でも、知らない人同士が偶然出会い、話のきっかけが生まれるような場を作れないだろうか——。
議題は移り【オンラインでも可能なレクリエーションとは何か】について話していた。

幹事A:rolling_eyes:「実現できるのかわからんけど、参加者をランダムにチーム分けして、点数を競い合うようなクイズやったら面白いんじゃね」
幹事B:nerd:「自前のクイズを出題して参加者がストレスなく回答を送信できて、その場ですぐ採点ができるってことよね」
幹事C:upside_down:「そんなサービスあるんかなあ」
幹事D:no_mouth:「近いことできるサービスは見つけたけど高い。とても出せない」
幹事A:rolling_eyes:「作ればよくね」

そんな軽いノリで、我々はオンライン飲み用クイズアプリを作ることになった。

# 1. この記事を読むと嬉しい人
- 嬉しい人
    - webをすこし書ける何かやってみたい人
    - 自前でアプリ作ってまで、大人数のオンラインレクを成功させてみたい人
- 嬉しくない人
    - 技術的にすごい話を求めている人

今回の記事は、知識にはほとんど踏み込まず実践について書いた記事となります。
この体験談を通して、比較的初心者の方が「自分にもできそうだぞ」と【身の回りの課題解決・やりたいこと実現】をするきっかけを作れたら幸いです。

# 2. 目次
- [0. はじまり](#0-はじまり)
- [1. この記事を読むと嬉しい人](#1-この記事を読むと嬉しい人)
- [2. 目次](#2-目次)
- [3. 宴会幹事衆立志編（状況整理〜デザイン）](#3-宴会幹事衆立志編状況整理デザイン)
    - [3-1. 状況整理](#3-1-状況整理)
    - [3-2. 機能列挙](#3-2-機能列挙)
    - [3-3. 手段](#3-3-手段)
    - [3-4. デザインカンプ](#3-4-デザインカンプ)
- [4. 無限実装編](#4-無限実装編)
    - [4-1. こんなんできました](#4-1-こんなんできました)
    - [4-2. 細々としたこと](#4-2-細々としたこと)
        - 全部で8項目あります（長いので目次は割愛）
- [5. おわりに](#5-おわりに)
- [6. ところでお前ら「学科コンパ」言ってるけど、どこの学科なの](#6-ところでお前ら学科コンパ言ってるけどどこの学科なの)

作ってみた系記事ということで、定義部分のお話をけっこう長くしています。

# 3. 宴会幹事衆立志編（状況整理〜デザイン）
## 3-1. 状況整理
まずはクイズアプリを囲う状況の整理から。
あとでクイズアプリが持つべき機能を挙げるにあたり関係する情報や、アプリが持つべき役割の境界をはっきりさせます。

- 参加予定人数は100人前後。ドタキャンドタ参があり、可変
    - チームを分ける場合、事前に分けることは不可能→その場で振り分け
- 会話に使用するサービスは[Gather.town](https://gather.town/)
    - ゆえに行くべき場所さえ提示すればユーザーが移動してくれる
    - 使い方については他の幹事が書いた[こちら](https://note.com/misaki_ddd/n/n4e782042513a)をご覧ください

## 3-2. 機能列挙
状況をある程度踏まえ、クイズアプリが持つと望ましい機能を列挙します。
列挙するうちに参加者側と管理者側に二分できると気づき、最終的にこのような形になりました。

- 参加者側
    - 参加登録ができる
    - 登録直後、チームに分かれる
        - 各チームの人数を均等にしたい
        - 各チーム内の学年も偏らないようにしたい
    - それぞれがチームの答えを編集できる
    - 他のチームの答えにはアクセスできない
- 管理者側
    - 回答を採点できる
    - 各チームの回答を後で参照し、正誤を判定し直せる
    - 各チームの点数を保持できる
    - 制限時間のカウントダウンを表示できる
    - 回答をロックできる
    - チームごとの点数を比較し、順位を表示できる
    - チームのメンバーを参照でき、追加/削除ができる

## 3-3. 手段
機能の列挙ができたら、次はその実現方法を考えます。

- データも多くならないのでObjectがそのまま入れられるデータベースがあるとラク
- 同じような表示のページが多くなりそう→テンプレートを使い回せるフレームワークを使いたい
- 今回は稼働時間が数時間→終わったらすぐ止められて課金も止まるとうれしい

これらの需要を踏まえ、結果的に**Nuxt.js×GCP（App Engine & Firestore）**でつくることにしました。
AngularやReactではない理由は、いちばん筆者が早く書けるのはNuxtだったからです。
AWSではない理由は、筆者のAWSアカウントが無料期間を過ぎていたからです...。


# 3-4. デザインカンプ
整理した機能を踏まえ、

- どんな画面必要なの？
- どういう配置にするの？

を考える必要があります。
いきなり実装を始めるよりは、スケッチをする要領でデザインだけ決めると（試行錯誤のサイクルが）速くてよいかと思います。

寝てたら他の幹事が[XD](https://www.adobe.com/jp/products/xd.html)でデザインを作ってくれていました。ごめんなさい開発してなくて。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/545780/b6d268c1-905e-47d5-f6e4-ee5f0b808e5f.png)
できた報告をもらったあと、その幹事と僕で電話をして諸々がこれで良さそうかについてすり合わせました。

特に白熱した（させた）議題は、「[ふざけたフォント](http://pm85122.onamae.jp/851ch-yw.html)を最初の画面に導入するか」でした。こういうツールにはカオスさが必要だと思うんです、僕。

ということで（？）ページ構成およびそれぞれの受け持つ機能は以下のようになりました。

- 参加者が触れるページは2ページのみ
    - トップページ
        - 参加登録機能をもつ
    - チーム別回答編集ページ
        - トップページで参加登録したあと自動振り分け
        - チームの答えを編集できる
- 管理者が触れるページは5ページ
    - 問題編集ページ
        - あらかじめ用意したスロットにある問題を編集できる
        - 現在表示されている問題の制御も可能
    - チーム一覧ページ
        - チームごとにメンバーが表示され、メンバーの編集が可能
        - チームごとの点数・順位がわかる
    - 問題表示ページ
        - 問題を表示する
        - 制限時間を表示
        - 回答のロックも可能
    - チームの回答一覧を表示
        - ここでは正誤判定を見せない
        - 表示してるうちに管理者が別ページで正誤判定
    - チームの回答&正誤一覧を表示
        - よくあるクイズ番組のイメージで、各チームの回答に色を付けて正誤を表示
        - 実はクリックすると正誤を入れ替えられるようにしておく
        - 「次の問題」ボタンを押すと参加者に見える問題を変更可能。ページ遷移も発生



そういえば[ニューモーフィズム](https://qiita.com/apple-yagi/items/59b626a371ea80cdee6e)の採用も考えました（考えてない）。これ、最近流行ってますけど難易度高いなあって思っています。余談ですね。

# 4. 無限実装編
ページのレイアウトや機能が決まりました。使うフレームワークやサービスが決まりました。あとは形にするのみ。
Nuxtは書き慣れていたし仕様は決まっていたので1日で終えられる！
と息巻いて実装をはじめましたが、終わってみれば（飲み会当日までの）4日も掛かってしまいました。

[**よもやよもやだ！！**](https://kimetsu.com/anime/)

---

時間がかかった原因はデータ構造について事前に深く考えていなかったことと、GCPを初めて触ること、apiを立てる経験もほとんどなかったことですね。詰まっていたというよりはただただ時間がかかっていました。

以降では、まず初めに完成版のページをお見せしたのち【自分だったら気になるかもしれない、想像すればわかる**わけではない**こと】について思いついた順に書きます。

最初は実装について細かく書くつもりだったんですが、この投稿の趣旨はNuxt入門/Firestore入門/App Engine入門ではないので基本的なことについてはだいたいすっ飛ばします。~~杜撰な設計の部分もたくさんあるので~~

## 4-1. こんなんできました
デザインはもっとやれた気しかしないという感じですが、時間が切れてしまいました。~~ニューモーフィズムしたかった~~

### 参加者画面
![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/545780/1d456f51-113d-d5ae-2f0f-b3e8eca4da90.png)
### 管理者画面
問題/回答表示画面は当日、画面共有機能で参加者に見せていました。
![2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/545780/b677ee40-7b96-d5bf-8b7f-884e64ff6d8e.png)
![3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/545780/3fa682b5-db25-29bd-4c83-14c0bac5e913.png)
![4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/545780/3a71dfed-8fbf-83b0-cb8c-8abc1fff9338.png)
![5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/545780/f1701e6d-d325-4943-f21b-197b7eeb71b4.png)

## 4-2. 細々としたこと
### 4-2-1. 「NuxtのアプリをどうやってApp Engineに上げたの？」
「Nuxt App Engine」でググるとたくさん先駆者の情報が出てくるのでそれをお読みください。（初っ端からぶん投げ）

### 4-2-2. 「apiはどうやって立てたの？」
[Nuxt.jsにexpressを使ってapiを立てる機能](https://ja.nuxtjs.org/docs/2.x/configuration-glossary/configuration-servermiddleware/#%E3%82%AB%E3%82%B9%E3%82%BF%E3%83%A0-api-%E3%82%A8%E3%83%B3%E3%83%89%E3%83%9D%E3%82%A4%E3%83%B3%E3%83%88)がありましたのでこれを使いました。
Firestoreとどうやって通信するかは、[公式ドキュメントに `Node.js`の場合について書いてあった](https://firebase.google.com/docs/firestore/quickstart?hl=ja#node.js)のでそれらを~~そのまま使用~~参考にしました。

最初は「クライアントで保持していたデータをもとに全データを書き換える」ものだけ立てて置いてたんですが、途中で「当日は同時に複数チームから変更飛んでくるんだからそんなことしたら成り立たなくね...？」と気づき（最初から気づけ）部分的な変更を受け付ける必要があることを知りました。
そこからはムスケル（単純な肉体労働の意）の日々でした。ここで一番時間を取られました。

### 4-2-3. 「管理者画面はどうやって管理者だけに見せてたの？」
Nuxtには「pagesフォルダ内のハイフンから始まるファイルは読まない」という機能があるので、それをうまく使っていました。gitにはハイフン付きのファイルを置いて、App Engineにはそれを読み込ませていました。管理者みんなにはローカルにコードを落とし、ハイフンを外して起動してもらっていました。

### 4-2-4. 「『登録時にチームを均等に振り分け』はどうやって実現した？」
- トップページで参加登録時に学年を訊くようにし
- クライアントがAPIに参加リクエストを送った際、API側で各チームのメンバーと学年の分布を確認
- クライアントに所属するべきチームをお知らせ

というシステムを作って実現しました。

### 4-2-5. 「『他のチームの回答を編集できない』はどうやって実現した？」
【各チームの回答画面のURLをあらかじめ決めたランダムな文字列に設定】することで対処しました。これにより、参加者にログイン状態を持たせずとも参加者は現実的には他のチームの画面に移ることができなくなります。
また、何らかのトラブルが起きて参加者がそのページから離れてしまった場合を想定し、同じ名前と同じ学年で再度参加登録がなされた際には以前のチームのURLを案内するようにしました。
「これはこれでなりすましが起きるのでは？」という感じですが、これは実際に声を出して存在を見せなければいけないオンライン飲み会の場だったので今回のみ気にしなくてよかった感じです。

これが姿を相手に見せないタイプのゲームだった場合、やはり会員登録/ログイン機能を実装することになるのかなと思います。

### 4-2-6. 「Typescriptもちろん使ったよね」
使わなかったんですよね...。使えばよかった...
層が深めで複雑めなオブジェクトを扱っていたので、型があったほうがミスも少なく開発できたなあと今振り返って思います。

### 4-2-7. 「そういえば独自ドメイン使ってたよね」
このためだけ、1時間程度のためだけに`.tokyo`ドメインを取りました。だって100円足らずで安かったんですもん。
ムームードメインで取得してApp Engineとつなげました。「GAE カスタムドメイン」あたりでググるとよいかと。
割と簡単に設定できたのでみなさんもケチらずにやってみるのがおすすめです。

### 4-2-8. 「結果的にいくらくらいかかったの？」
GCPの請求はたしか400円程度だったと思います。それも開発時に何か余計に請求を発生させたのが理由だったような気がします。ドメイン代と合わせても、スタバでフラペチーノ頼むより安いって感じです。安く済んでハッピーでした。


# 5. おわりに
ということで今回は、リモート飲み会のためにクイズアプリを作ってみました。
先程も書きましたが、開発を1日で終えるつもりでした。でも全然終わらなかったです。甘かった。

実際飲み会の場で使ってみたところ、クイズの進行自体に不満の声が上がったりはしたものの特に深刻な問題はなく会を終えることができました。
「クイズ楽しかった」という声も聞こえてきて、作った甲斐があったと感激したのを覚えています。

大きなバグなくその役目を全うさせることができたのは、幹事たちで開いた強制デバッグmtgのおかげです...。デバッグ大事...ありがとうみんな...。

また、今回チャレンジをしてみて、僕はとっても勉強になりました。
やれるという確信と、多少バグっても許してもらえるような（今回がそのような機会だったかは疑問ですが...）場所のある方は、ぜひこのようなアプリの作成に取り組んでみてください。勉強になるし周囲に驚いてもらえるしで一石二鳥です。

コンパの運営っぽいお話も気になる方は他の幹事が書いた以下の記事もご覧ください。

- [100人規模のオンライン学科コンパを開催するまで](https://note.com/k_yamamoto/n/nc8a91e55d049)
- [【オンラインコンパ】約100名を大いに盛り上げられたワケ](https://note.com/tttocklll/n/ne87204b490db#bPYjS)

# 6. ところでお前ら「学科コンパ」言ってるけど、どこの学科なの
今回の飲み会は[東京大学工学部電気電子工学科・電子情報工学科（通称EEIC）](https://www.ee.t.u-tokyo.ac.jp/j/)のコンパでした。
弊学科では、Qiitaではおなじみ[アドベントカレンダー](https://qiita.com/advent-calendar/2020/eeic)も毎年作られています。気になる方は見てみてください。
また、来年度開催の五月祭にも企画を出す予定です。こちらもぜひ覗きに来てね！

また、今回の企画はOBであり教員である[川原圭博教授](https://www.akg.t.u-tokyo.ac.jp/)（リンクは研究室）の支援をいただいて実現できました。この場を借りてお礼を申し上げます。ありがとうございました。