1
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2019-09-23

はじめに

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

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

負けとなる場合
fliptrip-error.gif

勝ちとなる場合
fliptrip-fulltrip.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のチュートリアルは○×ゲームだけどどうなってたっけと読み直してみたら疑問に思っていたことはすべて書いてありました。すなわち、

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

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

Vue.js実装

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

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

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

  • dataの書き換えが普通の代入文でできる(裏では黒魔術が使われているようですが)
  • render関数ではなくtemplateなのであれこれできない。computedを使えばどうにかはできる。まあ処理が書けるとすぐに複雑化するのでtemplate式の方がいいと思いますが。と思ったらrenderも書けるのですね。

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

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

で、また浮上したのが「Vueの場合、状態は石が持つの?ゲームが持つの?」ということで、ガイドをざっと眺めた感じでは特に記述はなく、
「コンポーネントにdataを定義していいのだから石が持っていいのだろう。でもゲームにクリックされたことを使えないといけないし」とガイドを探したところ、イベントディスパッチの方法を見つけました。これを使うと石から盤、さらにゲームへのクリック通知が以下のように書けます。

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は変更されません。

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が要素繰り返しだけでインデックス的な繰り返しが書けないと思っていたためですが、実はできることがわかりました。
整数ループを使うと以下のように簡潔に書くことができます。

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>
    `

これで石の数は変わるわけですがゲーム状態をリセットしないといけません。これにはウォッチャを使用します。

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は使用を控えてくださいとなっていますね2。より良い方法を見つけたら書き直したいと思います。

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/

  1. 履歴の意味は異なりますが。

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

1
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?