JavaScriptによるスロットマシーン（ジャグ○ーもどき）

こんにちは。今回はスロットマシーンです。
ドットインストールの「JavaScriptでスロットマシンを作ろう」にアレンジを加えて、以下のことを実装しました。

・リーチフラグの導入し、それが立ったら画像を表示する（これが立たないと7を狙っても外れる）
・777を揃えると、上部パネルの画像を変更する
・777成立後の1ゲームはベルが揃う
・ベルが揃い、終了したらもとに戻る

周りも多少装飾してスロットマシーンぽくしてみました。
そうしてできた物が以下になります。

# 完成版

![animegif01.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478298/92c989fd-7f05-e24b-88ce-81f752336005.gif)

はい。なんだかどこかで見たような気がしないでもないですね。
ご想像の通り、ピエロ的なアレを参考にしました。
上記のアレンジ実装機能もそれに沿ったものです。なのでアレをご存じの方はアレの仕様に似てると思ってください。

では作って行きたいと思います。
今回も以前と同様に、初心者ならではのつまづきを含めて解説していきたいと思います。

# HTML

まずはhtmlです。

```html:html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <title>slot machine</title>
  <link rel="stylesheet" href="css/styles.css">
</head>
<body>
  <div id="wrapper">
  <header id="result_area" class="none"><img></header>
  <main></main>
  <div class="flex">
    <div id="gogo" class="gogo_off"><img></div>
    <div id="spin">SPIN</div>
  </div>
  <footer></footer>
</div>
  <script src="js/main.js"></script>
</body>
</html>
```
解説するとすれば、
`wrapper` = 大枠
`header` = 上部パネル（ボーナス中の画像を表示するエリア）
`main` = スロット部分
`flex` = ランプとSPINボタン
`footer` = パネル
というところです。


CSSについては特に難しいことはしていません。基本的なレイアウトと、透明度くらいのものです。
書くと長くなるので、一番最後に貼り付けたいと思います。

それではJavaScriptを書いていきます。

# クラスを使ってパネルを描画する

パネルとは、絵柄がくるくる回るエリアのことです。
この絵柄とストップボタンを`section`としてひとまとめにして、それを3つ並べることで`main`を構成しています。`main`の中身は以下のように出力されるので、こうなるようにクラスを設定しましょう。

```html:html
<main>

<section class="panel">
  <img src="img/cherry.jpg">
  <div class="stop">STOP</div>
</section>

<section class="panel">
  <img src="img/cherry.jpg">
  <div class="stop">STOP</div>
</section>

<section class="panel">
  <img src="img/cherry.jpg">
  <div class="stop">STOP</div>
</section>

</main>
```

では`section`を作るPanelクラスを作りましょう。
内容は、生成する`section`にPanelクラスをつけて、その中に`img`と`STOP`ボタンを作り、それらを`main`に対して追加していきます。

```JavaScript:JavaScript
  class Panel {
    constructor() {
      const section = document.createElement('section'); // section要素を生成
      section.classList.add('panel'); // panelクラスをつける

      this.img = document.createElement('img'); // プロパティとして設定
      this.img.src = 'img/seven.jpg'; // ここではとりあえずseven.jpgに設定

      this.stop = document.createElement('div'); // プロパティとして設定
      this.stop.textContent = 'STOP'; // テキストをSTOPに設定
      this.stop.classList.add('stop'); // STOPクラスを追加

      section.appendChild(this.img); // Sectionの子要素として追加
      section.appendChild(this.stop); // Sectionの子要素として追加

      const main = document.querySelector('main'); // mainを取得
      main.appendChild(section); // main内に追加
    }
  }
```

これでひとまずOKです。これをもって`new Panel`とインスタンスを生成すれば、ページに上記の要素が追加されます。

これらの要素はまとめて扱えるように、panelsという配列を宣言して、その中で3つインスタンスを生成します。



```JavaScript:JavaScript
  class Panel {
    constructor() {
      const section = document.createElement('section');
      section.classList.add('panel');

      this.img = document.createElement('img');
      this.img.src = 'img/seven.jpg';

      this.stop = document.createElement('div');
      this.stop.textContent = 'STOP';
      this.stop.classList.add('stop');

      section.appendChild(this.img);
      section.appendChild(this.stop);

      const main = document.querySelector('main');
      main.appendChild(section);
    }
  }

  const panels = [ // インスタンス生成
    new Panel(),
    new Panel(),
    new Panel(),
  ];
```

