Skip to content

Repository files navigation

Qalc

小学生向け計算ゲーム学習アプリ - みんなで楽しく算数を学ぼう!

React製のブラウザ計算ゲーム。小学1〜6年の算数を119コース・11,796問でカバーし、25種類の学習補助図・P2Pマルチプレイ(ボスバトル/じんとりバトル)・きせかえ・デイリーミッションを搭載。インストール不要・アカウント不要・オフライン対応。

デモ

https://qalc.giga-school.com/

3行でわかる Qalc

  • 開くだけで使える。 ログイン・アカウント登録・名簿登録なし。成績はその端末の localStorage にだけ残り、外に出ない
  • 中身は 119コース・11,796問。 小学1〜6年の学習指導要領の各領域をひととおりカバーし、25種類の「かんがえるどうぐ」(図・筆算・分数バーなど)が問題ごとに自動で出る
  • 1人でも学級全体でも遊べる。 ひとりで3モード、みんなであそぶ(P2P・サーバー不要)で5モード。協力レイドの「ボスバトル」とチーム戦の「じんとりバトル」は学級での使用が前提

特徴

5つのゲームモード

モード 説明 ひとりで みんなで
スコアアタック 制限時間内にハイスコアを目指す(1〜10分をスライダーで指定)
タイムアタック 全20問をできるだけ早くクリア。クリアタイムが記録に
サドンデス 1問でもまちがえたら終了。連続正解数が記録に
ボスバトル 協力モード。全員の正解でボスを攻撃し、制限時間内に何体たおせるかに挑戦
じんとりバトル チーム対戦モード。あか/あおに分かれて7×7の盤面をぬりあう。スペシャル・ラッキーマス・ラストスパートで逆転あり

ボスバトルとじんとりバトルは、みんなであそぶ(へや)専用。ひとりで遊ぶときは出てこない。

  • 正解した瞬間に自動で次の問題へ進むスピーディーな操作感。まちがえても止まらず、正解するまで同じ問題が続く(= correct は必ず100%になるので、学力の指標には「初回正答」を使う)
  • コンボシステム(5コンボ以上でフィーバーモード発動)
  • ドリルは複数まとめて選べる。学年でしぼりこむタブ(すべて/1〜6年/その他)つき。まだマスターしていないドリルを1つ「おすすめ」として提案する。出題順のシャッフルは既定でON
  • 手書きメモキャンバス付き(ペンボタン。左右どちらに出すかを切りかえられる)
  • とちゅうでやめても「つづきから」 - 「中断して保存」で切りあげると、次にホーム画面を開いたとき残りの問題から再開できる(ひとりで遊ぶモードのみ)

かんがえるどうぐ(学習補助ツール)

問題の内容(問題文・数の大きさ・演算の種類)に合わせて、使えるどうぐを全25種から自動判定src/learningTools/detect.jsgetAvailableTools())。ゲーム中に答え欄の左はしにある電球ボタンからワンタップで開けます。使えるどうぐが1つも無い問題では電球ボタン自体が出ません。

どうぐを開いて解いた問題は、学習ログに hint: true として分けて残ります。

