Chienomi

自分の中で比較する用のタイピングテスト

私はこれまで、キーボードのタイピング適性の指標のひとつとして、10fastfingersを採用してきた。

理由は、私が知っているタイピングテストの中で、もっとも無難なルールだと考えるからだ。

だが、あまりにも出題運に左右されるため、ついに私はキレた。 throughとthoughとthoughtを出してくるんじゃない。

参考になりそうな記録を出すのに合計6時間を費やし、私は決心した。

「もっと簡単に測定できて、結果が安定して、キーボードを比較する指標になりそうなタイピングソフトウェアを自作しよう」と。

それがChienomi Key Typing Test Method、略してKTTMである。{rel”external”}

概要

KTTMは固定のテキストを打つ時間を計測するワープロ検定方式である。

任意回数の試技が可能で、ベスト、ワースト、平均が表示される。 特に保存はしないので、リロードするとリセットされる。

大文字小文字、スペース数は区別される。

入力文字を間違えた場合、入力位置は間違えた時点で停止する。 例えばfoobarbを間違えてfoocarと入力したとすると、fooまで入力した時点で止まり、bが入力されるまで進まない。

誤打カウントは間違えた文字に対して入るため、同一の文字では何回間違えてもミスは1のまま。

考え方

誤打ペナルティ

誤打に対して入るのは停止位置あたり最大1で、かつこれを訂正せずに打ち直せるので、誤打ペナルティは最も軽い。

が、今回の目的においてはこれで十分。 というのも、「間違えたので進んでいない」を認識するのには時間がかかるので、ミスはそこそこ大きなタイムロスを生む。 スペースキーでワードを捨てて次に進むこともできないので、必ず打ち直さなくてはいけないし、途中からの打ち直しはリズムを取り戻すのが難しい。

このルールだと適当押しでいけると思うかもれしないが、適当押しはそんなに速度が出ない。よほどスペルが分からないような単語はトリルしてクリアするみたいなことはできるが、リザルトを考えるとミスせず打つほうが圧倒的に速い。

だから、「打鍵速度は上がりにくいがミスしづらいキーボード」に対してはちゃんとアドバンテージがつくようになっていて、速く打てても正確に打ちにくいキーボードは複数回の試技で平均点を稼ぐのが難しい。 ベストから平均点が遠い結果が出ている場合、誤打を招きやすい扱いにくいキーボードであることが透けて見えるのだ。

そもそも私は「誤打をすることが悪いことだ」とはあまり思わない。 誤打したとしても、誤打したことがはっきり認識できて、すみやかに訂正できるなら重要な問題ではないと思う。 だから、誤打に対する特別なペナルティはなし。

固定文

ランダムな単語が出題される場合、出題の運に結果が左右されてしまう。

多くの時間を割いてベストを目指す、という競技であればそれはそれでいいかもしれないが、私はキーボードの比較をしたいので、ばらつきは少なくあってほしい。 つまり、キーボードの打ちやすさが安定して結果に反映されて欲しいのだ。

10fastfingersの場合、極端に打ちにくい単語が出たり、識別が難しい単語が出たりするので、運に左右されすぎてベンチマークとして全然機能しない。

この問題は、計測時間を短くすればある程度緩和される。 私の感覚では、20秒未満が良い。それなら安定しやすい。

そして、そもそもの話として、固定文にすれば出題の運というものはなくなる。 単語の識別問題も、ある程度の回数やればなくなる。 「スペルがわからない」は難易度の性質を変えてしまうので、個人的に馴染みのある単語が出題されるようにした。

単語の「打ちづらさ」は常に固定の文が出るならば一定のハードルなので問題はない。 むしろ、「打ちづらい単語をいかにして越えるか」という点も問われるようになり、より意図を持ったテストが可能。

また、10fastfingersの場合、出題された単語に基づいて補正値がかかる。 これにより、一般的には実績よりも高いWPM/KPMが表示される。 これに対して固定文の場合は出題運を補正するという考え方は不要で、実績で表示できるというのも大きい。