これでインスタンスが3つ並んで`main`内に表示されていると思います。
ここで私がつまづいたところを解説します。

### 配列宣言＝実行

上記のpanelsという配列を宣言したところなのですが、私の意識の中では配列宣言というのは、要は**DB生成のようなもので、あとで使うものをいまのうちに用意しておく**というものでした。
従って、このあとに例えばforEachなどを使って呼び出さない限り実行されないと思っていたのですが、どうやら勘違いをしていました。実際は**配列を使ってクラスから各インスタンスを生成する場合は、宣言と同時に実行される（constructorの呼び出し）**ようです。

配列を使ってクラスからインスタンス生成は初めてだったので、ここがどうしても理解できず、人に聞いてようやくわかりました。もし同じ初心者の方で同じような点に躓いた方がいたら参考になると嬉しいです。

では戻ります。

# SPINボタンを使って各パネルの画像を回転させる

SPINボタンはidを設定してあるので、それを取得し、クリックイベントを追加します。
イベントはクリックしたら配列panelsの中の各要素をforEachを使って回し、panelで受け取ってスピンの処理をするように書きます。処理はpanelクラス内で`spin()`としてまとめましょう。


```JavaScript:JavaScript
  const spin = document.getElementById('spin'); // id="spin"取得
  spin.addEventListener('click', () => { // クリックイベント追加
    panels.forEach(panel => { // 配列panelsを回して
      panel.spin(); // panelクラス内のspin()を実行
    });
  });
```
ではpanelクラスの中でspin()を設定します。
constructorの下にイメージのsrc要素をランダムにするという処理を追加しましょう。
またそのランダムにする処理は別に設定します。

```JavaScript:JavaScript
  class Panel {
    constructor() {
      const section = document.createElement('section');
      section.classList.add('panel');

      this.img = document.createElement('img');
      this.img.src = 'img/seven.jpg';

      this.stop = document.createElement('div');
      this.stop.textContent = 'STOP';
      this.stop.classList.add('stop');

      section.appendChild(this.img);
      section.appendChild(this.stop);

      const main = document.querySelector('main');
      main.appendChild(section);
    }

    getRandomImage() {
      return images[Math.floor(Math.random() * images.length)]; // 配列imagesからランダムにイメージを取得
    }

    spin() { // spinをクリックされたら
      this.img.src = this.getRandomImage(); // getRandomImage()を実行
    }
  }

```

配列imagesはjsファイルの一番上にでも書いておきます。

```JavaScript:JavaScript
  const images = [ // 画像の配列
    'img/seven.jpg',
    'img/bell.jpg',
    'img/cherry.jpg',
    'img/bar.jpg',
    'img/grape.jpg',
    'img/piero.jpg',
    'img/sai.jpg',
  ];
```

これにより「SPIN」ボタンが押されるたびにランダムに画像が切り替わるようになりました。
ちなみに`constructor`内の`this.img.src = 'img/seven.jpg'; // ここではとりあえずseven.jpgに設定`としたところも、`getRandomImage()`を使うと初期値もランダムにできるので書き換えましょう。

```JavaScript:JavaScript
      this.img.src = this.getRandomImage(); // 初期値もランダムに
```


次はこれを自動でくるくる回るようにし、それを「STOP」ボタンで止められるようにしましょう。

# 「SPIN」ボタンと「STOP」ボタン

まずは自動で回るようにするには、`setTimeout`を使って、spin()を繰り返します。

```JavaScript:JavaScript
    spin() {
      this.img.src = this.getRandomImage();
      this.timeoutId = setTimeout(() => { // ランダム表示処理を繰り返す
        this.spin();
      }, 600); // とりあえず600ミリ秒ごとで
    }
```

次はこれを「STOP」ボタンで止めます。そのためにはボタンにクリックイベントとして`clearTimeout()`を追加します。
`clearTimeout()`には止めるべき`setTimeout`の引数が必要です。なのでまずそれを`timeoutId`として設定します。これを使って`setTimeout`の返り値を`clearTimeout()`に渡すことで、回転を止めるようにします。

