アマゾンバナーリンク

.Net Frameworkでのタイピングゲームの作り方(ファイル読み込みとかな入力対応編)

2020年1月3日

前回ローマ字入力ができるようにしました。
ポリモーフィズムの説明もしましたので、今回もそれを使ってかな入力にも対応してファイルから問題文を読み込めるようにしてみましょう。

ファイルから問題文を読み込む

読み込むファイルの仕様

読み込むファイルは上記の様な内容になってます。
まず1行目に漢字文、2行目には実際に入力するかな文字やローマ字を生成するためのかな文が書かれてます。
プログラム内では1行目の漢字文を List<string>型の ViewBase に、2行目のかな文をInputBaseに格納します。
[]の意味は、漢字の文字と、かなもしくはローマ字と同期させるためのものです。
例えば

環境は漢字2文字→ひらがなでは5文字
汚染は漢字2文字→ひらがなでは3文字
もっと細かく言うと
「環」は漢字1文字→ひらがなでは2文字
「境」は漢字1文字→ひらがなでは3文字
これはつまり漢字1文字分に対してひらがなが何文字分かという情報を保存するための物です

実際の処理

File.Existsでファイルが存在するかどうか、チェックしてから上から1行ずつList<string>型のViewBase(漢字文)とInputBase(かな文)に順番に代入していきます。このLoadSentenceはローマ字入力でもかな入力でも同じように使えるので基底クラスに定義します。
呼び出し側で
string file_name = Directory.GetCurrentDirectory() + “\word.txt";
のようにしてfile_nameを取得してこの関数に渡すことによって動作します。
実際にファイルを読み込むにはSystem.IOのStreamReaderを使ってます。
以下LoadSentence関数

protected void LoadSentence(string file_name)
{
    if (!File.Exists(file_name))
    {
        MessageBox.Show(Path.GetFileName(file_name) + "は存在しません",
            "エラー",
            MessageBoxButtons.OK,
            MessageBoxIcon.Error);
        return;
    }
    using (StreamReader sr = new StreamReader(
        file_name, Encoding.GetEncoding("Shift_JIS")))
    {
        string line = string.Empty;
        int cnt = 0;

        while ((line = sr.ReadLine()) != null)
        {
            // 全角文字を半角文字にする
            line = ChangeStr(line);
            switch (cnt)
            {
                case 0:
                    ViewBase.Add(line);
                    break;
                case 1:
                    InputBase.Add(line);
                    break;
            }
            cnt = (cnt + 1) % 2;
        }
    }
}

読み込んだ文章を加工する

次に読み込んだ文章をタイピングするための文章に加工します。
この中で使われているChangeInputTextは基底クラスで定義し派生クラスでオーバーライドしています。
ローマ字入力では、何も変化せずに文字列をそのまま返し、かな入力の時には、濁点と半濁点を分解する役割を果たします。RemoveBraceでは[]を削除し、MakeStrでは漢字一文字に対して、ひらがなが何文字かという情報を保存します。

protected void SetViewAndInputText()
{
    for (int i = 0; i < ViewBase.Count; ++i)
    {
        ViewText.Add(ViewBase[i]);
        InputTextk.Add(RemoveBrace(ChangeInputText(InputBase[i])));
        List<int> int_list = MakeStr(ChangeInputText(InputBase[i]));
        // []の数が違う場合エラーメッセージを出す
        if (ViewBase[i].Length != int_list.Count())
        {
            MessageBox.Show(string.Format("{0}行目の文章の[]の数が違います", i + 1));
            MessageBox.Show(string.Format("{0} を確認してください", ViewBase[i]));
        }
        StrNumList2D.Add(int_list);
    }
}
// []を削除をする
protected string RemoveBrace(string prev_str)
{
    string str = prev_str;
    string res_str = string.Empty;
    for (int i = 0; i < prev_str.Length; ++i)
    {
        if (str[i] == '[')
        {
            // [[を[として使えるようにするための処理
            if (i + 1 < str.Length)
            {
                if (str[i + 1] == '[')
                {
                    res_str += '[';
                    ++i;
                    continue;
                }
            }
        }
        else if (str[i] == ']')
        {
            // ]]を]として使えるようにするための処理
            if (i + 1 < str.Length)
            {
                if (str[i + 1] == ']')
                {
                    res_str += ']';
                    ++i;
                    continue;
                }
            }
        }
        else
        {
            res_str += str[i];
        }
    }
    return res_str;
}

文字をスクロールして色をつける機能

