私は小学2年生の子を持つ*1のですが、小学生といえば漢字練習があります。
うちの小学2年生は文字の読み書きがあまり好きではないようで、漢字練習もやはり好きではないようです。
一方で、ちょっとでもゲーム要素が入ると、たとえそれが勉強っぽいものでも、ずっと楽しんでやっているという単純さもあります。
じゃあ、漢字練習にもゲーム要素を加えられたらずっとやるのではないか?と考えました。親も単純です。
おそらく「漢字を練習したらスタンプ1つ」みたいなレベルのものには「やりたくない」が勝ってしまい、見向きもしないので、もっとゲーム要素が必要です。
ところで、私はソフトウェアエンジニアです。
最近のタブレットはペンもついているし、タブレットで実際に漢字を書くタイプのゲームができればいいのではないかと考えました。漢字は書かないと覚えない気がするので。
SPAを作ってどこか(GitHub Pagesとか)でホストしたらタブレットから簡単にやってもらうこともできそうです。
というわけで、最近漢字練習のためのnpm packageをコツコツ作成しています。
kakitori
kakitori は「漢字や仮名の書き取り」を組み込むためのライブラリです。
できること
- 漢字や仮名を、正しい筆順どおりに書けているかを画ごとに判定する
- そのうえで「とめ」「はね」「はらい」まで判定する
- 「学校」のような熟語を、マスに並べて1問の問題にする
- マスにふりがなを振る
- 「学[ ]」のように一部だけ書かせる穴埋め問題にする
- 縦書きで複数の問題を並べて、漢字練習帳のページのようなレイアウトを組む
- 書いた結果を取り出す(採点や記録などを想定)
3つのプリミティブ
kakitori は主に char、block、pageの3つのプリミティブで構成されていて用途に応じて使う設計です。
char

char はkakitoriの中の最小単位で1文字の書き取りを実現します。
import { char } from "@k1low/kakitori"; const c = char.create("学"); c.mount(document.getElementById("writer")!, { size: 300, showGrid: true, onCorrectStroke: (data) => console.log("OK", data.strokeNum), onMistake: (data) => console.log("NG", data.strokeNum), onComplete: ({ totalMistakes }) => console.log("done", totalMistakes), }); c.start();
これで「学」が表示されて、正しい筆順で書かないと先に進めないというインタラクションができます。
block

block は1問の単位です。マス目とふりがなで構成されています。
import { block } from "@k1low/kakitori/block"; block.create(document.getElementById("host")!, { spec: { cells: [ { kind: "guided", char: "学", mode: "write" }, { kind: "guided", char: "校", mode: "write" }, ], annotations: [ { cellRange: [0, 1], expected: "がっこう", mode: "write" }, ], }, });
「学」「校」のマスが縦に2つ並んだ問題が組めます。annotations に書いた「がっこう」が、ふりがな用のマスとして自動で寄り添う形で並びます。mode: "show" を指定すれば「お手本として字を見せるだけのマス」、mode: "write" で「書かせるマス」になるので、混ぜれば穴埋めもできます。
page