```JavaScript:JavaScript
  class Panel {
    constructor() {
      const section = document.createElement('section');
      section.classList.add('panel');

      this.img = document.createElement('img');
      this.img.src = this.getRandomImage();

      this.timeoutId = undefined; // 返り値用に用意 初期値はundefinedで良い

      this.stop = document.createElement('div');
      this.stop.textContent = 'STOP';
      this.stop.classList.add('stop');
      this.stop.addEventListener('click', () => { // 「STOP」ボタンのイベント追加
        clearTimeout(this.timeoutId); // this.timeoutIdを受け取ってそれを止める
      });

      section.appendChild(this.img);
      section.appendChild(this.stop);

      const main = document.querySelector('main');
      main.appendChild(section);
    }

    getRandomImage() {
      return images[Math.floor(Math.random() * images.length)];
    }

    spin() {
      this.img.src = this.getRandomImage();
      this.timeoutId = setTimeout(() => { // 返り値をthis.timeoutIdとする
        this.spin();
      }, 50);
    }
```
これで「STOP」ボタンで回転を止めることができました。
「SPIN」ボタンと違って、「STOP」ボタンは個々の要素を止めるので、panelクラス内にメソッドとして書くんですね。

# 停止させた要素を判定する
まず停止させた絵柄が、他の二枚と揃わなかった時に、その要素を薄くするようにしてみましょう。

## 要素を判定し、他の二枚と揃わなかった時に処理する
具体的には、
・停止させた枚数をカウント
・残り0枚になったら結果判定（の関数呼び出し）
・他の2枚と比較する
・一致しなかったら薄くする
という流れになります。

停止させた枚数のカウントは`panelsLeft`という変数名にして、初期値を3にし、「STOP」ボタンを押すたびにひとつ減らすという方法を取ります。そのカウントが0になったら結果判定`checkResult()`をします。これはconst spinの上にでも書いておきます。

```JavaScript:JavaScript
  let panelsLeft = 3; // 残りの枚数
```

カウントを減らす文と結果判定は、「STOP」ボタンのクリックイベントに追加します。

```JavaScript:JavaScript
  this.stop.addEventListener('click', () => {
    clearTimeout(this.timeoutId);
    panelsLeft--; // 残り枚数カウントを減らす
    if (panelsLeft === 0) { // 0になったら
      checkResult(); // 結果判定関数を呼び出し
    }
  });
```

そしてこの`checkResult()`関数ですが、この「STOP」ボタンだけでなく、全ての要素について判定するので、panelクラス構文内ではなく、外に書きましょう。
内容は、`checkResult()`が呼び出されたら、各パネルをひとつずつ調べて、他のパネルと比較します。
そのために用意するメソッドがふたつ。他の2つと比較するための`isUnmatched`メソッドと、一致しなかった場合に呼び出す`unmatch`メソッドです。これらを使ったこのように書いておきます。

```JavaScript:JavaScript
  function checkResult() {
    if (panels[0].isUnmatched(panels[1], panels[2])) {
      panels[0].unmatch();
    }
    if (panels[1].isUnmatched(panels[0], panels[2])) {
      panels[1].unmatch();
    }
    if (panels[2].isUnmatched(panels[0], panels[1])) {
      panels[2].unmatch();
    }
  }
```
まず`isUnmatched`です。これは個々に判定するので、panelクラス内に書くと良いでしょう。
引数として、他の要素をp1とp2で受け取ります。判定する要素のイメージのsrcプロパティ`this.img.src`が他の要素のイメージのsrcプロパティと異なっていたらtrue、そうでなかったらfalseを返すという処理です。
ここでは両方の要素が共に異なっていたらtrueなので論理演算子のAND`&&`を使います。
`this.img.src !== p1.img.src` かつ `this.img.src !== p2.img.src`ですね。

```JavaScript:JavaScript
    isUnmatched(p1, p2) {
      if (this.img.src !== p1.img.src && this.img.src !== p2.img.src) {
        return true;
      } else {
        return false;
      }
    }
```
ただし、このような書き方の場合、この条件自体をreturnしてあげれば同じ結果が得られます。従って