どうぐ 内容 出る条件の例
ブロック タップして数えられる算数ブロック(10のまとまり表示) 20までのたし算・ひき算、低学年の文章題
さくらんぼ くり上がり・くり下がりの分解をタップで確かめる 8+5 のようなくり上がり、13-9 のようなくり下がり
ひっ算 問題を筆算の形で表示(答えのマスは空欄)。小数は小数点をそろえて表示 2けた以上のたし算・ひき算・かけ算・わり算、小数のたし算・ひき算
くらい 位取り表に数を並べて表示(小数対応)。小数のしくみ・億/兆の4けた区切りにも対応 100以上の数や小数をふくむ問題、小数のしくみ、大きな数
とけい 問題文どおりの時刻・針の動きをアナログ時計で描画(動いた分は弧で表示) とけいクイズ
じかん 「はじめ→おわり」を2つの時計+矢印で表示。午前午後は24時間の直線、◯分は60分のまとまりで表示 時こくと時間
ならび 「まえから◯ばんめ」を人の並びの図で表示 ことば(じゅんじょ)
ながさ テープ図で「つなぐ・きりとる」を長さに比例した図で表示 ながさのけいさん
かさ めもり付きのますに水の量を描画(のむ分は色分け) かさのけいさん
たんい 単位のはしご図(km-m-cm-mm/kL-L-dL-mL/t-kg-g/㎢-ha-a-㎡-㎠/日-時間-分-秒)と換算の倍率 単位の換算をふくむ問題
えん 半径と直径の関係を円の図で表示(円周・円の面積は公式つき) 円と球、円の計算
アレイ かけ算・わり算・あまりを「◯こずつ◯れつ」の点の並びで表示 九九、九九あなうめ、わり算、あまり、かけ算のきまり、文章題
ぶんすう 分数バー/円図/仮分数の1のまとまり/通分バー/約分バー/分数のかけ算の面積図/小数との対応数直線 2〜6年の分数コース全般、分数と小数
かく 回転と直角の関係を扇形で表示。多角形は1つの頂点から三角形に分割して表示 角の大きさ、図形の角、正多角形
へいきん ぼうグラフを「ならした高さ」の点線つきで表示 平均
みはじ 道のり・速さ・時間の関係図(求めるものを?で表示) 速さ・時間・道のり
わりあい 二重数直線(量↔百分率/量↔こ数)、歩合・%・小数の対応表、比のまとまり図 割合、百分率、歩合、単位量あたり、比
ばいすう 1〜60の数の表を倍数・約数でぬり分け(公倍数・公約数は重なりで表示) 倍数と約数、公倍数・公約数、約分、比を簡単にする
しき □(x)を使った式をテープ図にして「□=◯−◯」の形を表示 □を使った式、文字と式、変わり方
ひょう xとyの対応表と「よこは×◯」の関係を表示 比例・反比例
データ ドットプロットで中央値・最頻値をハイライト データの代表値
ばあい ならべ方のかけ算チェーン、組み合わせの表、樹形図 場合の数
すうちょくせん 四捨五入する数を数直線上に表示(まんなかの値つき) がい数(四捨五入)
ずけい 長方形・三角形・平行四辺形・台形・ひし形・正多角形・直方体・角柱・円柱などのスケッチと公式。線対称の軸・四角形の性質・拡大図にも対応 面積・体積のけいさん、垂直・平行と四角形、対称な図形、拡大図と縮図
じゅんばん さきに計算する部分をハイライト 計算のきまり、四則混合

このほか、あわせて10(◯+?=10)では10フレーム(10のへや)が出ます。

リアルタイムマルチプレイ

PeerJS(WebRTC)によるP2P通信で、サーバー不要のマルチプレイを実現。

  • へやをつくる - 10桁のルーム番号を発行、QRコード・URL共有に対応
  • へやに入る - ルーム番号(10桁)とニックネームを入力して申しこみ、リーダーの許可で入室
  • リアルタイムスコア同期 & ライブランキング表示
  • リーダー(ホスト)もメンバーと一緒にゲームをプレイ可能

入室のきょか(リーダー承認)

ルーム番号を知っているだけでは中に入れません。番号を当てられても、参加者のなまえ一覧やゲームの内容は届きません。

  • 児童が申しこむと、リーダーの画面の「入りたい人」に並ぶ。児童側は「リーダーの許可を待っています」の状態で待つ
  • リーダーが〈いれる〉を押すまで参加者リストに載らず、参加者リスト・ゲーム開始・問題文・ボスやじんとりの状態はいっさい配信されない
  • 〈ことわる〉を押すと、その端末には「リーダーが きょかしませんでした」と表示して切断する
  • うけつけタイム(30秒) - 学級全員をいっせいに入れるためのボタン。押している間の申しこみは自動で許可される(ACCEPT_WINDOW_MS)。押しなおすまで延長されない
  • いま待っている申しこみをまとめて許可する〈ぜんいん いれる〉あり(30人学級でリーダーが30回タップせずに済むように用意した)
  • ゲーム中に届いた申しこみは「いま ゲーム中です」と返して切断する(だまって待たせない)
  • ルーム番号は crypto.getRandomValues による10桁(約90億通り)。児童がテンキーで打てるよう数字のままにし、画面では 4-3-3 に区切って表示する
  • なまえはひらがな・カタカナ・英数字の8文字まで。受けとったリーダー側でもかけ直すので、改造した端末から長い文字列や記号を送りこむことはできない
  • 通信の取り決めに版番号(PROTOCOL_VERSION)を持たせている。キャッシュに残った古いバージョンの端末が入ろうとすると「アプリが古いようです。ページを さいよみこみ してね」と表示する(原因のわからない「ルームが見つかりません」にはしない)

