JSで作る神経衰弱

#JSで作る、神経衰弱

![スクリーンショット 2020-10-14 11.28.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/536974/fa484f16-fb7b-e25a-d90a-585d3be4889a.png)

↓完成したプロダクトはこちら
[神経衰弱サンプル](https://my-ninth-j-query.shoyababa.vercel.app)

##【機能】
・1ターンに2つのカードをめくる事が出来る
・2つのカードが同じ数字ならば、表のままにする
・2つのカードが異なれば、裏返す（元に戻す）
・1度にめくれるのは2枚まで
・すべて、カードが捲れれば「Game over!」などのアラート
→その後、ゲームリセット

#コード
```sample.html
<body>
  <ul class="lists"></ul>
</body>
```

```sample.js
$(function() {
  'use strict';

  function cardShuffle() {
    const array1 = [...Array(8).keys()].map(i => ++i), // 元の配列i → ++i（前置インクリメント:i+1の値を返す）
          array2 = [...Array(8).keys()].map(i => ++i);

    let array = array1.concat(array2); // 1~8までの数字を2つずつの配列：concatで結合

    for(let i = 0; i < array.length; i++){
      let rand = Math.floor(Math.random() * (i+1));

      [array[i], array[rand]] = [array[rand], array[i]];
    }

    return array;
  }

  // <li>の作成
  const makeLiContents = (function() {
    let randNum = cardShuffle(), // 1~8までの数字を2つずつの配列、かつ、ランダム
        html = '',
        $ul = $('ul');

    for(let i = 0; i < randNum.length; i++) {
      html += '<li data-num=' + randNum[i] + '>?</li>';
    }

    $ul.append(html); // <li>要素の追加
  })();

  const openCard = (function(card) {
    let firstCardText, secondCardText, firstClickedData, secondClickedData,
        openFirst = true, // カード1枚目→true
        openFlag = false, // カードをめくることができるかどうか？のフラグ
        pair = 0; // そろったカード数

    return function toOpenCard(card) {
      let $listsLi = $(card),
      clickedData, clickedText;

      // フラグ=trueで処理を止める
      if($listsLi.text() === '?') {
        if (openFlag){
          return;
        } else {
          openFlag = true;
        }
      }

      // '?'のみをクリック可
      if($listsLi.text() === '?') {
        clickedData = $listsLi.data('num'); // クリックされたdata-numの取得
        clickedText = $listsLi.text(clickedData); // クリックされたdata-numをテキストに。

        // 1枚目の処理
        if(openFirst) {
          firstCardText = clickedText; // 1枚目にクリックされたカードの'テキスト'
          firstClickedData = clickedData; // 1枚目にクリックされたカードの'データ'
          openFirst = false;
          openFlag = false;

        // 2枚目の処理
        } else {
          secondCardText = clickedText;
          secondClickedData = clickedData;

          compareCard();

          openFirst = true;
        }

      }

      function compareCard() {
        // カードが1枚目と一致する時
        if(firstClickedData === secondClickedData) {
          pair++;
          openFlag = false;

          // ペアが全て揃うと。
          if(pair === 8) {
            setTimeout(function(){
              alert('Game Over!');
            }, 1000)
          }

        // 1枚目と一致しなかった時の処理
        } else {
          setTimeout(function(){
            openFlag = false; // setTimeoutが終わった後に、フラグがfalseに変わるため処理を止めることができる。
            firstCardText.text('?');
            secondCardText.text('?');
          }, 1000)
        }
      }
    };
  })();

  $('.lists').find('li').on('click', function() {
    openCard(this);
  });
});
```

#コードの説明をします

##①カードをシャッフルする処理
```sample.js
  function cardShuffle() {
    const array1 = [...Array(8).keys()].map(i => ++i),
          array2 = [...Array(8).keys()].map(i => ++i);

    let array = array1.concat(array2); // 1~8までの数字を2つずつの配列：concatで結合

    for(let i = 0; i < array.length; i++){
      let rand = Math.floor(Math.random() * (i+1));

      [array[i], array[rand]] = [array[rand], array[i]];
    }

    return array;
  }
```

・```[...Array(8).keys()].map(i => ++i)```：
以下記事が分かりやすいです。
[[...Array(10).keys()].map(i => ++i) ??](https://qiita.com/kajirikajiri/items/aff91ccfe3dc135ab7bd)

[...Array(8).keys()]は、
```Array(8)```で長さが8のカラの配列を作成。
その配列のkeyを取得して配列に格納しています。

```
console.log([...Array(8).keys()])

[0, 1, 2, 3, 4, 5, 6, 7]
```

```[...Array(8).keys()].map(i => ++i)```で配列のそれぞれの値に+1しています

・```let array = array1.concat(array2);```：concatメソッドは、配列や文字列を結合するためのメソッドです。

結合した配列がこちら

```
let array = array1.concat(array2)
console.log(array);

[1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8]
```

・```let rand = Math.floor(Math.random() * (i+1));```：
ランダムに並び替えた配列を、変数```rand```に格納しています。

・```[array[i], array[rand]] = [array[rand], array[i]]```：

[こちらの記事](https://designsupply-web.com/media/knowledgeside/5595/)を参考にした所、ここでの処理は Math.floor(Math.random())よりも精度の高いランダム処理を行っています。

>Math.floor(Math.random())では、たまたま乱数の比較結果で同じものが続く場合もあります。そこでより精度の高いシャッフルを行うために「フィッシャーイェーツのアルゴリズム」を使った配列のシャッフルという方法もあります


>```[array[i], array[rand]] = [array[rand], array[i]]```では、インデックスに応じて、配列の値同士を入れ替えているという形になります。

##②カードを構成するli要素を作成する処理
```sample.js
  // <li>の作成
  const makeLiContents = (function() {
    let randNum = cardShuffle(), // 1~8までの数字を2つずつの配列、かつ、ランダム
        html = '',
        $ul = $('ul');

    for(let i = 0; i < randNum.length; i++) {
      html += '<li data-num=' + randNum[i] + '>?</li>';
    }

    $ul.append(html); // <li>要素の追加
  })();
```

ここでは、クロージャ―を使用しています。

・```html = ''```：カラの文字列を用意します。
→for文で作成したDOMを格納するためです。
→一度に変数へ代入することでDOM操作の回数を減らすためです。

```
    for(let i = 0; i < randNum.length; i++) {
      html += '<li data-num=' + randNum[i] + '>?</li>';
    }
```
↑上記では、ランダムなカードの枚数だけli要素を作成して、変数```html```に代入しています

##③カードをめくる処理
```sample.js
    return function toOpenCard(card) {
      let $listsLi = $(card),
      clickedData, clickedText;

      // フラグ=trueで処理を止める
      if($listsLi.text() === '?') {
        if (openFlag){
          return;
        } else {
          openFlag = true;
        }
      }
```

・```let $listsLi = $(card)```：クリックされたli要素を取得しています

・```openFlag```：trueならば、めくることができます。
初期値ではfalse。

##④'?'のカードをクリックしたときの条件分岐
```sample.js
      if($listsLi.text() === '?') {
        clickedData = $listsLi.data('num');
        clickedText = $listsLi.text(clickedData);

        // 1枚目の処理
        if(openFirst) {
          firstCardText = clickedText; // 1枚目にクリックされたカードの'テキスト'
          firstClickedData = clickedData; // 1枚目にクリックされたカードの'データ'
          openFirst = false;
          openFlag = false;

        // 2枚目の処理
        } else {
          secondCardText = clickedText;
          secondClickedData = clickedData;

          compareCard();

          openFirst = true;
        }

      }
```

```clickedData = $listsLi.data('num');```：クリックされたdata-numの取得をしています。

```clickedText = $listsLi.text(clickedData);```：クリックされたdata-numをテキストに。

elseの後の2枚目の処理では、

compareCard();でカードの比較と、
openFirst = true;で、1枚目かどうか？をtrueにしています。

##⑤カードの比較処理
```sample.js
      function compareCard() {
        // カードが1枚目と一致する時
        if(firstClickedData === secondClickedData) {
          pair++;
          openFlag = false;

          // ペアが全て揃うと。
          if(pair === 8) {
            setTimeout(function(){
              alert('Game Over!');
            }, 1000)
          }

        // 1枚目と一致しなかった時の処理
        } else {
          setTimeout(function(){
            openFlag = false; // setTimeoutが終わった後に、フラグがfalseに変わるため処理を止めることができる。
            firstCardText.text('?');
            secondCardText.text('?');
          }, 1000)
        }
      }
```

カードが1枚目と一致する時、pair++;で、pairという揃ったカードのペアをカウントする変数に1ずつ足しています。

1枚目と一致しなかった時の処理では、
カードが空いているかどうかのフラグ```openFlag```をfalseにして、カードのテキストを表にする前の```?```に戻しています

#参考
[【JavaScript】神経衰弱で2枚のカードの判定前に３枚目のカードをクリックできないようにしたい](https://teratail.com/questions/230813)