```JavaScript:JavaScript
    isUnmatched(p1, p2) {
      return this.img.src !== p1.img.src && this.img.src !== p2.img.src;
    }
```
これでいいわけですね。

次に`unmatch`メソッドです。他とマッチしなかった場合は薄くしたいので、cssに`unmatched`クラスを作ってopacityを0.5くらいにしておいて、それをclassListで追加すれば良いと思います。※cssは省略

```JavaScript:JavaScript
    unmatch() {
      this.img.classList.add('unmatched');
    }
```
これで結果判定で薄くする処理ができました。


# ボタンを制御する
さて、ボタンの処理がかけたところで、ちゃんと動くように制御する必要があります。
なぜなら「SPIN」ボタンを連打すると「STOP」ボタン一回では止まらなくなっているからです。

これらの問題に対処するには、前回もやった**一回押したらもう押せなくする**を採用します。
まずビジュアル的にも押せないようにしたいので、押したら暗くしましょう。
とりあえずCSSにinactiveクラスというのでも作って、unmatchクラスと同じようにopacityを0.5にでもしておきます。
このクラスを`classList`で追加する処理はspin()のところですね。
「SPIN」ボタンを押した時にinactiveクラスがついているかチェックして、ついていたら以降の処理をしない、ついていなかったらinactiveクラスを追加した上で処理を行う、です。

```JavaScript:JavaScript
  spin.addEventListener('click', () => {
    if (spin.classList.contains('inactive')) { // inactiveクラスがついてるかチェック
      return; // ついていたらそのままreturn
    }
    spin.classList.add('inactive'); // inactiveクラスを追加する

    panels.forEach(panel => {
      panel.spin();
    });
  });
```
inactiveクラスがついてたらそのままreturnなので、forEachもしない、つまり絵柄回転もしないということですね。
これをそのまま「STOP」ボタンにも使います。


```JavaScript:JavaScript
  this.stop.addEventListener('click', () => {
    if (this.stop.classList.contains('inactive')) { // inactiveクラスがついてるかチェック
      return; // ついていたらそのままreturn
    }
    this.stop.classList.add('inactive'); // inactiveクラスを追加する
    clearTimeout(this.timeoutId);
    panelsLeft--;
    if (panelsLeft === 0) {
      checkResult();
    }
  });
```
このやり方は一回しか押してほしくない時に使えそうですね。

# その他の整備
ここまで作ってきたうえで、まだいくつか問題が残っていますので、それらを片付けていきます。

## 連続で遊べるようにする
「STOP」ボタンを3つ押した後、もう一度遊べるようにします。今のままだと、「SPIN」「STOP」ボタンも押せないし、絵柄は判定で薄くなってたりで遊べません。
そのため、まず「SPIN」ボタンなどのinactiveクラスを取ることにします。場所は結果判定の後が良いでしょう。つまりここですね。

```JavaScript:JavaScript
  this.stop.addEventListener('click', () => {
    if (this.stop.classList.contains('inactive')) {
      return;
    }
    this.stop.classList.add('inactive');
    clearTimeout(this.timeoutId);
    panelsLeft--;
    if (panelsLeft === 0) {
      checkResult();
      spin.classList.remove('inactive'); // 「SPIN」ボタンのinactiveクラスを外す
      panelsLeft = 3; // 残り枚数カウントを3に戻す
    }
  });
```

「STOP」ボタンのinactiveクラスを外すのは、次に「SPIN」ボタンを押したときが良いでしょう。
その処理は`activate`メソッドとしてクラス内に処理を書きます。まずは

```JavaScript:JavaScript
  spin.addEventListener('click', () => {
    if (spin.classList.contains('inactive')) {
      return;
    }
    spin.classList.add('inactive');

    panels.forEach(panel => {
      panel.activate(); // STOPボタンのinactiveクラスを外す
      panel.spin();
    });
  });
```
とし、クラス内の最後あたりに以下を書きます。

```JavaScript:JavaScript
  activate() {
    this.img.classList.remove('unmatched'); // 画像を暗くするためのunmatchedを外す
    this.stop.classList.remove('inactive'); // ボタンのinactiveを外す
  }
```
これでOKです。

## SPINする前にSTOPボタンが押せてしまう
これもなんだか変ですよね。そのため、一番最初のボタンを生成する段階で、inactiveクラスをつけてしまいます。