入室まわりのロジックは src/roomAccess.js に切りだしてテストしている(npm run test:room)。

協力モード「ボスバトル」

個性ゆたかな4体のボス(スライムキング・プルン、カウントドラゴン・ゴロン、まじょねこ・ニャルル、メカボス・カリキュロン)と連戦する協力レイド。ボスはドット絵イラスト(public/bosses/)で登場し、待機・被弾・攻撃・撃破それぞれにアニメーションがつく。

  • 全員の正解がボスへのダメージに。コンボ・フィーバーでダメージアップ

  • ボスも反撃してくる。技は全10種(太字が強化で追加された技)

    系統 効果
    問題かくし 妨害 問題文の上にヘドロが落ちて読めなくなる
    くらやみ 妨害 問題が ?????? になる
    テンキーシャッフル 妨害 数字キーの並びがバラバラに(全員同じ並び)
    入力こおり 妨害 数秒間入力できなくなる(1人または全員)
    チームHP攻撃 ダメージ チームHPに直接ダメージ
    かがみ文字 妨害 問題文が左右反転して表示される
    のろい 妨害 一定時間、与えるダメージが半分になる
    時限爆弾 ダメージ 制限時間内にチーム全体で規定数の正解を出さないとチームHPに大ダメージ
    きゅうしゅう ダメージ チームHPを吸い取ってボスが回復する
    バリア 妨害 一定時間、ボスへのダメージが半減する
  • 2系統どうじ進行 – 「ダメージ技」と「妨害技」はそれぞれ別のタイマーで飛んでくる。1つの抽選テーブルを共有していたころは片方を増やすともう片方が減ってしまったが、分離したことでダメージ技の頻度を約1.3〜2.7倍に増やしつつ、妨害技の頻度は下げずに済んでいる

  • げきおこモード – ボスのHPが30%を切ると激昂。攻撃間隔が約6割に縮み、攻撃力が1.4倍に

  • れんぞくこうげき – 3体目以降やげきおこ中は、1ターンに最大3連続で技を撃ってくる

  • 強化前とくらべて、ボスの体力は約1.5倍、攻撃頻度は約2.5倍

  • 正解でたまる「おうえん」ゲージで仲間のデバフ解除&チームHP回復&ダメージ2倍

  • たおすたびに次のより強いボスが登場。時間内の撃破数がチームの記録に

  • 演出:ボス登場カットイン/技名カットイン/画面シェイク/被ダメージ数字のポップ/HPバーの残像/ためこみ予兆リング/げきおこ時の集中線/ボス専用の効果音

  • チームHPが0になっても全滅はせず、「たてなおし」でゲームは続行(コンボリセット+ボス回復のペナルティ)

  • 結果発表では与ダメージ・おうえん回数を可視化し、アタック/サポート/コンボMVPを表彰

チーム対戦モード「じんとりバトル」

あか/あおの2チームに分かれて7×7の盤面をぬりあう対戦モード。問題を解く画面と盤面を同時に見ながら、「どこをねらうか」の作戦がカギ。

基本ルール

  • 正解するたびに、ねらったマスへ1ぬり(フィーバー中は2ぬり)。必要ぬり数がたまるとチームの色に
  • マスをぬるとまわりにインクがはねる(+1)。たまっていたマスが連鎖でぬれると「れんさ!」演出
  • マスをタップして自由にねらいを変更。仲間・敵のねらいもドットでリアルタイム表示
  • 敵のマスをうばうには+1、自陣にとなり合っていないマスも+1コスト → 前線を広げる陣形戦略が生まれる
  • ぬり終えると自動でとなりのマスへねらいが移り、前線がのびていく
  • うばえない本陣(🏠)から陣地を拡大。★マス(3pt)とまんなかのマス(5pt)は高コスト・高得点の争点

盛り上がりの仕掛け

要素 内容
スペシャル 正解10回でゲージが満タン。3種からランダムで発動できる(次に何が出るかは発動後のおたのしみ)
 💥 スーパーチャクチ ねらったマスに大量のぬり+まわり4マスにも飛び散る。★マスの一発うばいもねらえる
 🌈 スプラッシュライン ねらったマスのたて1列・よこ1行をまとめてぬる。一気に戦況が変わる大技
 ⚡ インクラッシュ 12秒間、自分のぬりが3倍
