推しアイデア
学校のテストを「やらされるもの」から「点数を活かして遊びたい」というアイデア!
学校のテストを「やらされるもの」から「点数を活かして遊びたい」というアイデア!
バカとテストと召喚獣の試験召喚システムを再現したいと思ったから!
型安全な開発!
というナレーションから始まるバカとテストと召喚獣という作品に登場するシステムです。 概要は学力試験の点数をそのまま自身の能力値を持つ「召喚獣」として可視化し、クラス同士の対戦(試験召喚戦争)を可能にするシステムです。 このプロダクトはこの試験召喚システムという作品内に出てくるものを現実で再現していくプロダクトです。
デプロイ Figma ドメイン(Wikipedia) デモ動画①試験作成から採点送信までのフロー デモ動画②試験召喚システム完成版 デモ動画③管理者画面
Next.js 16.2.9 React 19.2.4 TypeScript 5.9.3 TailwindCSS 4.3.0 TanStack Query 5.101.0 Zustand 5.0.14 Vitest 4.1.9 ReactThreeFiber @pixiv/three-vrm
バトル・3D描画のCSRと、認証・管理画面のSSRをページ単位で使い分けるためNext.js(App Router)を採用しました。
TypeScriptはOpenAPIスキーマから型を自動生成することでフロントとバックの型ズレをコンパイル時に防ぎ、TailwindCSSはゲームのダークテーマをユーティリティクラスで素早く構築するために採用しています。
サーバー状態の取得・キャッシュにはTanStack Query、バトルのリアルタイム状態管理には軽量なZustand、テストはNext.jsとの相性が良いVitestを採用しました。
Ruby 4.0.5 Ruby on Rails 8.1.3 Go 1.26.3
APIサーバーにはRuby on Railsを採用! 開発の流れ・形が決まっている分RESTAPIを作る速度は爆速だろうという理由で採用! また、開発経験が多いメンバー2名両方が触れるバックエンドの技術として上がったため属人化を避けるために採用しました。 DBマイグレーション・認証・ジョブキューなどの機能が揃っており、開発速度を重視した選択です。
リアルタイムのバトルロジックにはGoを採用しています。WebSocketを使った同時接続処理に強く、Railsより軽量で高速なため、ゲームのリアルタイム通信に適していると判断しました。
ジョブキューにはSolid Queueを採用しています。AI採点のバックグラウンド処理をRailsのプロセス内で完結でき、Redisなどの外部依存を追加せずに済みます。認証にはJWTをHttpOnly Cookieで管理しており、XSSによるトークン窃取を防いでいます。
Vercel(フロントエンドホスティング) Raspberry Pi 4B+(デプロイサーバー) GitHub Actions Self-hosted Runner(CI/CD) Cloudflare Tunnel(外部公開) Supabase
バックエンドのデプロイには手元にあったRaspberry Pi 4B+を自前サーバーとして活用しました。
クラウドのホスティング費用をかけずに本番環境を構築できるため採用しています。 外部からのアクセスにはポート開放不要で安全にトンネルを張れるCloudflare Tunnelを使用しています。(マジ便利)
フロントエンドはVercelにデプロイしています。Next.jsとの親和性が高く、プッシュだけで自動デプロイが完結します。
CI/CDにはGitHub Actions Self-hosted Runnerを採用しました。RaspberryPi上でRunnerを動かすことでクラウドのCI利用枠を消費せず、自前環境でテスト・デプロイを自動化しています。
PostgreSQLはRailsのActiveRecordとの相性の良さと実績から選択しました。ローカル開発ではDockerを使用しています。
全体的に安さ、手軽さ、開発速度を重視した技術選定でハッカソンに合わせた選択基準になってます。
Docker(PostgreSQL のみ) 開発環境の管理にはNixを採用しました。Ruby・Go・Node.js・各種ツール系(lefthookやtaskfile.devなど)のバージョンをflake.nixで宣言的に管理することで、自分の環境では動くけど...といった問題を防ぎます。
DBのみDockerで構築しています。PostgreSQLをコンテナで立ち上げることでローカルへの直接インストールが不要になり、docker compose upだけで即座にDB環境が再現できます。
ローカルのDB周りでの接続に以前の開発で前詰まりまくった記憶があり、Dockerで行った(捨てやすくて良かったかも)
モノレポ構成のコマンド管理に使用しました 以前はpnpmを使ったり、Makefileを使ったりしてたが、よりモダンで柔軟なTaskfile.devを採用しました。タスクの依存関係や並列実行、環境変数の注入などが簡単にできるため、開発効率が上がりました。導入楽だし、今後も使っていきたい
task setupで開発環境のセットアップが一括で完了するようにして、環境作り直しになった時に楽できるようにできてにこにこハッピーちゃんです!
$task task: [default] task -l task: Available tasks for this project: * ci: 全サービスのCIチェックをローカルで全て実行する * default: タスク一覧 (aliases: list) * dev: 全サービスの開発サーバーをまとめて起動 * install: 全サービスの依存関係をまとめてインストール * lint: 全サービスのlintをまとめて実行する * scan: 全サービスのセキュリティスキャンをまとめて実行する * setup: 🚀 新規参加者向けセットアップ(install → .env → DB起動 → DB初期化 まで一括実行) * test: 全サービスのテストをまとめて実行する * db:create: データベースを作成する * db:migrate: マイグレーションを実行する * db:reset: DBをリセットして再セットアップする(開発用) * db:rollback: 直近のマイグレーションをロールバックする * db:seed: シードデータを投入する * db:seed:demo: デモ環境用の初期データを明示的に投入する * db:setup: DB作成・マイグレーション・シードをまとめて実行する * db:start: PostgreSQLをdocker-composeで起動し、healthyになるまで待つ * db:status: マイグレーションの状態を確認する * db:stop: PostgreSQLを停止する * deploy:seed:demo: デプロイ先へデモ環境用の初期データを投入する * hooks:install: lefthookのgitフックをインストールする * lint:fix: 全サービスのlintを自動修正する * openapi:generate: OpenAPI 仕様(docs/openapi.yaml)を再生成し、フロントの型定義を更新する * setup:check-tools: 必要ツールの存在確認 * setup:copy-env: .env ファイルをコピー(存在しない場合のみ) * setup:db: DB起動 → 作成 → マイグレーション → シード * setup:done: セットアップ完了メッセージ * setup:install: 依存関係のインストール