表示

私はタイピングテストでミスしているかどうかはわかりやすく見せたほうが良いと思っているので、可能な限りわかりやすい表示にした。

進捗を示すゲージが文の上に重なるのは絶対必要だと私は考えていた。

私のタイピングテストの原点はwith meにあり、あれは非常にわかりやすく快適なものだった。 まぁ、今考えると相当もっさりしていたけれど。

あのテストは進行が非常にわかりやすかった。 「わかりやすい」ことは「ダサい」ことに見えるかもしれないが、実用的にはわかりやすいことこそが良いと私は考える。

一方、間違えたときが難しい。 単に背景を赤くするだけだと、進まないことに対して「間違えたことを察知する」「間違えた箇所を探す」という手順が発生してしまい、リカバリに時間がかかる。

私としては、間違えたことを認識して修正する意思を持った時点で即座にやり直せるようにしたい。 なので、「間違えた」ということに対するアピールも重要だ。

考えられるのは画面全体の背景色を点滅させることだが、これは目が痛い上にもっさりした動作に感じられる。また、一度ミスするとミスは続く傾向があり、そうなるとチカチカして責められている感じが出てしまうだろう。

そこで、かなり早い段階で大きなインジケーターを置くという考えがあった。

さらに、10ffでは適切な文字サイズにするためにかなりブラウザを拡大する必要があるため、文字サイズはビューポートを元に決定するようにした。

Type-A

問題文

プログラミング系

プログラミング言語において基本的な構成要素となっている語を打たせるもの。 性質上、あまり速度は出づらいが、当該プログラミング言語に慣れている人ならば比較的打ちやすいものではあるだろう。

CSSは問題が作りやすいため3種類。

cssはcssの中で一般的な単語が並び、難易度は低い。

css2は使用機会が少ないプロパティを含むCSSを書かせる高難易度問題。スペースがあったりなかったりとか、.,の混在とか、スペルがぱっと出てこないようなワードとかも混ざっており非常に難しい。

css3は長かったり書きにくかったり識別しづらかったりのプロパティ名と値が並ぶ。 地味にすごく打ちづらいのが最後の!important。 importantという語自体もそんなに打ちやすいわけではないけれど、!がつくと一気に難しくなる。

hell compicatedは特殊なタイプの高難易度。 プログラミングやソフトウェアで登場機会の多い単語だけれど、音からも文字の並びからもスペルがわかりにくく覚えにくい単語が並ぶ。 個人的にはarithmeticが最高に厳しいと思う。マジでいつまで経っても間違える。

はるかみ系

chienomiとharukamyとhellkamyが登場する。

harukamyは私のタイピングスタイルで打ちやすい単語で構成されている。 私は単語のキーに指を乗せておいて順番に押していくオルガン方式を採用しており、一般に同じ指が連続するので打ちづらいとされる単語に強い。 一方で、手の移動量が多いtやyはミスタイプにつながりやすくて苦手だ。

私のタイピングスタイルに適合しているため、リザルトはだいたい650KPM前後、速い時は700KPMに迫る。 これを越えるのはかなり難しいと思う。

hellkamyは逆で、一般的にはそれほど打ちにくくないものの、私にとっては鬼門となるような並びになっている単語が並ぶ。 当然、tがやたら多い。 こっちは私は400KPMをちょっと越える程度で苦手なのが浮き彫りになる。

chienomiはChienomi関連、私関連、私がよくタイプしているコマンドが中心。 打ちやすくはないが、私としてはタイプ慣れしているので結構速くて500KPMを越えていける。

英文系

en wordsとen words long、en sentenceの3種類。

en wordsは「英米の差がなく、英語圏の町中でよく見かける単語」という英語ネイティブでない人にも難易度を下げる方向のもの。

