文化祭で使うために作ったQRコードでカジノの通貨を管理するシステムを作った時の話

# はじめに
この記事は私が文化祭でカジノをやることになりその際に作った通貨管理システムを作った際にあったことを
書き留めておく記事です。今後また同じようなものを作ろうとするときのためや、似たようなシステムを作ろうとしている人に
役立てればいいと思い、記憶が鮮明なうちに記事に残しておこうと思って書いたものです。誤字や脱字があるかもしれませんが、
そこは脳内補完していただければ幸いです。

追記
病み上がり＆徹夜で書いているため日本語が支離滅裂になってる可能性がありますが、そこも含めて楽しんでいただけると幸いです。構成図も今回初めて作ってみたものなのでかなり雑な感じですがご了承ください。

# 目次
>1.作った経緯について
>2.システムについて
>3.システムの問題点
>4.解決方法を模索した話
>5.最後に

# 1.作った経緯について
なぜ私がこのようなシステムを作ろうかと思ったのかについてですが、記事のタイトルにもある通り、
文化祭でカジノを行う話になった。その際他のクラスもカジノをやるということなので、カジノ内で使える通貨を共通化しようという話になった。そこで私はQRコードで客一人一人の持ち金を管理することを提案しそれが決定したため制作したということです。
# 2.システムについて
通貨管理システムの利点やどのような構造で実現したのか、何を用いたのかなどシステムの中身について詳しく書いていこうと思います。
## 目次＜システムについて＞
>2-1.システムを導入することによる利点
>2-2.システムに用いたもの
>2-3.システムの構造

## 2-1.システムを導入することによる利点
まずシステムを導入することによる利点についてですが、
まずは古典的なやり方、普通の物理的な通貨を使ってカジノを行った際の問題点について列挙しようと思います。

１．一度退出した客の持ち金がわからなくなる
→物理的な通貨を用いる以上退出してしまうと通貨を持ち出すことは難しいためリセットするしかなくなる。
２．物理的な通貨を大量に用意する必要があり不足する可能性がある
→物理的な通貨で客の持ち金を管理するためその分の枚数が必要になり複数の客が大量に通貨を保有すると枚数不足になる
３．他クラスと合同でやった際に通貨の持ち運びが困難である
→物理的な通貨なため教室外に物理的な通貨を持ち出すのは盗難などの可能性がある。
４．感染症対策が難しい
→物理的な通貨をいろいろな人が触り長時間保有するため感染症対策をするのが難しい

以上のような問題が発生するため物理的な通貨での運用はかなり難しいことがわかる。
しかしQRコードでの通貨管理をするシステムを導入することで、QRコードを持っている限り
客は持ち金を保持することができ、大量の通貨も用意する必要がなく、QRコードさえ持っていれば持ち金がわかるため、
他クラスと合同で行うことができ、物理的な通貨への接触する回数が少なくなるため感染症対策がある程度できる。
他にもすべての客の持ち金がデータとして管理されているため、どれだけの客が利用したのかや、ランキング付けも可能になる。
以上のことがこのシステムを導入することへの利点である。

## 2-2.システムに用いたもの
このシステムを導入する際に用いたソフト等を紹介しようと思います。

システムのベースとなるものは<font color="Red">**Unity**</font>を用いました。
Unityと聞いたらゲームを開発するためのソフトだと思う方が多いかと思いますが、プラグイン等を利用すればゲーム以外のものを開発することが可能で、今回のシステムを作るための各機能がUnityで実現可能だったのと、普段ゲームをUnityで開発していて使い慣れていたため、Unityを用いました。（コードはC#を使っています。）
QRコードを読み取るのに使ったプラグインはZxingを使いました。
Zxing：https://github.com/micjahn/ZXing.Net/releases
UnityHP:https://unity.com/ja
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/866543/86327fe0-915b-ee8b-ebf5-9112b5f827e1.png)


次に客の持ち金データの加工と管理はGoogleの<font color="Red">**Spreadsheet**</font>を用いました。
Unityで作ったアプリから送信された客の持ち金データをSpreadsheetに送信し加工・管理という感じです。
スプレッドシートの書き込みで参考にした記事
https://qiita.com/gtk2k/items/3f6260b40882d98973df

ざっくり以上の２つのソフトとサービスを用いてシステムを作りました。
ハードに関しては学校で使っているSurfaceGoを用いて行い。開発はMacです。

## 2-3.システムの構造
具体的にどのような感じでシステムが動いているかについて説明しようと思います。
![Untitled Diagram.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/866543/80571fa6-dd5c-f524-d664-6316717b0fcd.png)

ざっと図で説明するとこのような構造になっています。
各カジノの台ごとに端末がありその端末を担当が操作しデータを送信していく方式になっています。
![スクリーンショット 0004-11-08 23.53.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/866543/0e8cd258-049f-2087-e33b-275e76baea47.png)
↑受信シートを一部切り取ったもの
データ受信部はこのように各客の差分データが次々にセルに追加されて行くようになっています。（後々これが原因で問題が発生します。）
![スクリーンショット 0004-11-08 23.55.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/866543/39a12952-f55c-46c4-0d1d-a1142e266cf2.png)
↑管理シートを一部切り取ったもの
送信・管理部はこのように各客のデータが整列されていて生データからデータを統合してきれいなデータになっています。
データ加工についてはSpreadsheetの関数のSUMIFを用いて加工しています。

ちなみに、QRコードだけでは自分の残高が不明なため残高照会機も作成しました。
そのシステムの構造も一応紹介しようと思います。
システム自体は単純でQRコードを読み取るとSpreadsheetにデータ送信をリクエストし
受け取ったデータから自分のQRコードのIDと照らし合わせて対応しているデータを表示するという仕組みです。

![Untitled Diagram.drawio(2).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/866543/5c9f4ba6-581e-59e2-d9f4-25002cc6d3fe.png)

### 余談：実際どんくらい使用されていたのか
毎分３００までのリクエストなら無料で使えるものだったのでかなり余裕ありました。
カジノなので一回プレイするのに数分かかりますし、３００台も端末がなかったため無料の制限の範囲内には収まっていました。
参考程度にリクエスト回数のグラフを貼っておきます。

ー書き込みリクエスト回数ー
![スクリーンショット 0004-11-09 0.26.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/866543/d41738f0-ced9-af49-d7c6-1503d48427a8.png)

ー読み込みリクエスト回数ー
![スクリーンショット 0004-11-09 0.26.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/866543/93a142f7-219a-f21e-b0ca-32c714c3f110.png)

大体書き込みの際には事前に読み込んでいるので似たような分布のグラフになっています。
右側が１日目、左側が２日目、それぞれ昼休みの時間があるので真ん中に空きがあります。こうやって見るとどの時間帯に集中しているのかがよくわかりますね。

# 3.システムの問題点
ここからがこの記事を書こうと思った真の本題だったりします。
実は本番前日にある一つの欠陥が見つかっていました。しかもかなりまずい欠陥でした。しかし連日の作業によるデスマーチだったり、根本的なところから作り直さないと治らない欠陥だったため強行しました。ですが、このまま放置するのもあまり良くないだろうと思い。この記事にその問題点と解決方法を模索した話をまとめておこうと思い記事を書こうと思ったということです。

### 具体的な問題点
先程紹介したシステムの構造で操作アプリ側の部分をもう少し詳しく図に起こすとこの様になります。
（一部省略）
![Untitled Diagram.drawio(5).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/866543/1c1f80bb-ef0c-1372-d07f-c02fc8a3a344.png)

図の通りデータ送信が決定されるとデータ受信部の生データがあるシートのデータを一度受信し末尾のデータが入っているセルのその下のセルにデータを格納するような構造になっています。ここまではいいのですが、データ受信部のデータを受け取って格納するセルが決定しデータを送信するまでには処理する時間と送信する時間がかかります。その間（下図の赤枠の部分の間）または同時に別の端末から同じ操作を行うとデータを格納するセルが競合してしまうという問題が発生します。そうなるとデータ送信が完了するまでにかかった時間が長い方または送信が決定されるのが遅かったほうのデータが早かった方のデータを上書きしてしまい。早くデータを格納してしまった方の差分データがなくなってしまうという問題が発生しました。
![Untitled Diagram.drawio(6).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/866543/1bebdfe5-e61b-6555-cc59-70924fd3e767.png)

具体的な上書き発生の流れを書くとこのような感じになります。
![Untitled Diagram.drawio(7).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/866543/597d9e4b-9b74-1a03-724e-60d27a37671b.png)


### この問題を発見した経緯
ところでどのようにしてこの問題を発見したかですが、前日に予めQRコードを作成しているときです。（QRコードの作成の仕方は２秒毎くらいごとにQRコードを作成→スクショ→IDと初期の持ち金データ送信を繰り返す処理を行い一つずつ地道に作成する方法でやっていました。（自分は一番最初の作成ボタンをポチって放置するだけだったので何もする必要はありませんでした。まぁその後の一枚一枚画像を結合して印刷する作業が地獄でしたけど、、））私は時々ちゃんとデータが格納できているかを眺めていたのですが、いくつかデータが途切れている部分を発見しました。IDを見てみると合同先のクラスに渡していたQRコードのIDであることに気づきそのクラスに行ってみるとデバッグのために差分データを送信していました。その時私は気づいたということです。すぐに辞めるように言いましたが、私はどうしようもなく文化祭が終わるその時まで震えながら無事に終わるのを待っていました。結局なんどかデータの競合はありました。でもまぁ文化祭クオリティーって感じで許してもらえた感じがあったので良かったです？？？

# 4. 解決方法を模索した話
解決方法を模索しようとした話を物語のように話していこうと思います。

