AI Dev Lab
AdageQuest

AdageQuest ができるまで — ことわざを 5 層で覚えるクイズ

ことわざクイズの開発ログ。 48 件を漢字・読み・意味・中国古典の出典・英訳の 5 層で持たせ、 読み当て / 意味当て / 穴埋め / 故事から当てる の 4 モード × 3 難度で出題する仕組みを記録しています。

AdageQuest は、ことわざと故事成語を「漢字・読み・意味・出典・英訳」の 5 層で覚えるクイズです。48 件を厳選し、4 つの出題モード × 3 難度で回します。出題も採点も端末内で完結します。

#🎯 意味を当てるだけでは覚えられない

ことわざアプリの多くは「意味を 4 択から選ぶ」形式です。でもそれだけだと、選択肢の消去法で正解できてしまい、記憶に残りません

そこで 1 つのことわざに複数の面を持たせました。

例(呉越同舟)
漢字呉越同舟
読みごえつどうしゅう
意味仲の悪い者どうしが同じ場に居合わせること
出典『孫子』九地篇
英訳strange bedfellows

そして出題モードを層の組み合わせで作ります

  1. 読み当て — 漢字を見て読みを選ぶ
  2. 意味当て — 意味を選ぶ
  3. 穴埋め — 一部を隠して漢字を選ぶ
  4. 故事 → ことわざ — 由来のエピソードから逆引き

同じ 48 件でも、問われる面が変わると別の問題になります。データを増やさずに問題数を増やせる構造です。

#🏗️ 誤答をデータから作る

4 択の作り方が地味に重要でした。誤答(ディストラクタ)は、他のことわざの同じ層から取ってきます

ts
const distractors = sampleN(
  ADAGES.filter((a) => a.kanji !== picked.kanji),
  3,
  rng,
).map((a) => a.yomi)
const choices = shuffle([picked.yomi, ...distractors], rng)
return {
  prompt: picked.kanji,
  choices,
  answerIdx: choices.indexOf(picked.yomi),
  ...
}

これで誤答も実在することわざの読みになります。適当な文字列を並べると、明らかにおかしい選択肢ができて消去法が通ってしまう。「どれもありそう」に見える誤答でないと、クイズとして機能しません。

answerIdx をシャッフル後に indexOf で求めているのもポイントで、正解の位置を先に決めない。位置に偏りがあると、慣れた人が場所で当てられます。

#🎚️ 難度が足りないときに隣から借りる

3 難度に分けると、あるレベルの母数が 4 件を切ることがあります。そのままだと 4 択が作れません。

ts
let pool = ADAGES.filter((a) => a.level === level)
if (pool.length < 4) {
  pool = ADAGES.filter((a) =>
    level === "easy" ? a.level !== "hard" : level === "hard" ? a.level !== "easy" : true,
  )
}

足りなければ隣接する難度まで広げる、というフォールバックです。easy なら hard 以外、hard なら easy 以外。難度が 2 段飛ばしにならないようにしています。

データを足せば解消しますが、データ量に依存しない形にしておくと、後から件数を増減しても壊れません。

#📚 出典を調べるのが本体だった

実装より時間がかかったのが、48 件それぞれの出典を確かめる作業でした。

『論語』『荀子』『荘子』『列子』『史記』『戦国策』『韓非子』『淮南子』『後漢書』『晋書』『詩経』『春秋左氏伝』『景徳伝燈録』── ことわざ辞典に載っている出典を、篇名まで含めて確認していきます。

ここで方針を決めました。出典が特定できないものは載せない。 由来が諸説あるもの、後世の創作とされるものは外しました。48 件という数は「確認が取れたもの」の結果で、最初から目標にした数ではありません。

「数字や事実を出すなら、確かめられる範囲だけにする」というのは他のサービスでも同じで、干支の日柱を実装しなかったのも同じ理由です。

#🎲 シード付き乱数で出題を再現する

ts
export function makeRng(seed: number): () => number

出題順もシードから作ります。「さっきの問題をもう一度」ができるのと、同じ種を共有すれば同じセットを解けるためです。学習系では、この再現性が復習の導線になります。

自己ベストは localStorage に保存していて、サーバーには何も送りません。

#😓 苦労したところ

