vue.svg

【Vue 2】テトリス作ってみた

Vue 2

完成品

仕様定義

正直テトリスの細かい仕様はわかっていません。 思いつきで作っているので細かいところは目を瞑ってください。

基本ルール

  • 10×20 のフィールド
  • フィールド上部からランダムにテトリスミノが落下
  • テトリスミノで埋まったライン(横)は消え、消したラインの数でスコアを加算
    ライン数スコア
    110
    280
    3270
    4640
  • スコアに応じてレベルが上がり、テトリスミノの落下速度が早くなる
    レベルスコア落下速度(1 マスあたり)
    101s
    28000.9s
    327000.8s
    464000.7s
    5125000.6s
    6216000.5s
    7343000.4s
    8512000.3s
    9729000.2s
    101000000.1s
  • フィールドの上部が埋まり、次のテトリスミノが配置できなくなったらゲーム終了

テトリスミノ

テトリスミノは以下の 7 種類で、反時計回りに 90 度回転します。

操作

操作は以下のキーによって行います。

操作キー説明
左移動テトリスミノを左に移動させます。
フィールドの左端または移動先に配置済みのテトリスミノがある場合は移動できません。
右移動テトリスミノを右に移動させます。
フィールドの右端または移動先に配置済みのテトリスミノがある場合は移動できません。
ソフトドロップテトリスミノを 1 つ下に移動させます。
フィールドの下端または移動先に配置済みのテトリスミノがある場合は、テトリスミノを配置します。
ハードドロップテトリスミノを真下に配置します。
回転Spaceテトリスミノを反時計回りに回転させます。
開店後のテトリスミノがフィールド外となるまたは配置済みのテトリスミノと重なる場合は回転できません。
ストックShiftテトリスミノをストックし、次のテトリスミノを落下させます。
既にストックしている場合は、現在のテトリスミノと入れ替えます。
入れ替えた場合は再度上部から落下させます。

実装

実装については、個人的に重要だと思った点のみ記載します。 あくまでどのように考えて作ったのかという解説のため、実際のコードとは異なるところがあります。

すべてのコードを参照したい場合は、CodePen からどうぞ。

データ定義

以下のようにデータを定義しています。

データ説明
テトリスミノ2 次元配列(5×5)でテトリスミノの形状を定義し、以下の値で制御します。
0:なし
1:I-テトリスミノ
2:O-テトリスミノ
3:T-テトリスミノ
4:J-テトリスミノ
5:L-テトリスミノ
6:S-テトリスミノ
7:Z-テトリスミノ
フィールド2 次元配列(20×10)でフィールドの形状を定義します。
フィールドの座標(x, y)は配列のインデックスと連動し、左上を(0, 0)とします。
配列の値は上記テトリスミノと連動します。
現在のテトリスミノ(形状)落下しているテトリスミノの形状を保持します。
現在のテトリスミノ(座標)落下しているテトリスミノの座標を保持します。
次のテトリスミノ次に落下するテトリスミノの形状を保持します。
ストックストックしているテトリスミノの形状を保持します。

フィールドの描画

フィールドは以下のようにv-forディレクティブによって表示します。

<table>
  <tr v-for="(line, i) in field" :key="i">
    <td v-for="(cell, j) in line" :key="j" />
  </tr>
</table>

ただし、画面に表示するフィールドはデータとして定義しているものは使用しません。 算出プロパティによって、フィールドに現在のテトリスミノを適用したものを表示します。

これにより、テトリスミノの座標が変わったり、回転したりした場合に再描画され、動いているように見せることができます。

テトリスミノの色は、Filterを使ったクラスバインディングによって行います。

Vue3.0 からは Filter は廃止されます。

HTML
<td v-for="(cell, j) in line" :key="j" class="mino" :class="cell | minoClass" />
JavaScript
filters: {
  minoClass(val) {
    switch(val) {
      case 1: return 'mino-i';
      case 2: return 'mino-o';
      case 3: return 'mino-t';
      case 4: return 'mino-j';
      case 5: return 'mino-l';
      case 6: return 'mino-s';
      case 7: return 'mino-z';
      default: return '';
    }
  }
}

テトリスミノの回転

テトリスミノを回転させるために、以下のように要素の入れ替えを行います。 ただし、O-テトリスミノは回転させません。

移動・回転の可否判定

移動後・回転後のテトリスミノが以下の条件を満たす場合は、移動・回転ができると判断します。

  • テトリスミノとして定義している要素がフィールド外に出ない
  • テトリスミノがフィールドに配置されているテトリスミノと重ならない

移動・回転ができない場合は、テトリスミノはそのままの状態とします。 ただし、ソフトドロップやハードドロップの場合は、テトリスミノを配置するためにフィールドを更新します。

ラインの消し方

ラインは以下の手順で消します。

  • 上から順に消すラインを探索します。
  • 対象のラインがある場合、そのラインを上部のラインに更新します。
    上部のラインはさらに上部のラインで更新します。
    これを最上部まで繰り返します。
  • フィールドの最上部のラインをクリアします。
  • 上記を最下部になるまで繰り返します。

キー操作

今回のキー操作は、特定の要素にではなく Window そのものに設定したいため、Vue.js のv-onではなく通常のaddEventListenerを使用します。 そのままだとスクロールが動いてしまうため、preventDefault()でデフォルト動作を抑制しておきましょう。

const app = new Vue({
  //…
  methods: {
    handleKeydown(event) {
      //キー操作
    },
  },
  mounted() {
    window.addEventListener('keydown', this.handleKeydown)
  },
  beforeDestroy() {
    window.removeEventListener('keydown', this.handkeKeydown)
  },
})

落下の設定

テトリスミノの落下は、setInterval()によって一定時間で落下するように設定します。 落下を止めるのにsetInterval()を破棄するため、IntervalIdは毎回取得するようにします。

const app = new Vue({
  data: {
    intervalId: undefined,
  },
  methods: {
    //1マス下に移動
    down() {
      //省略
    },
    //落下を開始
    startDrop() {
      //1秒間に1マス下に移動
      this.intervalId = setInterval(this.down, 1000)
    },
    //落下を停止
    stopDrop() {
      clearInterval(this.intervalId)
    },
  },
})

所感

このテトリスは、この記事を掲載する 2 年ほど前に作成しています。 せっかく作ったので前のブログから引っ張ってきました。

改良点など多々ありそうですし、お世辞にも綺麗なコードではないかもしれませんが、ご参考までに。