Vue.jsでじゃんけんゲーム作りました

## はじめに
この記事は以前私が投稿した記事の続編となります。合わせて読んで頂けると嬉しいです:relaxed:
[Vueを扱えるようになりたいけどJavaScript書いたことがなかったので、JavaScriptとVueを同時に勉強する。](https://qiita.com/Mitsuzara/items/039cb367c4de7683d2d0)
今回は、**前回のコードを改修〜見た目実装までの手順を記載**しました！
完成品はこちらとなります:sparkles:
![ezgif-4-dce3ca87fab9.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/459087/57ad34b2-82a0-b8e4-2630-6eac0d993952.gif)

## 前回のコードを改修
まずは前回のコードの手直しから始めましょう。

前回の記事で改修案のコメントをくださった方ありがとうございます。
参考にさせていただきながら、進めていきたいと思います！

前回のコードはこちら

```game.vue 
<template>
  <div>
    <p>じゃんけん！</p>
    <button @click='guu'>グー</button>
    <button @click='tyoki'>チョキ</button>
    <button @click='paa'>パー</button>
    <p>わたしは{{ watashi }}</p>
    <p>あいては{{ aite }}</p>
    <p>この勝負…{{ result }}</p>
  </div>
</template>
<script>
export default {
  name: 'Game',
  data () {
    return {
      watashi: '',
      watashi_hand: '',
      aite: '',
      aite_hand: '',
      result: ''
    }
  },
  methods: {
    guu () {
      this.watashi = 'グー'
      this.watashi_hand = 0
      this.aite_no_te()
      this.syouhai()
    },
    tyoki () {
      this.watashi = 'チョキ'
      this.watashi_hand = 1
      this.aite_no_te()
      this.syouhai()
    },
    paa () {
      this.watashi = 'パー'
      this.watashi_hand = 2
      this.aite_no_te()
      this.syouhai()
    },
    aite_no_te () {
      switch (Math.floor(Math.random() * 3)) {
        case 0:
          this.aite = 'グー'
          this.aite_hand = 0
          break
        case 1:
          this.aite = 'チョキ'
          this.aite_hand = 1
          break
        case 2:
          this.aite = 'パー'
          this.aite_hand = 2
          break
      }
    },
    syouhai () {
      switch (((this.watashi_hand - this.aite_hand + 3) % 3)) {
        case 0:
          this.result = '引き分けっ'
          break
        case 1:
          this.result = 'あなたの負け…'
          break
        case 2:
          this.result = 'あなたの勝ち！'
          break
      }
    }
  }
}
</script>
```
この後、見た目を実装するために画像を挿入したりするので、そもそも必要のない部分が出てきます。
その部分に関しては改修の余地があっても**手直しせずに削除し、残す部分だけ手を加えて**いきます。

改修後はこちら

```game.vue
<template>
  <div>
    <p>じゃんけん！</p>
    <button v-for="item in shapes" :key="item.key" @click="select(item.key)">
      {{ item.label }}
    </button>
    <p>この勝負…{{ resultText }}</p>
  </div>
</template>
<script>
export default {
  name: 'Game',
  data () {
    return {
      shapes: [
        { label: 'グー', key: 0 },
        { label: 'チョキ', key: 1 },
        { label: 'パー', key: 2 }
      ],
      resultText: '',
      player: '',
      bot: ''
    }
  },
  methods: {
    select (key) {
      this.player = key
      this.result()
    },
    result () {
      this.bot = Math.floor(Math.random() * 3)
      switch (((this.player - this.bot + 3) % 3)) {
        case 0:
          this.resultText = '引き分けっ'
          break
        case 1:
          this.resultText = 'あなたの負け…'
          break
        case 2:
          this.resultText = 'あなたの勝ち！'
          break
      }
    }
  }
}
</script>
```

解説していきます。
`watashi`と`aite`に関しては画像やボタンで手がわかるように表現したいので、結果的にいらなくなるため消しました。
グー、チョキ、パーとそれに関連した数字を今までは`methods`内にその都度書いていましたが、これを`data`内に一箇所にまとめます。

```javascript

data () {
  return {
    shapes: [
      { label: 'グー', key: 0 },
      { label: 'チョキ', key: 1 },
      { label: 'パー', key: 2 }
    ],
    ...
```
グーの場合は0というふうに既に決まっているので、配列にまとめることができました。これでいちいち`グー`とか`=0`とか書かなくてよくなりました。

今度はボタンの部分を見ていきます。

```vue
<button @click='guu'>グー</button>
<button @click='tyoki'>チョキ</button>
<button @click='paa'>パー</button>
```
私はボタンタグを三つ配置していましたが、こちらは`for`文を使う事で**ボタンタグを一つに**することができます。

```vue
<button v-for="item in shapes" :key="item.key" @click="select(item.key)">
  {{ item.label }}
</button>
```
こうですね。
さらに`for`文を使用し`@click="select(item.key)"`となったことで、**クリック時の関数も一つに**することができます。

```javascript
guu () {
  this.watashi = 'グー'
  this.watashi_hand = 0
  this.aite_no_te()
  this.syouhai()
},
tyoki () {
  this.watashi = 'チョキ'
  this.watashi_hand = 1
  this.aite_no_te()
  this.syouhai()
},
paa () {
  this.watashi = 'パー'
  this.watashi_hand = 2
  this.aite_no_te()
  this.syouhai()
},
```
三つもあった関数が記述が

```javascript
select (key) {
  this.player = key
  this.result()
},
```
これ一つだけになります。
`syouhai`はダサいので`result`に名前を変更。`watashi_hand`もダサいので`player`に名前を変更。　`aite_no_te()`の処理は`bot`に名前を変えつつ`result`内に移動させます。

```javascript
result () {
  this.bot = Math.floor(Math.random() * 3)
  switch (((this.player - this.bot + 3) % 3)) {
    case 0:
      this.resultText = '引き分けっ'
      break
    case 1:
      this.resultText = 'あなたの負け…'
      break
    case 2:
      this.resultText = 'あなたの勝ち！'
      break
  }
},
```
`this.bot`ってせずに`swicth`文の中に直接`Math.floor(Math.random() * 3)`を書いてもいいんですが、画像の出しわけで`this.bot`は使いたいのでこういう書き方をしています。

## 画像の挿入とアニメーションの実装
さて、現時点では相手の手が表示されない為わからず、勝敗の結果しか表示されなくなっています。
今度は相手の手をグーチョキパー三つの画像で表示させていきます。

アニメーションの方法ですが、画像を配列に入れてそれを順番に表示って方法は既にされているかたがちらほら見かけてしまったので、
同じだとつまらなかったので私は**cssで頑張って**いきます。

まずはグーチョキパーの画像を用意しましょう。
私はフリー素材を拝借して色を塗りました。ちなみに、画像は`src/assets/`に入れましょう。

シンプルに表示させます。画像もちょうどいいサイズにcssをいじっておきました。

```vue
<p>じゃんけん！</p>
<img src="@/assets/rock.png">
<img src="@/assets/scissors.png">
<img src="@/assets/paper.png">
```
表示はこんな感じ。
![スクリーンショット 2019-08-01 17.58.56-min.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/459087/3220cc4a-e45a-8299-1ec0-aba825bc872f.png)

これを`bot`に入る数字に合わせて表示を出しわけます。ついでにボタンの横ではなくて上に表示したいので`div`で囲います。

```vue
<div>
  <img v-if="this.bot === 0" src="@/assets/rock.png">
  <img v-if="this.bot === 1" src="@/assets/scissors.png">
  <img v-if="this.bot === 2" src="@/assets/paper.png">
</div>
```
![スクリーンショット 2019-08-01 18.05.32-min.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/459087/58c8fc96-e171-2423-bbde-e15092406385.png)

これでボタンをかちかちするとランダムに画像が一つだけ表示されるようになりました。

ボタンを押したあとはこれで問題ないのですが、ボタンを押すまでの間は画像を**どぅるるるる**(伝われ)ってしたいのでまずはボタン押す前と後で表示を出しわけましょう。

```vue
<div v-if="pon">
  <img v-if="this.bot === 0" src="@/assets/rock.png">
  <img v-if="this.bot === 1" src="@/assets/scissors.png">
  <img v-if="this.bot === 2" src="@/assets/paper.png">
</div>
<div v-else>
  <img src="@/assets/rock.png">
  <img src="@/assets/scissors.png">
  <img src="@/assets/paper.png">
</div>
```

```javascript
data () {
  return {
    shapes: [
      { label: 'グー', key: 0 },
      { label: 'チョキ', key: 1 },
      { label: 'パー', key: 2 }
    ],
    resultText: '',
    player: '',
    pon: false
  }
},
methods: {
  select (key) {
    this.pon = true
    this.player = key
    this.result()
  },
```
ボタンを押す前と押した後で分けるために`pon`を実装しました。デフォルトでは`pon`は`false`で、ボタンをクリックすると`true`になりどれか一枚の画像が表示される仕組みです。

`pon`が`false`の時、3枚の画像を**どぅるるる**と表示させたいのですが、これはcssの`animation`で実装してきます。
cssに下記の記述を追加して、各画像の`class`に`animation`を追加します。

```vue
<div v-else>
  <img class="animation" src="@/assets/rock.png">
  <img class="animation" src="@/assets/scissors.png">
  <img class="animation" src="@/assets/paper.png">
</div>
```

```css
.animation {
  animation-name: img;
  animation-duration: 0.3s;
  animation-iteration-count: infinite;
  opacity: 0;
}
@keyframes img {
  0% {
    opacity: 0;
  }
  25% {
    opacity: 1;
  }
  50% {
    opacity: 0;
  }
}
```
こんな感じ。
![ezgif-4-4d39de42eeb1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/459087/bc0bb7d9-36f4-f6a7-7469-8c5048bfb181.gif)
画像が出たり消えたりしていますね。
次は各画像に`delay`をかけて表示するタイミングをずらして、3枚の画像を重ねます。

```vue
<div class="img-area">
  <div v-if="pon">
    ...
  </div>
  <div v-else>
    <img class="animation" src="@/assets/rock.png">
    <img class="animation scissors" src="@/assets/scissors.png">
    <img class="animation paper" src="@/assets/paper.png">
  </div>
</div>
```

```css
.img-area {
  position: relative;
  margin-bottom: 10px;
}
.animation.scissors {
  position: absolute;
  top: 0;
  left: 45%;
  animation-delay: 0.1s;
}
.animation.paper {
  position: absolute;
  top: 0;
  left: 45%;
  animation-delay: 0.2s;
}
```
![ezgif-4-dba5ce2f7029.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/459087/9172395a-68ed-b6c5-0029-df427168b198.gif)
完璧ですね。GIFなんでちょっと違和感ありますが、実際だと綺麗に**どぅるるる**してくれてます:thumbsup:

## ここでちょっと機能追加
じゃんけんをやり直すには、現状画面をリロードするしかなく不便なので「もう一度！」ボタンを実装します。

```vue
<button @click="retry">もう一度！</button>
```

```javascript
retry () {
  this.pon = false
  this.resultText = ''
  this.player = ''
},
```
これでok。`retry()`で`deta`内を最初の状態に戻しています。

## Bootstrapを使ってデザインを良くする
あとやりたい事は、
①ボタン周りのデザインをかっちょよくしたい:star:
②ボタンをクリックした時に、色を変えるなどして何のボタンをクリックしたかわかりやすくしたい:bulb:
この2点です。どちらも`Bootstrap`でちゃちゃっとできちゃいますので実装しちゃいます。
`Bootstrap`の適用方法・使い方に関してはここで説明すると長くなると思い、**別で記事を作成しました**ので興味がある方は参考にしてください。
[Vue.jsにBootstrapを適用させる方法](https://qiita.com/Mitsuzara/items/34d413698d1d88033ec0)

こちらの公式ページのテンプレートを使用します。
https://bootstrap-vue.js.org/

```vue
<div>
 <b-button>
   :pressed.sync="item.state" variant="outline-success"
   v-for="item in shapes" :key="item.key"
   @click="select(item.key)">
   {{ item.label }}
  </b-button>
</div>
<b-button variant="outline-danger" @click="retry">もう一度！</b-button>
```
ボタンがクリックされたらstateがtrueになり、色が緑色のままになるようにしました。
また、shapesにもデフォルトでstateがfalseになるように記述を追加しています。

ついでに全体的なcssをちょいちょいと調整します。
vueのlogoは、「vueで作ったよ！」って事が一目でわかるので残しておきました。ちょっとデカかったのでサイズはいじりましたが:sweat_smile:

完成コードはこちら:sparkles:

```vue
<template>
  <div>
    <p>じゃんけん！</p>
    <div class="img-area">
      <div v-if="pon">
        <img v-if="this.bot === 0" src="@/assets/rock.png">
        <img v-if="this.bot === 1" src="@/assets/scissors.png">
        <img v-if="this.bot === 2" src="@/assets/paper.png">
      </div>
      <div v-else>
        <img class="animation" src="@/assets/rock.png">
        <img class="animation scissors" src="@/assets/scissors.png">
        <img class="animation paper" src="@/assets/paper.png">
      </div>
    </div>
    <div>
      <b-button
        :pressed.sync="item.state" variant="outline-success"
        v-for="item in shapes" :key="item.key"
        @click="select(item.key)">
        {{ item.label }}
      </b-button>
    </div>
    <b-button variant="outline-danger" @click="retry">もう一度！</b-button>
    <div>
      この勝負…<br /><span class="text">{{ resultText }}</span>
    </div>
  </div>
</template>

<script>
export default {
  name: 'Game',
  data () {
    return {
      resultText: '',
      player: '',
      pon: false,
      shapes: [
        { label: 'グー', key: 0, state: false },
        { label: 'チョキ', key: 1, state: false },
        { label: 'パー', key: 2, state: false }
      ]
    }
  },
  methods: {
    select (key) {
      this.pon = true
      this.player = key
      this.result()
    },
    result () {
      this.bot = Math.floor(Math.random() * 3)
      switch (((this.player - this.bot + 3) % 3)) {
        case 0:
          this.resultText = '引き分けっ'
          break
        case 1:
          this.resultText = 'あなたの負け…'
          break
        case 2:
          this.resultText = 'あなたの勝ち！'
          break
      }
    },
    retry () {
      this.pon = false
      this.resultText = ''
      this.player = ''
      this.shapes = [
        { label: 'グー', key: 0, state: false },
        { label: 'チョキ', key: 1, state: false },
        { label: 'パー', key: 2, state: false }
      ]
    }
  }
}
</script>

<style scoped>
button {
  margin: 10px 5px;
  box-shadow: none !important;
}
img {
  width: 10%;
}
.animation {
  animation-name: img;
  animation-duration: 0.3s;
  animation-iteration-count: infinite;
  opacity: 0;
}
.animation.scissors {
  position: absolute;
  top: 0;
  left: 45%;
  animation-delay: 0.1s;
}
.animation.paper {
  position: absolute;
  top: 0;
  left: 45%;
  animation-delay: 0.2s;
}
.img-area {
  position: relative;
  margin-bottom: 10px;
}
.text {
  font-size: 50px;
}
@keyframes img {
  0% {
    opacity: 0;
  }
  25% {
    opacity: 1;
  }
  50% {
    opacity: 0;
  }
}
</style>
```

![ezgif-4-dce3ca87fab9.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/459087/57ad34b2-82a0-b8e4-2630-6eac0d993952.gif)
画像周りとか、retry()とかdata()とか、もっときれいに短く書けそうな気がします…
勝敗結果を保存して戦歴表示とかも…
でも、やりたかったことができて一旦キリがいいのでここでおしまいにします。

## さいごに
ここまで読んでくださってありがとうございました。
どなたかの参考になれば嬉しいです！
誤字脱字・発信した情報に間違い等ございましたら、指摘して頂けますと幸いです:bow:
また、説明にわかりづらい点がございましたら、改善致しますのでぜひコメントください:pray: