競技クイズ用得点表示ソフト「Score Watcher」を開発しました
競技クイズの大会などでプレイヤーの得点状況を可視化(いわゆる「得点表示」)する Web アプリ「Score Watcher」を公開しました。
競技クイズプレイヤーかつ開発者に該当する人が非常に少なそうなので需要があるかはわかりませんが、せっかく作ったので記事にも残しておきます。
背景
競技クイズの世界には複雑なルールや専門用語が多く、事前知識がないと理解が難しいものもあります。僕は現役のプレイヤーではないですが中学時代クイズ研究部に所属していたので、持ちうる限りの知識を使ってこのアプリを作ることになった背景を書こうと思います。
競技クイズではその勝敗を決定するために様々なルール(形式)が用いられます。早押しボタンの前につき、読み上げられる問題文を聞きながら答えがわかった時点でボタンを押して回答する、というのはほとんどの形式で共通していますが、「どのように」「何回正解したら」勝ち抜けられるのかは全く異なります。
最も単純なのは「N 回正解したら勝ち抜け、M 回誤答したら失格」という形式です。この形式は「N○M×」と呼ばれ、この中でも特に使われることが多い 7○3× は「ナナマルサンバツ」という漫画が存在するほど有名です。
次に「NbyN」という形式があります。この形式では N を 5 とすることが多いのですが、まず 0x5 という状態からスタートし、正解すれば左側の変数が増え(1x5)、誤答すれば右側の変数が減り(0x4)ます。左と右の変数をかけた値が N の 2 乗(今回は 25)を超えれば勝ち抜けです。
少し特殊な形式だと、「Z」という形式があります。
各プレイヤーはステージ 1 に立った状態でゲームを開始します。
いずれかのプレイヤーがステージをクリアしたとき、全員の正解数、誤答数および失格状態をリセットし、あるステージをクリアしたプレイヤーは次のステージに進みます。
各ステージの内容は次の通りです:
- ステージ 1: 1 回の正解でクリアです。誤答すると 1 問の間、解答権が剥奪されます
- ステージ 2: 2 回の正解でクリアです。1 問の誤答で失格となります
- ステージ 3: 3 回の正解でクリアです。2 間の誤答で失格となります
- ステージ 4: 4 回の正解でクリアです。3 問の誤答で失格となります
ステージ 5 に到達すれば勝ち抜けです。既定の人数が勝ち抜けたときゲームを終了します。
他にも「Nupdown」「SquareX」「アタックサバイバル」「Swedish10」など多くの形式が存在し、大会ごとに独自の形式が作られることもあります。
さて、現在の競技クイズの大会(特に中高生が主催する大会)ではこれらの形式の得点表示を Excel の関数や条件付き書式を組み合わせて作っているところが多いです。
3 年前の大会で自作した Excel での得点表示
実際 Excel を使うのは合理的ではあります。大会規模を考えるとホワイトボードなどで表示するには無理がありますし、人がいちいち計算していればいつか必ずミスります。Excel を使えば誰が問題に正解したか書き込めば得点が更新されるように表示できます。
しかし Excel での得点表示にも問題があります。セル単位でしか色の変更ができないのでデザインの幅には制限がかかりますし、何より毎回大会ごとに作るのは手間がかかりすぎます。また、そもそも競技クイズプレイヤーはコンピュータや Excel に熟達した人の集まりというわけではないので、あまりにも複雑な形式は Excel では対応しきれないこともあります。
主な機能
そこで今回開発した「Score Watcher」の登場です。Score Watcher は競技クイズで汎用的に使える得点表示ソフトで、一般的な得点表示で組み込まれている「プレイヤーの得点状況の表示」「勝ち抜け or 敗退状態の表示」「問題のスルー」「1 問前の問題文&答えの表示」などの機能を網羅しつつ、オリジナルの機能を多数搭載しています。
得点表示画面
その中でも特に紹介したいのが「スコアの手動更新」機能です。
得点表示ソフトを導入する上で最大の障壁となるのが「プログラムが間違っていたときその場で修正が効かない」ということだと思っています。Excel であれば関数が間違っていたとしてもスコアのセルの数字を手動で更新すれば得点表示としての役目は果たせます。しかしソフトウェア(今回は正確には Web アプリですが)になるとどうでしょう?何度も言いますが、競技クイズには複雑な形式が多いため、ソフトウェアのデバッグをしきれない可能性は十分に考えられます。Excel と違って画面上の文字列を好きに変えられるわけではないので、例えば当日になって不具合が発覚すれば悲惨なことになりそうです。
そこで本アプリではスコアの部分及びプレイヤーの背景の色を自由に変更できるモードを用意しました。
スコアの手動更新機能
技術的な話
言語は TypeScript 、フレームワークは Next.js で Vercel にデプロイしています。UI ライブラリは Chakra UI を使いました。PWA としてインストールが可能で、完全にオフラインの環境でも動作します。Next.js を使ってはいますが、試合データをローカル上に保管している関係でアプリ全体を SPA モードにしています。
得点の計算は computeScore
関数で一元的に行っています。undo 機能を実装しやすくするため、ボタンが押される度に試合内での得点履歴のログの配列を map で回し、その中でプレイヤーの配列をまた map で回して形式ごとに得点を更新しています。スコアの確定後配列をソートし勝ち抜け順位を確定させ、最後にプレイヤーの配列をもう一度 map で回して現在の state (勝ち抜け or 敗退 or その他)と表示するテキストを決定します。おそらくかなり非効率的なやり方です。こういうときに競プロをやっておくと役に立つんだと思います。
Dexie.js / Indexed DB
状態管理(これを状態管理と言っていいのかわからない)ですが、今回はすべて Indexed DB を使って管理しています。
今回は主に試合情報、試合のログ情報、プレイヤー情報、問題情報の 4 つを Dexie.js を通じて管理しています。普通はクラウドにデータベースを用意し API を通じて状態の取得・更新を行うと思いますが、今回はアプリの主な使われ方を考えると「どの端末からも表示が可能」なことより、「完全なオフライン環境で動作する」ことのほうが必要だと考え、状態はすべてローカルの端末上で管理することにしました。
Indexed DB は SQL のようなインターフェースでデータを保存でき、フィルターなどのクエリも使えます。value には文字列、数値、真偽値だけでなくオブジェクトを入れることもできます。複雑なデータは Object 型のネストを繰り返してしまいがちですが、フラットなデータ構造を強制されるのでコードの可読性も上がりました。
Tanstack Table
問題管理やプレイヤー管理のページで表示しているテーブルには、前から気になっていた Tanstack Table を使いました。Tanstack Table は UI を持たない headless UI のライブラリで、テーブルのページネーションやソート、フィルター、行の選択などの機能を提供してくれます。
今回初めて使ってみましたが、正直あまり使いやすくはなかったです。下の記事でも書かれていますが、ただセルに文字を表示するだけのために flexRender(cell.column.columnDef.cell, cell.getContext())
と書く必要があるのは少し冗長です。ドキュメントもあまり親切ではなく、Examples の長いコードの中から使いたい機能についての記述を探すと言った感じで、理解するまでにかなり時間がかかりました。
UI / UX
ショートカット機能や入力エリアの自動フォーカス移動など、細かい部分での使い勝手にはこだわりました。また正解数を赤色の数字のみで書くのではなく「7○」と表示するなど、色情報によらずとも数字の意味を理解できるように工夫しています。
アイコンはすべて tabler icons です。ちょっと前までは Material Symbols を使っていたのですが、最近は専ら tabler icons を使っています。インポート補完が効くのがつよすぎる。
スマートフォンでの表示にも対応しています。
さいごに
今回は割と忙しく終始何かに追われながらの開発でした。なのでブランチ管理もしていないし、コードもとても汚いです。冗長な記述がたくさんありバグがあったときに変更箇所がいくつもあって苦労しました。本当に「急がば回れ」ですね。反省しています。
とはいえ個人的には昔からずっと作りたかったソフトだったので、今回このような形でお披露目することが出来てとても嬉しいです。
ある程度使ってもらえそうであれば今後のアップデートも検討しています。不具合報告や機能要望は Twitter やフォームから受け付けてます!
Discussion