WEB制作
JavaScript | 13章:まとめの演習2 タイピングゲームをつくろう
INDEX
目次

13章:まとめの演習2 タイピングゲームをつくろう

本章では、ここまでに学習したイベントハンドラー、DOMの操作、変数・定数の使い方、配列、条件分岐などを組み合わせて、まとめの演習としてサンプルページを作成していきます。

【13-1】タイピングゲームの仕様

今回作成するタイピングゲームの仕様と使い方を、まずは以下の「作成する機能」とプレビュー動画で確認して下さい。

✔️ 作成する機能


  • 配列に問題文を格納する

  • ページロード時の処理

    • 1. もう1回ボタンを非表示にする

      2. 問題文の数(配列データの数)とクリアした問題数(初期値0)を表示する

      3. 得点(初期値0)を表示する

      4. 問題欄に1問目を表示する

      5. 解答欄にカーソルを立てる

  • データ送信時の処理

    • 1. 解答欄に入力されたテキストを取得する

      2. 現在表示されている問題文と解答が同一かどうかを判別する

        a. 同一だった場合、次の問題文を表示し、解答欄を空欄にして、カーソルを立てる

        b. 空欄または問題文と解と不一致だった場合、問題文はそのままで、解答欄を空欄にして、カーソルを立てる

        c. 1.と2.どちらの場合もイベントをキャンセルする(送信中止)

      3. 1問正解する度に、得点に100点加算される

      4. 1問正解する度に、問題をクリアした数がカウントされる

      5. 全問クリアしたとき

        a. アラートでメッセージを出す(「全問正解おめでとう! ボーナスポイント100点追加!」)

        b. ボーナスポイントを100点追加する

        c. 1問目を再表示する

        d. 送信ボタンを無効にする

        e. もう1回ボタンを表示する

  • もう1回ボタンクリック時の処理

    • ブラウザを更新する

  • Enter(return)キーを押したときと離したときの処理

    • 送信ボタンの色を変化させる



  • JS13演習データ.zip」をダウンロードして下さい。

    • typing-game_fin」フォルダ内の「typing.html」は完成データですので、こちらのファイルをブラウザ表示して、まずは自分で遊んでみて下さい。


    使用素材
    演習素材について
    • ダウンロードした「JS13演習データ」フォルダの中には、「typing-game_start」フォルダと「typing-game_fin」フォルダが入っています。
    • 「typing-game_start」フォルダには作業用のファイルが入っています。
    • 「typing-game_fin」フォルダには完成データが入っています。
    • 自分で書いたコードがうまく動作しないときは、完成データとの違いを探してみると良いでしょう。

  • それでは、タイピングゲームを作成していきましょう。

  • typing-game_start」フォルダ内の「typing.html」とjsフォルダ内の「typing.js」をエディターで開き、ブラウザプレビューして下さい。


  • <body>
      <!-- 問題数とクリアした数 -->
      <div class="clear">
        <span class="number-of-questions-cleared"></span>/<span
          class="number-of-questions"
        ></span>
      </div>
    
      <!-- 得点 -->
      <div class="score-box"><span class="score"></span>点</div>
    
      <div class="container">
        <h1>タイピングゲーム</h1>
        <h2>俳句で練習しよう</h2>
        <form class="typing-form">
          <!-- 問題文 -->
          <input type="text" class="elem-text elem1" />
          <!-- 解答欄 -->
          <input type="text" class="elem-text elem2" />
          <!-- 送信ボタン -->
          <input type="submit" value="解答を送信する" class="submit-btn" />
        </form>
        <!-- もう一回ボタン -->
        <button class="onemore-btn">もう1回!</button>
      </div>
    </body>
    typing-game_start/typing.html サンプルコード13-1

     (11599)

  • CSS(css/style.css)でここまでスタイリングしてあります。

  • JavaScriptで機能面を作成していきます。


  • 【13-2】下準備をしよう

  • これからタイピングゲームに必要な処理を作成していくにあたり、下準備として最初にやっておきたいことがあります。

  • それは、要素オブジェクトを作成し、オブジェクト一覧を作っておくことです。


  • 🟧 要素オブジェクトの作成

    • form要素やinput要素、button要素、span要素など、オブジェクトとして操作したいDOMの要素は全てquerySelectorメソッドでclass名を取得し、定数に代入します。

    • この作業を最初にまとめてやっておき、オブジェクト一覧を作成しておくと、見晴らしの良いコードを書くことができます。

    • また、今回はJavaScriptを外部ファイルにして作成しますので、 </body> の直前 に scriptタグを記述して、 typing.html と typing.js を関連付けしておきましょう。 (サンプルコード13-2参照)

    ✔️ オブジェクトとして必要なDOM要素

      今回はそれぞれのDOM要素を、下記のオブジェクト名を使い定義してタイピングゲームの処理を作成していきます。(サンプルコード13-3参照)

     (11607)


    ・・・中略・・・
    <script src="js/typing.js"></script>
    </body>
    </html>
    typing-game_start/typing.html サンプルコード13-2
    //オブジェクト一覧
    const formObj = document.querySelector(".typing-form");
    const elemQuestion = document.querySelector(".elem1");
    const elemAnswer = document.querySelector(".elem2");
    const submitBtn = document.querySelector(".submit-btn");
    const onemoreBtn = document.querySelector(".onemore-btn");
    const questionsCleared = document.querySelector(".number-of-questions-cleared");
    const numberOfQuestions = document.querySelector(".number-of-questions");
    const score = document.querySelector(".score");
    typing-game_start/typing.js サンプルコード13-3

    【13-3】初期画面を作成しよう

  • オブジェクトを作成したら、次はタイピングゲームの初期画面を作成します。

  • 初期画面とは、ページロード時のブラウザ表示のことです。


  • 初期画面として設定する項目は、以下のとおりです。

    ✔️ 初期画面の設定項目

      1. もう1回ボタンを非表示にする

      2. 問題文の数(配列データの数)とクリアした問題数(初期値0)を表示する

      3. 得点(初期値0)を表示する

      4. 問題欄に1問目を表示する

      5. 解答欄にカーソルを立てる

     (11616)


    🟧 1. もう1回ボタンを非表示にする

      まずは「もう1回ボタン」を非表示にします。

    • 「もう1回ボタン」はonemoreBtnというオブジェクト名で定義しました。(サンプルコード13-3参照)

    • この要素オブジェクトの下位オブジェクトであるstyleオブジェクトにアクセスしてCSSを操作します。(「6章 JavaScriptでCSSを操作しよう」参照)

    • CSSのdisplayプロパティの値をnoneと設定すれば非表示になります。

    • オブジェクト一覧よりも下に、以下のコードを記述します。

    //もう1回ボタンを非表示にする
    onemoreBtn.style.display = "none";
    サンプルコード13-4
     (11622)


    🟧 2. 問題文の数(配列データの数)とクリアした問題数(初期値0)を表示する

    配列に問題文を格納する

    問題文の数を表示するにあたり、まずは配列に問題文を格納します。

    1問正解する度に次の問題文を表示するためには、配列データのインデックス番号を使うと便利だからです。

    • constで配列名haikuWordsを宣言し、[ ]内に一文づつ「,(カンマ)」で区切りながら文字列を代入します。

    • 今回は全部で9問にしましたが、問題文の追加・削除は自由に行なって下さい。

    //配列に問題文を格納する
    const haikuWords = ["古池や", "蛙飛び込む", "水の音", "柿食えば", "鐘が鳴るなり", "法隆寺", "閑かさや", "岩にしみ入る", "蝉の声"]
    サンプルコード13-5

    問題文の数(配列データの数)を表示する

    配列データの数を取得し、span要素内へ出力します。

    • 配列に格納されているデータの数は、lengthプロパティで取得できます。

    • constで宣言した定数questionsNumに配列データの数を代入してから、サンプルコード13-3で作成したspan要素オブジェクトのnumberOfQuestionstextContentプロパティで出力します。

    //問題文の数を表示
    const questionsNum = haikuWords.length;
    numberOfQuestions.textContent = questionsNum;
    サンプルコード13-6
     (11645)


    クリアした問題数(初期値0)を表示する

    後ほど、クリアした問題数が1問正解する度に増えていく仕組みを作成しますが、この工程では、初期値である0を表示しておくだけにします。

    • letで宣言した変数questionsClearedNumに0を代入します。

    • letで宣言した変数を使う理由は、1問正解する度に1を加算し、値を再代入する必要があるためです。

      • constで宣言した定数には、値を再代入することができません。(「7章 変数と定数を学ぼう」を参照)

    • サンプルコード13-3で作成したspan要素オブジェクトのquestionsClearedtextContentプロパティで変数の中身を出力します。

    //クリアした問題数の初期化と表示
    let questionsClearedNum = 0;
    questionsCleared.textContent = questionsClearedNum;
    サンプルコード13-7
     (11675)


    🟧 3. 得点(初期値0)を表示する

    後ほど、得点が1問正解する度に100点ずつ増えていく仕組みを作成しますが、この工程では「クリアした問題数」と同様に、初期値である0を表示しておくだけにします。

  • letで宣言した変数scoreNumに0を代入します。

  • letで宣言した変数を使う理由は、1問正解する度に100を加算し、値を再代入する必要があるためです。

    • constで宣言した定数には、値を再代入することができません。(「7章 変数と定数を学ぼう」を参照)

  • サンプルコード13-3で作成したspan要素オブジェクトのscoretextContentプロパティで変数の中身を出力します。

  • //得点の初期化と表示
    let scoreNum = 0;
    score.textContent = scoreNum;
    サンプルコード13-8
     (11687)


    🟧 4. 問題欄に1問目を表示する

    配列から最初のデータ(1問目)を取り出し、問題欄のinput要素に表示します。

  • サンプルコード13-5で作成した配列データには、haikuWords[インデックス番号]でアクセスできます。

    • 1問目のデータにはhaikuWords[0]でアクセスできます。

    • 2問目のデータにはhaikuWords[1]でアクセスできます。(「11章 配列を学ぼう」参照)

  • 1問正解する度にインデックス番号に1ずつ加算することにより、次の問題文を表示するプログラムを後ほど書いていきます。

    • そのため、インデックス番号の初期値0をletで宣言した変数indexNumに格納しておくことにより、再代入が可能な状態にしておきます。

  • 問題欄のinput要素オブジェクトであるelemQuestionのvalueプロパティを使い、1つ目の配列データを表示します。

    • その際、elemQuestion.value = haikuWords[indexNum];と記述することにより、初期状態で0にしてある配列のインデックス番号を、1問正解する度に1、2、3、4、5、6・・・・と増やしていくことができます。

    • elemQuestion.value = haikuWords[0];と書いてしまうと、1問目が表示されはしますが、上記の処理ができません。

    //配列のインデックス番号の初期化
    let indexNum = 0;
    
    //1問目を表示
    elemQuestion.value = haikuWords[indexNum];
    サンプルコード13-9
     (11700)


    🟧 5. 解答欄にカーソルを立てる

  • 解答欄のinput要素オブジェクトelemAnswerのfocusメソッドで、カーソルを点滅させます。

  • //解答欄にカーソルを立てる
    elemAnswer.focus();
    サンプルコード13-10
     (11707)


    ここまでに記述したコード全体です。

    //オブジェクト一覧
    const formObj = document.querySelector(".typing-form");
    const elemQuestion = document.querySelector(".elem1");
    const elemAnswer = document.querySelector(".elem2");
    const onemoreBtn = document.querySelector(".onemore-btn");
    const submitBtn = document.querySelector(".submit-btn");
    const questionsCleared = document.querySelector(".number-of-questions-cleared");
    const numberOfQuestions = document.querySelector(".number-of-questions");
    const score = document.querySelector(".score");
    
    //配列に問題文を格納する
    const haikuWords = ["古池や", "蛙飛び込む", "水の音", "柿食えば", "鐘が鳴るなり", "法隆寺", "閑かさや", "岩にしみ入る", "蝉の声"]
    
    //もう一回ボタンを非表示にする
    onemoreBtn.style.display = "none";
    
    //問題文の数を表示
    const questionsNum = haikuWords.length;
    numberOfQuestions.textContent = questionsNum;
    
    //クリアした問題数の初期化と表示
    let questionsClearedNum = 0;
    questionsCleared.textContent = questionsClearedNum;
    
    //得点の初期化と表示
    let scoreNum = 0;
    score.textContent = scoreNum;
    
    //配列のインデックス番号の初期化
    let indexNum = 0;
    
    //1問目を表示
    elemQuestion.value = haikuWords[indexNum];
    
    //解答欄にカーソルを立てる
    elemAnswer.focus();
    typing-game_start/typing.js サンプルコード13-12
     (11717)


    【13-4】データ送信時の処理を作成しよう

    初期画面の表示が完成したら、データ送信時のイベント処理を作成します。

    作成する項目が多いですが、順番に片づけていきます。


    データ送信時のイベント処理として作成する項目は、以下のとおりです。

    ✔️ データ送信時のイベント処理項目

  • データ送信時の処理

    • 1. 解答欄に入力されたテキストを取得する

      2. 現在表示されている問題文と解答が同一かどうかを判別する

        
        a. 同一だった場合、次の問題文を表示し、解答欄を空欄にして、カーソルを立てる

        b. 空欄または問題文と解答欄が不一致だった場合、問題文はそのままで、解答欄を空欄にして、カーソルを立てる

        c. a.とb.どちらの場合もイベントをキャンセルする(送信中止)

      3. 1問正解する度に、得点に100点加算される

      4. 1問正解する度に、問題をクリアした数がカウントされる

      5. 全問クリアしたとき

        a. アラートでメッセージを出す(「全問正解おめでとう! ボーナスポイント100点追加!」)

        b. ボーナスポイントを100点追加する

        c. 1問目を再表示する

        d. 送信ボタンを無効にする

        e. もう1回ボタンを表示する

      6. Enter(return)キーを押したときと離したときに送信ボタンの色を変化させる


    submitイベントを設定する

  • データ送信時の処理を行うためには、submitイベントを設定します。

    • submitイベントは、フォームのデータ送信時(送信ボタンをクリックしたとき)に発生するイベントです。

    • submitイベントは、form要素に設定します。

    送信ボタンを使う理由
    • 今回作成するタイピングゲームにおいて、ユーザーが入力した解答が問題文と一致するかどうかを判別するタイミングを、submitイベント発生時にした理由は次のとおりです。

      • 送信ボタン<input type=”submit”>は、ボタンを直接クリックする以外にも、Enterキー(returnキー)の押下でもsubmitイベントが感知されるため、ゲームの操作がしやすい。

      • 通常のボタン<input type=”button”>、<button></button>は、Enterキー(returnキー)の押下では反応しない。


    formObj.addEventListener("submit", function (e) {
    //ここにsubmitイベント発生時の処理を作成する
    }
    サンプルコード13-13
  • formObjはサンプルコード13-3で作成したform要素オブジェクトです。

  • 関数の引数にはイベントオブジェクト名のeを記述しておきます。

    • イベントオブジェクトのpreventDefaultメソッドを使用した処理を作成するため、引数にeを記述しておきます。


    🟧 1. 解答欄に入力されたテキストを取得する

    問題文と解答が一致するかどうかを判別するためには、まずユーザーが入力した文章をテキストフォームから取得する必要があります。

  • サンプルコード13-3で作成した解答欄のinput要素オブジェクトであるelemAnswerのvalueプロパティを使い、入力内容を取得し、定数inputAnswerに代入します。

  • input要素オブジェクトのvalueプロパティは、入力内容の表示と取得のどちらも行うことが可能なプロパティです。

    • 今回は入力内容の取得のために使用しています。

    //解答欄に入力されたテキストを取得
      const inputAnswer = elemAnswer.value;
    サンプルコード13-10

    🟧 2. 現在表示されている問題文と解答が同一かどうかを判別する

  • 1.で取得した解答のテキストが、現在表示されている問題文と一致するかどうかを、if文で判別します。

  • if文で行う処理内容は次の3つです。

  • ✔️ if文で行う処理内容

    a. 同一だった場合、次の問題文を表示し、解答欄を空欄にして、カーソルを立てる

    b. 空欄または問題文と解と不一致だった場合、問題文はそのままで、解答欄を空欄にして、カーソルを立てる

    c. 1.と2.どちらの場合もイベントをキャンセルする(送信中止)


    条件式

    条件式は、inputAnswer == haikuWords[indexNum]となります。

    • inputAnswerが解答欄に入力されたテキスト、 haikuWords[indexNum]で配列データにアクセスします。

    • indexNumには、はじめ0が代入されていますので、最初の送信イベント発生時には、1問目のテキストが、==演算子により、解答文と等しいかどうかが判別されます。

    if (inputAnswer == haikuWords[indexNum]) {
    //条件式がtrueの場合の処理
    }
    サンプルコード13-11

    a. 同一だった場合、次の問題文を表示し、解答欄を空欄にして、カーソルを立てる

    問題欄と解答欄のテキストが同一だった場合の処理を作成します。

    • 次の問題文を表示する

      • ページロード時の変数indexNumには0が格納されていますので、elemQuestion.value = haikuWords[0];が実行され、1問目が表示されます。

        • 2問目を表示するためには、elemQuestion.value = haikuWords[1];となればよいので、indexNumに1を加算します。

      • 正解する度に、indexNumに1ずつ加算していけば、配列データの順番で問題文が切り替わっていきます。

        • indexNumに1ずつ加算」をインクリメント演算子++を使い、indexNum++;と表します。(「7章 変数と定数を学ぼう」参照)

      • 変数の中身が変化しただけでは、テキストフォームの表示は変わりません。

        • 次の行で、elemQuestion.value = haikuWords[indexNum];と記述し、再度問題欄に配列データを表示させます。

    • 解答欄を空欄にして、カーソルを立てる

      • 「解答欄を空欄にする」は、テキストフォームのvalueプロパティを空の文字列にするということですので、elemAnswer.value = "";となります。

      • 「テキストフォームにカーソルを立てる」はfocusメソッドを使い、elemAnswer.focus();となります。

    if (inputAnswer == haikuWords[indexNum]) {
      //条件式がtrueだった場合、次の問題文を表示する
      indexNum++;
      elemQuestion.value = haikuWords[indexNum];
      //解答欄を空欄にして、カーソルを立てる
      elemAnswer.value = "";
      elemAnswer.focus();
    }
    サンプルコード13-12

    b. 空欄または問題文と解答欄が不一致だった場合、問題文はそのままで、解答欄を空欄にして、カーソルを立てる

    • 「問題文と解答が同一だった場合」以外は、空欄または不一致の場合となります。

    • したがって、b.の条件は、サンプルコード13-12のif文以降にelse文を追加することにより表すことができます。

    if (inputAnswer == haikuWords[indexNum]) {
      //条件式がtrueだった場合、次の問題文を表示する
      indexNum++;
      elemQuestion.value = haikuWords[indexNum];
      //解答欄を空欄にする
      elemAnswer.value = "";
      //解答欄にカーソルを立てる
      elemAnswer.focus();
    } else {
      //条件式がfalseの場合(空欄・問題文と不一致)
      //解答欄を空欄にする
      elemAnswer.value = "";
      //解答欄にカーソルを立てる
      elemAnswer.focus();
    }
    サンプルコード13-14

    c. a.とb.どちらの場合もイベントをキャンセルする(送信中止)

    • 最後に、a.とb.どちらの場合もイベントをキャンセルすることにより、データの送信を中止します。

      • データの送信を中止しないと、HTMLの仕様でページが更新され、初期表示に戻ってしまいます

    • if〜else文のブロック{}の外側で最後にイベントオブジェクトのpreventDefaultメソッドを参照すれば、submitイベント発生時に必ずデータの送信が中止されます。

    • 最後の問題文(配列データ)が表示された後も変数indexNumには1が加算されます。

      • その結果、配列内には存在しないhaikuWords[9]を参照し、問題欄に表示しようとするため、9問目の次はエラーメッセージであるundefined(未定義値)が表示されます。

      • この不具合は後ほど解決していきます。

    formObj.addEventListener("submit", function (e) {
    
    ・・・中略・・・
    
    if (inputAnswer == haikuWords[indexNum]) {
      //条件式がtrueだった場合、次の問題文を表示する
      indexNum++;
      elemQuestion.value = haikuWords[indexNum];
      //解答欄を空欄にする
      elemAnswer.value = "";
      //解答欄にカーソルを立てる
      elemAnswer.focus();
    } else {
      //条件式がfalseの場合(空欄・問題文と不一致)
      //解答欄を空欄にする
      elemAnswer.value = "";
      //解答欄にカーソルを立てる
      elemAnswer.focus();
    }
    //イベントキャンセル
    e.preventDefault();
    
    });
    サンプルコード13-15

    🟧 3. 1問正解する度に、得点に100点加算される

    • サンプルコード13-8の変数scoreNumが得点を格納するための変数です。

    • 変数scoreNumには、ページロード時の得点の初期値として0が代入されています。

    • この変数scoreNumに100加算する処理は、scoreNum += 100;です。

      • この処理を、if文の条件式がtrueだった場合に実行します。

    • 次に、score.textContent = scoreNum;を記述し、加算処理後の変数の中身をspan要素オブジェクトであるscore内にtextContentプロパティで出力します。

    //得点の初期化と表示
    let scoreNum = 0;
    score.textContent = scoreNum;
    サンプルコード13-8(再掲)
    if (inputAnswer == haikuWords[indexNum]) {
      //条件式がtrueだった場合、次の問題文を表示する
      indexNum++;
      elemQuestion.value = haikuWords[indexNum];
      //解答欄を空欄にする
      elemAnswer.value = "";
      //解答欄にカーソルを立てる
      elemAnswer.focus();
      //1問正解する度に100点加算される
      scoreNum += 100;
      score.textContent = scoreNum;
    } else {
      //条件式がfalseの場合(空欄・問題文と不一致)
      //解答欄を空欄にする
      elemAnswer.value = "";
      //解答欄にカーソルを立てる
      elemAnswer.focus();
    }
    サンプルコード13-16

    🟧 4. 1問正解する度に、問題をクリアした数がカウントされる

    • サンプルコード13-7の変数questionsClearedNumがクリアした問題数を格納するための変数です。

    • 変数questionsClearedNumには、ページロード時のクリアした問題数の初期値として0が代入されています。

    • この変数questionsClearedNumに1加算する処理は、インクリメント演算子++を使い、questionsClearedNum++;で表すことができます。

      • この処理を、if文の条件式がtrueだった場合に実行します。

    • 次に、questionsCleared.textContent = questionsClearedNum;を記述し、加算処理後の変数の中身をspan要素オブジェクトであるquestionsCleared内にtextContentプロパティで出力します。

    //クリアした問題数の初期化と表示
    let questionsClearedNum = 0;
    questionsCleared.textContent = questionsClearedNum;
    サンプルコード13-7(再掲)
    if (inputAnswer == haikuWords[indexNum]) {
      //条件式がtrueだった場合、次の問題文を表示する
      indexNum++;
      elemQuestion.value = haikuWords[indexNum];
      //解答欄を空欄にする
      elemAnswer.value = "";
      //解答欄にカーソルを立てる
      elemAnswer.focus();
      //1問正解する度に100点加算される
      scoreNum += 100;
      score.textContent = scoreNum;
      //1問正解する度に問題をクリアした数がカウントされる
      questionsClearedNum++;
      questionsCleared.textContent = questionsClearedNum;
    } else {
      //条件式がfalseの場合(空欄・問題文と不一致)
      //解答欄を空欄にする
      elemAnswer.value = "";
      //解答欄にカーソルを立てる
      elemAnswer.focus();
    }
    サンプルコード13-17

    🟧 5. 全問クリアしたとき

    続いて、全問クリアしたときの処理を作成していきます。

    以下のa〜eまでの処理を順番に作成します。


    ✔️ 全問クリアしたときの処理内容

    a. アラートでメッセージを出す(「全問正解おめでとう! ボーナスポイント100点追加!」)

    b. ボーナスポイントを100点追加する

    c. 1問目を再表示する

    d. 送信ボタンを無効にする

    e. もう1回ボタンを表示する


    まずは、全問クリアしたかどうかを判別するための条件分岐を記述します。

    if文の条件式と、そのif文をどこに記述するかが重要です。


    if文は入れ子構造にできる

    全問クリアしたときの処理内容はどこに書けばよいでしょうか?

  • クリアした問題の数と問題数が同じになったタイミングが、全問クリアしたときになります。

  • したがって、if文は以下のようになります。

    • 変数questionsClearedNumにはクリアした問題の数が入っており、定数questionsNumには問題数が入っていますので、両者が等しいかどうかを判別する演算子==を使い以下の条件式を記述します。

    if (questionsClearedNum == questionsNum) {
      //全問クリアしたときの処理
    }
    サンプルコード13-18
    このif文を、以下のように、問題文と解答が同一かどうかを判別するif文の{ }内に入れ子にして記述すると、目的の挙動を実現することができます。

    if (inputAnswer == haikuWords[indexNum]) {
    ・・・中略・・・
      if (questionsClearedNum == questionsNum) {
        //全問クリアしたときの処理
      }
    }
    サンプルコード13-19
  • このように、if文を入れ子構造にすることをネストするといいます。

  • 親のif文の条件式がtrueである間に、子のif文の条件式がtrueになると、ネストされた方の処理が実行されるというしくみになっています。


  • a. アラートでメッセージを出す(「全問正解おめでとう! ボーナスポイント100点追加!」)

    • 入れ子にしたif文の{ }内に、全クリアしたときの処理内容を書いていきます。

    • alertメソッドでメッセージを出力します。

    if (inputAnswer == haikuWords[indexNum]) {
    ・・・中略・・・
      if (questionsClearedNum == questionsNum) {
       //全問クリアしたときの処理
        //alertメソッドでメッセージを出力
                    alert("全問正解おめでとう! ボーナスポイント100点追加!");
      }
    }
    サンプルコード13-20

    b. ボーナスポイントを100点追加する

    • 続いて、得点にボーナスポイントを100点追加する処理を加えます。

    • ここまでに獲得した得点は、変数scoreNumに入っていますから、

    • scoreNum += 100;で100を加算した後、span要素オブジェクトである定数scoreのtextContentプロパティを使って、最終的な得点を出力します。

    if (inputAnswer == haikuWords[indexNum]) {
    ・・・中略・・・
      if (questionsClearedNum == questionsNum) {
       //全問クリアしたときの処理
        //alertメソッドでメッセージを出力
                    alert("全問正解おめでとう! ボーナスポイント100点追加!");
                    //ボーナスポイントを100点追加する
                    scoreNum += 100;
        score.textContent = scoreNum;
      }
    }
    サンプルコード13-21

    c. 1問目を再表示する

    • ゲームが終了したしるしとして、問題欄には1問目を再表示します。

    • 配列のインデックス番号が格納されている変数indexNum0を再代入してから、input要素オブジェクトである定数elemQuestionのvalueプロパティに配列データhaikuWords[indexNum]を=でセットすれば、1問目が再表示されます。

    if (inputAnswer == haikuWords[indexNum]) {
    ・・・中略・・・
      if (questionsClearedNum == questionsNum) {
       //全問クリアしたときの処理
        //alertメソッドでメッセージを出力
                    alert("全問正解おめでとう! ボーナスポイント100点追加!");
                    //ボーナスポイントを100点追加する
                    scoreNum += 100;
        score.textContent = scoreNum;
        //1問目を再表示
        indexNum = 0;
        elemQuestion.value = haikuWords[indexNum];
      }
    }
    サンプルコード13-22

    d. 送信ボタンを無効にする

    • このままでは、タイピングゲームをさらに続けていくことができ、正解するたびに得点とクリアした問題数が加算されてしまいます。

    • そこで、送信ボタンを無効にすることにより、イベント処理が実行されないようにします。

    • フォームのボタン要素オブジェクトは、無効状態を指定するためのdisabledというプロパティを持っています。このプロパティの値は初期設定がfalseとなり、有効の状態になっています。

      • これを無効にするには、trueをセットします。

    if (inputAnswer == haikuWords[indexNum]) {
    ・・・中略・・・
      if (questionsClearedNum == questionsNum) {
       //全問クリアしたときの処理
        //alertメソッドでメッセージを出力
                    alert("全問正解おめでとう! ボーナスポイント100点追加!");
                    //ボーナスポイントを100点追加する
                    scoreNum += 100;
        score.textContent = scoreNum;
        //1問目を再表示
        indexNum = 0;
        elemQuestion.value = haikuWords[indexNum];
        //送信ボタンを無効にする
        submitBtn.disabled = true;
      }
    }
    サンプルコード13-23

    e. もう1回ボタンを表示する

    • タイピングゲームを初期画面に戻すための「もう一回ボタン」を表示させます。

    • サンプルコード13-4で非表示にした「もう1回ボタン」を表示するためには、CSSのdisplayプロパティの値をblockに設定すればよいので、onemoreBtnオブジェクトの下位オブジェクトのstyleオブジェクトにアクセスし、onemoreBtn.style.display = "block";と記述します。

    if (inputAnswer == haikuWords[indexNum]) {
    ・・・中略・・・
      if (questionsClearedNum == questionsNum) {
       //全問クリアしたときの処理
        //alertメソッドでメッセージを出力
                    alert("全問正解おめでとう! ボーナスポイント100点追加!");
                    //ボーナスポイントを100点追加する
                    scoreNum += 100;
        score.textContent = scoreNum;
        //1問目を再表示
        indexNum = 0;
        elemQuestion.value = haikuWords[indexNum];
        //送信ボタンを無効にする
        submitBtn.disabled = true;
        //もう一回ボタンを表示する
        onemoreBtn.style.display = "block";
      }
    }
    サンプルコード13-24

    【13-5】もう1回ボタンクリック時の処理を作成しよう

    • もう1回ボタンクリック時に、初期画面の状態に戻します。

    • 簡単な方法としては、ページをリロード(更新)すれば、初期画面に戻りますので、今回はその方法を採用します。

    • ブラウザオブジェクトのlocationオブジェクトが持っているreloadメソッドを使うと、ページをリロードすることができます。

    • もう1回ボタンにclickイベントを設定し、location.reload();と記述すれば完了です。

    //もう一回ボタンクリック時にページを更新する
    onemoreBtn.addEventListener("click", function () {
      location.reload();
    })
    サンプルコード13-25

    【13-6】Enter(return)キーを押したときと離したときの処理を作成しよう

      今回作成しているタイピングゲームのサンプルデータは、プレビュー動画でキーボードのEnter(return)キーを押している様子を見せることができないので、押したタイミングを視覚化するために、keydownイベントとkeyupイベント発生時に、「解答を送信する」ボタンの背景色を変化させています。

     (11970)

      if文の条件分岐でEnter(return)キーが押されたかどうかを判別する

    • イベントオブジェクトのkeyプロパティを使うと、押したキーに割り当てられた文字列を返します。

    • 例えば、

      • Enterキーの場合は”Enter”、
      • 数字の1の場合は”1”、
      • 右の方向キーの場合は”ArrowRight”をそれぞれ返します。
      • if文の条件式をe.key == "Enter"と記述すれば、Enter(return)キーが押された場合にtrueが返ってきます。

      • 各キーに割り当てられた文字列は、下記のページで調べることができます。

      • JavaScript Key Code

    • keydown、keyupイベントは共にdocumentオブジェクトに設定します。

      • documentはページ全体のDOMにアクセスできるため、キーボード関連のイベントがページ全体に対して確実に反応します。

    // enter(return) キーを押したときに送信ボタンの色変化
    document.addEventListener("keydown", function (e) {
      if (e.key == "Enter") {
        submitBtn.style.backgroundColor = "#4fe1e4";
      }
    })
    
    document.addEventListener("keyup", function (e) {
      if (e.key == "Enter") {
        submitBtn.style.backgroundColor = "#00bec1";
      }
    })
    サンプルコード13-26

    【13-7】完成

    完成したコード全体です。


    <body>
      <!-- 問題数とクリアした数 -->
      <div class="clear">
        <span class="number-of-questions-cleared"></span>/<span
          class="number-of-questions"
        ></span>
      </div>
    
      <!-- 得点 -->
      <div class="score-box"><span class="score"></span>点</div>
    
      <div class="container">
        <h1>タイピングゲーム</h1>
        <h2>俳句で練習しよう</h2>
        <form class="typing-form">
          <!-- 問題文 -->
          <input type="text" class="elem-text elem1" />
          <!-- 解答欄 -->
          <input type="text" class="elem-text elem2" />
          <!-- 送信ボタン -->
          <input type="submit" value="解答を送信する" class="submit-btn" />
        </form>
        <!-- もう一回ボタン -->
        <button class="onemore-btn">もう1回!</button>
      </div>
    
      <script src="js/typing.js"></script>
    </body>
    typing.html サンプルコード13-27
    //オブジェクト一覧
    const formObj = document.querySelector(".typing-form");
    const elemQuestion = document.querySelector(".elem1");
    const elemAnswer = document.querySelector(".elem2");
    const onemoreBtn = document.querySelector(".onemore-btn");
    const submitBtn = document.querySelector(".submit-btn");
    const questionsCleared = document.querySelector(".number-of-questions-cleared");
    const numberOfQuestions = document.querySelector(".number-of-questions");
    const score = document.querySelector(".score");
    
    //配列に問題文を格納する
    const haikuWords = [
      "古池や",
      "蛙飛び込む",
      "水の音",
      "柿食えば",
      "鐘が鳴るなり",
      "法隆寺",
      "閑かさや",
      "岩にしみ入る",
      "蝉の声",
    ];
    
    //もう一回ボタンを非表示にする
    onemoreBtn.style.display = "none";
    
    //問題文の数を表示
    const questionsNum = haikuWords.length;
    numberOfQuestions.textContent = questionsNum;
    
    //クリアした問題数の初期化と表示
    let questionsClearedNum = 0;
    questionsCleared.textContent = questionsClearedNum;
    
    //得点の初期化と表示
    let scoreNum = 0;
    score.textContent = scoreNum;
    
    //配列のインデックス番号の初期化
    let indexNum = 0;
    
    //1問目を表示
    elemQuestion.value = haikuWords[indexNum];
    
    //解答欄にカーソルを立てる
    elemAnswer.focus();
    
    //送信イベントの設定
    formObj.addEventListener("submit", function (e) {
      //解答欄に入力されたテキストを取得
      const inputAnswer = elemAnswer.value;
      //if文で現在表示されている問題文と同一かどうか判別する
      if (inputAnswer == haikuWords[indexNum]) {
        //条件式がtrueだった場合、次の問題文を表示する
        indexNum++;
        elemQuestion.value = haikuWords[indexNum];
        //解答欄を空欄にして、カーソルを立てる
        elemAnswer.value = "";
        elemAnswer.focus();
        //1問正解する度に100点加算される
        scoreNum += 100;
        score.textContent = scoreNum;
        //1問正解する度に問題をクリアした数がカウントされる
        questionsClearedNum++;
        questionsCleared.textContent = questionsClearedNum;
    
        //全問クリアしたらアラートでメッセージを出す
        //ボーナスポイント100点追加する
        if (questionsClearedNum == questionsNum) {
          //全問クリアしたときの処理
          //alertメソッドでメッセージを出力
          alert("全問正解おめでとう! ボーナスポイント100点追加!");
          scoreNum += 100;
          score.textContent = scoreNum;
          //1問目を再表示
          indexNum = 0;
          elemQuestion.value = haikuWords[indexNum];
          //送信ボタンを無効にする
          submitBtn.disabled = true;
          //もう一回ボタンを表示する
          onemoreBtn.style.display = "block";
        }
      } else {
        //条件式がfalseの場合(空欄・問題文と不一致)
        elemAnswer.value = "";
        elemAnswer.focus();
      }
      //イベントキャンセル
      e.preventDefault();
    });
    
    //もう一回ボタンクリック時にページを更新する
    onemoreBtn.addEventListener("click", function () {
      location.reload();
    });
    
    // enter(return) キーを押したときに送信ボタンの色変化
    document.addEventListener("keydown", function (e) {
      if (e.key == "Enter") {
        submitBtn.style.backgroundColor = "#4fe1e4";
      }
    });
    
    document.addEventListener("keyup", function (e) {
      if (e.key == "Enter") {
        submitBtn.style.backgroundColor = "#00bec1";
      }
    });
    typing.js サンプルコード13-28

    これで「13章:まとめの演習2 タイピングゲームをつくろう」の解説を終わります。

    最後にJavaScript課題「DOMの操作」に取り組みましょう。

    WEBCOACH | キャリアチェンジまでの全てを学ぶマンツーマンWEBスクール
    © 2020 by WEBCOACH