JSで作る、'数字当てゲーム'『NumerOn』

#はじめに
![スクリーンショット 2020-10-24 11.27.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/536974/b7846335-3995-de44-74c6-a45a7c9246ae.png)

・NumerOnのルールについては[こちら](https://baby-0105.github.io/JS_numeron/html/rule.html)でご確認ください。

・今回、実際に作成したNumerOnの完成版は[こちら](https://baby-0105.github.io/JS_numeron/html/home.html)

・jQueryなどのライブラリ一切使わず、純粋なJavaScriptのみ作成しました。

・自分自身で復習という意味も込めて、コードの説明を順々に行っていきます。
是非参考にしていただければと思います。

#完成した、全体のコード

全体のコードといっても、ゲーム進行ページ全体のコードです。

```numeron.html
<head>
  <meta charset="utf-8" />
  <title>JS_numeron_main</title>
  <meta name="description" content="JS_numeron_TOP" />
  <link rel="stylesheet" href="../css/styles.css" />
</head>

<body>
  <div class="three_number_wrap">
    <div class="number_wrap">
      <div class="my_number">
        <p class="my_number_text">my number</p>
        <p class="my_num" id="my_num"></p>
      </div>
    </div>
    <div class="numeron-form">
      <input type="text" maxlength='3' id="input_num" value="">
      <input type="submit" id='numeron_btn' value="numeron!">
    </div>
    <div class="eatBite_wrap" id="eat_bite"></div>
    <ul class="" id="result_block"></ul>
  </div>

  <script src="../js/numeron.js"></script>
</body>
```

```numeron.js
{
  'use strict';

  // ランダムな三桁の数字
  let makeRivalNumArray = (function() {
    let rivalNumArray = [];

    return function() {
      //　配列の長さが3になるまで繰り返す。
      for (let i = 0; rivalNumArray.length < 3; i++) {
        const randNum = Math.floor(Math.random() * 10) // 0~9までのランダムな整数

        if(!rivalNumArray.includes(randNum)){ // 一度出た数字がなければ、配列に数字を足す
          rivalNumArray.push(randNum);
        }
      }

      return rivalNumArray;
    }
  })();

  let rivalNumText = makeRivalNumArray().join('');
  console.log(rivalNumText);

  // eat（場所も数字も同じ数）
  function eat(myNumArray) { // eatは変化する値なので、const × → let
    let eatNum = 0;

    for(let i=0; i < myNumArray.length; i++) { // eatの数
      if (myNumArray[i] === makeRivalNumArray()[i]) {
        eatNum++;
      }
    }

    return eatNum;
  }

  // bite（数字のみ同じ数）
  function bite(myNumArray) {
    let EatBiteArray = makeRivalNumArray().concat(myNumArray), // 2つの配列を足した配列

        // 2つの配列の重複した要素を、重複せずに取り出したもの
        biteArray = EatBiteArray.filter(function(val, i, arr) {
                      // arr.indexOf(val)：配列の中でその要素が最初に現れるindex番号の取得
                      return arr.indexOf(val) === i && i !== arr.lastIndexOf(val);
                    });

    return biteArray.length - eat(myNumArray);
  }

  // その時々の結果の描画
  function writeResultText(myNum, myNumArray, gameNum) { // 引数の順番は大事！
    const result_block   = document.getElementById('result_block'),
          eatBite        = document.getElementById('eat_bite'),
          li             = document.createElement('li');

    let writeEatNum     = eat(myNumArray),
        writeBiteNum    = bite(myNumArray),
        writeEatBiteNum = `${writeEatNum}EAT ${writeBiteNum}BITE`;

    li.textContent = `${gameNum}回目：${myNum} → ${writeEatBiteNum}`;
    result_block.appendChild(li);

    eatBite.textContent = writeEatBiteNum; // ?eat ?bite 描画（main）
  }

  // 数字が重複していないかのチェック
  function checkDuplicate(inputNum) {
    let newInputNum = new Set(inputNum);
    return newInputNum.size === inputNum.length
  }

  function advanceGame(gameNum) {
    const input = document.getElementById('input_num'),
          inputNum = input.value.replace(/[^\d-.]/g,''); // 数字であれば値を返す（replace(/[^\d-.]/g,'')）

    // 3文字以内、かつ、異なる数字→テキスト表示 / そうでないなら、ゲーム進まない
    if(inputNum.length === 3 && checkDuplicate(inputNum)) {
      let myNumArray = [], // 選んだ数字の定義
          my_num = document.getElementById('my_num');

      my_num.innerHTML = inputNum; // my numberの欄に打った数字表示
      myNumArray = my_num.innerHTML.split('').map(str => parseInt(str, 10)); // my_numの数字の文字列をデータ型numberとして配列に格納

      setTimeout(function() {
        writeResultText(my_num.innerHTML, myNumArray, gameNum); // その時々の結果の描画

        // 3eat 0biteになった時
        if(eat(myNumArray) === 3) {
          setTimeout(function() {
            alert(`NumerOn! 相手の数字は、'${rivalNumText}'でした！`);
            location.reload(); // ゲームリセット（ページの再読み込み）
          }, 1000);
        }
      }, 1000);
    } else {
      alert('※3ケタの異なる数字（半角）で入力してください!');
    }

    input.value = ''; // ボタンクリック後、inputの中をカラに。
  }

  let btnClick = (function() {
    let gameNum = 0; // ゲーム回数

    return function() {
      gameNum++; // ゲーム回数のカウント

      advanceGame(gameNum);
    }
  })();


  document.getElementById('numeron_btn').addEventListener('click', function() {
    btnClick();
  });
}
```

順に、説明していきます。

#コードの説明

##①ランダムな三桁の数字

```numeron.js
  // ランダムな三桁の数字
  let makeRivalNumArray = (function() {
    let rivalNumArray = [];

    return function() {
      //　配列の長さが3になるまで繰り返す。
      for (let i = 0; rivalNumArray.length < 3; i++) {
        const randNum = Math.floor(Math.random() * 10) // 0~9までのランダムな整数

        if(!rivalNumArray.includes(randNum)){ // 一度出た数字がなければ、配列に数字を足す
          rivalNumArray.push(randNum);
        }
      }

      return rivalNumArray;
    }
  })();

  let rivalNumText = makeRivalNumArray().join('');
  console.log(rivalNumText);
```

この部分でもそうですが、グローバル変数をできるだけ使わないために**「クロージャー」**を使用しています。

クロージャーが分からない人は[こちらの記事](https://wemo.tech/904)を参考にしてみてください。

```const randNum = Math.floor(Math.random() * 10)```：Math.random() * 10で0~10までのランダムな数字を生成。
Math.floor()で、その数値を整数へと変換し。```変数randNum```へと代入しています。

##②場所も数字も同じ数：eat

```numeron.js
// eat（場所も数字も同じ数）
function eat(myNumArray) {
  // eatは変化する値なので、const × → let
  let eatNum = 0;

  for(let i=0; i < myNumArray.length; i++) { // eatの数
    if (myNumArray[i] === makeRivalNumArray()[i]) {
      eatNum++;
    }
  }

  return eatNum;
}
```

・```引数myNumArray```には、別関数でプレイヤーが入力した数字の配列が代入されます。

```myNumArray[i] === makeRivalNumArray()[i]```：プレイヤーが選んだ数字の配列と、隠された数字の配列の[i]番目の数字がそれぞれ一致するか比較しています。

比較をmyNumArrayの配列の長さの文だけループを回しています。

##③場所は違うが、数字が同じ数：bite

```numeron.js
function bite(myNumArray) {
  let EatBiteArray = makeRivalNumArray().concat(myNumArray), // 2つの配列を足した配列

      // 2つの配列の重複した要素を、重複せずに取り出したもの
      biteArray = EatBiteArray.filter(function(val, i, arr) {
        // arr.indexOf(val)：配列の中でその要素が最初に現れるindex番号の取得
        return arr.indexOf(val) === i && i !== arr.lastIndexOf(val);
      });

  return biteArray.length - eat(myNumArray);
}
```

・```引数myNumArray```には、別関数でプレイヤーが入力した数字の配列が代入されます。

```let EatBiteArray = makeRivalNumArray().concat(myNumArray)```：concat()ですが、2つ以上の配列を結合させるためのメソッドです。

結合した新たな配列を```変数EatBiteArray```へと代入しています。

続いて、

```numeron.js
// 2つの配列の重複した要素を、重複せずに取り出したもの
biteArray = EatBiteArray.filter(function(val, i, arr) {
  // arr.indexOf(val)：配列の中でその要素が最初に現れるindex番号の取得
  return arr.indexOf(val) === i && i !== arr.lastIndexOf(val);
});
```

この部分のコードですが、

filter()メソッドでは、条件を満たす要素で新たな配列を作成するメソッドです。

その条件は、()の中の```(val, i, arr)```この部分です。

・```val```：コールバック関数の各要素
・```i```：要素のindex
・```arr```：array自身

上記のコードではあまりにも伝わりにくいので

####【例】filter()を噛み砕いて説明

```sample.js
let array = [0, 2, 2, 3, 4, 4, 4, 5];

// 重複を削除した配列
let array = [0, 2, 2, 3, 4, 4, 4, 5];

// 重複を削除した配列
let result = array.filter(function(val, i, arr) {
        // console.log(arr); // [0, 2, 2, 3, 4, 4, 4, 5]
        // console.log(arr.indexOf(val)); // 0, 1, 1, 3, 4, 4, 4, 7
        // console.log(arr.lastIndexOf(val)); // 0, 2, 2, 3, 6, 6, 6, 7
        // console.log(arr.indexOf(val) === i); // true true false true true false false true
        // console.log(i !== arr.lastIndexOf(val)); // false true false false true ture false false
        return arr.indexOf(val) === i && i !== arr.lastIndexOf(val);
    });

console.log(result); // [2, 4]
```

```indexOf()``` メソッド：**指定された値が最初に現れたインデックスを返します。**

```lastIndexOf() メソッド```：逆方向に検索し、指定された値が最後に現れるインデックスを返します。
>[MDN( lastIndexOf() )](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/String/lastIndexOf)

上記の例で言えば、

```sample.js
console.log(arr.indexOf(val)); // 0, 1, 1, 3, 4, 4, 4, 7
```

となるため、

```sample.js
console.log(arr.indexOf(val) === i); // true true false true true false false true
```

で、重複する値を削除した配列を返しています。

続いて、こちらも同じく「逆方向に検索」おこなって

```sample.js
 console.log(arr.lastIndexOf(val)); // 0, 2, 2, 3, 6, 6, 6, 7
```

```sample.js
console.log(i !== arr.lastIndexOf(val)); // false true false false true ture false false
```

```numeron.js
arr.indexOf(val) === i && i !== arr.lastIndexOf(val);
```

#④結果の描画
```numeron.js
  // その時々の結果の描画
  function writeResultText(myNum, myNumArray, gameNum) { // 引数の順番は大事！
    const result_block   = document.getElementById('result_block'),
          eatBite        = document.getElementById('eat_bite'),
          li             = document.createElement('li');

    let writeEatNum     = eat(myNumArray),
        writeBiteNum    = bite(myNumArray),
        writeEatBiteNum = `${writeEatNum}EAT ${writeBiteNum}BITE`;

    li.textContent = `${gameNum}回目：${myNum} → ${writeEatBiteNum}`;
    result_block.appendChild(li);

    eatBite.textContent = writeEatBiteNum; // ?eat ?bite 描画（main）
  }
```

・```result_block.appendChild(li);```でゲーム回数を積み重ねるごとに結果である<li>要素を子ノードとして追加していきます。

#⑤入力した数字が重複していないかのチェック

```numeron.js
  function checkDuplicate(inputNum) {
    let newInputNum = new Set(inputNum);
    return newInputNum.size === inputNum.length
  }
```

```new Set(inputNum)```：**Setオブジェクトでは重複した要素を格納できません。**
[MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Set)

つまり、重複した要素を弾くことができます。

inputNum（入力された文字）は3桁の正の整数が入りますが、重複した数字の場合ははじきます。（3桁がどうかのチェックはこの後です）

ここで、異なる数字を返すことができればOKです。

ですが、次が複雑です。

sizeとlengthでチェック？このコードについて説明します。

```return newInputNum.size === inputNum.length```

・```newInputNum.size```：sizeプロパティは、Set オブジェクト内の要素の数を返します。

・```inputNum.length```:lengthプロパティは配列の最後のインデックスに1加えた値を取得することができます。

詳しくは↓を参考に
[配列の要素の数を取得する](https://www.javadrive.jp/javascript/array/index6.html)

この後に出てきますが、```inputNum```には3桁の数字しかtrueにならない条件分岐をおこないます。

つまり、```newInputNum.size === 3```となるような値を返すため、結果として**重複しない3桁の正の整数**が出来上がります。

[JavaScriptで配列の重複チェックを超簡単＆スマートにやる方法](https://pisuke-code.com/js-check-duplicated-array-values/)

#⑥ゲーム進行の関数

```numeron.js
function advanceGame(gameNum) {
    const input = document.getElementById('input_num'),
          inputNum = input.value.replace(/[^\d-.]/g,'');

    // 3文字以内、かつ、異なる数字→テキスト表示 / そうでないなら、ゲーム進まない
    if(inputNum.length === 3 && checkDuplicate(inputNum)) {
      let myNumArray = [],
          my_num = document.getElementById('my_num');

      my_num.innerHTML = inputNum; // my numberの欄に打った数字表示
      myNumArray = my_num.innerHTML.split('').map(str => parseInt(str, 10));

      setTimeout(function() {
        writeResultText(my_num.innerHTML, myNumArray, gameNum); // その時々の結果の描画

        // 3eat 0biteになった時
        if(eat(myNumArray) === 3) {
          setTimeout(function() {
            alert(`NumerOn! 相手の数字は、'${rivalNumText}'でした！`);
            location.reload(); // ゲームリセット（ページの再読み込み）
          }, 1000);
        }
      }, 1000);
    } else {
      alert('※3ケタの異なる数字（半角）で入力してください!');
    }

    input.value = ''; // ボタンクリック後、inputの中をカラに。
  }
```

3行目：```input.value.replace(/[^\d-.]/g,'');```：数字であれば値を返す

・```replace(/[^\d-.]/g, '')```では、第一引数を第二引数へ置き換えています。

```inputNum.length === 3 && checkDuplicate(inputNum)```：入力された文字が3文字以内、かつ、異なる数字ならばtrue

```myNumArray = []```：ここに、選んだ数字を格納します。

```myNumArray = my_num.innerHTML.split('').map(str => parseInt(str, 10));```：my_numの数字の文字列をデータ型numberとして配列に格納しています

#参考
⇩set()
[JavaScriptで配列の重複チェックを超簡単＆スマートにやる方法](https://pisuke-code.com/js-check-duplicated-array-values/)

[JavaScriptで重複のない配列にするfilterの使い方を詳しく解説](https://www.suzu6.net/posts/95-js-duplication/)