0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-12-14

はじめに

スクリーンショット 2020-10-24 11.27.05.png

・NumerOnのルールについてはこちらでご確認ください。

・今回、実際に作成したNumerOnの完成版はこちら

・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);

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

クロージャーが分からない人はこちらの記事を参考にしてみてください。

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() )

上記の例で言えば、

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);でゲーム回数を積み重ねるごとに結果である

要素を子ノードとして追加していきます。

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

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

new Set(inputNum)Setオブジェクトでは重複した要素を格納できません。
MDN

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

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

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

ですが、次が複雑です。

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

return newInputNum.size === inputNum.length

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

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

詳しくは↓を参考に
配列の要素の数を取得する

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

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

JavaScriptで配列の重複チェックを超簡単&スマートにやる方法

⑥ゲーム進行の関数

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で配列の重複チェックを超簡単&スマートにやる方法

JavaScriptで重複のない配列にするfilterの使い方を詳しく解説

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?