2020年9月8日
ゲーム作りで学ぶVue.js (7) Vuexのmutationsでstateを更新

ミューテーションでステートを更新

本章ではstore.jsで定めたステートをミューテーションで更新する処理を行います。

現状のstore.jsはこのようになっています。

src/store.js

import Vue from "vue"
import Vuex from "vuex"

Vue.use(Vuex)

const komaIndexList = ["fu", "kyosha", "keima", "gin", "kin", "kaku", "hisha"]

const store = new Vuex.Store({
  state: {
    komaList: {
      fu: { point: 1, label: "歩" },
      kyosha: { point: 3, label: "香" },
      keima: { point: 4, label: "桂" },
      gin: { point: 6, label: "銀" },
      kin: { point: 7, label: "金" },
      kaku: { point: 9, label: "角" },
      hisha: { point: 10, label: "飛" }
    },
    playerPoint: 0,
    comPoint: 0,
    playerKomadai: [],
    comKomadai: [],
    playerSelectable: Object.assign([], komaIndexList),
    comSelectable: Object.assign([], komaIndexList)
  },
  mutations: {}
})
export default store

空になっているmutationsに更新する関数を作っていきます。

src/store.js

mutations: {
  // Playerの選択可能な駒を更新
  updatePlayerSelectable(state, komaIndex) {
    const index = state.playerSelectable.indexOf(komaIndex)
    state.playerSelectable.splice(index, 1)
  },
  // Comの選択可能な駒を更新
  updateComSelectable(state, komaIndex) {
    const index = state.comSelectable.indexOf(komaIndex)
    state.comSelectable.splice(index, 1)
  },
  // Playerのポイントを加算
  addPlayerPoint(state, point) {
    state.playerPoint += point
  },
  // Comのポイントを加算
  addComPoint(state, point) {
    state.comPoint += point
  },
  // Playerの駒台に駒を追加
  addPlayerKoma(state, komaIndex) {
    state.playerKomadai.push(komaIndex)
  },
  // Comの駒台に駒を追加
  addComKoma(state, komaIndex) {
    state.comKomadai.push(komaIndex)
  }
}

たくさんありますが処理はどれもシンプルです。
全ての関数の一つ目の引数はstateになるのはVuexの決まりです。
ミューテーションを実行する時は2番目以降の引数を与えてれば良いです。

選択可能な駒の更新では渡ってきたkomaIndex(fuやkinなど)を配列から削除しています。
ポイント加算はポイントが渡ってくるので、それを足すだけです。
駒台に駒追加はkomaIndexを配列に追加しています。

またmutationsに定めた関数をミューテーションハンドラ、第2引数をペイロードと呼びます。

ミューテーションを実行

ミューテーションをコンポーネントから実行するには$store.commit(ミューテーションハンドラ, ペイロード)とすればできます。

betでComの駒が決まった後に使用できる駒の更新を呼び出します。

src/components/ShogiPoker.vue