en words longはあまり聞き慣れないものを含む名詞中心の問題。 この問題はタイピングを競う問題として使用できるように考えられているため、他の問題よりもだいぶ長い。 正直、私はやりたいとは思わない。1度試した限りだと、370KPMだった。

en sentenceは英語の文章。 文章だと明確に英語ネイティブにとって有利になるけど、これも需要かなと思って作った。

en sentenceの文章

適度な長さで、平易で、かつ物語を感じさせる文を作るのに苦労した。

私よりもCopilotのほうがいい翻訳をするので、次の原文をCopilotに翻訳してもらった。

Bobはピアノが好きだ。観客は母親だけだったが、それに不満を持ったことはない。Aliceはヴァイオリンが嫌いだ。毎日繰り返される観客のない演奏は、きたるステージの前奏に過ぎない。AliceはBobのピアノが好きだ。自由で、太陽のように温かい。BobはAliceのヴァイオリンが好きだ。精緻で、無表情の奥の情熱が滲み出すようだ。

問題文の注意事項

問題文にはステータスがあり、ステータス次第では今後変更される可能性がある。

In developmentは編集中なので変更される。

Maybe fixedは問題文を変更するつもりはないが、スペルミスや不適切な語に関するチェックが完了していない。

Fixedは問題がないことを確認しており、不測の事態が起きなければ変更されない。

Type-B

概要

Type-Aは結局のところ「なるべく速く打て」というものなので、上振れることがあるという問題は結局変わらない。 し、非常に大きな問題として、ある程度やると指が混乱して無限に間違えるようになる。 例えば一回socialをscoialとタイプしてしまうと、何回やってもscoialになる。

あと、700KPMに近づいてくると、脳の処理速度が追いつかない。 どのようにタイピングするかが分からなくなって運指できなくなり、さらに次の単語の認識が追いつかない脳のバッファアンダーランが発生する。

何より、限界の速度でのタイピングって日常的なタイピングと(少なくとも私の中では)完全に別物なので、目的から乖離しているように感じられた。 私のキーボードレビューは日常的なタイピングにおいて快適に楽しく打てるかということを述べているのであって、タイピングコンテストのためにキーボードを求めている人のために解説しているわけではない。

だからテストすべきことはそういうことではないはずなのだ。

そこで考えたのが、「間違えないように慎重なタイプで許されて、かつ安定できるか」を測るもの。 成功しつづけることで継続でき、徐々にKPM閾値が増加するようにするものだ。

ルールはこんな感じ

  • KPMの閾値とミスタイプ閾値がある
  • 終了条件は以下のAND
    • 最低試技数を満たしている
    • 最低通過試技数を満たしている
    • 失格した試技がある
  • 表示されるのは通過した試技の数、平均KPM、最高通過KPM閾値

まずは最低試技数をノーミスでクリアするのが目標。 そこからは、どこまで試技数を伸ばせるかというのが一番の軸。

コードの話

前置き

基本的にこのソフトウェアは即席なのであんまり凝ったコードではないという、コード品質があまり高くない。

JavaScriptのDOM操作を書く人が今どきは少ないというのはあるので、そこは見どころになるかもしれないけれど、全体的には普通というか、基礎を積み重ねているだけのコードという感じ。

ただ、同じような傾向のコードでもDLSite Voice Utilsと比べると手抜き度合いが少ない。

innerHTML

手抜き要素筆頭のElement.prototype.innerHTML

それなりに気を遣っても潜在的に問題になることが多いので、この手抜きはいまいちなのではないか、と最近思う。 実際、DLSite Voice UtilsでもinnerHTMLは減っていっている。

ただ、Mewductでもやっていたテクニックである

elm.innerHTML = ""
elm.appendChild(document.createTextNode(str))

は結構良い気がする。 が、

elm.textContent = str

のほうがいいじゃんって気づいた。 あるいは、モダン想定なら

elm.replaceChildren(document.createTextNode(str))

