Vue.js初心者が学習のためにシンプルなマインスイパーを作った

# はじめに
この記事は 2019 年まで素の JavaScript でコーディングしていた人間が初めて Vue.js を学習したときの記録です:notepad_spiral:
↓のURLにアクセスするとアプリに触れます。
https://vue-mine.firebaseapp.com

# 何を作ったか？
こんな感じのアプリです。
![vue-mine.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/150372/56cedea5-55fb-cd04-a6b4-cb0ac5f9c855.gif)

見た目はあれです。今回は簡単な仕上がりを目指しました:thumbsup:（適当）
データバインディングの動きの確認をする程度の目的ですので、セルは単純な色分けで区別しています。 

# 開発環境
Windows10
Node.js v12.13.1
Vue.js 2.6.10
vue/cli 4.1.1
Atom 1.41.0
Github https://github.com/t-ube/vue-mine

# 作成時につまづいた点と答え
## Q. まずどのファイルをいじればいいのかわからない
### A. <プロジェクトフォルダ>\src\App.vue を編集する

よっしゃさっそく作り始めたるわーと思い、コマンドラインで

```
vue create <プロジェクト名>
```

と打ち込んだ直後に僕は手が止まりましたが、 @567000 様の記事を参考にして、まずは `App.vue` 内の `<template>` を色々といじるところから始めました。
[Vue.js を vue-cli を使ってシンプルにはじめてみる][1]
[1]:https://qiita.com/567000/items/dde495d6a8ad1c25fa43

## Q. 表示条件の分岐ってどうやる？
### A. 表示する要素のタグ内に制御用のif文を書く

Vue.js ではタグ内の v-if ディレクティブに指定した値が評価されて表示が切り替わります。
:point_down:のコードを例にとります。

```App.vue
<p v-if="gameover">Game Over</p>
<p v-else-if="complete">Clear</p>
<p v-else>Please click green cell</p>
```

まず、変数 `gameover` の値が真のときに `Game Over` が表示されます。
それ以外でかつ変数 `complete` が真のときに `Clear` が表示されます。
それ以外のときには `Please click green cell` が表示されます。

素の JavaScript で getElementById で要素を取得してから表示/非表示の切り替えとか面倒なことをしていたのが嘘のようです。Vue.js は素晴らしいです:relaxed:

## Q. マインスイーパーのセルの並べ方ってどうやる？
### A. v-for で要素を並べて grid-template を使う

Vue.js / CSS の合わせ技です。

```App.vue
<template>
～略～
<div id="map">
      <div v-for="cell in grid_size*grid_size"
～略～
</template>

<script>
～略～
#map {
  --grid-size: 6;
  display: grid;
  grid-template-columns: repeat(var(--grid-size), 30px);
  grid-template-rows: repeat(var(--grid-size), 30px);
  justify-content: center;
  align-content: end;
  padding: auto;
  margin: auto;
}
～略～
```
Vue.js では要素のタグ内に v-for ディレクティブを付けることで、同じ要素を繰り返し配置できます。

@miyauchoi 様の記事を参考にさせていただきました。
[200行のVue.jsでスネークゲームを作った][2]
[2]:https://qiita.com/miyauchoi/items/f753e5fa0209ab2034dd

## Q. for 文で要素配置したらエラーになるんだけど？
### A. v-bind:key が必要
v-for ディレクティブで要素を連続配置したらコンパイルエラーです。本当にありがとうございました。

```
./src/App.vue
Module Error (from ./node_modules/eslint-loader/index.js):
error: Elements in iteration expect to have 'v-bind:key' directives (vue/require-v-for-key) at src\App.vue:8:7:
   6 |     <p v-else>Please click green cell</p>
   7 |     <div id="map">
>  8 |       <div v-for="cell in grid_size*grid_size"
     |       ^
   9 |        v-on:click="dig_cell(cell-1)"
  10 |        :class="{
  11 |          cell: true,
```

理由はエラーメッセージに書かれているので一目瞭然ですね！
僕は軽く１時間は悩みましたがね。
:point_down:のようにコードを直しました。

```App.vue
<div v-for="cell in grid_size*grid_size"
       v-on:click="dig_cell(cell-1)"
       v-bind:key="cell.id"
       :class="{
```
要素に値をバインドさせるためにidが必須とのことなので、v-for ディレクティブが動作しない怪現象に悩んだら v-bind:key を指定しているかを確認しましょう。

## Q. クラスってどうやって作るのかわからない
### A. コンポーネント化する（多分）
Google 先生で「Vue.js クラス」って検索しても CSS しかでてきません。
データを構造化したいときはどうするのでしょうか:disappointed_relieved::sos:
コンポーネント化するっぽいのですが。
これは諦めて今後の課題としました。
というわけで、セルごとのパラメータ（周囲の地雷の数、地雷フラグ、掘削フラグ）はすべて独立した配列で管理しています。

```App.vue
data () {
    return {
～略～
      surround_bomb: [],
      is_bomb: [],
      is_dig: [],
～略～
    }
```
気持ちが悪いですね。早いところ修正したいと思います。

## Q. セル要素の二次元座標はパラメータ化する？
### A. 座標パラメータは不要で、配列サイズから計算する

配列のインデックスと座標の変換関数を用意します。
配列のインデックスを得るための関数 `get_index` に渡した座標が二次元座標の領域を超えている場合、存在しないものとして -1 を返しています。

```App.vue
get_cell_x(index){
   return (Math.floor(index%this.grid_size));
},
get_cell_y(index){
   return (Math.floor(index/this.grid_size));
},
get_index(x,y){
   if(x < 0 || y < 0) return -1;
   else if(x>=this.grid_size || y>=this.grid_size) return -1;
   return ((y*this.grid_size)+x);
}
```

セルの周囲の地雷を数えるための関数 `count_surrounding_bombs` に配列のインデックスを渡すと内部で座標に変換し、周囲の 8 セル分をチェックします。 

```App.vue

is_bomb_index(index){
   return (this.is_bomb[index] == 1);
},
is_bomb_cell(x,y){
   return (this.is_bomb_index(this.get_index(x,y)));
},
count_surrounding_bombs(index) {
   let x = this.get_cell_x(index);
   let y = this.get_cell_y(index);
   let counts = 0;
   if(this.is_bomb_cell(x-1,y-1)) counts += 1;
   if(this.is_bomb_cell(x,y-1))   counts += 1;
   if(this.is_bomb_cell(x+1,y-1)) counts += 1;
   if(this.is_bomb_cell(x-1,y))   counts += 1;
   if(this.is_bomb_cell(x+1,y))   counts += 1;
   if(this.is_bomb_cell(x-1,y+1)) counts += 1;
   if(this.is_bomb_cell(x,y+1))   counts += 1;
   if(this.is_bomb_cell(x+1,y+1)) counts += 1;
   return counts;
}
```

# 感想
Vue.js のデータバインディングって強力ですね。
ミニゲームを簡単に作れてしまうのがすごいです:relaxed:
次はコンポーネント化にチャレンジしてみます。