bet() {
  // comの駒をランダムに決める
  // 省略

  // 使用可能な駒の更新
  this.$store.commit("updatePlayerSelectable", this.playerSelectingKoma) // 追加
  this.$store.commit("updateComSelectable", this.comSelectingKoma) // 追加

  this.battle()

これで使用した駒にclass="unselectable"がついて、表示が薄くなります。

07-010selectopacity

次はポイントの加算を呼び出します。
battleでPlayerが勝てばComの駒ポイントを加算、Comが勝てばPlayerの駒ポイントを加算します。
さらに駒台にもそれぞれ駒を追加します。

src/components/ShogiPoker.vue

// 対決
battle() {
  // PlayerとComの駒ポイントを比較
  const playerKomaPoint = this.komaList[this.playerSelectingKoma].point
  const comKomaPoint = this.komaList[this.comSelectingKoma].point
  if (playerKomaPoint > comKomaPoint) {
    this.phaseResult = "WIN"
    this.$store.commit("addPlayerPoint", comKomaPoint) // 追加
    this.$store.commit("addPlayerKoma", this.comSelectingKoma) // 追加
  } else if (playerKomaPoint < comKomaPoint) {
    this.phaseResult = "LOSE"
    this.$store.commit("addComPoint", playerKomaPoint) // 追加
    this.$store.commit("addComKoma", this.playerSelectingKoma) // 追加
  } else {
    this.phaseResult = "DRAW"
  }
}

これでPlayerが勝った時はComの駒ポイントが加算されて、駒台にComの駒が表示されました。

07-020playerpoint

リセットボタン

リセットボタンをクリックした時の処理をemitとミューテーションを使って実装します。
リセットするには全ての変数が初期値になれば良いので、ShogiPoker.vuedata()store.jsstateを全て初期値に戻します。

emitを使ってdata()を初期値にします。

src/components/ShogiPoker.vue

methods: {
  battle() {
    // 省略
  },
  // 以下追加
  // リセット
  reset() {
    this.phase = 1
    this.gameResult = null
    this.phaseResult = null
    this.playerSelectingKoma = null
    this.comSelectingKoma = null
    this.canBattle = false
  }
}
src/components/ShogiPoker.vue

<reset-btn @reset-data="reset" />
src/components/ResetBtn.vue

<template>
  <div @click="clickReset()" class="reset-btn">リセット</div>
</template>

<script>
export default {
  methods: {
    clickReset() {
      this.$emit("reset-data")
    }
  }
}
</script>

これでリセットボタンがクリックされたらShogiPoker.vueresetが呼ばれます。

次にstateの変数を初期化します。

src/store.js

mutations: {
  // Comの駒台に駒を追加
  addComKoma(state, komaIndex) {
    state.comKomadai.push(komaIndex)
  },
  // 以下追加
  // リセット
  reset(state) {
    state.playerPoint = 0
    state.comPoint = 0
    state.playerKomadai = []
    state.comKomadai = []
    state.playerSelectable = Object.assign([], komaIndexList)
    state.comSelectable = Object.assign([], komaIndexList)
  }
}
src/components/ResetBtn.vue

methods: {
  clickReset() {
    this.$store.commit("reset") // 追加
    this.$emit("reset-data")
  }
}

これでゲーム途中でもリセットボタンで最初の状態に戻すことができます。

ゲーム結果の表示

ゲームのおおよその機能は出来上がったので、残っている処理を仕上げていきます。

終了時にゲーム結果を表示します。

ゲーム結果を決めるsetGameResultを作ります。

src/components/ShogiPoker.vue

methods: {
  // リセット
  reset() {
    // 省略
  },
  // 以下追加
  // ゲーム結果の表示
  setGameResult() {
    if (this.playerPoint > this.comPoint) {
      this.gameResult = "勝ち"
    } else if (this.playerPoint < this.comPoint) {
      this.gameResult = "負け"
    } else {
      this.gameResult = "引き分け"
    }
  }
}

PlayerとComの点数を比べて勝ち or 負け or 引き分けをgameResultに代入しています。

これをbattleのあとにphaseが7なら呼び出します。

src/components/ShogiPoker.vue

// ○手目をクリック
bet() {
  // 省略

  if (this.phase < 7) {
    this.phase++
  } else {
    this.setGameResult() // 追加
  }
},

これで終了時にゲーム結果が表示されるようになります。

08-010gameresult

対決可能な条件

ゲーム自体はほぼ完成しました。
ただいくつかバグがあるので手を加えていきます。

現状では○手目をクリックすると必ずbetが動いてしまいます。
ゲーム終了後と駒が未選択の時は処理が行われないようにしなければなりません。
このためにdata()にあるcanBattleで制御していきます。

betの最初でcanBattleがfalseならその後の処理に進まないようにreturnさせます。

src/components/ShogiPoker.vue

// ○手目をクリック
bet() {
  // 対決不可なら処理をしない
  if (!this.canBattle) {
    return
  }

  // comの駒をランダムに決める
  // 省略
},

そして必要な箇所でcanBattleを切り替えていきます。

trueにするのはPlayerが駒を選択したときです。

src/components/ShogiPoker.vue

// playerが駒を選択
select(komaIndex) {
  if (this.playerSelectable.includes(komaIndex)) {
    this.playerSelectingKoma = komaIndex
    this.canBattle = true // 追加
  }
},

そしてbetが実行されると決まった直後にfalseにすれば、再び駒が選択されるまでは処理されません。

src/components/ShogiPoker.vue

// ○手目をクリック
bet() {
  // 対決不可なら処理をしない
  if (!this.canBattle) {
    return
  }  
  this.canBattle = false // 追加

  // 省略
},

これで対決の制御ができてバグがなくなりました。

最後に体裁を整えるためにPlayerが駒を選択したら、Comが前に選んでいた駒の非表示にします。

src/components/ShogiPoker.vue

// playerが駒を選択
select(komaIndex) {
  if (this.playerSelectable.includes(komaIndex)) {
    this.playerSelectingKoma = komaIndex
    this.canBattle = true
    this.comSelectingKoma = null // 追加
  }
},

これで全て完成しました。
Vueでの開発のイメージがついてもらえたら幸いです。

ここまでのコードはこちらにあります。

番外編 としてこのゲームの最高点を取る方法を考えたのでこちらもご覧ください。

連載記事