故事の要約をどこまで書くか。 「臥薪嘗胆」の背景を全部書くと長すぎ、削ると意味が通りません。登場人物と、何が起きて、どう転んだかの 3 要素だけに絞り、2〜3 文に収めました。達磨、太公望、孔子、杜牧、項羽、廉頗と藺相如、管仲と鮑叔、伯牙と鍾子期 ── どれも面白い話なので、削るのが惜しい作業でした。

英訳の付け方。 直訳すると意味が通らず、意訳すると別のことわざになります。対応する英語のことわざがあるものはそれを当て、無いものは意味の説明にしました。「一石二鳥 = kill two birds with one stone」のようにそのまま対応するものは、実は多くありません

4 択の選択肢の長さ。 意味当てモードは選択肢が長文になり、スマートフォンだと縦に伸びます。文字数の近いものを優先して選ぶようにして、長さで正解が推測されないようにもしました。

#🔭 今後の拡張

  • 件数を増やす(出典の確認が取れた順に)
  • 間違えた問題だけを復習するモード
  • 四字熟語・慣用句への拡張
  • 読み上げ(音で覚える)

#💡 このサービスから言えること

学習コンテンツは、データの持ち方で問題数が決まります。 1 件に 5 層を持たせたから、48 件で 4 モード分の問題が作れました。「問題」を直接データにしていたら、192 問を手で書くことになっていたはずです。

そして誤答の質がクイズの質です。 正解を用意するより、「ありそうな間違い」を用意するほうが難しく、そこが手応えを決めます。他の実データから借りてくるのが、いちばん確実な方法でした。

[ ./next_action ]

読んだら、 AdageQuest を実際に動かす。

この開発ログは AdageQuest をどう作ったかの記録です。 読み終わったらそのままサービス本体へ戻って、 実物で価値を確かめてください。

[ ./related_logs ]

関連する開発ログ

all logs →
ToonCast

ToonCast ができるまで — AnimeGANv2 をブラウザで動かす

AnimeGANv2 の小さな ONNX (約9MB) を onnxruntime-web (単一スレッド WASM=COOP/COEP不要、 color-revive で承認済みライブラリの再利用) で実行。 512x512・[-1,1] 正規化で推論し、 結果を元解像度に戻して表示する設計記録。 写真は端末内処理。

read log →
ColorRevive

ColorRevive ができるまで — onnxruntime-web で白黒写真をカラー化

DeOldify の量子化 ONNX を onnxruntime-web (CDN side-load・単一スレッド WASM=COOP/COEP不要) で実行。 256x256 でモデル推論し、 輝度は元写真・色だけ AI を YCbCr で再合成して輪郭を保つ設計記録。 写真は端末内処理。

read log →
PhotoTwin

PhotoTwin ができるまで — CLIP画像埋め込みで似た写真を見つける

CLIP (Xenova/clip-vit-base-patch32) の image-feature-extraction を transformers.js の CDN ESM で side-load し、 各写真を正規化ベクトル化。 cosine 類似度で重複・似た写真をブラウザ内で検出する設計記録 (新ライブラリ追加なし=what-cam と同じ CLIP の再利用)。

read log →
AkinFind

AkinFind ができるまで — ブラウザ内で完結する embeddings 意味検索

多言語の文章埋め込みモデル (Xenova/multilingual-e5-small) を transformers.js の CDN ESM で side-load し、 各文を正規化ベクトル化。 cosine 類似度で意味検索と似ている文ペア検出を全て端末内で行う設計記録。

read log →
WhatCam

WhatCam ができるまで — CLIP のゼロショット画像分類をブラウザで動かす

CLIP (Xenova/clip-vit-base-patch32) を transformers.js の CDN ESM で side-load し、 写真と候補ラベルの近さをブラウザ内で計算。 日本語ラベルを英語プロンプトに変換し、 図鑑と自由入力の両モードで「これ何?」を判定する設計記録。

read log →
DepthCast

DepthCast ができるまで — 1枚の写真をAIの深度推定で立体にする

Depth Anything (transformers.js) を CDN ESM で side-load し、 1枚の写真から深度マップを推定。 WebGL2 フラグメントシェーダで深度に比例した視差 (iterative backward parallax) を作り、 赤青アナグリフ / WebM 書き出しまで端末内で完結させた設計記録。

read log →