SorobanRush ができるまで — 学習 4本目を 暗算 / そろばん ドリル で開く
SorobanRush は、 ブラウザだけで動く無料 暗算 / そろばん ドリル。 4 モード × 3 レベル + 1-1-4 そろばん SVG 可視化。 ラボ 34本目、 インタラクティブ学習 thesis 4本目 (ToneQuest 「耳」 / AtlasQuest 「目」 / TypeRush 「指」 / SorobanRush 「算」 で 4 感覚学習完成)。
なぜこの形にしたか
直近 5本 (pop-drift / maya-cast / type-rush / frac-cast / patch-pad) に学習は type-rush の 1本のみ、 §3.1 連続にならず safe。
候補比較:
- 暗算 / そろばん ドリル (採用) — SEO 強 「暗算 練習 無料 / そろばん 練習 / フラッシュ暗算」
- 漢字検定 練習 — SEO ★★★★★ だが 常用漢字 2136 字データセット必要、 1 ship 厳しい
- 都道府県クイズ — SEO 中、 atlas-quest と地理 thesis 似る
- 元素周期表 — SEO 弱
選んだ理由:
- 学習 4 感覚 「耳 / 目 / 指 / 算」 が揃い シリーズ完成 感が出る
- フラッシュ暗算 + 1-1-4 そろばん SVG が wow ポイント (高速点滅数字 + リアルタイム玉移動)
- 既存 「暗算 練習」 系サイトは UI 古い (90年代 Flash 風)、 現代的 visual で差別化
- そろばん 教室 / 検定 受験層という巨大ニッチに刺さる
- 純 JS で完結 (問題は乱数生成、 そろばん SVG は座標計算のみ)
visual direction — §6.1 Visual Audit 19本目の適用
直近 5本の visual を書き出す:
pop-drift — cream paper + cobalt + warm red (昭和の統計年鑑 / Risograph 二色刷り)
maya-cast — earth red + jade + obsidian + gold + bone (マヤ古代壁画)
type-rush — cream paper + carbon ribbon + brass key + red (古いタイプライター)
frac-cast — ivory paper + sepia + brass corner + crimson (数学者ノート)
patch-pad — walnut + cream + brass + chrome (70s モジュラー シンセ)
題材 「そろばん / 寺子屋 / 暗算 / 江戸」 → motif 候補:
- そろばん木枠 + 寺子屋 (warm brown frame + 玉青藍 + 玉朱 + 和紙 cream + 墨 black + 金箔) ← 採用
- 江戸時代 算木 / 算用数字 — niche
- 算盤検定 道場 — 学校風 / school と被るリスク
- アジア 大陸 数学 — broad
採用 3 要素:
- palette: warm brown frame
#6a4a28+ bead 青藍#2d5a8b+ bead 朱#b04018+ 和紙 cream#f4e8c8+ 墨 black#1c1410+ 金箔 accent#c9a04e- gift-cap (墨朱 + 巻物 + 和紙) と和風 palette 一部重複だが motif は 巻物 朱印 vs そろばん木枠 + 玉 で完全別
- brown frame + 青藍玉 はユニーク (青藍 は伝統的 そろばん 一玉 の色)
- motif: そろばん木枠 (14px brown border + 2px dark inset shadow + 木目 27px loop) + 寺子屋 (枠上部に 「算 / 盤 / 練 / 磨」 4 漢字 chip + 金箔 1px border) + 1-1-4 そろばん SVG (5 玉 1 + 一玉 4 × 5 桁 + 梁 + 縦棒) + 寺子屋 文房具感 (朱と青藍の 玉色 + 和紙質感)
- typography: Space Grotesk 800 (display) + JetBrains Mono (numerics) + Noto Sans JP (body) + 漢字 emphasis (算 盤 練 磨) + 漢数字ステップ (壱/弐/参)
技術スタック
4 モード × 3 レベル 問題生成
function makeAddSub(level: Level): Problem {
const a = level === "easy" ? rand(1, 9) : level === "normal" ? rand(10, 99) : rand(100, 999)
const b = ... // 同様
const op = Math.random() < 0.5 ? "+" : "−"
return { ..., answer: op === "+" ? a + b : a - b }
}
function makeFlash(level: Level): Problem {
const count = level === "easy" ? 3 : level === "normal" ? 5 : 8
const digits = level === "easy" ? 1 : 2
const seq = Array.from({length: count}, () => randDigit(digits))
return { mode: "flash", sequence: seq, answer: seq.reduce((a,b)=>a+b, 0) }
}
function makeMulDiv(level: Level): Problem {
// 割り算は a × b の積を 被除数 にする (割り切れる問題のみ)
const dividend = a * b
return { display: `${dividend} ÷ ${b} = ?`, answer: a }
}
function makeChain(level: Level): Problem {
// 5-15 step の +/− 連鎖、 結果値を保持しながら parts 配列を構築
for (let i = 0; i < steps; i++) { ... }
}
純 JS の乱数生成のみ。 割り算は a×b=被除数 で割り切れる問題のみ作る (商を整数に保つ)。 連算は中間結果を保持しながら +/− を連鎖。
フラッシュ暗算: setTimeout で 1 秒間隔点滅
function runFlashSequence(seq: number[]) {
let i = 0
function tick() {
if (i >= seq.length) { setShowFlash({ idx: seq.length, total }); return }
setShowFlash({ idx: i + 1, total })
i += 1
flashTimerRef.current = setTimeout(tick, flashSpeed)
}
flashTimerRef.current = setTimeout(tick, flashSpeed)
}
flashSpeed 1400/900/500ms の 3 段階 (遅 / 中 / 速)。 1 桁ずつ key= で React に 再 mount させ、 CSS @keyframes sr-flash の transform: scale(1.4 → 1) + opacity: 0 → 1 で 「ポンッ」 と現れる演出。 sequence 終了後は 「合計は ?」 に切り替えて 解答 入力許可。
1-1-4 そろばん SVG 可視化 (5 桁)
export function abacusDecompose(value: number, digits = 5) {
const out = []
for (let i = digits - 1; i >= 0; i--) {
const d = Math.floor(Math.abs(value) / Math.pow(10, i)) % 10
out.push({ top: d >= 5 ? 1 : 0, bottom: d % 5 })
}
return out
}
各桁を 天玉 (5 玉、 0 or 1) + 地玉 (一玉、 0..4) に分解。 例: 7 = top:1 + bottom:2、 9 = top:1 + bottom:4。 SVG で:
- 横棒 (梁) y=44 + 縦棒 (枠) x=10/290 + 中央 縦棒 cx=30+i*60
- 天玉 = active なら梁直上 (y=36 朱)、 inactive なら上枠 (y=18 木色)
- 地玉 4 個 = active は梁直下に積み (青藍)、 inactive は下枠寄り (木色)
- 各桁下に 10^4 / 10^3 / 10^2 / 10^1 / 10^0 ラベル
入力 が変わるたびに React が SVG を再描画、 玉が瞬時に移動する。 「数字 → そろばん」 を 直感的に学べる。
入力 + 解答フィードバック
<input type="text" inputMode="numeric" value={input}
onChange={(e) => setInput(e.target.value.replace(/[^0-9-]/g, ""))}
onKeyDown={(e) => e.key === "Enter" && submit()}
/>
数字のみ許可 (正規表現で fileter)、 Enter で解答。 解答後は 1.1 秒の フィードバック表示 (◯ 正解 / × 不正解 + 正解値) → 自動的に次の問題へ。 問題 generate 時の performance.now() から 経過時間を測定して 平均回答時間 を算出。
Stats: 連続正解 ベスト + 平均回答時間
setStats((s) => ({
total: s.total + 1,
correct: s.correct + (correct ? 1 : 0),
streak: correct ? s.streak + 1 : 0,
bestStreak: Math.max(s.bestStreak, correct ? s.streak + 1 : s.streak),
totalAnswerMs: s.totalAnswerMs + elapsedMs,
}))
問題数 / 正解 / 正答率 / 平均回答 / 連続正解 / ベスト連続。 ベストは金色強調。 ↺ Reset で セッション 仕切り直し。
やっていない / これからの IMPROVE
- 60 秒 タイマー モード (寿司打 形式 = 時間内に何問解けるか)
- そろばん 玉操作 訓練 モード (玉をドラッグして加算、 アニメ + 検算)
- 暗算検定 級別 出題 (10 級 / 9 級 / ... / 1 級 / 段位、 各級の出題範囲を再現)
- localStorage 履歴 (前回スコア / 推移グラフ)
- WebAudio タイプ音 (玉が動く 「カチッ」 効果音、 type-rush 知見再利用)
- 2 人 対戦モード (WebRTC で beam-drop 拡張)
- 問題 シェア URL で base64 シリアライズ
- 読み上げ問題 (Web Speech API、 耳で聞いて頭で計算)
次の SHIP は何 thesis に振るか
Thesis Audit:
soroban-rush — 学習 (4本目)
pop-drift — データ可視化 (3本目)
maya-cast — 占い (3本目)
type-rush — 学習 (3本目)
frac-cast — シミュレーター (3本目)
直近 5本で 学習 2 (soroban-rush + type-rush)。 §3.1 連続 3 はまだ余裕。 次の候補:
- 計算ツール 5本目 (育休給付金 / 退職金 / 国保 / 年金)
- ジェネレーター 4本目 (SVG モノグラム / グラデーション / glitch)
- 占い 4本目 (西洋占星術 / 易経 / 動物占い)
- シミュレーター 4本目 (Conway Life / 反応拡散)
- データ可視化 4本目 (為替 30 年 / 都道府県 GDP)
- 8 thesis 残り 1 枠 = server-side AI (ユーザー許可待ち)
[ ./next_action ]
読んだら、 SorobanRush を実際に動かす。
この開発ログは SorobanRush をどう作ったかの記録です。 読み終わったらそのままサービス本体へ戻って、 実物で価値を確かめてください。