リッチテキストボックスではSelectionColorを使うことによって、指定した文字数だけ文字色を変えることができます。ここでは、打ち終わった文字列をGray、現在打とうとしてる文字をBlue、ミスした文字をRed、これから打つ文字をBlackにしてます。

引数の説明

  • 第一引数は描画する対象のリッチテキストボックス
  • 第二引数はタイピングする問題文
  • 第三引数は現在タイピングしている文字の位置
  • 第四引数はスクロールを開始する基準となる位置
  • 第五引数はこの文字数以上の場合はスクロールする
  • 第六引数はタイプミスした時に色を変えるためのフラグ
protected void DrawSentence(RichTextBox rtb, string sentence, int str_pos, int scroll_base, int scroll_disp_max, bool typemissflag)
{
    int stno = 0;
    // 現在の文字位置(View)がスクロールさせる基準位置(View)より大きくて、入力文を一度に画面へ表示する文字数よりtextlengthが多い時
    if (scroll_base < str_pos && scroll_disp_max < sentence.Length)
    {
        stno = str_pos - scroll_base; //現在の文字位置(ローマ字)からスクロールさせる基準位置(ローマ字)を引いた数をstno_eに入れる
    }
    else
    {
        stno = 0;
    }

    //現在の選択状態を覚えておく
    int prev_select_start = rtb.SelectionStart;
    int prev_select_length = rtb.SelectionLength;
    // 最後の一文字の場合
    if (sentence.Length <= str_pos)
    {
        rtb.Text = sentence.Substring(stno, str_pos - stno);
        // 打ち終わった文字の色
        rtb.Select(0, rtb.Text.Length); // 開始位置、変えたい文字数を選択
        rtb.SelectionColor = System.Drawing.Color.Gray;
    }
    else // 最後の一文字以外の場合
    {
        string buf1 = sentence.Substring(stno, str_pos - stno); // 打ち終わった文字列
        string buf2 = sentence.Substring(str_pos, 1); // 現在打とうとしてる文字
        int end_pos = Math.Max(0, Math.Min(sentence.Length - str_pos - 1, scroll_disp_max - str_pos + stno - 1));
        string buf3 = sentence.Substring(str_pos + 1, end_pos); // これから打つ文字列
        rtb.Text = buf1 + buf2 + buf3;

        // 打ち終わった文字の色が存在するなら
        if (!string.IsNullOrEmpty(buf1))
        {
            // 打ち終わった文字の色
            rtb.Select(0, buf1.Length); // 開始位置、変えたい文字数を選択
            rtb.SelectionColor = System.Drawing.Color.Gray;
        }

        // 次に打つまたは、ミスタイプした文字
        rtb.Select(buf1.Length, 1);
        rtb.SelectionColor = typemissflag ? System.Drawing.Color.Red : System.Drawing.Color.Blue;

        // それより後ろにある文字
        rtb.Select(buf1.Length + buf2.Length, buf3.Length);
        rtb.SelectionColor = System.Drawing.Color.Black;
        rtb.Select(prev_select_start, prev_select_length);
    }
}

この関数をローマ字入力ではDrawRomaTextで使い、かな入力ではDrawKanaTextで使ってます。
ここまででファイルから読み込んで問題文に色をつけて打たせることができるようになりました。

次にかな入力に対応させましょう。
新たにCTypingクラスを継承して、CKanaTypingクラスを作ります。