?マス(ラッキーマス) 盤面に4つ。とると「スペシャル満タン」「インクばくはつ(まわり8マス)」「ラッシュ」のどれかが当たる
ラストスパート のこり30秒で全員のぬりが2倍に。画面が赤くもえ、最後の30秒で逆転がおきる
カウントダウン のこり5秒は画面いっぱいの数字+カウント音
ぜんめんうばいあい 盤面が全部うまっても試合は続行。時間いっぱいまで陣地のうばいあいになる

演出

  • マス確保時のインクスプラット(不定形なブロブ+しぶきの粒)、あと1回でぬれるマスの鼓動アニメ
  • スペシャル発動の斜め帯カットイン、実況テロップ(うばった/れんさ/ラッキー/ぎゃくてん!/うまった)
  • チームスコアバーはぬり率%表示・リード側に👑・接戦時は「せっせん!」・ラストスパート中は赤く点滅
  • じんとり専用の効果音(インク・スペシャル・ラッキー・カウントダウン)とバイブレーション
  • 結果発表はぬり率カウントアップ+盤面がぱらぱら開くアニメ。ぬり/こうけん/うばい/スペシャル/コンボの5種MVPを表彰

その他

  • ロビーでチーム分けを自由に編成(参加時は人数の少ない側へ自動割当)
  • 時間切れで終了。合計ポイントの多いチームが勝利

119の計算コース(小学1〜6年・11,796問)

学年別にプリセットされた計算問題コース群。学習指導要領の各領域(数と計算/図形/測定・変化と関係/データの活用)をひととおりカバーしています。

コース名は 学年_単元 の形式で、コース一覧は学年順にならぶ。ことば(〜) が付くものは文章題。

学年 コース数 収録している単元
1年 19 あわせて10、10といくつ、3つのかず、たしざん/ひきざん(10まで)、くりあがり、くりさがり、おおきいかず、なん十のけいさん、とけいクイズ、文章題(いくつといくつ/あわせて・のこりは/ちがい/3つのかず/じゅんじょ/かずのならび/おおきい・ちいさい/おおきさくらべ/かたちづくり)
2年 25 九九(一〜九の段を段ごと・全段まとめ・あなうめ)、なん十の計算、2けたのたし算・ひき算、3けた・4けたの計算、数のしくみ(1000まで)、時こくと時間、分数、文章題(たんい/おおきい・ちいさい/かたち/かけ算/かけ算のきまり/ながさのけいさん/かさのけいさん)
3年 23 わり算、あまり、大きいわり算、かけ算(2けた×1けた/3けた×1けた/2けた×2けた/3けた×2けた)、何十のかけ算、3けたのたし算・ひき算、暗算、大きい数の計算、小数のたし算・ひき算、分数のたし算・ひき算、小数と分数、時間(秒と分)、□を使った式、文章題(わり算/あまりのあるわり算/円と球/長さと重さのたんい)
4年 18 わり算(1けた/2けたでわる)、小数×整数、小数÷整数、小数のたし算・ひき算、分数たし算・ひき算(1より大きい)、仮分数と帯分数、大きな数(億・兆)、がい数(四捨五入)・見つもり、計算のきまり、文章題(角の大きさ/面積のたんい/面積のけいさん/小数のしくみ/垂直・平行と四角形/変わり方)
5年 20 小数のかけわり、小数と10・100の計算、倍数と約数、公倍数・公約数、約分、通分、分数たしひき、分数と小数、割合(小数→%・くらべる量・もとにする量)、単位量あたりの大きさ、3.14のけいさん、文章題(百分率/歩合/平均/図形の角/図形の面積/台形・ひし形の面積/正多角形と円/体積のけいさん)
6年 13 分数かけわり、分数と小数のまじった計算、文字と式、速さ・時間・道のり、円の計算、比のけいさん、比を簡単にする、場合の数、文章題(対称な図形/拡大図と縮図/立体の体積/データの代表値/比例・反比例のけいさん)
チャレンジ 1 四則混合

九九・単位換算のように規則で作れるものは、静的な一覧ではなく実行時に生成している(src/data/problems.jsgenerateDynamicProblems())。

