ピュアJSでかな入力の薙刀式練習タイピングソフトを作った

かな入力の薙刀式を練習するために、JavaScriptでタイピングソフトを作ってみました。
#薙刀式とは
かな入力の配列の一つです。よく出る表現を楽に打つことを目的とした配列です。
薙刀式を考案された大岡さんのページはこちらです。
http://oookaworks.seesaa.net/article/456099128.html

かな入力配列はほかにもありますので、良かったら調べてみてください。（タイピング速度最速は新下駄配列だといわれています。）
#作成したタイピングソフト
https://akrolayer.github.io/Portfolio/NaginataTyping.html

QWERTY配列のままタイピングすると薙刀式のかなが入力されるようにしています。実際に配列を変えるのは設定変更が面倒くさく、ハードルになると思うので、このソフトで体験してもらいたいと考えて作りました。実際に良いと思い、慣れてきたら実際に配列変更していただくとよいと思います。

このサイトのタイピングゲームの基本機能部分を参考にさせていただきました。ありがとうございます。
https://www.pazru.net/js/key/3.html

コードはこちらです。
<details><summary>薙刀式の押下されたキーの組み合わせで入力される文字を返す関数</summary><div>

```JavaScript
function reaction(keyStatus){ //入力された文字の判定 
    if(keyStatus[32]){//シフト同時押し
        if(keyStatus[70]){//シフトと左手濁音キー(f)押し
            if(keyStatus[81]) return "ヴ";//q
            if(keyStatus[87]) return "み";//w
            if(keyStatus[69]) return "り";//e
            if(keyStatus[82]) return "め";//r
            if(keyStatus[85]) return "ざ";//u
            if(keyStatus[73]) return "よ";//i
            if(keyStatus[79]) return "ゆ";//o
            if(keyStatus[80]) return "え";//p
            if(keyStatus[65]) return "せ";//a
            if(keyStatus[83]) return "ね";//s
            if(keyStatus[68]) return "に";//d
            if(keyStatus[71]) return "ち";//g
            if(keyStatus[72]) return "わ";//h
            if(keyStatus[74]) return "の";//j
            if(keyStatus[75]) return "も";//k
            if(keyStatus[76]) return "づ";//l
            if(keyStatus[186]) return "や";//;
            if(keyStatus[90]) return "ほ";//z
            if(keyStatus[88]) return "ひ";//x
            if(keyStatus[67]) return "を";//c
            if(keyStatus[86]) return "、";//v
            if(keyStatus[66]) return "ぬ";//b
            if(keyStatus[78]) return "お";//n
            if(keyStatus[77]) return "。";//m
            if(keyStatus[188]) return "む";//,
            if(keyStatus[190]) return "ぶ";//.
            if(keyStatus[191]) return "れ";///
            return "ま";//f
        }
        if(keyStatus[74]){//シフトと右手濁音キー(j)押し
            if(keyStatus[81]) return "ヴ";//q
            if(keyStatus[87]) return "み";//w
            if(keyStatus[69]) {
                if(keyStatus[79]) return "でゅ";
                return "り";//e
            }
            if(keyStatus[82]) {
                if(keyStatus[186]) return "じゃ";
                if(keyStatus[79]) return "じゅ";
                if(keyStatus[80]) return "じぇ";//p
                if(keyStatus[73]) return "じょ";
                return "め";//r
            }
            if(keyStatus[85]) return "さ";//u
            if(keyStatus[73]) return "よ";//i
            if(keyStatus[79]) return "ゆ";//o
            if(keyStatus[80]) return "え";//p
            if(keyStatus[65]) return "ぜ";//a
            if(keyStatus[83]) {
                if(keyStatus[186]) return "ぎゃ";
                if(keyStatus[79]) return "ぎゅ";
                if(keyStatus[73]) return "ぎょ";
                return "ね";//s
            }
            if(keyStatus[68]) return "に";//d
            if(keyStatus[70]) return "ま";//f
            if(keyStatus[71]) {
                if(keyStatus[186]) return "ぢゃ";
                if(keyStatus[79]) return "ぢゅ";
                if(keyStatus[73]) return "ぢょ";
                if(keyStatus[80]) return "ぢぇ";//p
                return "ぢ";//g
            }
            if(keyStatus[72]) return "わ";//h 
            if(keyStatus[75]) return "も";//k
            if(keyStatus[76]) return "つ";//l
            if(keyStatus[186]) return "や";//;
            if(keyStatus[90]) return "ぼ";//z
            if(keyStatus[88]) {
                if(keyStatus[186]) return "びゃ";
                if(keyStatus[79]) return "びゅ";
                if(keyStatus[73]) return "びょ";
                return "び";//x
            }
            if(keyStatus[67]) return "を";//c
            if(keyStatus[86]) return "、";//v
            if(keyStatus[66]) return "ぬ";//b
            if(keyStatus[78]) return "お";//n
            if(keyStatus[77]) return "。";//m
            if(keyStatus[188]) return "む";//,
            if(keyStatus[190]) return "ふ";//.
            if(keyStatus[191]) return "れ";///
            return "の";//j
        }
        if(keyStatus[86]){//シフトと左手半濁音キー(v)押し//右手は存在しない
            if(keyStatus[81]) return "ヴ";//q
            if(keyStatus[87]) return "み";//w
            if(keyStatus[69]) return "り";//e
            if(keyStatus[82]) return "め";//r
            if(keyStatus[85]) return "さ";//u
            if(keyStatus[73]) return "ょ";//i
            if(keyStatus[79]) return "ゅ";//o
            if(keyStatus[80]) return "ぇ";//p
            if(keyStatus[65]) return "せ";//a
            if(keyStatus[83]) return "ね";//s
            if(keyStatus[68]) return "に";//d
            if(keyStatus[70]) return "ま";//f
            if(keyStatus[71]) return "ち";//g
            if(keyStatus[72]) return "ゎ";//h
            if(keyStatus[74]) return "ぁ";//j
            if(keyStatus[75]) return "ぃ";//k
            if(keyStatus[76]) return "ぅ";//l
            if(keyStatus[186]) return "ゃ";//;
            if(keyStatus[90]) return "ほ";//z
            if(keyStatus[88]) return "ひ";//x
            if(keyStatus[67]) return "を";//c 
            if(keyStatus[66]) return "ぬ";//b
            if(keyStatus[78]) return "ぉ　";//n
            if(keyStatus[77]) return "。";//m
            if(keyStatus[188]) return "む";//,
            if(keyStatus[190]) return "ぷ";//.
            if(keyStatus[191]) return "れ";///
            return "、";//v
        }
        //清音
        if(keyStatus[81]) {
            if(keyStatus[80]) return "ヴぇ";
            if(keyStatus[78]) return "ヴぉ";
            if(keyStatus[79]) return "ヴゅ";
            return "ヴ";//q
        }
        if(keyStatus[87]) {
            if(keyStatus[186]) return "みゃ";
            if(keyStatus[79]) return "みゅ";
            if(keyStatus[73]) return "みょ";
            return "み";//w
        }
        if(keyStatus[69]) {
            if(keyStatus[186]) return "りゃ";
            if(keyStatus[79]) return "りゅ";
            if(keyStatus[73]) return "りょ";
            return "り";//e
        }
        if(keyStatus[82]) {
            if(keyStatus[186]) return "しゃ";
            if(keyStatus[80]) return "しぇ";
            if(keyStatus[79]) return "しゅ";
            if(keyStatus[73]) return "しょ";
            return "め";//r
        }
        if(keyStatus[85]) return "さ";//u       
        if(keyStatus[83]) {
            if(keyStatus[186]) return "きゃ";
            if(keyStatus[79]) return "きゅ";
            if(keyStatus[73]) return "きょ";
            return "ね";//s
        }
        if(keyStatus[65]) return "せ";//a
        if(keyStatus[68]) {
            if(keyStatus[186]) return "にゃ";
            if(keyStatus[79]) return "にゅ";
            if(keyStatus[73]) return "にょ";
            return "に";//d
        }
        if(keyStatus[70]) return "ま";//f
        if(keyStatus[71]) {
            if(keyStatus[186]) return "ちゃ";
            if(keyStatus[79]) return "ちゅ";
            if(keyStatus[80]) return "ちぇ"
            if(keyStatus[73]) return "ちょ";
            return "ち";//g
        }
        if(keyStatus[72]) return "わ";//h
        if(keyStatus[74]) {
            if(keyStatus[76]) return "つぁ";
            if(keyStatus[75]) return "つぃ";
            return "の";//j
        }
        if(keyStatus[75]) return "も";//k
        if(keyStatus[76]) {
            if(keyStatus[80]) return "つぇ";
            if(keyStatus[78]) return "つぉ";
            return "つ";//l
        }
        
        if(keyStatus[90]) return "ほ";//z
        if(keyStatus[88]) {
            if(keyStatus[186]) return "ひゃ";
            if(keyStatus[79]) return "ひゅ";
            if(keyStatus[73]) return "ひょ";
            return "ひ";//x
        }
        if(keyStatus[80]) return "え";//p
        if(keyStatus[67]) return "を";//c
        if(keyStatus[86]) return "、";//v
        if(keyStatus[66]) return "ぬ";//b
        if(keyStatus[78]) return "お";//n
        if(keyStatus[77]) return "。";//m
        if(keyStatus[188]) return "む";//,
        if(keyStatus[190]) {
            if(keyStatus[74]) return "ふぁ";
            if(keyStatus[75]) return "ふぃ";
            if(keyStatus[80]) return "ふぇ";
            if(keyStatus[78]) return "ふぉ";
            if(keyStatus[79]) return "ふゅ";
            return "ふ";//.
        }
        if(keyStatus[191]) return "れ";///
        if(keyStatus[186]) return "や";//;
        if(keyStatus[79]) return "ゆ";//o
        if(keyStatus[73]) return "よ";//i
    }
    else{//シフトなし
        if(keyStatus[74]){//右手濁音キー(j)押し
            if(keyStatus[81]) return "ヴ";//q
            if(keyStatus[87]) return "ば";//w
            if(keyStatus[69]) {
                if(keyStatus[75]) return "でぃ";
                return "で";//e
            }
            if(keyStatus[82]) return "じ";//r
            if(keyStatus[73]) return "る";//i
            if(keyStatus[79]) return "す";//o
            if(keyStatus[80]) return "へ";//p
            if(keyStatus[65]) return "ろ";//a
            if(keyStatus[83]) return "ぎ";//s
            if(keyStatus[68]) {
                if(keyStatus[76]) return "どぅ";
                return "ど";//d
            }
            if(keyStatus[70]) return "が";//f
            if(keyStatus[71]) return "っ";//g
            if(keyStatus[72]) return "く";//h
            if(keyStatus[75]) return "い";//k
            if(keyStatus[76]) return "う";//l
            if(keyStatus[186]) return "ー";//;
            if(keyStatus[90]) return "ぼ";//z
            if(keyStatus[88]) return "び";//x
            if(keyStatus[67]) return "げ";//c
            if(keyStatus[86]) return "ご";//v
            if(keyStatus[66]) return "ぞ";//b
            if(keyStatus[78]) return "た";//n
            if(keyStatus[77]) return "な";//m
            if(keyStatus[188]) return "ん";//,
            if(keyStatus[190]) return "ら";//.
            if(keyStatus[191]) return "れ";///
            return "あ";//j
        }
        if(keyStatus[70]){//左手濁音キー(f)押し
            if(keyStatus[81]) return "ヴ";//q
            if(keyStatus[87]) return "は";//w
            if(keyStatus[69]) return "て";//e
            if(keyStatus[82]) return "し";//r
            if(keyStatus[73]) return "る";//i
            if(keyStatus[79]) return "ず";//o
            if(keyStatus[80]) return "べ";//p
            if(keyStatus[65]) return "ろ";//a
            if(keyStatus[83]) return "き";//s
            if(keyStatus[68]) return "と";//d
            if(keyStatus[71]) return "っ";//g
            if(keyStatus[72]) return "ぐ";//h
            if(keyStatus[74]) return "あ";//j
            if(keyStatus[75]) return "い";//k
            if(keyStatus[76]) return "う";//l
            if(keyStatus[186]) return "ー";//;
            if(keyStatus[90]) return "ほ";//z
            if(keyStatus[88]) return "ひ";//x
            if(keyStatus[67]) return "け";//c
            if(keyStatus[86]) return "こ";//v
            if(keyStatus[66]) return "そ";//b
            if(keyStatus[78]) return "だ";//n
            if(keyStatus[77]) return "な";//m
            if(keyStatus[188]) return "ん";//,
            if(keyStatus[190]) return "ら";//.
            if(keyStatus[191]) return "れ";///
            return "か";//f
        }
        if(keyStatus[86]){//左手半濁音キー(v)押し
            if(keyStatus[81]) return "ヴ";//q
            if(keyStatus[87]) return "は";//w
            if(keyStatus[69]) return "て";//e
            if(keyStatus[82]) return "し";//r
            if(keyStatus[73]) return "る";//i
            if(keyStatus[79]) return "す";//o
            if(keyStatus[80]) return "ぺ";//p
            if(keyStatus[65]) return "ろ";//a
            if(keyStatus[83]) return "き";//s
            if(keyStatus[68]) return "と";//d
            if(keyStatus[70]) return "か";//f
            if(keyStatus[71]) return "っ";//g
            if(keyStatus[72]) return "く";//h
            if(keyStatus[74]) return "あ";//j
            if(keyStatus[75]) return "い";//k
            if(keyStatus[76]) return "う";//l
            if(keyStatus[186]) return "ー";//;
            if(keyStatus[90]) return "ほ";//z
            if(keyStatus[88]) return "ひ";//x
            if(keyStatus[67]) return "け";//c
            if(keyStatus[66]) return "そ";//b
            if(keyStatus[78]) return "た";//n
            if(keyStatus[77]) return "な";//m
            if(keyStatus[188]) return "ん";//,
            if(keyStatus[190]) return "ら";//.
            if(keyStatus[191]) return "れ";///
            return "こ";//v
        }
        if(keyStatus[77]){//右手半濁音キー(m)押し
            if(keyStatus[81]) return "ヴ";//q
            if(keyStatus[87]) return "ぱ";//w
            if(keyStatus[69]) return "て";//e
            if(keyStatus[82]) return "し";//r
            if(keyStatus[73]) return "る";//i
            if(keyStatus[79]) return "す";//o
            if(keyStatus[80]) return "へ";//p
            if(keyStatus[65]) return "ろ";//a
            if(keyStatus[83]) return "き";//s
            if(keyStatus[68]) return "と";//d
            if(keyStatus[70]) return "か";//f
            if(keyStatus[71]) return "っ";//g
            if(keyStatus[72]) return "く";//h
            if(keyStatus[74]) return "あ";//j
            if(keyStatus[75]) return "い";//k
            if(keyStatus[76]) return "う";//l
            if(keyStatus[186]) return "ー";//;
            if(keyStatus[90]) return "ぽ";//z
            if(keyStatus[88]) return "ぴ";//x
            if(keyStatus[67]) return "け";//c
            if(keyStatus[86]) return "こ";//v
            if(keyStatus[66]) return "そ";//b
            if(keyStatus[78]) return "た";//n
            if(keyStatus[188]) return "ん";//,
            if(keyStatus[190]) return "ら";//.
            if(keyStatus[191]) return "れ";///
            return "な";//m
        }
        if(keyStatus[81]) {
            if(keyStatus[74]) return "ヴぁ";
            if(keyStatus[75]) return "ヴぃ"
            return "ヴ";//q
        }
        if(keyStatus[87]) return "は";//w
        if(keyStatus[69]) {
            if(keyStatus[75]) return "てぃ";
            return "て";//e
        }
        if(keyStatus[82]) return "し";//r
        if(keyStatus[73]) return "る";//i
        if(keyStatus[79]) return "す";//o
        if(keyStatus[80]) return "へ";//p
        if(keyStatus[65]) return "ろ";//a
        if(keyStatus[83]) return "き";//s
        if(keyStatus[68]) {
            if(keyStatus[76]) return "とぅ"
            return "と";//d
        }
        if(keyStatus[70]) return "か";//f
        if(keyStatus[71]) return "っ";//g
        if(keyStatus[72]) return "く";//h
        if(keyStatus[74]) return "あ";//j
        if(keyStatus[75]) return "い";//k
        if(keyStatus[76]) return "う";//l
        if(keyStatus[186]) return "ー";//;
        if(keyStatus[90]) return "ほ";//z
        if(keyStatus[88]) return "ひ";//x
        if(keyStatus[67]) return "け";//c
        if(keyStatus[86]) return "こ";//v
        if(keyStatus[66]) return "そ";//b
        if(keyStatus[78]) return "た";//n
        if(keyStatus[77]) return "な";//m
        if(keyStatus[188]) return "ん";//,
        if(keyStatus[190]) return "ら";//.
        if(keyStatus[191]) return "れ";///
    }
}
```