```JavaScript:JavaScript

      this.stop.classList.add('stop', 'inactive');

```

はい。これで概ねスロットマシーンとしての動きは完成です。
あとはジャグ○ーっぽく仕上げていきたいと思います。

# ジャグ○ーっぽく仕上げる
最初にも書きましたが、それっぽく仕上げるために、追加する処理がいくつかあります。

・リーチフラグの導入し、それが立ったら画像を表示する（これが立たないと7を狙っても外れる）
・777を揃えると、上部パネルの画像を変更する
・777成立後の1ゲームはベルが揃う
・ベルが揃い、終了したらもとに戻る

以上です。それに伴い、フラグを用意する必要があるのでこれも解説します。

## 状態管理
**・hitNumとgogoNum**
今までのものだと、いつでも777が揃います。それではつまらないので、あらかじめ用意した定数hitNumと、スロットを回すごとにランダムで設定されるgogoNumが一致したときに初めて777を揃えることできるようにしましょう。

**・リーチフラグ（reachFlag）**
定数hitNumと回転するごとにランダムで設定するgogoNumが一致したらリーチフラグ（reachFlag）が立つので、これがたったら、画面上にそのことを知らせるため画像を切り替えます。
![gogogif01.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478298/bd15054e-6400-1f15-bdb8-2d178c3d028c.gif)
<span style=“font-size: 60%;”>（なんと神々しい・・）</span>

それをif文で判定し、trueなら777を揃えることが可能になり、その処理を実行できます。
ちなみにfalseの場合は、777を狙っても滑ってチェリーになるようにします。

**・777が揃ったフラグ（get777）**
777が揃った時に立つフラグです。これが立っていると次のゲームは、ベルが揃います。元でいうボーナスゲームのようなものです。1ゲームだけなのですが、これを入れたかったのでこのフラグをもたせました。ちなみにこれが立っている間は上部パネルが変わるようになっています。

流れとしては以下のようになります。
※少々書き方があやしいですが気になさらずに


![No-00.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478298/b777eceb-7466-89ce-13d3-ae0a4ebbb259.jpeg)



## フラグ判定

では上記に必要なものを宣言しておきましょう。

```JavaScript:JavaScript
  const hitNUm = 0; // 当たりのナンバー
  let gogoNum = 0; // 回転ごとにランダムで決まるナンバー
  let reachFlag = 0; // リーチフラグ管理
  let get777 = 0; // ボーナスゲーム管理
```
まずは回転ごとに決まるナンバーです。これは全停止したら決まるので、「STOP」ボタンのクリックイベント内、panelsLeftが0になったらのところに追加します。ここでは検証のしやすいように、1/3で当たるようにしました。

それで決まる`gogoNum`を持って`checkResult`で色々と判定することにしたいので引数に`gogoNum`を入れます。


```JavaScript:JavaScript
        if (panelsLeft === 0) {
          gogoNum = Math.floor(Math.random() * 3); //  回転ごとにランダムで決まるナンバー

          checkResult(gogoNum); // ランダムな数 gogoNum を持ってcheckResultする
          spin.classList.remove('inactive');
          panelsLeft = 3;
        }
      });
```

次は`checkResult`です。
hitNumとgogoNumが一致しているかをチェックして、それに合わせた処理を書きます。
各処理は
・reachFlagをたたせる
・reachFlagが立ったよ、ということを画像で知らせるための処理を行う

後者については`gogoFlash()`という関数を作って呼び出すことにします。


```JavaScript:JavaScript  
  function checkResult(gogoNum) { // gogoNumを受け取って以下の処理

    if (hitNUm === gogoNum) { // hitNUm と gogoNum が一致していたら
      gogoFlash(1); // gogoFlash()関数を呼び出す
      reachFlag = 1; // reachFlagを1にする（フラグを立たせる）
    }


    if (panels[0].isUnmatched(panels[1], panels[2])) {
      panels[0].unmatch();
    }
    if (panels[1].isUnmatched(panels[0], panels[2])) {
      panels[1].unmatch();
    }
    if (panels[2].isUnmatched(panels[0], panels[1])) {
      panels[2].unmatch();
    }
  }
```

