年末まで毎日webサイトを作り続ける大学生 〜52日目 JavaScriptで神経衰弱ゲームを作る〜

##はじめに
こんにちは！@70days_jsです。

今日は神経衰弱を作りました。

全部クリアすると、なんと！

最後にとっておきの画像を見ることができます！
（大声では言えないですが、思わず興奮してしまう画像です...）

ぜひやってみてください。

ちなみにバグもありまして、素早く連続でクリックすると何枚もカードが開けてしまうのでご了承ください。直す時間がありませんでした。

今日は52日目。(2019/12/9)
よろしくお願いします。

##サイトURL
https://sin2cos21.github.io/day52.html

##やったこと
外観と動作はこんな感じです。↓
![test3.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/516248/8019d388-99d6-98be-7c64-ce7767b2602b.gif)

ほぼ全てJavaScriptで作りました。一部cssのアニメーションも使っています。
のでhtmlは何もなし↓

```html
<body></body>
```

JavaScript長いですが一応全て載せます↓

```javascript

window.onload = function() {
  createCardWrapper(); //wrapperを作る
  createCard(); //cardを作る
};

let divWrapper; //一番大枠のdiv
let countDisplay;
let countDisplaySpan = document.getElementById("countDisplaySpan");
let card = [
  { 0: "" },
  { 1: "" },
  { 2: "" },
  { 3: "" },
  { 4: "" },
  { 5: "" },
  { 6: "" },
  { 7: "" },
  { 8: "" },
  { 9: "" },
  { 10: "" },
  { 11: "" },
  { 12: "" },
  { 13: "" },
  { 14: "" },
  { 15: "" }
]; //それぞれのcardにつけるid
let image = ["1", "2", "3", "4", "5", "6", "7", "8"]; //cardの画像名
let imageNumber = 0;
let beforeCard = ""; //前にクリックしたcard
let id; //クリックしたcardのid(=key)
let countClick = 2; //1回目か2回目かの判断
let goal = 0; //全て当てたかどうかの判断

function clicked(e) {
  open(e); //cardクリック時のモーション
  successOrFailure(e);
}

//cardクリック時のモーション
function open(e) {
  id = e.target.id;
  e.target.classList.add("click-none");
  e.target.classList.add("card-rotate");
  delayImageDisplay(1000).then(function() {
    e.target.style.backgroundImage = "url(day52/" + card[id][id] + ".png)";
    e.target.classList.remove("card-rotate");
  });
}
function successOrFailure(e) {
  if (countClick % 2 == 0) {
    //1回目

    countClick++;
    countDisplaySpan.innerHTML =
      countClick -
      2 +
      ", 奇数, 前回のカード" +
      beforeCard +
      ",ポイント: " +
      goal;
    beforeCard = id;
  } else {
    //2回目、かつ成功
    if (card[beforeCard][beforeCard] === card[id][id]) {
      console.log("success");
      countDisplaySpan.innerHTML =
        countClick -
        2 +
        ", 偶数, 前回のカード" +
        beforeCard +
        ",ポイント: " +
        goal;
      beforeCard = id;
      countClick++;
      goal += Number(card[id][id]);
      if (goal === 36) {
        setTimeout(goaal, 2000);
      }
    } else {
      //2回目、かつ失敗
      e.target.classList.add("card-rotate");
      countClick++;
      countDisplaySpan.innerHTML =
        countClick -
        2 +
        ", 偶数, 前回のカード" +
        beforeCard +
        ",ポイント: " +
        goal;
      delayImageDisplay(1000)
        .then(function() {
          e.target.classList.remove("card-rotate");
        })
        .then(function() {
          let before = document.getElementById(beforeCard);
          beforeCard = id;

          setTimeout(function() {
            before.style.backgroundImage = "url(day52/day52_card.png)";
            e.target.style.backgroundImage = "url(day52/day52_card.png)";
            console.log(before);
            console.log(e.target);
            e.target.classList.remove("card-rotate");
            e.target.classList.remove("click-none");
            before.classList.remove("click-none");
          }, 1000);
        });
    }
  }
}

//image配列の順番をシャッフルする
function shuffle() {
  for (var i = image.length - 1; i > 0; i--) {
    let random = Math.floor(Math.random() * (i + 1));
    let tmp = image[i];
    image[i] = image[random];
    image[random] = tmp;
  }
}

//一番大枠のwrapperを作成する関数
function createCardWrapper() {
  countDisplay = document.createElement("div");
  countDisplaySpan = document.createElement("span");
  countDisplaySpan.setAttribute("id", "countDisplaySpan");
  countDisplay.innerHTML = "クリック回数: ";
  countDisplay.appendChild(countDisplaySpan);
  document.body.appendChild(countDisplay);

  divWrapper = document.createElement("div");
  divWrapper.setAttribute("id", "divWrapper");
  document.body.appendChild(divWrapper);
}
//cardを16枚作る関数
function createCard() {
  shuffle(); //画像をシャッフルする
  for (var i = 0; i < card.length; i++) {
    let div = document.createElement("div");
    let key = Object.keys(card[i]); //a,b..pと順番に入る
    div.setAttribute("id", key);
    card[i][key] = image[imageNumber]; //cardと画像が結びついた（画像は2枚ずつ)
    div.setAttribute("class", "card");
    // div.style.backgroundImage = "url(day52/" + image[imageNumber] + ".png)";
    if (imageNumber >= 7) {
      imageNumber = 0;
      shuffle(); //画像をシャッフルする
    } else {
      imageNumber++;
    }
    divWrapper.appendChild(div);
    div.addEventListener("click", clicked);
  }
  console.log(card);
}

//画像の表示を遅らせるための関数
function delayImageDisplay(delay) {
  return new Promise(function(resolve) {
    setTimeout(resolve, delay);
  });
}
function goaal() {
  goalDiv = document.createElement("div");
  goalImg = document.createElement("img");
  goalDiv.innerHTML = "おめでとうございます！";
  goalImg.setAttribute("class", "goalImg");
  goalDiv.setAttribute("class", "goalDiv");
  goalImg.setAttribute("src", "day52/secret.jpg");
  goalDiv.appendChild(goalImg);
  document.body.appendChild(goalDiv);
}
```

