数学ガールの秘密ノート／ビットとバイナリー 第4章を実装してみた×2

#はじめに

『数学ガールの秘密ノート／ビットとバイナリー』ネタ第二段です。

第4章では「フリップ・トリップ」というゲームが話題となっています。どんなゲームかは説明するより見た方が早いので、

負けとなる場合
![fliptrip-error.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/141091/e5c26f2f-6116-11db-8de7-9a5e4d5cb61f.gif)

勝ちとなる場合
![fliptrip-fulltrip.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/141091/afd01430-7bf0-f1ec-6f55-4e491a45c382.gif)

つまり、

- 石を一つずつ反転させる
- 同じパターンを二回出したら負け
- エラーにならずに全パターンを出したら勝ち

例によってTwitterで誰かが実装しているのを見かけたので、「今度こそWebpack使ってイマドキなJSを書こう」と
思ったものの試作品をin-browser Babel transformer使って書いてみたら100行ぐらいで書けたので、「これ別にガチでモジュール化する必要ねえな」と考え直し

Reactで書いたものをVueでも書いてみることにしました。コードは以下に置いてあります。
https://gist.github.com/junjis0203/4b26b3ef265879aa264f139f2ec13b5a

# React実装

React版にはプロトタイプがありこちらになります。
https://gist.github.com/junjis0203/0910a106e7a8da2304f4fa648a6f9ed0

当然、「石」、「石をまとめたゲーム」というコンポーネントを作り実装したわけですが、とりあえず動かすのを目的に書いていたので「多分これReactの流儀に反してるな」と作りながら思っていました。具体的には

- Stoneが状態を持っていいのか
- Gameが持つ石全体の情報およびこれまでの履歴ってReact的な状態？とりあえず普通のインスタンス変数として持つか（あかん）
- 石全体情報（Gameのstones）って普通に書き換えちゃっていいのかな