では`gogoFlash()`関数を書いていきましょう。
これに仕組みは、**フラグが立ってない時と立っている時の画像を2つ用意し、状況に応じて切り替える**ことにします。
リーチフラグに関する画像をgogoRampという配列にしておきます。そして受け取ったフラグに基づいて処理を実行するように書きます。


```JavaScript:JavaScript
  const gogoRamp = [ // 画像の配列
    'img/gogo_off.jpg',
    'img/gogo_on.jpg',
  ];

  const gogo = document.getElementById('gogo'); // エリアの取得
  function gogoFlash(e) { // フラグが立つと 1 を持って呼び出される
    let gogoRampNum = e; // その値をいったんgogoRampNumに入れる
    document.querySelector('#gogo img').src = gogoRamp[gogoRampNum]; // 配列を使ってimgのsrcプロパティを切り替える
  }

  gogoFlash(0); // 初期値は0にしておく
```
このようになりました。
ちなみに、777が揃った時にヘッダーパネルも同様にフラグを持って切り替えます。なのでそれも一緒に書いておきましょう。


```JavaScript:JavaScript
  const headerImage = [
    'img/gogo_header01.jpg',
    'img/gogo_header02.jpg',
  ];
  const gogoRamp = [
    'img/gogo_off.jpg',
    'img/gogo_on.jpg',
  ];

  const header = document.getElementsByTagName('header');
  function headerFlash(e) {
    let headerFlagNum = e;
    document.querySelector('header img').src = headerImage[headerFlagNum];
  }
  const gogo = document.getElementById('gogo');
  function gogoFlash(e) {
    let gogoRampNum = e;
    document.querySelector('#gogo img').src = gogoRamp[gogoRampNum];
  }

  headerFlash(0);
  gogoFlash(0);
```

これで、何度かゲームを遊ぶとフラグがたったことがわかりやすく表示されると思います。


## reachFlagが立っていなかったときの処理

次はreachFlagが立っていないときは、3つ目の画像を止めた際に強制的にチェリーの絵柄になるよう設定し、777を揃えることができないようにしましょう。
それには`check777`という関数を使います。やることは前に書いた`isUnmatched`関数と同じで、止まった絵柄をチェックして、777なら**(絵柄のurlに'seven'が含まれていたら）**trueというものです。
これもpanelクラス内に書きます。

```JavaScript:JavaScript
    check777() { // 全部7だったらtrue
      return this.img.src.indexOf('seven') !== -1 && panels[1].img.src.indexOf('seven') !== -1 && panels[2].img.src.indexOf('seven') !== -1;
    }

```

次は`checkResult`関数内に追加です。

```JavaScript:JavaScript
    if (reachFlag === 0) { // reachFlagが立っていなかったら
    headerFlash(0);
      if (panels[0].check777(panels[1], panels[2])) { // 777が揃った時
        panels[2].img.src = 'img/cherry.jpg'; // チェリーを表示させる
      }
    }
```
これで仮に狙って7を揃えても、右の絵柄が滑ってチェリーになります。
ちなみに`headerFlash(0);`は、ボーナスゲームなどを終えて再度ゲームを始めた時にリセットできるようにここに書いています。

## reachFlagが立っていたときの処理
次は揃う方ですね。これもifを使って条件分岐していきます。
その条件は、「reachFlagが立っている」「check777でtrueが帰ってきてる」です。

これらの条件が揃ったときに行う処理は
・`gogoFlash`に0を渡す（リーチフラグ成立表示（ゴーゴーランプ)をオフにする）
・`headerFlash`に1を渡す（上部パネルのボーナス成立表示をオンにする）
・`reachFlag`に0を代入する（リーチフラグ非成立状態へ移行する）
・`get777`に1を代入する（ボーナスゲームを始めるためのフラグを立たせる）
です。これを`checkResult`関数内に書いていきましょう。

```JavaScript:JavaScript
    if (reachFlag === 1 && panels[0].check777()) { // ボーナスフラグが立ちつつ、777が揃ったら
      gogoFlash(0); // リーチフラグ成立表示をオフにする
      headerFlash(1); // 上部パネルのボーナス成立表示をオンにする
      reachFlag = 0; // リーチフラグ非成立状態へ移行する
      get777 = 1; // ボーナスゲームを始めるためのフラグを立たせる
    }
```
これにより上部パネルが変更され、ボーナスゲーム中をアピールしています。