public class CKanaTyping : CTyping
{
    string[] SearchStr = new string[2];
    protected const int ST_COL = 204;
    protected const int ST_ROW = 3;
    protected string[,] St = new string[ST_COL, ST_ROW];
    public CKanaTyping(RichTextBox view_rich_text, RichTextBox input_rich_text) : base(view_rich_text, input_rich_text)
    {
        #region SetSt
        for (int i = 0; i < ST_COL; i++)
        {
            for (int k = 0; k < ST_ROW; k++)
            {
                St[i, k] = string.Empty;
            }
        }
        St[0, 0] = "あ"; St[0, 1] = "ア";
        St[1, 0] = "い"; St[1, 1] = "イ";
        St[2, 0] = "う"; St[2, 1] = "ウ";
        St[3, 0] = "え"; St[3, 1] = "エ";
        St[4, 0] = "お"; St[4, 1] = "オ";
        St[5, 0] = "か"; St[5, 1] = "カ";
        St[6, 0] = "き"; St[6, 1] = "キ";
        St[7, 0] = "く"; St[7, 1] = "ク";
        St[8, 0] = "け"; St[8, 1] = "ケ";
        St[9, 0] = "こ"; St[9, 1] = "コ";
        St[10, 0] = "さ"; St[10, 1] = "サ";
        St[11, 0] = "し"; St[11, 1] = "シ";
        St[12, 0] = "す"; St[12, 1] = "ス";
        St[13, 0] = "せ"; St[13, 1] = "セ";
        St[14, 0] = "そ"; St[14, 1] = "ソ";
        St[15, 0] = "た"; St[15, 1] = "タ";
        St[16, 0] = "ち"; St[16, 1] = "チ";
        St[17, 0] = "つ"; St[17, 1] = "ツ";
        St[18, 0] = "て"; St[18, 1] = "テ";
        St[19, 0] = "と"; St[19, 1] = "ト";
        St[20, 0] = "な"; St[20, 1] = "ナ";
        St[21, 0] = "に"; St[21, 1] = "ニ";
        St[22, 0] = "ぬ"; St[22, 1] = "ヌ";
        St[23, 0] = "ね"; St[23, 1] = "ネ";
        St[24, 0] = "の"; St[24, 1] = "ノ";
        St[25, 0] = "は"; St[25, 1] = "ハ";
        St[26, 0] = "ひ"; St[26, 1] = "ヒ";
        St[27, 0] = "ふ"; St[27, 1] = "フ";
        St[28, 0] = "へ"; St[28, 1] = "ヘ";
        St[29, 0] = "ほ"; St[29, 1] = "ホ";
        St[30, 0] = "ま"; St[30, 1] = "マ";
        St[31, 0] = "み"; St[31, 1] = "ミ";
        St[32, 0] = "む"; St[32, 1] = "ム";
        St[33, 0] = "め"; St[33, 1] = "メ";
        St[34, 0] = "も"; St[34, 1] = "モ";
        St[35, 0] = "や"; St[35, 1] = "ヤ";
        St[36, 0] = "ゆ"; St[36, 1] = "ユ";
        St[37, 0] = "よ"; St[37, 1] = "ヨ";
        St[38, 0] = "ら"; St[38, 1] = "ラ";
        St[39, 0] = "り"; St[39, 1] = "リ";
        St[40, 0] = "る"; St[40, 1] = "ル";
        St[41, 0] = "れ"; St[41, 1] = "レ";
        St[42, 0] = "ろ"; St[42, 1] = "ロ";
        St[43, 0] = "わ"; St[43, 1] = "ワ";
        St[44, 0] = "を"; St[44, 1] = "ヲ";
        St[45, 0] = "ん"; St[45, 1] = "ン";
        St[46, 0] = "ぁ"; St[46, 1] = "ァ";
        St[47, 0] = "ぃ"; St[47, 1] = "ィ";
        St[48, 0] = "ぅ"; St[48, 1] = "ゥ";
        St[49, 0] = "ぇ"; St[49, 1] = "ェ";
        St[50, 0] = "ぉ"; St[50, 1] = "ォ";
        St[51, 0] = "っ"; St[51, 1] = "ッ";
        St[52, 0] = "ゃ"; St[52, 1] = "ャ";
        St[53, 0] = "ゅ"; St[53, 1] = "ュ";
        St[54, 0] = "ょ"; St[54, 1] = "ョ";
        St[55, 0] = "ア"; St[55, 1] = "ア";
        St[56, 0] = "イ"; St[56, 1] = "イ";
        St[57, 0] = "ウ"; St[57, 1] = "ウ";
        St[58, 0] = "エ"; St[58, 1] = "エ";
        St[59, 0] = "オ"; St[59, 1] = "オ";
        St[60, 0] = "カ"; St[60, 1] = "カ";
        St[61, 0] = "キ"; St[61, 1] = "キ";
        St[62, 0] = "ク"; St[62, 1] = "ク";
        St[63, 0] = "ケ"; St[63, 1] = "ケ";
        St[64, 0] = "コ"; St[64, 1] = "コ";
        St[65, 0] = "サ"; St[65, 1] = "サ";
        St[66, 0] = "シ"; St[66, 1] = "シ";
        St[67, 0] = "ス"; St[67, 1] = "ス";
        St[68, 0] = "セ"; St[68, 1] = "セ";
        St[69, 0] = "ソ"; St[69, 1] = "ソ";
        St[70, 0] = "タ"; St[70, 1] = "タ";
        St[71, 0] = "チ"; St[71, 1] = "チ";
        St[72, 0] = "ツ"; St[72, 1] = "ツ";
        St[73, 0] = "テ"; St[73, 1] = "テ";
        St[74, 0] = "ト"; St[74, 1] = "ト";
        St[75, 0] = "ナ"; St[75, 1] = "ナ";
        St[76, 0] = "ニ"; St[76, 1] = "ニ";
        St[77, 0] = "ヌ"; St[77, 1] = "ヌ";
        St[78, 0] = "ネ"; St[78, 1] = "ネ";
        St[79, 0] = "ノ"; St[79, 1] = "ノ";
        St[80, 0] = "ハ"; St[80, 1] = "ハ";
        St[81, 0] = "ヒ"; St[81, 1] = "ヒ";
        St[82, 0] = "フ"; St[82, 1] = "フ";
        St[83, 0] = "ヘ"; St[83, 1] = "ヘ";
        St[84, 0] = "ホ"; St[84, 1] = "ホ";
        St[85, 0] = "マ"; St[85, 1] = "マ";
        St[86, 0] = "ミ"; St[86, 1] = "ミ";
        St[87, 0] = "ム"; St[87, 1] = "ム";
        St[88, 0] = "メ"; St[88, 1] = "メ";
        St[89, 0] = "モ"; St[89, 1] = "モ";
        St[90, 0] = "ヤ"; St[90, 1] = "ヤ";
        St[91, 0] = "ユ"; St[91, 1] = "ユ";
        St[92, 0] = "ヨ"; St[92, 1] = "ヨ";
        St[93, 0] = "ラ"; St[93, 1] = "ラ";
        St[94, 0] = "リ"; St[94, 1] = "リ";
        St[95, 0] = "ル"; St[95, 1] = "ル";
        St[96, 0] = "レ"; St[96, 1] = "レ";
        St[97, 0] = "ロ"; St[97, 1] = "ロ";
        St[98, 0] = "ワ"; St[98, 1] = "ワ";
        St[99, 0] = "ヲ"; St[99, 1] = "ヲ";
        St[100, 0] = "ン"; St[100, 1] = "ン";
        St[101, 0] = "ァ"; St[101, 1] = "ァ";
        St[102, 0] = "ィ"; St[102, 1] = "ィ";
        St[103, 0] = "ゥ"; St[103, 1] = "ゥ";
        St[104, 0] = "ェ"; St[104, 1] = "ェ";
        St[105, 0] = "ォ"; St[105, 1] = "ォ";
        St[106, 0] = "ッ"; St[106, 1] = "ッ";
        St[107, 0] = "ャ"; St[107, 1] = "ャ";
        St[108, 0] = "ュ"; St[108, 1] = "ュ";
        St[109, 0] = "ョ"; St[109, 1] = "ョ";
        St[110, 0] = "ー"; St[110, 1] = "ー";
        St[111, 0] = "゛"; St[111, 1] = "゙";
        St[112, 0] = "゜"; St[112, 1] = "゚";
        St[113, 0] = "「"; St[113, 1] = "「";
        St[114, 0] = "」"; St[114, 1] = "」";
        St[115, 0] = "、"; St[115, 1] = "、";
        St[116, 0] = "。"; St[116, 1] = "。";
        St[117, 0] = "・"; St[117, 1] = "・";
        St[118, 0] = "ー"; St[118, 1] = "ー";
        St[119, 0] = "゙"; St[119, 1] = "゙";
        St[120, 0] = "゚"; St[120, 1] = "゚";
        St[121, 0] = "「"; St[121, 1] = "「";
        St[122, 0] = "」"; St[122, 1] = "」";
        St[123, 0] = "、"; St[123, 1] = "、";
        St[124, 0] = "。"; St[124, 1] = "。";
        St[125, 0] = "・"; St[125, 1] = "・";
        St[126, 0] = " "; St[126, 1] = " ";
        St[127, 0] = " "; St[127, 1] = " ";
        St[128, 0] = "1"; St[128, 1] = "ヌ";
        St[129, 0] = "2"; St[129, 1] = "フ";
        St[130, 0] = "3"; St[130, 1] = "ア"; St[130, 2] = "ァ";
        St[131, 0] = "4"; St[131, 1] = "ウ"; St[131, 2] = "ゥ";
        St[132, 0] = "5"; St[132, 1] = "エ"; St[132, 2] = "ェ";
        St[133, 0] = "6"; St[133, 1] = "オ"; St[133, 2] = "ォ";
        St[134, 0] = "7"; St[134, 1] = "ヤ"; St[134, 2] = "ャ";
        St[135, 0] = "8"; St[135, 1] = "ユ"; St[135, 2] = "ュ";
        St[136, 0] = "9"; St[136, 1] = "ヨ"; St[136, 2] = "ョ";
        St[137, 0] = "0"; St[137, 1] = "ワ"; St[137, 2] = "ヲ";
        St[138, 0] = "1"; St[138, 1] = "ヌ";
        St[139, 0] = "2"; St[139, 1] = "フ";
        St[140, 0] = "3"; St[140, 1] = "ア"; St[140, 2] = "ァ";
        St[141, 0] = "4"; St[141, 1] = "ウ"; St[141, 2] = "ゥ";
        St[142, 0] = "5"; St[142, 1] = "エ"; St[142, 2] = "ェ";
        St[143, 0] = "6"; St[143, 1] = "オ"; St[143, 2] = "ォ";
        St[144, 0] = "7"; St[144, 1] = "ヤ"; St[144, 2] = "ャ";
        St[145, 0] = "8"; St[145, 1] = "ユ"; St[145, 2] = "ュ";
        St[146, 0] = "9"; St[146, 1] = "ヨ"; St[146, 2] = "ョ";
        St[147, 0] = "0"; St[147, 1] = "ワ"; St[147, 2] = "ヲ";
        St[148, 0] = "a"; St[148, 1] = "チ";
        St[149, 0] = "b"; St[149, 1] = "コ";
        St[150, 0] = "c"; St[150, 1] = "ソ";
        St[151, 0] = "d"; St[151, 1] = "シ";
        St[152, 0] = "e"; St[152, 1] = "イ";
        St[153, 0] = "f"; St[153, 1] = "ハ";
        St[154, 0] = "g"; St[154, 1] = "キ";
        St[155, 0] = "h"; St[155, 1] = "ク";
        St[156, 0] = "i"; St[156, 1] = "ニ";
        St[157, 0] = "j"; St[157, 1] = "マ";
        St[158, 0] = "k"; St[158, 1] = "ノ";
        St[159, 0] = "l"; St[159, 1] = "リ";
        St[160, 0] = "m"; St[160, 1] = "モ";
        St[161, 0] = "n"; St[161, 1] = "ミ";
        St[162, 0] = "o"; St[162, 1] = "ラ";
        St[163, 0] = "p"; St[163, 1] = "セ";
        St[164, 0] = "q"; St[164, 1] = "タ";
        St[165, 0] = "r"; St[165, 1] = "ス";
        St[166, 0] = "s"; St[166, 1] = "ト";
        St[167, 0] = "t"; St[167, 1] = "カ";
        St[168, 0] = "u"; St[168, 1] = "ナ";
        St[169, 0] = "v"; St[169, 1] = "ヒ";
        St[170, 0] = "w"; St[170, 1] = "テ";
        St[171, 0] = "x"; St[171, 1] = "サ";
        St[172, 0] = "y"; St[172, 1] = "ン";
        St[173, 0] = "z"; St[173, 1] = "ツ"; St[173, 2] = "ッ";
        St[174, 0] = "A"; St[174, 1] = "チ";
        St[175, 0] = "B"; St[175, 1] = "コ";
        St[176, 0] = "C"; St[176, 1] = "ソ";
        St[177, 0] = "D"; St[177, 1] = "シ";
        St[178, 0] = "E"; St[178, 1] = "イ";
        St[179, 0] = "F"; St[179, 1] = "ハ";
        St[180, 0] = "G"; St[180, 1] = "キ";
        St[181, 0] = "H"; St[181, 1] = "ク";
        St[182, 0] = "I"; St[182, 1] = "ニ";
        St[183, 0] = "J"; St[183, 1] = "マ";
        St[184, 0] = "K"; St[184, 1] = "ノ";
        St[185, 0] = "L"; St[185, 1] = "リ";
        St[186, 0] = "M"; St[186, 1] = "モ";
        St[187, 0] = "N"; St[187, 1] = "ミ";
        St[188, 0] = "O"; St[188, 1] = "ラ";
        St[189, 0] = "P"; St[189, 1] = "セ";
        St[190, 0] = "Q"; St[190, 1] = "タ";
        St[191, 0] = "R"; St[191, 1] = "ス";
        St[192, 0] = "S"; St[192, 1] = "ト";
        St[193, 0] = "T"; St[193, 1] = "カ";
        St[194, 0] = "U"; St[194, 1] = "ナ";
        St[195, 0] = "V"; St[195, 1] = "ヒ";
        St[196, 0] = "W"; St[196, 1] = "テ";
        St[197, 0] = "X"; St[197, 1] = "サ";
        St[198, 0] = "Y"; St[198, 1] = "ン";
        St[199, 0] = "Z"; St[199, 1] = "ツ"; St[199, 2] = "ッ";

        St[200, 0] = "<"; St[200, 1] = "、";
        St[201, 0] = ">"; St[201, 1] = "。";
        St[202, 0] = "<"; St[202, 1] = "、";
        St[203, 0] = ">"; St[203, 1] = "。";
        #endregion
        SetChangeWord();
        string file_name = Directory.GetCurrentDirectory() + "\\word.txt";
        LoadSentence(file_name);
        SetViewAndInputText();
        SetSearchStr();
        DrawKanaText(TextPos, StrPosK, StrPosA, TypeMissFlag);
    }
    // かなロックを使わないで済ますためにこの関数でキーコードを変換する
    void SetChangeWord()
    {
        // キーボード1行目
        Cw[0, 0] = "D1"; Cw[0, 1] = "ヌ"; Cw[0, 2] = "ヌ";
        Cw[1, 0] = "D2"; Cw[1, 1] = "フ"; Cw[1, 2] = "フ";
        Cw[2, 0] = "D3"; Cw[2, 1] = "ア"; Cw[2, 2] = "ァ";
        Cw[3, 0] = "D4"; Cw[3, 1] = "ウ"; Cw[3, 2] = "ゥ";
        Cw[4, 0] = "D5"; Cw[4, 1] = "エ"; Cw[4, 2] = "ェ";
        Cw[5, 0] = "D6"; Cw[5, 1] = "オ"; Cw[5, 2] = "ォ";
        Cw[6, 0] = "D7"; Cw[6, 1] = "ヤ"; Cw[6, 2] = "ャ";
        Cw[7, 0] = "D8"; Cw[7, 1] = "ユ"; Cw[7, 2] = "ュ";
        Cw[8, 0] = "D9"; Cw[8, 1] = "ヨ"; Cw[8, 2] = "ョ";
        Cw[9, 0] = "D0"; Cw[9, 1] = "ワ"; Cw[9, 2] = "ヲ";
        Cw[10, 0] = "OemMinus"; Cw[10, 1] = "ホ"; Cw[10, 2] = "ホ";
        Cw[11, 0] = "Oem7"; Cw[11, 1] = "ヘ"; Cw[11, 2] = "ヘ";
        Cw[12, 0] = "Oem5"; Cw[12, 1] = "ー"; Cw[12, 2] = "ー";

        // キーボード2行目
        Cw[13, 0] = "Q"; Cw[13, 1] = "タ"; Cw[13, 2] = "タ";
        Cw[14, 0] = "W"; Cw[14, 1] = "テ"; Cw[14, 2] = "テ";
        Cw[15, 0] = "E"; Cw[15, 1] = "イ"; Cw[15, 2] = "ィ";
        Cw[16, 0] = "R"; Cw[16, 1] = "ス"; Cw[16, 2] = "ス";
        Cw[17, 0] = "T"; Cw[17, 1] = "カ"; Cw[17, 2] = "カ";
        Cw[18, 0] = "Y"; Cw[18, 1] = "ン"; Cw[18, 2] = "ン";
        Cw[19, 0] = "U"; Cw[19, 1] = "ナ"; Cw[19, 2] = "ナ";
        Cw[20, 0] = "I"; Cw[20, 1] = "ニ"; Cw[20, 2] = "ニ";
        Cw[21, 0] = "O"; Cw[21, 1] = "ラ"; Cw[21, 2] = "ラ";
        Cw[22, 0] = "P"; Cw[22, 1] = "セ"; Cw[22, 2] = "セ";
        Cw[23, 0] = "Oemtilde"; Cw[23, 1] = "゙"; Cw[23, 2] = "゙";
        Cw[24, 0] = "OemOpenBrackets"; Cw[24, 1] = "゚"; Cw[24, 2] = "「";

        // キーボード3行目
        Cw[25, 0] = "A"; Cw[25, 1] = "チ"; Cw[25, 2] = "チ";
        Cw[26, 0] = "S"; Cw[26, 1] = "ト"; Cw[26, 2] = "ト";
        Cw[27, 0] = "D"; Cw[27, 1] = "シ"; Cw[27, 2] = "シ";
        Cw[28, 0] = "F"; Cw[28, 1] = "ハ"; Cw[28, 2] = "ハ";
        Cw[29, 0] = "G"; Cw[29, 1] = "キ"; Cw[29, 2] = "キ";
        Cw[30, 0] = "H"; Cw[30, 1] = "ク"; Cw[30, 2] = "ク";
        Cw[31, 0] = "J"; Cw[31, 1] = "マ"; Cw[31, 2] = "マ";
        Cw[32, 0] = "K"; Cw[32, 1] = "ノ"; Cw[32, 2] = "ノ";
        Cw[33, 0] = "L"; Cw[33, 1] = "リ"; Cw[33, 2] = "リ";
        Cw[34, 0] = "Oemplus"; Cw[34, 1] = "レ"; Cw[34, 2] = "レ";
        Cw[35, 0] = "Oem1"; Cw[35, 1] = "ケ"; Cw[35, 2] = "ケ";
        Cw[36, 0] = "Oem6"; Cw[36, 1] = "ム"; Cw[36, 2] = "」";

        // キーボード4行目
        Cw[37, 0] = "Z"; Cw[37, 1] = "ツ"; Cw[37, 2] = "ッ";
        Cw[38, 0] = "X"; Cw[38, 1] = "サ"; Cw[38, 2] = "サ";
        Cw[39, 0] = "C"; Cw[39, 1] = "ソ"; Cw[39, 2] = "ソ";
        Cw[40, 0] = "V"; Cw[40, 1] = "ヒ"; Cw[40, 2] = "ヒ";
        Cw[41, 0] = "B"; Cw[41, 1] = "コ"; Cw[41, 2] = "コ";
        Cw[42, 0] = "N"; Cw[42, 1] = "ミ"; Cw[42, 2] = "ミ";
        Cw[43, 0] = "M"; Cw[43, 1] = "モ"; Cw[43, 2] = "モ";
        Cw[44, 0] = "Oemcomma"; Cw[44, 1] = "ネ"; Cw[44, 2] = "、";
        Cw[45, 0] = "OemPeriod"; Cw[45, 1] = "ル"; Cw[45, 2] = "。";
        Cw[46, 0] = "OemQuestion"; Cw[46, 1] = "メ"; Cw[46, 2] = "・";
        Cw[47, 0] = "OemBackslash"; Cw[47, 1] = "ロ"; Cw[47, 2] = "ロ";
    }