まず文化祭終了後に解決方法を模索しようとしたのですが、あまりいい解決方法が思いつかずに、結局あきらめてその時は終わってしまったのだが、基本情報技術者試験の勉強をしているときに、データベースの分野を勉強していたら似たような話が出てきて、この話を応用といいますか、利用すれば解決できるのではないかと思い。解決方法を模索するのを再開したということです。ここではその時思いついた案について列挙しようと思います。
### 案１＜更新している目印のためのセルを用意する＞
ー概要ー
データベースの勉強しているときに、排他制御という単語が出てきて、排他制御をしていないとデータの整合性が取れないということがわかり、排他制御をするためにロックを行うと書いてあった。（基本情報に関しては勉強中のため誤りがあるかもしれません）なので、誰かが生データを読み込んだとき、目印用のセルを０から１に変えて更新している目印をつける。そうすることによって各端末が読み込む前にそのセルを最初に読み０か１かで判断し１の場合は一定間隔で再読み込みを行い０になるまで待機し、更新中の端末は変更が完了したら０に変更する処理を行えば、待機中の端末は０になったら読み込みを行い１に変えることを行えば、同じ場所を変更せずにすむと思った。
ー懸念点ー
待機端末が増えるとデータ変更に時間がかかりすぎてUX的にあまり良くない。（台の回転効率が悪くなる）変更中の端末が何かしらのバグやエラーが発生し０に戻す処理が行えなくなったときにデッドロック的なことが起きてしまい。一生待ちが続いてしまう。
他にも待機端末が複数台になった場合、０に変更されたときに同時に読み込んでしまったら結果的に書き込むセルがかぶってしまう可能性がある。

構成図
![Untitled Diagram.drawio(11).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/866543/96a00a6a-d8cc-8e9b-6162-709d1ef27ca4.png)

### 案２＜GAS側でどうこうする＞
ー概要ー
この案は操作アプリ側でセルを決定するのではなく、Spreadsheet側でセルを指定してもらう方法だ。私自身GAS？についてあまり詳しくないからあまり具体的なことは言えないが、なんとかしてSpreadsheetのGASの配列に差分データを追加していき、GAS側でセルに書き込んで処理していくという方法を思いついたが、自分自身先程も言ったとおりGASとUnityの連携に関してあまり詳しくないというか素人なのでこれ以上は何も言えないのが現状である。


構成図
![Untitled Diagram.drawio(10).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/866543/ba78d7c4-487c-8bb7-ec42-d3177a60356c.png)

### 案３＜QRコードにセル情報を格納してそれをもとに変更する＞
ー概要ー
この案は書き込み決定時に生データのデータを読み込んでセルを指定するのではなく、QRコードを作成時にID部とセル指定部を作り、QRコードを読み込んだときにID部とセル指定部で分割し。送信するときにセル指定部の値を元にデータの書き込みを行う方法である。これはデータ受信部がなくなり、送信・管理部が受信の役割も行うことになるため、全体のシステム構成がシンプルになり管理しやすくなるという利点もあり、QRコード作成時にセル指定部を追加して作成するのと若干のコードを書き換えるためであり、今回思いついた解決案の中で一番現実的であると私的には思っている。

ー懸念点ー
この懸念点は今回のシステムの要件の範囲内では問題はないが、一つ懸念点を上げるとするならば、同じQRコードを保持する人が複数人現れて同時に書き換えようとしたときに整合性が取れなくなってしまう可能性があるが、カジノのシステムのような場合は懸念点にはならないと思った。

構成図
![Untitled Diagram.drawio(9).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/866543/4cd9137d-38ef-3593-390c-ba08196aa026.png)

### 案４＜データ受信部を複数に増やして分散させる＞
ー概要ー
この案はデータ受信部を一つのシートにするのではなく、複数枚シートを作成してそのシートを各端末ごとに書き込み決定時にランダムに書き換えるシートを決めて、書き込むことによって競合する確率が０にはならないがかなり減少するという案だ。
ー懸念点ー
まずこの案の懸念点はそもそも根本的？には解決できていないということだ。分散させて競合する確率は下がっているが０にはなっていないので根本的な解決方法ではない。そしてもう一つ懸念点がありそれはなにかデータに問題が起きたときに生データの方を書き換える作業がデータが分散されているため探すのに時間がかかること。管理がしにくくなるというのも懸念点だと思った。

構成図
![Untitled Diagram.drawio(8).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/866543/fb0c6eb4-b764-c86f-9b27-0d12ca82c999.png)

以上の方法が私が模索した解決策だ。
結果的にはQRコードにセル指定用の部分を追加してやる案３が確実だし他の利点もあると感じた。

# 5.最後に
最後にまずはここまで読んでいただき大変有り難うございます。
このシステムを作り運用してわかったことはたくさんあり、そこから学べたことはたくさんあったと思う。一番印象に残ったことは使用するユーザーは奇想天外な使い方をしてきたり、デベロッパーの想定しているよりもかなり雑に扱う（ボタン連打等）ためそこも考慮して作成しなければならないということがわかった。あとは実機でのデバッグをもっと早めにやるべきだったと後悔している。（今回は実機と開発している端末のスペック差がかなりあり挙動にだいぶ差が生じていた。）次回はこの教訓を生かしてよいシステムづくりを心がけようと思う。