このボーナスゲーム状態（get777 = 1）になると、次のゲームではどこを押してもベルが揃うようにします。
この処理は、ボタンを押すごとに走るので、panelクラス内に書きます。

```JavaScript:JavaScript
      this.stop.addEventListener('click', () => {
        if (this.stop.classList.contains('inactive')) {
          return;
        }
        this.stop.classList.add('inactive');
        clearTimeout(this.timeoutId);
        if (get777 === 1) { // get777フラグが立っていたら
          this.img.src = 'img/bell.jpg'; // 強制的にベルを表示
        }
        panelsLeft--;

        if (panelsLeft === 0) {
          gogoNum = Math.floor(Math.random() * 3);

          checkResult(gogoNum);
          spin.classList.remove('inactive');
          panelsLeft = 3;
        }
      });
```

そしてこのボーナスゲームが終わる＝get777が1のまま`checkResult`に進んだらget777を0に戻す処理を`checkResult`関数内に書きます。


```JavaScript:JavaScript
    if (get777 === 1) { // get777が1だったら
      get777 = 0; // get777を0に戻す
    }
```

こうすることで、全てのフラグが0になり、最初と同様にまた遊べるようになりました。



## あとがき
以上で完成となります。いかがでしたでしょうか。もともとはドットインストールの講義のものなのですが、勉強も兼ねて思いっきりアレンジしてみました。本当はまだ、リーチフラグがたってなくて777を揃える際、右を最後にしてない場合でも、右側が滑るという問題点があるのですが、かなり長くなりそうなので、一旦ここで終了とさせていただきます。

JavaScriptを収めた方々が見たら、なんでそれ使ってんのとかたくさんあると思いますが、初心者の私にできることで構成してみました。もしこうしたらもっと良くなるよとか、これにはこういう関数の使い方をしたほうがいいよなどありましたらぜひ教えて下さい。


ありがとうございました。


## 完成版

```JavaScript:JavaScript
'use strict';

{
  const images = [
    'img/seven.jpg',
    'img/bell.jpg',
    'img/cherry.jpg',
    'img/bar.jpg',
    'img/grape.jpg',
    'img/piero.jpg',
    'img/sai.jpg',
  ];
  const headerImage = [
    'img/gogo_header01.jpg',
    'img/gogo_header02.jpg',
  ];
  const gogoRamp = [
    'img/gogo_off.jpg',
    'img/gogo_on.jpg',
  ];

  const hitNUm = 0;
  let gogoNum = 0;
  let reachFlag = 0;
  let get777 = 0;

  const header = document.getElementsByTagName('header');
  function headerFlash(e) {
    let headerFlagNum = e;
    document.querySelector('header img').src = headerImage[headerFlagNum];
  }
  const gogo = document.getElementById('gogo');
  function gogoFlash(e) {
    let gogoRampNum = e;
    document.querySelector('#gogo img').src = gogoRamp[gogoRampNum];
  }

  headerFlash(0);
  gogoFlash(0);

  class Panel {
    constructor() {
      const section = document.createElement('section');
      section.classList.add('panel');
      this.img = document.createElement('img');
      this.img.src = this.getRandomImage();

      this.timeoutId = undefined;

      this.stop = document.createElement('div');
      this.stop.textContent = 'STOP';
      this.stop.classList.add('stop', 'inactive');
      this.stop.addEventListener('click', () => {
        if (this.stop.classList.contains('inactive')) {
          return;
        }
        this.stop.classList.add('inactive');
        clearTimeout(this.timeoutId);
        if (get777 === 1) {
          this.img.src = 'img/bell.jpg';
        }
        panelsLeft--;

        if (panelsLeft === 0) {
          gogoNum = Math.floor(Math.random() * 3);

          checkResult(gogoNum);
          spin.classList.remove('inactive');
          panelsLeft = 3;
        }
      });

      section.appendChild(this.img);
      section.appendChild(this.stop);

      const main = document.querySelector('main');
      main.appendChild(section);
    }

    getRandomImage() {
      return images[Math.floor(Math.random() * images.length)];
    }

    spin() {
      this.img.src = this.getRandomImage();
      this.timeoutId = setTimeout(() => {
        this.spin();
      }, 600);
    }

    isMatched(p1, p2) {
      return this.img.src === p1.img.src && this.img.src === p2.img.src;
    }

    isUnmatched(p1, p2) {
      return this.img.src !== p1.img.src && this.img.src !== p2.img.src;
    }

    unmatch() {
      this.img.classList.add('unmatched');
    }

    activate() {
      this.img.classList.remove('unmatched');
      this.stop.classList.remove('inactive');
    }

    check777() {
      return this.img.src.indexOf('seven') !== -1 && panels[1].img.src.indexOf('seven') !== -1 && panels[2].img.src.indexOf('seven') !== -1;
    }
  }

  function checkResult(gogoNum) {


    if (get777 === 1) {
      get777 = 0;
    }
    if (reachFlag === 0) {
    headerFlash(0);
      if (panels[0].check777(panels[1], panels[2])) {
        panels[2].img.src = 'img/cherry.jpg';
      }
    }

    if (hitNUm === gogoNum) {
      reachFlag = 1;
      gogoFlash(1);
    }
    if (reachFlag === 1 && panels[0].check777()) {
      gogoFlash(0);
      headerFlash(1);
      reachFlag = 0;
      get777 = 1;
    }

    if (panels[0].isUnmatched(panels[1], panels[2])) {
      panels[0].unmatch();
    }
    if (panels[1].isUnmatched(panels[0], panels[2])) {
      panels[1].unmatch();
    }
    if (panels[2].isUnmatched(panels[0], panels[1])) {
      panels[2].unmatch();
    }
  }

    const panels = [
      new Panel(),
      new Panel(),
      new Panel(),
    ];

    let panelsLeft = 3;

    const spin = document.getElementById('spin');
    spin.addEventListener('click', () => {
      if (spin.classList.contains('inactive')) {
        return;
      }
      spin.classList.add('inactive');
      panels.forEach(panel => {る
        panel.activate();
        panel.spin();
      });
    });

}

```