    // 判定用の文字列を返す(KeyPressFuncが呼ばれる度に呼ばれる)
    public override string GetKeyString(string input_str, bool shift, bool showkeypress_mode = false)
    {
        for (int i = 0; i < 48; ++i)
        {
            if (input_str == Cw[i, 0])
            {
                return !shift ? Cw[i, 1] : Cw[i, 2];
            }
        }
        return null;
    }
    #region DecStr
    private string[,] DecStr = new string[,]
    {
        {"が","か","゛"},
        {"ぎ","き","゛"},
        {"ぐ","く","゛"},
        {"げ","け","゛"},
        {"ご","こ","゛"},
        {"ざ","さ","゛"},
        {"じ","し","゛"},
        {"ず","す","゛"},
        {"ぜ","せ","゛"},
        {"ぞ","そ","゛"},
        {"だ","た","゛"},
        {"ぢ","ち","゛"},
        {"づ","つ","゛"},
        {"で","て","゛"},
        {"ど","と","゛"},
        {"ば","は","゛"},
        {"び","ひ","゛"},
        {"ぶ","ふ","゛"},
        {"べ","へ","゛"},
        {"ぼ","ほ","゛"},
        {"ぱ","は","゜"},
        {"ぴ","ひ","゜"},
        {"ぷ","ふ","゜"},
        {"ぺ","へ","゜"},
        {"ぽ","ほ","゜"},

        {"ヴ","ウ","゛"},

        {"ガ","カ","゛"},
        {"ギ","キ","゛"},
        {"グ","ク","゛"},
        {"ゲ","ケ","゛"},
        {"ゴ","コ","゛"},
        {"ザ","サ","゛"},
        {"ジ","シ","゛"},
        {"ズ","ス","゛"},
        {"ぜ","セ","゛"},
        {"ゾ","ソ","゛"},
        {"ダ","タ","゛"},
        {"ヂ","チ","゛"},
        {"ヅ","ツ","゛"},
        {"デ","テ","゛"},
        {"ド","ト","゛"},
        {"バ","ハ","゛"},
        {"ビ","ヒ","゛"},
        {"ブ","フ","゛"},
        {"ベ","ヘ","゛"},
        {"ボ","ホ","゛"},
        {"パ","ハ","゜"},
        {"ピ","ヒ","゜"},
        {"プ","フ","゜"},
        {"ペ","ヘ","゜"},
        {"ポ","ホ","゜"},
    };
    #endregion
    // かなの濁点を分解する処理 例:が→か゛
    protected override string ChangeInputText(string input_base)
    {
        string str = string.Empty;
        for (int i = 0; i < input_base.Length; ++i)
        {
            bool update = false;
            for (int j = 0; j < DecStr.GetLength(0); ++j)
            {
                if (DecStr[j, 0] == input_base[i].ToString())
                {
                    update = true;
                    str += DecStr[j, 1] + DecStr[j, 2];
                    break;
                }
            }
            if (!update)
            {
                str += input_base[i];
            }
        }
        return str;
    }