</div></details>

<details><summary>タイピングゲームの基本機能</summary><div>

```JavaScript
document.onkeydown = keydownFunction;  //キー押下時に関数keydownFunction()を呼び出す
document.onkeyup = releaseFunction;

var originSentence = "";
var count=0;             //何問目か格納
var typStart,typEnd;   //開始時と終了時の時刻を格納
var SentenceLength=0;
var typeLetter ="";
var keyStatus = new Array();
var SentenceArray = new Array("")//問題文をべた書きしているため省略

//タイピングゲームの問題をセットする関数
function SetSentence()
{
    //問題文とカウント数をクリアする
    count=0;
    SentenceLength=Sentence.length;
    originSentence=Sentence;
    //問題枠に表示する
    document.getElementById("waku").style.backgroundColor="aliceblue";
    document.getElementById("waku").innerHTML = originSentence;
}

function keydownFunction(e)
{
    //入力されたキーのキーコードを取得
    if(e.keyCode>30){   
        keyStatus[e.keyCode]=true;
        typeLetter = reaction(keyStatus);
    }
    return false;
}

function releaseFunction(e){
    if(typeLetter==Sentence.charAt(0)||typeLetter==Sentence.slice(0,2)){
        if (count==0)
        { 
            typStart = new Date();
        }
        count++;

        document.getElementById("waku").style.backgroundColor="aliceblue";
        if (count < SentenceLength)
        {
            //問題文の頭の一文字を切り取る
            if(typeLetter==Sentence.slice(0,2)){
                sliceSentence=Sentence.substring(0,2);
                Sentence = Sentence.slice(2);
                count++;
            }
            else{
                sliceSentence=Sentence.substring(0,1);
                Sentence = Sentence.slice(1);
            }
            
            if(Sentence.charAt(0)=="　"){
                Sentence = Sentence.slice(1);
                count++;
            }
            //問題枠に表示する
            document.getElementById("pushKey").innerText="入力された文字：" + typeLetter;
            document.getElementById("waku").style.backgroundColor="palegreen";
            var aliceblue=function(){document.getElementById("waku").style.backgroundColor="aliceblue"}
            setTimeout(aliceblue,200);
            document.getElementById("waku").innerHTML = Sentence;
        }    
        else
        {
        //全文字入力していたら、終了時間を記録する
        typEnd = new Date();
        
        //終了時間－開始時間で掛かったミリ秒を取得する
        var elapsed = typEnd - typStart;
        
        //1000で割って「切捨て」、秒数を取得
        var sec = Math.floor( elapsed/1000 );
        
        //1000で割った「余り(%で取得できる）」でミリ秒を取得
        var msec = elapsed % 1000;
        
        //問題終了を告げる文字列を作成
        var fin="GAME終了　時間："+sec+"秒"+msec+"\n";
        
        //問題枠にゲーム終了を表示
        document.getElementById("waku").innerHTML = fin;
        }
    }
    else{
        if(typeLetter!=undefined){
            document.getElementById("pushKey").innerText="入力された文字：" + typeLetter;  
        }
        else document.getElementById("pushKey").innerText="入力された文字："
    } 
    keyStatus.map(function(value, index, array){
        return array[index]=false;
    });
    return false;
}
```
</div></details>