page は block を漢字練習帳のページに似た縦書きグリッドに構成することができます。
import { page } from "@k1low/kakitori/page"; page.create(document.getElementById("host")!, { writingMode: "vertical-rl", columns: 5, cellsPerColumn: 8, cellSize: 96, blocks: [ { id: "q1", spec: { /* block の spec をそのまま */ } }, { id: "q2", spec: { /* ... */ } }, // ... ], });
columns x cellsPerColumn の格子が用意されて、各 block を右上から流し込んでいく感じです。1つのblock が列をまたぐ場合の改行も自動でやってくれます。
「とめ」「はね」「はらい」判定
漢字の運筆判定そのものは Hanzi Writer という素晴らしい既存ライブラリの仕組みに乗っかっています。筆順どおりに線をなぞらせる、というところまではこれだけでできます。
kakitori 独自の機能として、その上に「とめ」「はね」「はらい」の判定を追加できるようにしています。文部科学省および文化庁は、「とめ」「はね」「はらい」について、「骨組みが適切であれば、正誤に影響しない」という見解(常用漢字表の字体・字形に関する指針)を示してはいるものの、字の練習という観点では大事だろうと判断して追加しています。
各画ごとに、ペンを離す直前の運筆の角度や速度を見て、判定しています。
書いた結果を取り出す
書いた結果も取得できます。
const result = p.result(); // { // complete: true, // matched: true, // blocks: [ // { id: "q1", // cells: [ // { kind: "guided", chars: [ // { character: "学", complete: true, matched: true, // perStroke: [...], mistakes: 1, strokeEndingMistakes: 0 } // ]}, // ... // ], // annotations: [...] // }, // ... // ] // }
「何文字書いたか」「画ごとの一致度はどれくらいか」「『とめ』を何回間違えたか」みたいな情報がツリー構造で返ってきます。collectCharResults() という補助関数を使うとページ全体の文字結果をフラットに取り出してフィルタすることもできるので、ゲーム的な採点処理にそのまま流し込めるはずです*2。
文字データのこと
文字データは @k1low/hanzi-writer-data-jp というforkedパッケージ経由で読み込んでいて、その元データは主に animCJK です。
animCJKは漢字の書き順や運筆情報を持つsvgファイルを作成するという狂気の1人プロジェクトです*3。すごい。感謝しかないです。
ただ animCJK が使っているフォントは小学校で習う字形とちょっと違うところがあります。たとえば「日」や「田」の中の横画が、フォントだと隣の縦画とちょっと離れているのですが、書き取り練習だとくっつけて書きます。「糸偏」や「竹冠」のように、筆順や形が日本の教科書と違う部首もあります。
どちらも正しいのですが、目的は「書き取り」なので、これらを日本の小学校で習う字形に直す subAnimJ というプロジェクトをはじめて animCJK と併用しています。あと、animCJKには数字(0〜9)が含まれていないので、数字用に animNumber というのも作って、これも取り込んでいます。
subAnimJやanimNumberをやりはじめて、さらにanimCJKの偉大さがわかります。subAnimJなどはまだまだ十分ではありません*4。引き続きやっていこうと思っています。
使うときのライセンス上の注意事項
kakitori 自体は実行時に文字データを fetch しているだけなのですが、kakitori を使ったWebアプリを公開する場合、デフォルト設定だと裏で @k1low/hanzi-writer-data-jp を経由して animCJK / subAnimJ / animNumber / Unihan由来のデータを利用することになるので、各上流プロジェクトのライセンスへの帰属表記をどこかに記載しておくのが良いと思います。
例となるHTMLスニペットを kakitori のREADMEに置いてあるので、よろしければ参考にしてください。
「とめ」「はね」「はらい」データのこと
「とめ」「はね」「はらい」を判定するためには「ここはとめる」「ここははねる」「ここははらう」というラベルを、各文字の各画につけたデータが必要になります。これがいい感じのオープンデータとして見つからなかったので、自分で作ることにしました。
それが @k1low/kakitori-data というパッケージです。
たとえば「あ」のデータはこんな感じになっています。
{ "character": "さ", "strokeEndings": [ { "types": ["tome"] }, { "types": ["tome", "hane"] }, { "types": ["tome"] } ] }
strokeEndings[i] が i 画目の終わり方を表しています((types を配列にしているのは、字によっては「とめ」も「はね」も許容されるケースがあるためです))。
現在の進捗としては、ひらがなと数字(0〜9、全角・半角)はだいたい埋まったのですが、漢字はまだほぼ手付かずです*5。これもコツコツやっていこうと思っています。
このデータは正解がない(というか正解は「とめ」でも「はらい」でも「はね」でもいいっぽい)ので、他の人からの貢献を受け付けにくいなあと悩んでいます。
「正解がないのはわかっているが、それでも文字を綺麗に書くためにとめはねはらいをしっかり意識して欲しい」という「思い」からの機能なのです。
なお、@k1low/kakitori-data にデータがなければ判定をしないし、そもそも判定を外すこともできるので、安心してください。
ところで、根本的な問題にぶつかっている
ここまで一見すると順調なプロジェクトなのですが、実は、今、根本的な問題にぶつかっています。結構前から気づいていたのですが、いまだ解決の糸口が見つかっていない問題です。
それは、 私にゲームを作るセンスどころか能力が全くないこと です。
「kakitori でレイアウトもふりがなも結果取得もできるんだから、あとはそれっぽく組み合わせればゲームになるだろう」と最初は思っていたのですが、全く進みません。
何をどうしたらゲームが作れるのか、全く手が出ないのです。
「ゲーム要素を加えればやってくれるはず」という発想で kakitori を作り始めたのに、その「ゲーム要素」が私からは生み出せない、というのが今です。厳しい。
みなさんにお願い
「漢字書き取り」に同じような課題を持つみなさん。
kakitori は頑張って作っていきます(すみません。まだAPIもガンガン変えると思います)。
なので、もし、みなさんが kakitori を使って漢字ゲームを作ったら私にも教えていただけないでしょうか。子にやらせたいです。
どうかよろしくお願いいたします。私も引き続きめげずにゲームを考えてみます。









