すでに作ったゲームサイトを、実際にJQueryからVueにしてみた

https://qiita.com/yanaitti/items/63859d786e146d3ed57f

以前、こちらのサイトでカードを利用したボードゲームをオンラインで遊べるようにしてみた。

https://qiita.com/yanaitti/items/338c4948810288d15cdb

そして、ここでは以前作ったJQueryのオンラインのボードゲームをVueにしてみたらこうなる。
と言うイメージを書いてみました。

# その後

## まずはVueの勉強

JQueryをVueかにしようと思い、オンラインサイトを色々見てみた、、、

結果的に、良く分からなかった。やはりホントは違い必要最小限しか書いていないため、Vue.jsやらVUE CLIとか良く分からなかったので、Amazonで電子書籍を買った

https://www.amazon.com/Vue-js-Nuxt-js%E8%B6%85%E5%85%A5%E9%96%80/dp/4798056596

この本は中々良かったです。内容としては、最初にVue.jsをCDNを使う方法。
簡単なVueならこれで問題なし。
そして、Vue cliを使った本格的なプロジェクト構築。
その後にNuxt.jsを使ったページ管理ができるフレームワークの構築。
最後にはFirebaseとの接続など書いてった。

けど、今回はVue cliのプロジェクトまでわかれば良かったので、そこまで読んでからあとは実践。

# その結果

まずはこちらがJQueryで頑張ったもの

## AsIs

``` html:index.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>THE GAME(ザ・ゲーム) ONLINE</title>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/style.css') }}">
    <script src="{{ url_for('static', filename='js/script.js') }}"></script>
  </head>
  <body>
    このゲームは１～５人用となっています。<br/>
    <div id='app2'>
      <div id='entry' style='display:none'>
        <input id="cName_inp" placeholder="ニックネームを入力してください"><br/>
        <br/>
        {% if gameid %}
        {% else %}
        <button id='createGame'>ゲームを作る(Make a Game)</button><br/>
        Game ID: <span id='gId'></span><br/>
        <input type='text' id='uriWgId' /><input type='button' id='clickCopy' value='copy'><br/>
        {% endif %}
        Game Status: <span id='gStatus'></span><br/>
        <hr/>
        <button id="joinGame">ゲームに参加する(Join the Game)</button><br/>
        {% if gameid %}
        <input type='hidden' id="gId_inp" value='{{ gameid }}'><br/>
        {% else %}
        <input id="gId_inp" placeholder="GameIDを入力してください"><br/>
        {% endif %}
        Your ID: <span id='cId'></span><br/>
        Your Name: <span id='cName'></span><br/>
        <hr/>
        <h2>Member Applying</h2>
        <span id='applyingList'></span>
        <br/>
      </div>
      <div id='sec1' style='display:none'>
        <hr/>
        <button id="startGame">ゲームを始める(Let's start the Game)</button><br/>
        <input type='checkbox' name='rule_type' id="rule_type" value='original'>オリジナルルール(Use Original Rule)<br/>
        1.&nbsp;直前がぞろ目ならぞろ目を出せる(大小制限なし)<br/>
        2.&nbsp;10単位での戻りが無制限(大小制限なし)<br/>
      </div>
      <div id='sec2' style='display:none'>
        <h2>Member Rotation</h2>
        <span id='routeList'></span>
        <hr/>
        Your Turn: <span id='yTurn'></span><br/>
        Your Cards:<br/>
        <span id='holdCards'></span><br/>
        <div id='sec3' style='display:none'>
          <input type='number' min='2' max='99' id="cardNum_inp" placeholder="番号を入力してください" style="width:200px;"><br/>
          <button id="putCard">カードを出す(Put the cards)</button><br/>
          <br/>
          <button id="nextPlayer">次の人へ(Go on Next user)</button><br/>
        </div>
        <hr/>
        Game Cards:<br/>
        <input type='radio' name='area' id='area' value='0'/>[100&nbsp;>&nbsp;2]&nbsp;<span id='gameCards0'></span><br/>
        <input type='radio' name='area' id='area' value='1'/>[100&nbsp;>&nbsp;2]&nbsp;<span id='gameCards1'></span><br/>
        <input type='radio' name='area' id='area' value='2'/>[1&nbsp;>&nbsp;99]&nbsp;<span id='gameCards2'></span><br/>
        <input type='radio' name='area' id='area' value='3'/>[1&nbsp;>&nbsp;99]&nbsp;<span id='gameCards3'></span><br/>
        Submited Cards in turn:<br/>
        <div id='submitCards'></div>
        <hr/>
      </div>
      <br/>
      <span id='message'></span><br/>
    </div>
  </body>
</html>
```