```css:css
body {
  background: #bdc3c7;
  font-size: 16px;
  font-weight: bold;
  font-family: Arial, sans-serif;
  padding: 0;
  margin: 0;
}
#wrapper {
  border-left: 10px solid #fff;
  border-right: 10px solid #fff;
  width: 450px;
  text-align: center;
  margin: 0 auto;
  background-color: #b94047;
  padding: 0;
}
main {
  width: 300px;
  background: #ecf0f1;
  padding: 20px;
  border: 4px #fff solid;
  border-radius: 12px;
  margin:16px auto;
  display: flex;
  justify-content: space-between;
}
header {
  width: 350px;
  height:100px;
  background: #ecf0f1;
  border: 4px #fff solid;
  margin:0 auto;
  text-align: center;
  font-size: 2em;
  /* background-image: url("../img/gogo_header01.jpg"); */

}

footer {
    width: 350px;
    height:200px;
    margin-top: 20px;
    margin:0 auto;
    background-image: url("../img/footer01.jpg");
}

.panel img {
  width: 90px;
  hright: 110px;
  margin-bottom: 4px;
}

.stop {
  cursor: pointer;
  width: 90px;
  height: 32px;
  background: #ef454a;
  box-shadow: 0 4px 0 #d1483e;
  border-radius: 16px;
  line-height: 32px;
  text-align: center;
  font-size:14px;
  color: #fff;
  user-select: none;
}
.flex {
  width: 300px;
  box-sizing: border-box;
  display: flex;
  margin: 0 auto;
}

#gogo {
  margin:0;
}
.gogo_off {
}

#spin {
  cursor: pointer;
  width: 200px;
  height: 80px;
  background: #3498db;
  box-shadow: 0 4px 0 #2880b9;
  border-radius: 18px;
  line-height: 80px;
  text-align: center;
  color: #fff;
  user-select: none;
  margin:0 auto 10px 15px;;
}


.unmatched {
  opacity: 0.5;
}
.inactive{
  opacity: 0.5;
}


.flagarea {}


.flagarea div {
  margin: 10px auto;
  border: 1px solid #fff;
  text-align: center;
  padding:15px;
  font-size: 20px;
  width:250px;
  background: #EEE;
}

```