    public void SetSearchStr()
    {
        SearchStr[0] = string.Empty;
        SearchStr[1] = string.Empty;
        string str_buf = string.Empty;

        while (true)
        {
            str_buf = InputTextk[TextPos].Substring(StrPosK, 1);
            if (str_buf == " " || str_buf == " ")
            {
                // " "である分だけ進める
                ++StrPosK;
                ++StrPosA;
            }
            else
            {
                break;
            }
        }

        for (int i = 0; i < ST_COL; ++i)
        {
            if (str_buf == St[i, 0])
            {
                SearchStr[0] = St[i, 1];
                SearchStr[1] = St[i, 2];
                break;
            }
        }
    }
    int ScrollBaseK = 10;
    int ScrollBaseV = 10;
    int ScrollDispMaxK = 20;
    int ScrollDispMaxV = 20;
    public void DrawKanaText(int text_pos, int str_posk, int str_posa, bool typemissflg)
    {
        // ViewText描画
        DrawSentence(ViewRichTextBox, ViewText[text_pos], str_posa, ScrollBaseV, ScrollDispMaxV, typemissflg);
        // InputText描画
        DrawSentence(InputRichTextBox, InputTextk[text_pos], str_posk, ScrollBaseK, ScrollDispMaxK, typemissflg);
    }

