Firebase+Vue.jsでカタンの大会システムを作る ～設計編～

## 前回の記事
[Firebase+Vue.jsでカタンの大会システムを作る ～構想編～ - Qiita](https://qiita.com/koujiYamada/items/be2770a8ac3a0e5208f1)

実装していく過程でこちらの内容は修正していく予定です。
なので初稿段階では**検討中**ということをご留意いただければ。
今後の実装状況で見直しが必要と感じたら訂正していきます。

## Firestoreでモデリング
Firesotreのモデリングの経験が全くないので手探りで考えていきます。

まずは大会情報のモデルを考える。
前回の機能検討では漏れていたけど、大会別にデータを管理し、かつ1つのシステムで複数の大会データを管理するなら必要。

```
competitions: Collection
  name: string // 大会名
  game_times: number // ゲーム回数
  match_rule: array[number] // マッチングルール
```
ざっくり上記のように、大会名とゲーム回数、マッチングルールがあればよいように思う。
他に必要だとすると、大会のページを開く際のURLにつかうalias的な文字列か？

次に大会に紐づくプレイヤー情報やゲーム結果が必要だが、RDBの文法だったらそれぞれテーブルに分けてリレーションすればOKのはず。
しかしながらFirestoreはドキュメント志向なので、それが正解とは思えない。
実装していくうちに最適解に出会えるかもしれないが、ひとまずは`SubCollection`として内包してみる。
今のところ大会を横断してデータを参照する予定がないので、大会モデルの直下に置いてみる

```
competitions: Collection
  name: string
  game_times: number
  match_rule: array[number]

  players: SubCollection
    reception_number: number // 受付No.
    name: string // 名前
    rank: number // 順位
    total_result: map{ // 総合成績
      win_count: number // 勝ち数
      victory_point : number // 合計勝利点
      coverage_point : number // 合計占有点
    }
    game_results: array[map{ // 各ゲーム結果
      game_time: number // 何回戦か
      table_number: number // テーブル番号
      is_win: boolean // 勝利フラグ
      victory_point: number // 勝利点
      coverage_point: number // 占有点
    }]
```
コンセプトは順位表を表示しやすいデータ構造。
順位は固定で持たせずにSQLなどで都度計算するほうがデータの不整合が起きなくて好みなんだけど、Firebaseの仕組みを考えると固定で持たせるほうがベターなのかな。
そもそも順位というのは常に変動するわけではなく、**成績が登録されたときのみ**変化するはず。
であれば成績登録をトリガーとしてデータの更新を行い、描画時は取得した情報をそのまま出すほうが効率がいいのではという考えです。

ただ、問題はその成績登録画面でのデータの取得が少し面倒なことになりそうな感じがします。
そこを両立するためにreferenceを使って少し調整したのがこんな感じ。

```
competitions: Collection
  name: string
  game_times: number
  match_rule: array[number]

  players: SubCollection
    reception_number: number
    name: string
    rank: number
    total_result: map{
      win_count: number
      victory_point : number
      coverage_point : number
    }
    game_results: array[map{
      is_win: boolean
      victory_point: number
      coverage_point: number
    }]

  results: SubCollection
    game_time: number
    table_number: number
    players: array[ref(competitions/players)]
```


## 機能別の検討
ざっくりと列挙していきます。

### 【公開側】大会ルール確認
基本的に静的ページなのでVue.js側でコンポーネント1つ作って終わりの予定。
ただし、占有点のマトリックスを表示するとしたら、その表の元データはロジックとしてFirebase側で持たせる予定なので、そのリストを取得する必要はあるかもしれない。
が、表示毎に無駄なアクセスするのも避けたいので、データ保持箇所が分散してでもVue.jsで完結させるのもやむなしか。

### 【公開側】順位表閲覧
先のモデリングの通り、Firestoreから持ってきた`competitions.players`を愚直に出力する。（ソートぐらいはJSでやる必要があるかも）
なお、整形する際、途中棄権・途中参加を考慮して整形すること。
受け取るデータ形式としては以下のようなイメージ

### 【公開側】座席表閲覧
こちらも`competitions.results`を整形して表示するだけの機能。
表示順としては最新の座席が一番上、過去の座席に関してもスクロールで確認できればOK。
（個人ページが実装され、対戦履歴が見れるようになれば最新座席情報だけ表示すれば十分かも）

### 【公開側】個人成績閲覧
機能としては「【公開側】順位表閲覧」の簡易版といったところか。

### 【管理側】参加者登録
`competitions.players`にデータを追加する機能。
更新も管理的にあったほうがいいが、特に問題はないかな。

### 【管理側】参加者事前登録
サポート的な機能なので最初のモデリングで全く考慮していなかった機能。
`competitions`に含めてもいいんだけど、無駄に大きくなるのも避けたいので全く異なるコレクションとしたほうがいいかもしれない。

```
registrations: Collection
  competition: ref(competitions)
  players: array[map{
    number: number
    name: string
  }]
```
的な感じか。

### 【管理側】対戦カード自動マッチング
ここはちょっとめんどくさい。
カタンは基本的に4人でやるゲームだが、3人でもできる。
大会参加者が4の倍数でなれば3人テーブルを作って調整するしかない。

まず3人卓の数を`4 - (参加者数 % 4)`で求める。（6人未満は2卓以上作れないのでエラー終了させる）
その後プレイヤーをランダムに並び替え、4人卓から順に作り、残りを3人卓に割り当てる。
この際、過去に同卓したプレイヤーがいる場合は待機列に戻し、最後まで割り当てられないプレイヤーが残ってしまったら最初からやり直す。
今はやり直し上限を20としてて、規定回数やり直しても同卓が出る場合はどうしようもないと判断して同卓のまま割り当てを完了させる。

本当はもっとしっかりしたアルゴリズム組めばいいのかもしれないけど、とても複雑になりそうなので若干力業だけど妥協。

結果は`competitions.results`に書き込む。

### 【管理側】対戦カード手動調整
過去の運営で起こったけど、大会管理上マッチング後に調整が必要なケースがある。
理由はいろいろあると思うのでここでは言及しないが、システムとしてはあったほうがいいので用意だけはしておく。
単純に2プレイヤー選択してその入れ替えという仕組みでよさそう。
その時同卓チェックを行い、アラートを出すけど強行もできる程度の縛りで。

### 【管理側】ゲーム結果登録
`competitions.results`をもとにゲーム結果入力一覧を用意し、そこから個別のゲーム結果を登録するフォームを表示する。
ひとまずは各人の勝利点と勝利フラグの記録だけ。
入力後確認画面が表示され、その際に占有点の確認もできるとよい。
記録スタッフは確認を兼ねて口頭で読み上げてあげると参加者が手元に持っているスコアシートへの記載が楽になるので参加者が喜ぶ。

結果は`competitions.players.game_results`に書きこむ。
この書き込みをトリガーとして、参加者全員の順位を更新する`Cloud Functions`を動かす。

## 次回予告
なかなか時間が取れずに間が空いてしまうけど、そろそろ実装に入りたい…