そういえば[Reactのチュートリアル](https://ja.reactjs.org/tutorial/tutorial.html)は○×ゲームだけどどうなってたっけと読み直してみたら疑問に思っていたことはすべて書いてありました。すなわち、

- Stoneにはstateは持たせない。親コンポーネントからpropsで渡されたものを使って自身を描画する。クリックされたことは親に上げて親で状態の更新を行うべし。さらに描画以外にメソッドも必要ないのでクラスではなく関数で書けばよい。
- 履歴も状態である。
- ミュータブルはよくない。イミュータブルにすべし。先にArray.slice()して複製を行え。

ということでチュートリアルに従って書き換えたら、
大体チュートリアルと同じプログラム構造になりました(笑)[^1]
https://gist.github.com/junjis0203/4b26b3ef265879aa264f139f2ec13b5a#file-fliptrip-react-html

[^1]: 履歴の意味は異なりますが。

# Vue.js実装

次にReact版をVueに移植。
というより、Vueを使ったことがなかったので簡単なプログラムを移植してみてReactとVueの違いを眺めるというのが目的です。

というわけで[ガイド](https://jp.vuejs.org/v2/guide/)を読みながらまったり移植しましたが2時間ぐらいでできました。
https://gist.github.com/junjis0203/4b26b3ef265879aa264f139f2ec13b5a#file-fliptrip-vue-html

Reactとここが違うなと思った個所は

- dataの書き換えが普通の代入文でできる（裏では黒魔術が使われているようですが）
- render関数ではなくtemplateなのであれこれできない。computedを使えばどうにかはできる。まあ処理が書けるとすぐに複雑化するのでtemplate式の方がいいと思いますが。と思ったら[renderも書ける](https://jp.vuejs.org/v2/guide/render-function.html)のですね。

なおVue的には`app.number = 4`とするとReactiveに石の数が増える（ゲーム状態はリセット）ようにすべきな気がしますがそこはできていません。
→Vueのドキュメント読み直してやる方法を把握・実装しました。後述。

## 状態を保持するべきコンポーネントとイベントディスパッチの話

で、また浮上したのが「Vueの場合、状態は石が持つの？ゲームが持つの？」ということで、ガイドをざっと眺めた感じでは特に記述はなく、
「コンポーネントにdataを定義していいのだから石が持っていいのだろう。でもゲームにクリックされたことを使えないといけないし」とガイドを探したところ、[イベントディスパッチの方法](https://jp.vuejs.org/v2/guide/components.html#%E5%AD%90%E3%82%B3%E3%83%B3%E3%83%9D%E3%83%BC%E3%83%8D%E3%83%B3%E3%83%88%E3%81%AE%E3%82%A4%E3%83%99%E3%83%B3%E3%83%88%E3%82%92%E8%B3%BC%E8%AA%AD%E3%81%99%E3%82%8B)を見つけました。これを使うと石から盤、さらにゲームへのクリック通知が以下のように書けます。

```js:fliptrip-vue.html抜粋
  Vue.component('stone', {
    methods: {
      handleClick: function() {
        this.toggle = !this.toggle;
        this.$emit('stone-click', this.index);
      }
    },
    template: `
      <div class="stone-wrapper">
        <div
          class="stone"
          v-bind:class="classObject"
          v-on:click="handleClick"
        />
      </div>
    `,

  Vue.component('board', {
    methods: {
      handleClick: function(i) {
        this.$emit('stone-click', i);
      }
    },
    template: `
      <div className="board">
        <stone
          v-for="stone in stones"
          v-bind:key="stone.key"
          v-bind:index="stone.index"
          v-on:stone-click="handleClick"
        ></stone>
      </div>
    `

  Vue.component('game', {
    methods: {
      handleClick: function(i) {
        // 反転、履歴への積み、勝ち負け判定
      }
    },
    template: `
      <div class="game">
        <board
          v-bind:number="number"
          v-on:stone-click="handleClick"
        />
        <div class="message">
          {{ message }}
        </div>
      </div>
    `
```

Reactの場合、イベントハンドラのプロパティが渡されてるか調べてから呼ばないといけませんが、この仕組みなら渡されなくてもイベントが無視されるだけなので安全ですね。

## number再代入時に石の数が増えるようにする処理

単純移植したものだと`app.number = 4`としても石の数がReactiveに変わらなかったのですがドキュメントを読んでたらReactiveにする方法がわかりました。

元々の実装では以下のように渡された`number`からdataとして`stones`を作っていました。これだとnumberが変わってもstonesは変更されません。

```js:friptrip-vue.html修正前抜粋
  Vue.component('board', {
    props: ['number'],
    data: function() {
      const stones = [];
      for (let i = 0; i < this.number; i++) {
        stones.push({index: i});
      }
      
      return {
        stones: stones
      };
    },
    template: `
      <div className="board">
        <stone
          v-for="stone in stones"
          v-bind:key="stone.index"
          v-bind:index="stone.index"
          v-on:stone-click="handleClick"
        ></stone>
      </div>
    `
```

stonesを作っているのはv-forが要素繰り返しだけでインデックス的な繰り返しが書けないと思っていたためですが、[実はできる](https://jp.vuejs.org/v2/guide/list.html#%E7%AF%84%E5%9B%B2%E4%BB%98%E3%81%8D-v-for)ことがわかりました。
整数ループを使うと以下のように簡潔に書くことができます。

```js:frriptrip-vue.html修正後抜粋
  Vue.component('board', {
    props: ['number'],
    template: `
      <div className="board">
        <stone
          v-for="i in number"
          v-bind:key="i"
          v-bind:index="i"
          v-on:stone-click="handleClick"
        ></stone>
      </div>
    `
```

これで石の数は変わるわけですがゲーム状態をリセットしないといけません。これには[ウォッチャ](https://jp.vuejs.org/v2/guide/computed.html#%E3%82%A6%E3%82%A9%E3%83%83%E3%83%81%E3%83%A3)を使用します。

```js:frriptrip-vue.html修正後抜粋
  Vue.component('game', {
    props: ['number'],
    watch: {
      // reset game state when number is reassigned
      number: function(newNumber, oldNumber) {
        const stones = Array(newNumber).fill(false);
        this.history = [stones.slice()];
        this.fulltrip = this.error = false;
      }
    },
```

また石も全部白に戻さないといけません。`$children`を使うことで実装が行えますが、[$childrenは使用を控えてください](https://jp.vuejs.org/v2/api/#parent)となっていますね[^2]。より良い方法を見つけたら書き直したいと思います。

[^2]: ドキュメントを呼んだわけではなく、デベロッパーツールでinspectしてて「あっこれ使えばいけそう」見つけたもので(笑)

```js:frriptrip-vue.html修正後抜粋
  Vue.component('board', {
    props: ['number'],
    watch: {
      // reset stone state when number is reassigned
      number: function(newNumber, oldNumber) {
        for (let c of this.$children) {
          c.toggle = false;
        }
      }
    },
```

# おわりに

というわけで秘密ノート／ビットとバイナリーで紹介されているフリップトリップをReactとVueで実装してみました。Vueは初見でしたがなかなかおもしろいなと思いました。

さて、フリップトリップですが必勝法があります。それは・・・、自分で読んで確認してください(笑)
https://note11.hyuki.net/