```javascript:script.js
var timeout = 1000;
var timer = '';

$(function() {
  var gId = '';
  var cId = '';
  $('#entry').show();

  $('#clickCopy').click(function(){
    var text = $('#uriWgId').val();
    var clipboard = $('<textarea></textarea>');
    clipboard.text(text);
    $('body').append(clipboard);
    clipboard.select();
    document.execCommand('copy');
    clipboard.remove();
  });

  // Create Game
  $('#createGame').click(function() {
    $('#message').empty();
    $.ajax('/create' + '/' + $('#cName_inp').val(),
      {
        type: 'get',
      }
    )
    .done(function(data) {
      $('#gId').text(data);
      $('#cId').text(data);
      $('#cName').text($('#cName_inp').val());
      $('#gStatus').text('waiting');
      $('#uriWgId').val(location.href + data + '/join');
      gId = data;
      cId = data;
      $('#sec1').show();
      timer = setTimeout(status_check(gId, cId), timeout)
    })
    .fail(function() {
      $('#message').text('エラーが発生しました');
    });
  });

  // Join Game
  $('#joinGame').click(function() {
    $('#message').empty();
    $.ajax('/' + $('#gId_inp').val() + '/join/' + $('#cName_inp').val(),
      {
        type: 'get',
      }
    )
    .done(function(data) {
      _tmp = data.split(' ,');
      $('#cId').text(_tmp[0]);
      $('#cName').text(_tmp[1]);
      $('#gStatus').text(_tmp[2]);
      gId = $('#gId_inp').val();
      cId = _tmp[0];
      timer = setTimeout(status_check(gId, cId), timeout)
    })
    .fail(function() {
      $('#message').text('エラーが発生しました');
    });
  });

  // Start Game
  $('#startGame').click(function() {
    $('#message').empty();
    $.getJSON('/' + gId + '/start/' + $('input:checkbox[name="rule_type"]:checked').val(),
      {
        type: 'get',
      }
    )
    .done(function(data) {
    })
    .fail(function() {
      $('#message').text('エラーが発生しました');
    });
  });

  // Put your card
  $('#putCard').click(function() {
    $('#message').empty();
    // put your card
    $.ajax('/' + gId + '/' + cId + '/set/' + $('input[name="area"]:checked').val() + '/' + $('#cardNum_inp').val(),
      {
        type: 'get',
      }
    )
    .done(function(data) {
      $('#message').text(data);
    })
    .fail(function() {
      $('#message').text('エラーが発生しました');
    });
    $('#cardNum_inp').val('')
  });

  // Next player
  $('#nextPlayer').click(function() {
    $('#message').empty();
    $.ajax('/' + gId + '/next',
      {
        type: 'get',
      }
    )
    .done(function(data) {
      // console.log(data)
      $('#message').text('次の人に移動しました');
    })
    .fail(function() {
      $('#message').text('エラーが発生しました');
    });
  });
});

var status_check = function(gId, cId){
  setTimeout(function(){
    $('#message').empty();
    // all status
    $.getJSON('/' + gId + '/status',
      {
        type: 'get',
      }
    )
    .done(function(data) {
      console.log(data)
      $('#gStatus').text(data.status);
      playerPos = 0;

      // Applying List
      $('#applyingList').empty();
      for(var pIdx in data.players){
        // console.log(data.players[pIdx])
        $('#applyingList').append(data.players[pIdx].nickname + '(' + data.players[pIdx].playerid + ')' + ',');
        if(cId == data.players[pIdx].playerid){
          playerPos = pIdx;
        }
      }

      if(data.status == 'started'){
        $('#entry').hide()
        $('#sec2').show()
        // route List
        $('#routeList').empty();
        for(var rIdx in data.routelist){
          if(data.routelist[rIdx].playerid == data.routelist[data.routeidx].playerid){
            $('#routeList').append('<b>&gt;' + data.routelist[rIdx].nickname + '</b><br/>');
          }else{
            $('#routeList').append(data.routelist[rIdx].nickname + '<br/>');
          }
        }

        // game cards
        $('#gameCards0').text(data.hightolow[0])
        $('#gameCards1').text(data.hightolow[1]);
        $('#gameCards2').text(data.lowtohigh[0]);
        $('#gameCards3').text(data.lowtohigh[1]);

        // hold cards
        $('#holdCards').text(data.players[playerPos].holdcards.join(','))
        // submit cards
        $('#submitCards').text(data.submit.join(','))

        // checking turn
        if(data.routelist[data.routeidx].playerid == cId){
          $('#yTurn').text('your turn')
          $('#sec3').show();
          if(data.submit.length >= 2){
            $('#nextPlayer').prop('disabled', false);
          }else{
            $('#nextPlayer').prop('disabled', true);
          }
        }else{
          $('#yTurn').text('not your turn')
          $('#sec3').css('display', 'none');
        }
      }
    })
    .fail(function() {
      $('#message').text('エラーが発生しました');
    });
    timer = setTimeout(status_check(gId, cId), timeout)
  }, timeout);
}
```