    public override void KeypressFunc(string input_str)
    {
        System.StringSplitOptions option = System.StringSplitOptions.RemoveEmptyEntries;
        string[] lines = input_str.Split(new char[] { ',' }, option);
        // シフトキーしか押してない時は無効
        if (lines[0] == "ShiftKey")
        {
            return;
        }
        // 文字の保存
        // lines.Length 1.シフトを押してない 2.シフト押している
        string temp_str = GetKeyString(lines[0], lines.Length == 2 ? true : false);
        // 一致判定
        if (SearchStr[0] == temp_str || SearchStr[1] == temp_str)
        {
            TypeMissFlag = false;
            ++StrPosK;
            string input_testk = InputTextk[TextPos];
            int length = input_testk.Length;
            // 一文終了
            if (StrPosK >= length)
            {
                StrPosK = 0;
                StrPosA = 0;
                // 全文終了
                if (InputTextk.Count <= ++TextPos)
                {
                    // 打ち切り終了
                    TypeEnd();
                    return;
                }
            }

            SetSearchStr();
            StrPosA = ChangeStrNum(TextPos, StrPosK);
        }
        else // ミス時
        {
            TypeMissFlag = true;
        }
        DrawKanaText(TextPos, StrPosK, StrPosA, TypeMissFlag);
    }
    void TypeEnd()
    {
        TextPos = 0;
        SetSearchStr();
        DrawKanaText(TextPos, StrPosK, StrPosA, TypeMissFlag);
    }
}

