Hack U KOSEN 2022 参加記

本記事は[長野高専 Advent Calender 2022](https://qiita.com/advent-calendar/2022/nnct)の23日目の記事です.

はじめまして！
長野高専の5年生(2022年度時点)のJumpeaceです.
あまりブロクを投稿したことがないので, 拙い文章になるかもしれません.

本記事では, 参加した[Hack U KOSEN 2022](https://hacku.yahoo.co.jp/kosen2022/)に関して書こうと思います.
※本当は[BonyChops](https://qiita.com/BonyChops)(あとで紹介)が投稿する予定だったのですが, 忙しいらしいので代理で投稿します.
イベントを開催していただいたヤフー株式会社(主催)の方々, 独立行政法人国立高等専門学校機構(共催)の方々に感謝申し上げます.

# Hack U Kosen 2022 とは
ヤフー株式会社が主催しているオンラインハッカソンイベントです.
2022/11/28〜12/17の期間で行われました.
参加資格は高専生であることです.
開発テーマは「！（びっくり）」で, 商用利用可能な技術ならば何でも使用できます.

- 公式サイト ... https://hacku.yahoo.co.jp/kosen2022
- 発表会の配信アーカイブ ... https://www.youtube.com/watch?v=VIwtxB-X24k

# なんで参加しようと思ったの？
BonyChopsくん(あとで紹介)の「高専最後の思い出としてハッカソンに出ないか？」という言葉に魅了されて参加しようと思いました.
それと, 社会に出るまでにチーム開発の経験をたくさん詰みたいという思いもありました.

# 何を製作したの？
TENTENというオンライン寮点呼サービスを製作しました.
製作背景には

- 決まった時間にやらなければならない
- 廊下に出て対面でやらなければならない
- 点呼当番が巡回しなければならない

という点呼の現状があります.
この現状を解決するために, TENTENを製作しました.

- 作品リンク: https://nnct-tenko.b7s.dev 
　※2022/12/23時点では動作することを確認しています

# チーム名
チーム名は**衝撃のイナズマZ**になりました.
どっかの歌詞を使ったみたいです.

# チームメンバーと開発担当
アルファベット順に紹介します.

-  BonyChops
    インフラ, バックエンドを担当

    - ポートフォリオ ... https://bonychops.com
    - Twitter ... https://twitter.com/BonyChops
    - Github ... https://github.com/BonyChops

- Jumpeace(私です)
    フロントエンド, 管理パネルを担当

    - Twitter ... https://twitter.com/jumpeace_tech
    - Github ... https://github.com/jumpeace

- Take 
    顔認識, 顔方向推定を担当

    - Twitter ... https://twitter.com/TIchi56212367
    - Github ... https://github.com/Token-05

- Zoroのすけ
顔認識, 顔方向推定, 管理パネルを担当

    - Twitter ... https://twitter.com/zoro_nosuke
    - Github ... https://github.com/zoronosuke

# 仕様
ここでは仕様に関して書こうと思います.

## 不正対策
オンライン点呼にするとどうしても不正が考えられてしまうので, Zoroのすけくんと15分くらい議論していました.
いいアイデアがなかなか出ない中, BonyChopsくんの神の一声で決まりました.

1. IPチェック ... 寮のネットワークが同一IPのため, それを利用して寮にいるかを判断する
2. 顔認証 ... 登録された顔を利用して顔認証を行い, 本人かどうかを判断する
3. 3チャレンジ ... サーバーがランダムに上/下/左/右に向いて下さいと3回指示を出すので, その方向に向いてもらうことでbotがやっていないかを判断する.

## インフラ構成
フロントエンドに Firebase Hostings, React を利用することになりました.
また, バックエンドと管理パネルに Cloud Run, Fastify(Node.js), 顔認識等に Python を利用することになりました.
さらに, 認証，データベース，ストレージ等は GCP や Firebase を活用することになりました.

<img
    src="https://user-images.githubusercontent.com/52094083/205087008-723967ad-33a2-4bb3-bc06-cb6ec1571b1b.png"
    alt="インフラ構成"
    width=90%
/>

# 開発関連の話に行く前に...
チームメンバーと開発担当で書いたように, 僕はフロントエンドと管理パネルしか担当していないので, 顔認識や顔方向推定, バックエンドに関してのことは割愛させていただきます.

# 1日目(11/28) 開発キックオフ
ここからは開発関連の話に関して書こうと思います.
初日はオンラインで開発キックオフがありました.
思った以上にメンターさんと話す時間が多めだったのが印象に残っています.
ヤフーの社員さんと話す機会なんてなかなかないので, ほんとに貴重な機会でした.

# 1日目(11/28) 不正対策
オンライン点呼にすると不正される可能性が高くなるので, その対策をZoroのすけくんと議論しました.
20分くらいなかなかいい案が出ない中, BonyChopsくんの神の一声ですぐに決まりました.
不正対策は以下の3つになりました.

1. IPチェック ... 寮生が使用できるネットワーク(同一グローバルIP)を利用して, 寮にいるかを判定する.
2. 顔認証 ... 顔を登録し, 顔の認証を行う.
3. 3チャレンジ ... ランダム方向をユーザーに指示することで, 人間であることを証明する.

# 2日目(11/29) 友たちがコロナにかかる...
2日目の朝, Discordから連絡が来ました.
何だろうと思って開くと, こんな文が...

「コロナなっちゃったー」

まさかまさかの急展開でした.
前日にその友達とそこそこ話していたので, 濃厚接触者にはなりませんでしたが, 自主的に隔離生活をすることになりました.

それでも, 自分の進捗は生やさなければならないので, 部屋にこもりながら開発をしていました.
ミーティングもオンラインで行いました.
開発はそこそこ捗ったけど, ずっと一人だと精神的にきつくて, 自分にはリモートワークはきついかもなぁと思いました.

# フロントエンドの開発
話がそれてしまったので戻します.
自主隔離していた期間はずっとフロントエンドの開発をしていました.
フロントエンドはReactを使用することになりました.
実装する機能としては, 以下の4つがありました.

- ログイン・ログアウト機能
- 点呼履歴機能
- 顔登録機能
- 点呼機能

## ログイン・ログアウト機能
まずログイン・ログアウト機能に関してです.
この機能には[Firebase Authorization](https://firebase.google.com/products/auth?hl=ja)を使用しました.
あまり使ったことがなかったですが, 今までよく使ってた[Django](https://www.djangoproject.com/)に比べてめちゃくちゃ楽だなぁと感じました.
Djangoはソーシャルログインするのに, いろんなライブラリを入れなければならないし, 何かうまく動かないし...
Firebase Authorizationだとこんな感じ(主要な部分のみ)
※ https://firebase.google.com/docs/auth/web/google-signin から引用
```js
import { getAuth, signInWithPopup, GoogleAuthProvider } from "firebase/auth";

const auth = getAuth();
signInWithPopup(auth, provider)  
  .then((result) => {
    // This gives you a Google Access Token. You can use it to access the Google API.
    const credential = GoogleAuthProvider.credentialFromResult(result);
    const token = credential.accessToken;
    // The signed-in user info.
    const user = result.user;
    // ...
  }).catch((error) => {
    // Handle Errors here.
    const errorCode = error.code;
    const errorMessage = error.message;
    // The email of the user's account used.
    const email = error.customData.email;
    // The AuthCredential type that was used.
    const credential = GoogleAuthProvider.credentialFromError(error);
    // ...
  });
```
こんな簡単にログイン処理が書けるのはびっくりしました.
今後, メイン技術を変えようかなーと思わされました.

それとログイン画面は, Webで調べたよさそうなデザインを参考にデザインも工夫しました.
![Screenshot from 2022-12-21 19-51-37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/844256/cfeaf80d-9c29-28e0-bb51-3a55641b8cf1.png)

## 点呼履歴機能
次に点呼履歴機能に関してです.
点呼履歴は日ごとにやっているかどうかが分かればいいので, カレンダーで表すことにしました.
こんな感じのカレンダーは2年前くらいに作ったことがあったのですが, その時は某プログラミングスクールのコードをコピペで作っていたので, ほぼ初めてみたいな感じでした.
前月と翌月の日もカレンダーで表示するのが少し難しかったです.
![Screenshot from 2022-12-21 19-28-40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/844256/084f109a-645b-c0f4-becd-be1456eac512.png)

## 顔登録機能
その次に顔登録機能に関してです.
この機能はWebカメラの制御と[Cloud Storage](https://cloud.google.com/)への送信の実装が必要でした.

Webカメラの制御は, [react-webcam](https://www.npmjs.com/package/react-webcam) のライブラリを使って実装しました.
以前にWebカメラの制御をしたときは, ライブラリ無しでBlobやBufferなどのデータ変換をやっていたのでかなり苦労したのですが, react-webcamは本当に楽でした.
※ https://www.npmjs.com/package/react-webcam から引用

```jsx
const videoConstraints = {
  width: 1280,
  height: 720,
  facingMode: "user"
};

const WebcamCapture = () => (
  <Webcam
    audio={false}
    height={720}
    screenshotFormat="image/jpeg"
    width={1280}
    videoConstraints={videoConstraints}
  >
    {({ getScreenshot }) => (
      <button
        onClick={() => {
          const imageSrc = getScreenshot()
        }}
      >
        Capture photo
      </button>
    )}
  </Webcam>
);
```

Cloud Storage への送信もかなり楽でした.
環境設定だけできてしまえれば, これくらいのコード数でできてしまいます.
```js
import { getStorage, ref, uploadBytes } from "firebase/storage";

// path: 画像を保存するパス
// data: 送信するデータ
const storage = getStorage();
const storageRef = ref(storage, path);
await uploadBytes(storageRef, data);
```

## 点呼機能
最後に点呼機能に関してですが, この機能がバグの温床だったし, 実装も難しかったので一番厄介でした.
ステートの管理とWebSocketがかなり苦労しました.

まず, ステートの管理について書きます.
どれだけ複雑だったか調べるために, 点呼機能で使っていたReact Hooksの関数の数を数えてみました.
- useState関数 ... 6個
- useReducer関数 ... 5個
- useEffect関数 ... 8個
- useCallback関数 ... 12個
- useRef関数 .. 1個

人によっては少ないと思う人もいると思いますが, 僕にとってはいつもの3倍くらいは使ってた気がします.

最初なんとなくこれでいいだろみたいな感じで実装したのですが, 後でステートの処理の間違いによるバグがけっこう出てきました.
それと, デプロイの時にWarningがあると拒否されるのがなかなか辛かったです.
特に, useEffect関数で起きるこのWarningが厄介すぎました.

```console
useEffect has amissing dependency
```

実はこのWarningを直せなかったのは僕の知識不足が原因でした.
僕は, 「useEffect関数で最初のレンダリングの時にしか実行されるようにするならば, 第二引数は必ず空配列でならなければならない」とずっと考えていました.
ただあるときこんなことをひらめきました.

**「useEffect関数の第二引数の配列にuseEffect関数で使用している関数を入れても, 関数自体が変わることはほとんどないから, 実質初回だけレンダリングされることになるのではないか」**

このひらめきのおかげで, Warningを直すことができました.

次に, WebSocketに関して書きます.
WebSocketの実装には [socket.io-client](https://www.npmjs.com/package/socket.io-client) というライブラリを使いました.
クライアント側はこんな感じで簡単に書けます.
※ https://socket.io から引用

```js
import { io } from "socket.io-client";

const socket = io("ws://localhost:3000");

// receive a message from the server
socket.on("hello", (arg) => {
    console.log(arg);
});

// send a message to the server
socket.emit("howdy", "storanger");
```

こんな簡単に書けるなら苦労しないだろうと思っていました.
まあ実際にサーバーへ送信する処理は書いて動かして, CORSのエラーを解消したら動きました.
ただ, サーバーから受信する処理は, サーバーから送信しているのにも関わらず何も受信されませんでした.
かなり悩んで諦めかけてたところにBonyChopsくんから神の一声が...

**「たぶんWebSocketのソケットを2つ作ってしまって, 別のソケットで受信しようとしてしまっているのではないか」**

はっとさせられました.
HTTPSだとセッション管理は自動でやっているので何も考えなくていいけど, WebSocketは一つのソケットでやり取りしているのでセッション管理まで考えて実装しなければならないのでした.
自分のコード見たら, 送信や受信をするたびにソケットを新しく作っていました.
そりゃ動かないわけだ...
BonyChopsくんアドバイスのおかげでなんとか最後まで実装することができました.

# 管理パネル
管理パネルはZoroのすけくんと一緒に作りました.

データベースは[Firestore]()を使いました.
Firestoreに対する操作はデータの取得しか行いませんでした.
Firestoreでデータを取得するのはこんな感じで簡単にできます.
※ https://firebase.google.com/docs/firestore/query-data/get-data?hl=ja から引用

```js
const cityRef = db.collection('cities').doc('SF');
const doc = await cityRef.get();
if (!doc.exists) {
  console.log('No such document!');
} else {
  console.log('Document data:', doc.data());
}
```

Firestoreのデータの取得のコードが簡単だったし, Firestoreから取得したデータを加工して表示するだけだったので, フロントエンドに比べたらだいぶ楽でした.
管理パネルはこんな感じでできました.

![Screenshot from 2022-12-22 11-46-11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/844256/195b01a2-55dd-c76d-e765-f057a13f8f37.png)

# 発表会のスライド作り
スライド作りはBonyChopsくんが担当しました.
なにか手伝えることはないかなと思って, スライドの参考にするために [昨年の Hack U Kosen の発表会](https://www.youtube.com/watch?v=uzsjm_9tSwY) を見たのですが, スライドの完成度がかなり高くて衝撃を受けました.
一番印象的だったのは, 冒頭に「なぜ〇〇ができないのか？それは△△(アプリ名)がないからだ」と発表していた発表です.
ちなみにそのチームが昨年の最優秀賞だったのですが, そりゃ最優秀賞取るわと思いました.
開発はだいぶいい感じに進んでいましたが, スライドも作り込まないと最優秀賞って取れないんだなぁと感じました.
それで, スライド作りを本気で手伝おうと決意しました.
BonyChopsくんが作ったスライドはだいぶ良かったのですが, 最優秀賞を取りたかったのでだいぶきつくアドバイスしました.
BonyChopsくん, そのときはきつくあたってしまいすまなかったです...
<!-- ↑ぶち○します by Bony -->

<!-- せっかくなので，作ったスライドも紹介していいですよ -->
完成したスライドがこちらです．

https://nnct-tenko-slide.web.app

# 発表会
いよいよ発表会の当日です.
発表会の日程はこんな感じでした.

- 13:00〜 プレゼン(全17チーム, 僕たちのチームは11番目)
- 14:40〜　展示会
- 16:45〜　結果発表

まずはプレゼンでした.
最初からすごいものばかりで, 今年やばいのではと焦りました.
そしていよいよ僕たちのチームの番になり, BonyChopsくんの発表を静かに見守ります...
最初の方がかなり早口で大丈夫かなぁと心配だったのですが, 気づいたら何回もプレゼン見てるのに自分たちも魅了されていました.
これはいけるかもしれないって思わせるようなプレゼンでした.

プレゼンが終わって次は展示会でした.
展示会では審査員の方やスタッフの方, 他のチームの方が見に来ます.
僕はけっこうコミュ障だったのでかなり心配だったのですが, みんなが助けてくれました.
前にもハッカソン出たことあるのですが, 審査員の方がその時よりもだいぶいい反応をしていたので, これはいけるかもしれないと本気で思いました.

# 結果
最優秀賞をいただきました！
チームメンバーのみんなには感謝しかないです！

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">最優秀賞 受賞チームは Hack ID 11 衝撃のイナズマz の TENTEN です<br>おめでとうございます👏👏👏<br>審査員のヤフー渡邊から一言「審査員全員一致の最優秀賞でした。身近なニーズと発展性および、不正やセキュリティやシステムアーキテクチャの側面からも完成度が高かった」<a href="https://twitter.com/hashtag/hacku?src=hash&amp;ref_src=twsrc%5Etfw">#hacku</a> <a href="https://twitter.com/hashtag/hackukosen2022?src=hash&amp;ref_src=twsrc%5Etfw">#hackukosen2022</a></p>&mdash; Hack U🤘/Yahoo! JAPAN (@hackujp) <a href="https://twitter.com/hackujp/status/1604022880945475584?ref_src=twsrc%5Etfw">December 17, 2022</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

# まとめ
ハッカソンの期間は, チームメンバーから技術などを吸収できたり, チーム開発に置ける時間管理やタスク管理に関して学べてとても有意義な期間になりました.
加えて, ヤフー株式会社の社員さんから貴重な話を聞けて, さらにスキルアップしようというモチベーションにも繋がりました.
最優秀賞を取ったチームは Hack Day に招待されるということで, Hack Day でも頑張りたいと思います.

最後まで読んでいただき, ありがとうございました.