<details><summary>htmlのbody部分</summary><div>
<body onload="SetSentence();" bgcolor="#ffffff" text="#000000" >

<h1>タイピングゲーム</h1>
<p>下のプルダウンメニューからLessonを選択してください</p>
<select name="name" id="name">
    <option value="Lesson1">右手心臓部１</option>
    <option value="Lesson2">左手心臓部１</option>
    <option value="Lesson3">両手心臓部２</option>
    <option value="Lesson4">センターシフト</option>
    <option value="Lesson5">短文</option>
    <option value="Lesson6">半濁音、小書き</option>
    <option value="Lesson7">拗音</option>
    <option value="Lesson8">拗音２、外来音</option>
    <option value="Lesson9">その他</option>
    <option value="Lesson10">マイナー音（ふ）</option>
    <option value="Lesson11">マイナー音（ほ）</option>
    <option value="Lesson12">マイナー音（せ）</option>
    <option value="Lesson13">マイナー音（へ）</option>
    <option value="Lesson14">マイナー音（ヴ）</option>
    <option value="Lesson15">半濁音練習</option>
    <option value="Lesson16">人差し指内側キー濁音</option>
    <option value="Lesson17">左手マイナーキー</option>
  </select>
  <input type="button" value="Lesson切替" onclick="changeSentence()" />
<input type="button" value="1つ進める" onclick="OneSlice()"><br>
<div id="pushKey">入力された文字：</div>
<div id="waku"></div>
<input type="button" value="問題初期化" onclick="SetSentence()"><br>
<p>配列図</p>
<img id="image" src="images/naginata.jpg" alt="薙刀式配列図">
</body>
</div></details>

実装としては、入力されたキーコード部分がtrueになる配列を用意して（同時押しが存在するため）、条件分岐して薙刀式で入力した場合の文字列として返し、問題文と比較して合っていたら問題文を削るというものです。薙刀式の条件分岐を作成するのに時間がかかりました。

正解すると画面が黄緑色になるよう実装しています。理由は、不正解時に赤くなるようにしていた場合同時押しにすると無条件で赤くなってしまう問題に直面したからです。配列で比較しているために問題が起こっているのでしょうか。自分では解決に至りませんでしたので正解で黄緑色になるようにしました。

#終わりに
キーコードを取得して、実際に入力されるものを変えるという処理を応用すれば他の配列もタイピングソフトを作成することは可能なので、作ってみようと思います。