## ToBe

```vuejs:App.vue
<template>
  <v-container>
    <v-flex xs10>
    <v-card-title class="font-weight-bold">THE GAME</v-card-title>
    <v-card-text>このゲームは１～５人用となっています。</v-card-text>
      <br/>
    <div v-if="game == '***'">
      <v-text-field v-model="cName_inp" label="ニックネームを入力してください" filled></v-text-field><br/>
      <div v-if="!game_id">
        <v-btn v-on:click='createGame'>ゲームを作る(Make a Game)</v-btn><br/>
      </div>
      <div v-else>
        <v-btn v-on:click="joinGame">ゲームに参加する(Join the Game)</v-btn><br/>
      </div>
    </div>
    <div v-else>
      <!-- {{ game }} -->
      <div v-if="game.status == 'waiting'">
        Your ID: {{ client_id }}<br/>
        Your Name: {{ nick_name }}<br/>
        <!-- clint:{{ client }} -->
        <div v-if="!guest">
          <v-row>
            <v-col cols='12' sm='6'>
              <v-text-field v-model='uriWgId' readonly></v-text-field>
            </v-col>
            <v-col cols='12' sm='6'>
              <v-btn @click='clickCopy'>copy</v-btn>
            </v-col>
          </v-row>
          <br/>
          <v-btn v-on:click="startGame">ゲームを始める(Let's start the Game)</v-btn><br/>
          <v-checkbox v-model='rule_type'>
            <template v-slot:label>
              オリジナルルール(Use Original Rule)<br/>
              1.&nbsp;直前がぞろ目ならぞろ目を出せる(大小制限なし)<br/>
              2.&nbsp;10単位での戻りが無制限(大小制限なし)<br/>
            </template>
          </v-checkbox>
        </div>
      </div>
      <div v-else-if="game.status == 'started'">
        Your ID: {{ client_id }}<br/>
        Your Name: {{ nick_name }}<br/>
        Last cards: {{ game.stocks.length }}<br/>
        <br/>
        Your Turn: {{ (turn? 'Your turn':'Not your turn') }}
        <br/>
        Your Cards:<br/>
        <table>
          <tr>
            <td v-for="(value, key) in client.holdcards" :key="key">
              <ul>
                <template v-if='turn'>
                  <li v-on:click='putCard(value)' class='card'>
                    {{ value }}
                  </li>&nbsp;
                </template>
                <template v-else>
                  <li class='card'>
                    {{ value }}
                  </li>&nbsp;
                </template>
              </ul>
              <!-- <template v-if='turn'>
                <span v-on:click='putCard(value)' class='card'>
                  {{ value }}
                </span>&nbsp;
              </template>
              <template v-else>
                <span class='card'>
                  {{ value }}
                </span>&nbsp;
              </template> -->
            </td>
          </tr>
        </table>
        <br/>
        <br/>
        <template v-if="turn">
          <v-btn v-on:click="nextPlayer">次の人へ(Go on Next user)</v-btn><br/>
        </template>
      </div>
      <hr/>
      <br/>
      <h2>Member Applying</h2>
      <span v-for="(player, key) in game.players" :key="key">
        {{ player.nickname }}({{ player.playerid }})&nbsp;
      </span>
      <br/>
      <h2>Game Cards</h2>
      下から配置したい場所をエリアを選択してください<br/>
      <table class='board'>
        <tr v-for="(hightolow, key) in game.hightolow" :key="key" class='board'>
          <td v-on:click='setArea(key)' class='board'>
            <template v-if="key === sel_area">
              ★
            </template>
            <ul>
              <li v-for="(card, key) in hightolow" :key="key">
                <span class='card'>
                  {{ card }}
                </span>&nbsp;
              </li>
            </ul>
          </td>
        </tr>
      </table>
      <table>
        <tr v-for="(lowtohigh, key) in game.lowtohigh" :key="key" class='board' >
          <td v-on:click='setArea(key+2)' class='board'>
            <template v-if="key+2 === sel_area">
              ★
            </template>
            <ul>
              <li v-for="(card, key) in lowtohigh" :key="key">
                <span class='card'>
                  {{ card }}
                </span>&nbsp;
              </li>
            </ul>
          </td>
        </tr>
      </table>
    </div>
    {{ error_message }}
    </v-flex>
  </v-container>
</template>

<script>
const axios = require('axios');

export default {
  data: function(){
    return {
      cName_inp: '',
      game: '***',
      error_message: '',
      guest: false,
      turn: false,
      rule_type: false,
      game_id: '',
      uriWgId: 'aaaa',
      area: '',
      sel_area: '',
    }
  },
  mounted() {
    this.game_id = this.$route.query.game_id;
    this.guest = (this.$route.query.game_id != ''? true : false);
    console.log(process.env.NODE_ENV);
  },
  methods: {
    clickCopy: function() {
      navigator.clipboard.writeText(this.uriWgId)
      .then(() => {
          // console.log("copied!");
          alert('copied!');
      })
      .catch(e => {
          console.error(e);
      })
    },
    createGame: function() {
      axios.get(process.env.VUE_APP_API_BASE_URL+'create/'+this.cName_inp).then((res) => {
        this.game_id = res.data;
        this.client_id = this.game_id;
        this.guest = false;
        // this.uriWgId = 'http://localhost:5000/?game_id='+this.game_id;
        this.uriWgId = window.location.href+'?game_id='+this.game_id;
        console.log(res.data);
        setInterval(() => {
          this.status_check();
        }, 1000);
      })
    },
    joinGame: function() {
      axios.get(process.env.VUE_APP_API_BASE_URL+this.game_id+'/join/'+this.cName_inp).then((res) => {
        console.log(res.data);
        this.client_id = res.data.split(',')[0].trim();
        this.guest = true;
        setInterval(() => {
          this.status_check();
        }, 1000);
      })
    },
    startGame: function() {
      axios.get(process.env.VUE_APP_API_BASE_URL+this.game_id+'/start'+(this.rule_type?'/original':'')).then((res) => {
        this.game = res.data;
        // console.log(this.game);
      })
    },
    nextPlayer: function() {
      axios.get(process.env.VUE_APP_API_BASE_URL+this.game_id+'/next');
      this.error_message = '';
    },
    putCard: function(value) {
      if(this.sel_area === ''){
        alert('下から配置したいエリアを選択してください');
        return;
      }
      axios.get(process.env.VUE_APP_API_BASE_URL+this.game_id+'/'+this.client_id+'/set/'+this.sel_area+'/'+value)
      .then(res => {
        this.error_message = res.data;
      })
    },
    setArea: function(value) {
      this.sel_area = value;
    },
    status_check: function() {
      axios.get(process.env.VUE_APP_API_BASE_URL+this.game_id+'/status').then((res) => {
        this.game = res.data;
        // console.log(this.game);

        this.client = res.data.players.find((v) => v.playerid.trim() == this.client_id.trim());
        this.nick_name = this.client.nickname;
        if(this.game.status == 'started'){
          this.turn = (this.game.routelist[this.game.routeidx].playerid == this.client_id? true: false);
        }
      })
    },
  },
}
</script>
```

Vue.jsでも少しゴミゴミした書き方になってしまいましたが、JQueryとVueの違いを理解してもらうには十分かと思います。

# 結果的にわかったこと

- コードをhtml/jsで分けて考えず、htmlを1つのjsと考えながらプラグラミングできる便利さ
- データの反映について、いちいちjs側で出力ロジックを記載しなくても、必要な変数を更新すれば勝手に反映される

なんか、htmlへ表示する際に、わざわざ出力ロジックを書く必要がないのが大変便利。

ということで、今後はVue.jsをメインに使っていこうと思います。
また、Nuxt.jsや、Firebaseなどのリアルタイムデータベースを使ったものにバージョンアップしていきたいと思います。

なお、Vueで書き直した「THE GAME」はこちらのリンクから遊べます。

https://the-game-vue.herokuapp.com/