コースのカスタマイズ(ホーム画面の「コース管理」)

  • オリジナル問題の作成・編集・削除
  • 共有コード - 作ったコースを文字列にして配り、貼りつけて取りこむ
  • AI(ChatGPT / Claude)で問題を自動生成し、CSVインポート - 画面にプロンプトのひな形が用意してある。形式は 問題,答え で、正解が複数あるときは | 区切り
  • にがて克服ボックス - まちがえた問題を自動で集めて重点練習(最新50問まで。正解するとその場でボックスから抜ける)。ここでの学習は報酬の減衰対象外

ゲーミフィケーション

  • レベルシステム - EXPを獲得してレベルアップ。レベルに応じて7段階のランクが上がる(🌱かけだし → 🥉初級 → 🥈中級 → 🥇上級 → 💎達人 → 👑計算マスター → 🌌計算神)。レベルアップ時はコインボーナス(1レベルにつき50コイン)&スター紙吹雪でお祝い

  • れんぞく学習ストリーク - 毎日続けるとホーム画面に🔥連続日数を表示。ストリークが長いほど高報酬ミッションが出やすくなる

  • デイリーミッション - 毎日3つのミッションをクリアしてコインを獲得(プレイ回数・モード別プレイ・コンボ・スコア・サドンデス連続正解・ボス/じんとり参加の全49種から抽選)

  • きせかえショップ - コインでアバターパーツとテーマを購入。アバターは重ね着式で、着せた見た目がホーム画面と参加者リストに出る

    カテゴリ 種類数
    ベース(動物・キャラ) 91
    ぼうし 100
    かお 57
    もちもの 88
    はいけい 38
    エフェクト 29
    称号 36
    テーマ(配色) 32
  • ふしぎなたまごガチャ - 1回500コイン。ショップには並ばないガチャ限定品50種がここからだけ出る。レアリティは N / R / SR / UR の4段階(重み 50 : 30 : 15 : 5)で、ダブったぶんはレアリティに応じてコインが返る(100 / 200 / 400 / 1000)

  • がんばりグラフ - 直近7日間の獲得EXPを棒グラフで可視化

  • まなびのきろく - 学習ログ(後述の study.v1)を読み出し、直近7日の「あそんだ回数・集中した時間・1回めで正解した率」と、まちがえの多かった問題を児童本人に見せる

  • マスター判定 & 報酬バランス - 1セッション10問以上・正答率95%以上・1問5秒以内を3セッション連続で満たすとそのドリルは⭐マスターに(お祝い演出つき)。正答率が80%を下回ると解除される。マスター済みドリルを楽々クリアしたとき(正答率90%以上)はEXP・コイン・ミッション進捗が0.2倍、同じドリルの同日3回目は0.5倍・4回目は0.25倍・5回目以降は0.1倍に減衰し、「つぎのドリルへの挑戦」を促す(プレイ自体はいつでも自由。にがて克服ボックスと、苦戦した復習は常に満額)