①ログイン ②テストを解く(アプリ外) ③解いたテストをアップロードする ④採点結果が返ってくる ⑤採点結果をもとに、召喚獣を召喚してバトルをする ⑥リザルト画面



①ログイン ②試験設定 ③模範解答をアップロードする ④生徒の答案を提出後、模範解答をもとにAIが自動採点 ⑤手動で確認し採点結果を生徒に送る





ダッシュボードを作成し、画像では見えていませんが、クイックアクションで簡単にボタンを押せるようにしました。

他にも、試験作成、試験設定、点数管理、AI自動採点、クラス管理、生徒一覧の閲覧画面があります。
管理者ダッシュボード、ユーザー管理(生徒、先生、管理者含めて)
クラス設定、全体成績、試召戦争ログ
当初の実装:Tesseract OCR 背景:原作は手書きのテストを手作業で採点してたけど、現代の技術ではテストの採点を自動化できると考え実装を始めた。 教師側の模範解答と生徒側から送られてくる答案を自動採点の最初のアプローチは、オープンソースのOCRエンジン Tesseractを使って答案をテキスト化する方式をとった
実際に日本語テキスト付きのテスト答案PDFを読み込ませてみると、以下の問題が起きた
精度を上げようと前処理(画像の二値化・解像度向上)も試んでみたけどうまくいかなかった。
そこで答案画像をそのまま Gemini APIに送信して採点させる方式に変更した。テキスト抽出を挟まないため、手書きの文字や図表も含めて採点できるようになった。
geminiAPIがRPD20という制約上、デプロイ環境では Gemini API キーを環境変数に設定していないため、AI自動採点機能は本番環境では動作してないけど、ローカル環境では正常に動作するようになった。
RailsのRSpecを使用することでopenapi.jsonを生成することができ、スキーマ駆動で型安全な通信が行えました
フロントエンドはopenapi-typescript で自動生成したことでバックエンドへの取得処理を書く時などに存在しないパスを書くと型エラーが出て実行前に気づくことができてハッピー!最高!
この形にしたのは最小で型安全が再現できそうだったからで、他のやつ(ProtoBufとか)を導入するには時期的に重そうだった バックエンドもTypeScriptにすると共通型を使えるので便利だしtRPCとかも楽に使えるので個人開発規模だとTypeScriptがバックエンドの候補に上がりそうだ
実際のSwaggerUIが見れる!
https://bakatest-api.uomi.site/api-docs/index.html
うん → 運 → テストは最終的に運だよね → バカテスの1話で主人公が鉛筆コロコロ→ 試験召喚システム
学校のテストって意欲的にやる人より、めんどくさいと感じている人が多いと感じているので「点数を活かして楽しく遊べるプロダクト」を作りたかった。学生が解いたテストの点数で遊ぶので生徒のやる気と学力向上にもつながると考える。
バカとテストと召喚獣には振り分け試験があり、クラスごとに教室の設備が変わる。それを再現するために、AクラスからFクラスまでの生徒のUIをランクごとに変化させた。また、A,B/C,D/E,Fごとにフォントも変えた。
また、モバイルUIも見やすいように整えた。

Aクラス

Bクラス

Cクラス
Dクラス
Eクラス

Fクラス

本作の主人公・吉井明久は「観察処分者」という特別な身分を持ち、原作では召喚獣がダメージを受けると本人にもダメージがフィードバックされます。この設定を忠実に再現するため、召喚獣バトルで受けたダメージに応じて実際に電気ショックなどの物理的ダメージをプレイヤーに与える仕組みの実装を検討しています。
スマホを通したMRで召喚獣バトルができるようにしたい
「試験召喚獣召喚!サモン!Go!」と言って、Gopherくんを呼び出して言語の特性(Goは非常に軽量で高速に動作する言語であるため、バトルにおける素早さが異常に高く、相手の攻撃を回避しやすい)で言語同士を戦わせるアプリを作るアイデアも初期出てた。