ローマ字入力に比べるとかな入力の処理は複数の打ち分けがないため構造がシンプルです。
プログラミングを見る手順として、まずコンストラクタの処理を見て、ロードの処理を見て、キー入力された時に呼ばれるKeypressFunc関数を見るとよいです。
前回説明したようにKeypressFuncは関数のオーバーライドを使って、ローマ字入力とかな入力で異なった処理になるような実装にしてます。

実行結果

ここまで作ったプロジェクトはこちらからダウンロードできます。

今回の講座をすべて通して学んだ事

  • 複数のローマ字入力の打ち分ける処理
  • クラスを有効活用したローマ字入力とかな入力両方への対応、
  • ファイルからの問題文読み込み
  • 文字のスクロール
  • これから打つ文字、打った文字、正解時、ミス字の文字色の

これらのタイピングゲームに必要な最低限の機能を実装しました。まだまだ細かい点では沢山ありますが、難しい事はないです。ただ意外と作業量が多いんですよね。

最後にお願い

もしこの講座を通して、タイピングゲームを作ろうと思う人が、少しでもいたらすごく嬉しいです。
そして、その時には、ぜひかな入力も実装するのをお願いしたいです。私はかな入力のタイパーなんですが、かな入力に対応してるタイピングゲームが本当に少ないのがとても残念です。ローマ字入力よりも作る手間は確実に少ないので、ぜひかな入力にも対応するのを検討していただければ嬉しいです。