でいい。

いや、言い訳をさせてほしい。 ベストではないということは意識にあったのだ。 ただ、textContentreplaceChildrenが思い出せなくて、さっと検索したけど出てこなかったので、innerHTMLで書いたのだ。 そして、記事を書くにあたり、なんかあったよな……と思ってGeminiに聞いて思い出したのだ。

ちなみに、replaceChildrenは結構新しくて、textContentもまあまあ新しい。

var, let, const

なんか「混ぜてはいけない」とか言われているらしいが、別に混ぜても問題ない。 ちゃんと理解しているならば。

最も基本になるvarはスコープを関数全体で持っている。 だから、

function foo() {
  console.log(bar)
  var bar
}

はvalidである。

ただ、こういう使い方をするつもりであれば、今どきvarを使う理由はあまりない。

重要なのは、トップレベルでvarで宣言したとき、変数はグローバルスコープを持つ、ということだ。

varはネストしたスコープを持つので

function foo() {
  var bar = 100
  ;
  (function() {
    var bar
    bar = 200
  })()
  console.log(bar)
}

foo()

100になるのだが、トップレベルはファイルをまたいで共有されるので、例えばsettings.js

var settings = {
  //...
}

とやっておいて、それを使うスクリプトで

var settings

とかやっても問題ない。

また、グローバルスコープを持っているので、インスペクターのコンソールから直接覗くことができる。

要はファイルに閉じていてほしいかどうかということである。 ファイルに閉じないスコープを持って欲しいならグローバル変数としてvar宣言をするといい。

ちなみに、グローバルに宣言されたvarwindowのプロパティになるので、varを使わずに

window.foo = function() { /* ... */ }

とかやっても等価である。そこはお好みで。

constletは宣言したところからブロック内がスコープ。 トップレベルで宣言した場合も、ファイルに閉じている。

関数内で宣言するなら、varを使うのは互換性のためとか、古いコードのためくらいしか理由がない。 ただ、「変数はあらかじめ宣言するもの」スタイルであれば、varのほうが相性はいい。 constを基本とすると、値が生まれてから宣言するしかないので、変数宣言を探せるエディタを使わないと名前の管理がしづらいのだ。

ノード実体

これはプリミティブなJavaScriptを学習する上ではひとつのハードルになるやつ。

まずElementはドキュメント上に存在してもしなくてもHTMLの要素として振る舞う。 そして、HTMLの要素はNodeが持っているすべての状態を保持している。

例えば

const elm = document.createElement("div")

とした場合、このelmはドキュメント上にはないが、ちゃんと有効な要素であり、

const elm = document.createElement("div")
const child = document.createElement("span")
elm.appendChild(child)

とした場合、elmはドキュメント上になくても子要素を持っている。

同様に、

const elm = document.getElementById("Foo")

のようにして変数に入れた要素も、ドキュメントから取り除かれても消滅しない。 だから

for (let i=0; i<test_string.length; i++) {
  const elm = document.createElement("span")
  if (test_string[i] === " ") {
    elm.className = "space_char"
  } else {
    elm.className = "normal_char"
  }
  elm.appendChild(document.createTextNode(test_string[i]))
  test_string_ary.push(test_string[i])
  test_string_elm_ary.push(elm)
  test_text_field.appendChild(elm)
}

というコードは、test_string_ary[i]で文字が得られ、test_string_elm_ary[i]で対応する要素が得られる。 要素をドキュメント上に追加しても配列からはなくならないし、同一の存在を指している。

これはdocument.getElementById()とかでやっていることの逆向きなのだが、逆向きになるとそうなるということが分からなくなるという人は結構いる。

ちなみにチャットログみたいなもので探索頻度が高い場合(querySelectorとか使うような)は、配列に入れておくというのはかなり有効。 ブラウザの場合ドキュメント上に存在する要素はどのみち存在しているものなので、配列でもっておくコストは非常に安い。