cssも一応全て載せておきます。↓

```css
body {
  margin: 0;
  height: 100vh;
  display: flex;
  justify-content: center;
  align-items: center;
  flex-direction: column;
}

img {
  max-width: 100%;
  max-height: 100%;
}

#divWrapper {
  width: 600px;
  height: auto;
  background-color: rgba(80, 80, 200, 0.3);
  display: flex;
  justify-content: center;
  align-items: center;
  flex-wrap: wrap;
}

.card {
  display: inline-block;
  width: 130px;
  height: 130px;
  background-color: rgba(50, 50, 50, 0);
  /* border: solid 1px black; */
  margin: 1%;
  background-image: url("day52/day52_card.png");
  background-size: cover;
}

.card:hover {
  opacity: 0.2;
}

.card-rotate {
  animation: rotate;
  animation-duration: 1s;
}

@keyframes rotate {
  0% {
    transform: rotateY(0deg);
  }
  100% {
    transform: rotateY(92deg);
  }
}

.click-none {
  pointer-events: none;
}

.goalDiv {
  position: absolute;
  background-color: black;
  width: 100vw;
  height: 100vh;
  display: flex;
  justify-content: center;
  align-items: center;
  color: white;
  flex-direction: column;
  font-size: 3em;
}

.goalImg {
  display: inline-block;
}
```

###肝になる部分
ちょっと今回は長いので大事なところだけ説明していきます。
 
> let card = [
...

この変数↑は配列ですが、その中身はhash形式で情報を保存しています。↓

> { 0: "" },
  { 1: "" },
  { 2: "" },
...

神経衰弱のカードにはそれぞれidが割り振られており、このhashのキー(0~15)はそのidと全て対応しています。
このhashはのちにvalueに1~8の値を持ちます。
この数字は画像の名前と一致しており、それぞれ2回ずつhashに割り当てることで対になる2枚のカードを実現しています。
これがhashのvalueに入る数字です。↓

> let image = ["1", "2", "3", "4", "5", "6", "7", "8"]; //cardの画像名

このままでは順番通りに画像が入ってしまうので、image配列の中身をシャッフルします。↓

> function shuffle() {
  for (var i = image.length - 1; i > 0; i--) {
    let random = Math.floor(Math.random() * (i + 1));
    let tmp = image[i];
    image[i] = image[random];
    image[random] = tmp;
  }
}

###promiseを使う
ちなみに、今回promiseも少し使ってみました。
やっぱりまだ理解しきれていないですが。↓

> function delayImageDisplay(delay) {
  return new Promise(function(resolve) {
    setTimeout(resolve, delay);
  });
}

> function open(e) {
  id = e.target.id;
  e.target.classList.add("click-none");
  e.target.classList.add("card-rotate");
  delayImageDisplay(1000).then(function() {
    e.target.style.backgroundImage = "url(day52/" + card[id][id] + ".png)";
    e.target.classList.remove("card-rotate");
  });

ただ、しっかり順番通りに動いてくれているので、なんとなく便利だということは分かりました。
いずれちゃんと理解しようと思います。

###ゴールページについて
神経衰弱を全てやり終えたら思わず興奮してしまう画像を用意しています。

変数を用意します。↓

>let goal = 0;

カードが当てることができたら、goal変数に画像の値分（0~8)数値を足します。↓

>   goal += Number(card[id][id]);

画像はは0~8なので、全て足し合わせると36になります。36になると関数を実行するようにしています。↓

> if (goal === 36) {
        setTimeout(goaal, 2000);
      }
    } else {...

関数の中身です。secret.jpgが例の画像です。↓

> function goaal() {
  goalDiv = document.createElement("div");
  goalImg = document.createElement("img");
  goalDiv.innerHTML = "おめでとうございます！";
  goalImg.setAttribute("class", "goalImg");
  goalDiv.setAttribute("class", "goalDiv");
  goalImg.setAttribute("src", "day52/secret.jpg");
  goalDiv.appendChild(goalImg);
  document.body.appendChild(goalDiv);
}

secret画像は国宝級の画像を用意しました。
皆さんもぜひ試してみてください！（アプリでは全て見れます）↓
![スクリーンショット 2019-12-09 21.54.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/516248/c9dd273e-ddc4-09dc-4a86-a45302b29ae7.png)



##感想
ちょっと今日は説明が意味不明すぎることになっていると思います。
申し訳ありません。

というのも、手当たり次第に作っていたら色々と複雑になってしまい、全てを言語化していたら膨大な量になってしまうと判断したからです。
今後は最初にきちんと設計して、なるべくシンプルなコードを書けるように尽力します

最後まで読んでいただきありがとうございます。明日も投稿しますのでよろしくお願いします。

##参考
1. [アイコン素材ダウンロードサイト「icooon-mono」 | 商用利用可能なアイコン素材が無料(フリー)ダウンロードできるサイト | 6000個以上のアイコン素材を無料でダウンロードできるサイト ICOOON MONO](https://icooon-mono.com/)
2. [パブリックドメインQ：著作権フリー画像素材集](https://publicdomainq.net/)

画像を使用させていただきました。