学習ログ(study.v1

GIGA山の学習アプリ群で共通の形式(スキーマ版 study.v1 / appId qalc / appVersion 1.0.0)で、学習の記録を端末に残す。

  • 保存のみ。外部送信は一切行わない。 氏名・出席番号などの識別情報も持たない(識別子は送信ページが付与する)
  • 保存先は localStorage のキー study.records.v1(最大500件、古いものから削除)
  • 1セッション(結果発表まで)で1レコード。とちゅうでやめた記録も status: "aborted" として残す
    • 「ポイントもらって終わる」/「中断して保存」を選んだとき
    • タブを離れたまま5分もどらなかったとき(離れた時刻で締めるので、待っていた時間は学習時間に入らない)
    • pagehide(タブが破棄される直前)。Chromebookはメモリ不足やスリープでタブごと消えるため、ここで確定しないと記録中のぶんが失われる。保存後は次のレコードが自動的に始まるので、bfcacheから戻って学習が続いても取りこぼさない
  • 主指標は 初回正答(firstTryCorrect。Qalcは正解するまで次に進めないため、correct は横断比較に使えない
  • 設問層(items)には設問ID・初回正答・解答回数・かかった時間・どうぐ使用(hint)・誤答内容を残す。文章題の設問IDは問題文ではなくハッシュ(容量と改訂への耐性のため)。1レコード200件までで、summary.attempted は必ず items の件数と一致させる(超えたぶんの実数は ext.itemsTruncated に残す)
  • ボスバトル・じんとりバトル(multiplayer: true)は妨害や盤面戦略が入るため、学力指標としては集計しない(取り組み量としてのみ扱う)
  • ホーム画面の「まなびのきろく」は、このログを読み出して児童本人に成長を見せる機能(読み出し専用)

ファイル構成は仕様書の3層構成に従う。

ファイル 役割
src/studyLog.js 全アプリ共通・不変の保存処理(ロジック版 1.1)。Qalc固有の事情で書きかえない
src/studySession.js Qalcのゲーム進行から study.v1 レコードを組み立てる。単元ID表・appVersion はここ
src/studyStats.js 読み出し(集計・表示用)。ログへの書き込み・削除はしない

study.records.v1 は複数アプリ共通の学習ログです。このアプリ専用のキーではないため、 リセット処理やクリーンアップの対象に含めないでください。

単元ID(unit.id)は改訂しても不変であることが最も重要な要件。コースの表示名を変えるときは src/studySession.jsUNIT_IDS で名前だけを差しかえ、IDは据えおき、旧名称も同じIDに向けること。

組み立ての検証は npm run test:studylog で実行できる。

アクセシビリティ

  • 漢字にふりがな(ruby)対応
  • 32種のカラーテーマ(ダーク・さくら・うみ・サイバー・まよなか・にじいろなど。ショップで購入して切りかえる)
  • 効果音 & BGM(Web Audio APIによる合成音。音源ファイルは持たない)、ヘッダーのスピーカーボタンでON/OFF切替。既定はOFF(教室で一斉に鳴らないように)
  • モバイルファースト設計(タッチ操作・バイブレーション対応)
  • 「戻る」操作対応(スマホ・タブレット) - 画面下のナビゲーションバーの戻るボタン、または画面の左右どちらかの端から中央へのスワイプで、1つ前の階層の画面にもどれる。ダイアログや手書きメモが開いているときはそれを閉じ、ゲーム中は「途中でやめますか?」の確認、みんなであそぶ中は「へやから出ますか?」の確認をはさむ。履歴に番人を積んでいるので、戻る操作でブラウザが前のページへ移動したり、ホーム画面に追加したアプリ(PWA)が終了したりすることはない

技術スタック

カテゴリ 技術
フレームワーク React 19
ビルドツール Vite 5
スタイリング Tailwind CSS 3
アニメーション Framer Motion
アイコン Lucide React
P2P通信 PeerJS (WebRTC) ※同梱・「みんなであそぶ」を選んだときに読みこむ
QRコード qrcode ※同梱・へやを作ったときに読みこむ
エフェクト Canvas Confetti ※同梱・ゲーム画面で先読み
フォント Zen Maru Gothic ※バンドルに同梱(woff2のみ)
データ保存 LocalStorage
デプロイ GitHub Pages

通信と保存データ(校内で使うとき)

端末に保存されるもの・外に出ないもの

学習ログ・成績・コイン・きせかえ・にがて問題は、すべてその端末の localStorage に保存され、外部へ送信されない。アカウント登録もなく、氏名・メールアドレス・生年月日などは一切あつかわない。広告やアクセス解析のタグも入っていない。

キー 中身
qalc_stats レベル・EXP・コイン・所持アイテム・装備・テーマ・ミッション・ストリーク・コース別の周回とマスター状況
qalc_problems 問題データ(プリセット+自作コース)
qalc_defaults_version プリセット問題の版。上がると新しいコースを取りこむ
qalc_mistakes にがて克服ボックス(最新50問)
qalc_resume 中断したゲームの続き
qalc_exp / qalc_streak / qalc_last_date 旧形式からの引きつぎ用(qalc_stats が無いときだけ読む)
study.records.v1 学習ログ(後述。複数アプリ共通のキー

「みんなであそぶ」で他の端末に伝わるのは、児童が自分で入力したニックネーム(8文字まで)とスコア・コンボだけ。

共用端末で使う場合、学習ログはその端末に残るため、次に使う児童からも見える点に注意(個人の特定はできない)。

校内フィルタリングで許可が必要なホスト

ホスト 用途 ブロックした場合
qalc.giga-school.com アプリ本体の配信 アプリが開けない
0.peerjs.com みんなであそぶ(へやの番号のやりとり) みんなであそぶだけが使えなくなる
stun.l.google.com / eu-0.turn.peerjs.com / us-0.turn.peerjs.com みんなであそぶ(端末同士のつなぎ役) 環境によってつながらない

みんなであそぶを使わせない運用にする場合は、0.peerjs.com をブロックすればひとりで遊ぶモードだけに限定できる

ひとりで遊ぶだけなら、必要な通信は qalc.giga-school.com のみ。 JavaScript(PeerJS・QRCode・Canvas Confetti)と本文フォント(Zen Maru Gothic)はすべて同梱しており、外部CDNやGoogleへの通信は発生しない。フォントは unicode-range でサブセットに分かれているため、画面に出た文字ぶんだけ(ホーム画面なら14ファイル程度)が落ちてきて Service Worker にキャッシュされる。2回目以降は通信ゼロ、オフラインでも本来の書体で表示される。

受けとったデータの検証

P2P で届くメッセージは、開発者ツールから改造した端末なら何でも送ってこられる。そのため両方向で型と範囲をたしかめている(src/roomAccess.jsparseMemberMessage / parseHostMessage)。

  • 知らない type は捨てる。範囲外の数値は はじくのではなく丸める(通信のゆらぎでゲームが止まらないように)
  • メンバー→リーダー: ボスへの与ダメージは上限90(calcRaidDamage の最大値)、じんとりのマスは 0〜48、1正解あたりのぬり数は12まで。巨大な数値を送ってボスを1発でたおすことはできない
  • リーダー→メンバー: game_start は決まった7つのキーだけを組み立てなおす(以前は届いた値を画面の状態にまるごと混ぜていたため、知らないキーで上書きできた)。問題文は文字列200文字までにしぼる
  • 参加者リストのなまえは受けとる側でもかけ直す(長い名前で画面をくずされない)

Content Security Policy

index.html<meta http-equiv="Content-Security-Policy"> で指定している。GitHub Pages は HTTP レスポンスヘッダを設定できないため meta 方式で、この方式では frame-ancestors / report-uri / sandbox は効かない。

  • script-src 'self' … 外部CDNからのスクリプト読み込みを禁止
  • style-src'unsafe-inline' が必要 … テーマ切り替えが動的な <style>、Framer Motion が style 属性を書きかえるため。外すとテーマとアニメーションが壊れる
  • font-src 'self' … フォントも同梱したので外部は不要。data: を許可しなくてよいよう、Vite の assetsInlineLimit でフォントの data: URI 化を止めている(vite.config.js
  • connect-src … PeerJS のシグナリング(0.peerjs.com)のみ許可
  • WebRTC の ICE(STUN/TURN)は CSP では制御できない(stun: / turn: を書いてもブラウザが無視する)。接続先は src/roomAccess.jsPEER_OPTIONS に明示している

CSP を変更したら npm run build && npm run preview で本番と同じ成果物を開き、全画面を踏んでコンソールに違反が出ないことを確認すること。 npm run dev では HMR のため CSP を外している(vite.config.js)ので、開発サーバでは検証できない。

Service Worker

静的アセットをキャッシュ優先で持つため、中身を変えたら public/sw.jsAPP_VERSION を上げること。上げ忘れると、旧バージョンを持った端末が新バージョンのへやに入れず「アプリが古い」と言われ続ける。

キャッシュは2本に分かれている。

  • qalc-cache-static-<版> … アプリの外枠(index.html / offline.html / アイコン / manifest)。版を上げると作りなおす
  • qalc-cache-runtime-v1 … ファイル名にハッシュが付いた JS / CSS / フォント。中身が変わればファイル名も変わるので版をまたいで残す

activate では qalc-cache- で始まるキャッシュしか消さない。 いまは qalc.giga-school.com がこのアプリ専用のオリジンだが、旧配信元の gigayama.github.io は複数のアプリが同一オリジンを共有していた。caches.keys() を全消しする書き方にすると、その形に戻したとたん他のアプリがオフラインで起動しなくなる。

installskipWaiting()しない。新しい版は待機させ、児童が「さいしんに する」を押したときだけ SKIP_WAITING を受けて入れかわる(計算のとちゅうで勝手に切りかわらないように)。

📲 PWA

public/manifest.webmanifestid / scope / start_url は、すべて "./" にしてある。独自ドメイン qalc.giga-school.com ではアプリがドメイン直下に置かれるためで、旧構成の /Qalc/ のようなリポジトリ名の絶対パスに戻すと、scope がページの URL を含まなくなり PWA としてインストールできなくなる

同じ理由で、Service Worker の登録先(src/pwa.jsx)と public/sw.js の先読み一覧も、リポジトリ名の絶対パスで書いてはいけない。実際にここが /Qalc/sw.js/Qalc/… のまま残っており、登録も先読みも全件 404 になっていた。どちらも失敗を握りつぶす作りなので、画面には何も出ないまま「オフラインで開けない・インストールできない」だけが残る。npm run checkE1c がこれを見るようにしてある。

ファイル 役割
public/pwa-install.js beforeinstallprompt<head> の最上部で受け取る。React より先に動く必要があるので外部ファイルに分けてある(CSP が script-src 'self' のためインラインにできない)
src/pwa.jsx インストールボタン(iOS は「ホーム画面に追加」の手順を案内)、更新のお知らせ、Service Worker の登録
public/offline.html 圏外のときに出す画面。この1ファイルだけで完結させてある

♿ 表示とアクセシビリティ

項目 どこ
文字サイズの追従(clamp() tailwind.config.jsfontSize。下限は Tailwind のもとの値のままなので、狭い画面の見た目は変わらない
提示モード(電子黒板) src/presentation.jsxbody.presentation--fs-scale が 1.5 になり text-* が一括で大きくなる
児童名の伏せ字 <PupilName>。提示モード中だけ「た○○」になる。教材名・ボス名には使わない
動きの配慮 CSS は src/index.css、Framer Motion は MotionConfig、紙ふぶきと振動は prefersReducedMotion()
Canvas の鮮明化 canvasDpr()(上限2)。手書きとQRコードの両方に適用
セーフエリア src/index.css--safe-*。ヘッダー上端・フッター下端・左右に適用

⚠️ 制限とクォータ

制限 効いてくる場面
学習ログの保存件数 500件(studyLog.js 超えた分は古いものから消える
1セッションの記録問題数 200件 それ以上はログに残らない
にがて克服ボックス 50問まで 超えた分は古いものから消える。正解した問題はその場で抜ける
リーダーが配る問題 2000問まで へやで大量のコースを選んだときに切る
ニックネーム 8文字まで 画面がくずれないよう受けとる側でも切る
ボスへの与ダメージ 1回あたり90まで 改造した端末から巨大な値を送れないように
じんとりのぬり数 1正解あたり12まで 同上
問題文の長さ 200文字まで リーダーから配るときに切る
Canvas のバッファ 4096px まで レイアウトが暴走しても端末が固まらないようにする保険
iOS Safari の localStorage 7日未使用で消える ホーム画面に追加すれば回避できる

「みんなであそぶ」に自動再接続はない。 校内Wi-Fiが混んで切れた場合、児童には「リーダーとの接続が切れました」と出て、ホームにもどってから入り直す必要がある。ゲームのとちゅうで切れると、その回の記録は中断(status:"aborted")として残る。自動で入り直す仕組みを入れると、抜けたはずの端末が勝手に戻ってきて参加者リストが二重になるおそれがあるため、あえて手動にしている。

セットアップ

# 依存関係のインストール
npm install

# 開発サーバーの起動(HMR のため CSP は外れている)
npm run dev

# 本番ビルド
npm run build

# CSP を含めた本番相当の確認(build のあとに実行する)
npm run preview

検査(CI が回しているものと同じ)

npm test              # 下の3本をまとめて実行
npm run test:studylog #   学習ログ(study.v1)の組み立て
npm run test:room     #   入室のきょか・受けとったデータの検証
npm run test:battle   #   ボスバトル/じんとりバトルのロジック

npm run lint          # ESLint(未使用変数・useEffect の依存もれ)
npm run check         # 品質ゲート(LICENSE・CSP・PWA一式・画像/ファイルの大きさ・初回JSの大きさ)+コントラスト比
npm run check:contrast # コントラスト比だけ

npm run build && npm run measure # 実測ツール(PWA・オフライン・アイコン・各画面。詳細は tools/measure/README.md)

.github/workflows/verify.yml がプルリクエストごとに audit → test → lint → build → check を回す。 .github/workflows/deploy.ymlmain への push で同じ検査をしてから GitHub Pages へ配る。

ライセンス

MIT License — 詳しくは LICENSE を参照してください。

© 2026 Qalc GIGA山

Releases

Packages

Contributors

Languages