Vue + Vuex + TypeScript でテトリスを作った話

## TL;DR

[ここ](https://quizzical-hamilton-e9e889.netlify.com/)で遊べます.

リポジトリは[こちら](https://github.com/pione30/TypeScript-Vue-Tetris)

### 操作方法

  - カーソルキー (左下右) または A, S, D : 移動
  - 上キーまたは W : ハードドロップ
  - J : 左回転
  - K : 右回転
  - R または U : ホールド

一応おまけでスマホでも操作できるようにしてありますが PC 推奨です.
ARE も現代的な T-Spin も無いストイックなやつを目指して作りました. (しかしホールド機能は付けたかったので付けました)

## きっかけ

普段フロントエンドの開発をすることは少ないのですが, 遅ればせながら最近になってぼちぼち情報に触れるようになり, 自分も勉強してなんか書いてみようかなという気持ちになったのが昨年末あたり.

ちょうどその頃なぜかたまたま Classic Tetris World Championship (CTWC) の動画[^ctwc-2018-final]をよく見る日々が続いており, テトリスでも作って自分で遊ぶか〜と思ったのがきっかけです.

とりあえず React の前に Vue から入ろうと思い, Vue と TypeScript の公式ドキュメントを一通り読んでから取り掛かり, 寝る前の隙間時間にちまちま作っていきました.

せっかくなので自分用の備忘録として残しておこうと思って書いたのがこの記事というわけですが, これもちまちま書いていたらこんな時間が経ってしまった.

## Vue + TypeScript

Vue の[公式ガイド](https://jp.vuejs.org/v2/guide/)を上から順に読んでいくと,

  - 基本的にはコンポーネントと呼ばれるものを定義し,

    - 親から子コンポーネントへは `v-bind` を使って `props` としてデータを受け渡す

    - 子は親の状態を直接変化させない

    - 子から親に何かを伝えたいときはカスタムイベントを発火して親が `v-on` の形で受け取る

    - `props` は子の中では immutable なものとして扱い, `props` を使って宣言的に記述された[算出プロパティ](https://jp.vuejs.org/v2/guide/computed.html#%E7%AE%97%E5%87%BA%E3%83%97%E3%83%AD%E3%83%91%E3%83%86%E3%82%A3)が親からの `props` の変更に対してリアクティブに変化する

      - それがテンプレートにも反映される

  - 結局のところ[単一ファイルコンポーネント](https://jp.vuejs.org/v2/guide/single-file-components.html)を基本の形としてコードを書いていく

ということがわかります.

続いて TypeScript の[公式ドキュメント](https://www.typescriptlang.org/docs/home.html)を読んでいくわけですが, そこで素朴な疑問がひとつ浮かびます. つまり TypeScript では `const answer: number = 42` のように変数を定義するのですが, Vue コンポーネントの `data` や `computed` などのオプションはオブジェクトリテラルの形で定義されます. すると TypeScript の `variableName: type` という表記とオブジェクトリテラルの `key: value` という表記が衝突してしまうのではないか？という疑問です.

この疑問は TypeScript の [Quick Start](https://www.typescriptlang.org/samples/index.html) から辿れる [TypeScript-Vue-Starter](https://github.com/Microsoft/TypeScript-Vue-Starter) の中で解決されます.
README を読んでいくと, 一番最後のほうに「デコレータを使ってコンポーネントを定義する ([Using decorators to define a component](https://github.com/Microsoft/TypeScript-Vue-Starter#using-decorators-to-define-a-component))」という章があります. それによれば, [vue-property-decorator](https://github.com/kaorun343/vue-property-decorator) という便利なパッケージをインストールすると

```typescript:HelloDecorator.vue
import { Vue, Component, Prop } from "vue-property-decorator";

@Component
export default class HelloDecorator extends Vue {
    @Prop() name!: string;
    @Prop() initialEnthusiasm!: number;

    enthusiasm = this.initialEnthusiasm;

    increment() {
        this.enthusiasm++;
    }
    decrement() {
        if (this.enthusiasm > 1) {
            this.enthusiasm--;
        }
    }

    get exclamationMarks(): string {
        return Array(this.enthusiasm + 1).join('!');
    }
}
```

のように, `@Prop()` というデコレータをつけることでその変数が `props` として扱われるようになったり, `get` アクセサをつけることでそのメソッドが算出プロパティとして扱われるようになったりするという話です.
( 上記 README 内での書き方が若干まぎらわしいのですが, vue-property-decorator は [vue-class-component](https://github.com/vuejs/vue-class-component) を依存先として持つので, インストールするのは vue-property-decorator だけで十分です )

デコレータの詳細については[公式ドキュメント](https://www.typescriptlang.org/docs/handbook/decorators.html)やほかの方の記事に解説を譲るとして, 利用者視点で言えば vue-property-decorator によって型を付けながらコンポーネントを定義することができるようになるというわけです.

vue-property-decorator を使ったさらなる書き方についてはこちらの記事も大変参考になりました:

  - [TypeScriptでVue.jsを書く – Vue CLIを使った開発のポイントを紹介](https://mae.chab.in/archives/60167)

---------------------------------------

**追記 20190521**

Vue 3.x から公式で Class API を導入することが議論されていましたが却下され, 代わりに "Composition Functions" と呼ばれるパターンを用いたスタイルを導入することがアナウンスされました:

https://github.com/vuejs/rfcs/pull/17#issuecomment-494242121

したがってデコレータを利用した上記の書き方は 3.x 以降, 非推奨となることが予想されます.

---------------------------------------

### tsconfig.json

この時点で tsconfig.json は基本的に以下のような内容になっていると思います:

```tsconfig.json
{
    "compilerOptions": {
        "outDir": "./built/",
        "sourceMap": true,
        "strict": true,
        "noImplicitReturns": true,
        "experimentalDecorators": true,
        "module": "es2015",
        "moduleResolution": "node",
        "target": "es5"
    },
    "include": [
        "./src/**/*"
    ]
}
```

この記事を書いている 2019 年 4 月時点では `"experimentalDecorators": true` を明記する必要があります.

### webpack.config.js

今回は私は勉強のため [vue-cli](https://github.com/vuejs/vue-cli) を使わず手動で (?) 必要なパッケージをインストールしたり webpack.config.js を書いたりすることにしました.

基本的には上記の TypeScript-Vue-Starter 内の指示に従えば良いと思いますが, いくつか注意点があったので記録しておきます.

#### VueLoaderPlugin

webpack で Vue の単一ファイルコンポーネントを扱うために [vue-loader](https://github.com/vuejs/vue-loader) が必要ですが, vue-loader v15 からは `plugins` に `VueLoaderPlugin` を指定することが必須であるとドキュメントに記載があります:

```webpack.config.js
const VueLoaderPlugin = require('vue-loader/lib/plugin')

module.exports = {
  module: {
    rules: [
      // ... other rules
      {
        test: /\.vue$/,
        loader: 'vue-loader'
      }
    ]
  },
  plugins: [
    // make sure to include the plugin!
    new VueLoaderPlugin()
  ]
}
```

#### ランタイム限定ビルドの利用

Vue 公式ドキュメントの[ランタイム + コンパイラとランタイム限定の違い](https://jp.vuejs.org/v2/guide/installation.html#%E3%83%A9%E3%83%B3%E3%82%BF%E3%82%A4%E3%83%A0-%E3%82%B3%E3%83%B3%E3%83%91%E3%82%A4%E3%83%A9%E3%81%A8%E3%83%A9%E3%83%B3%E3%82%BF%E3%82%A4%E3%83%A0%E9%99%90%E5%AE%9A%E3%81%AE%E9%81%95%E3%81%84)という項を読むと, vue-loader を使用していれば, (単一ファイルコンポーネントのテンプレートはプリコンパイルされるので) 「完全ビルドに比べおよそ 30% 軽量」なランタイム限定ビルドを利用することができる, と書かれています.

つまり基本的に単一ファイルコンポーネントの形でコードを書き, エントリポイントとなる `index.ts` で `render` 関数を使ってルート要素に描画していれば, `vue.esm.js` ではなく `vue.runtime.esm.js` を利用することができます:

```src/idnex.ts
import Vue from 'vue';
import App from './components/App.vue';

new Vue({
  el: '#app',
  render: h => h(App)
})
```

```webpack.config.js
module.exports = {
  // ...
  resolve: {
    alias: {
      'vue$': 'vue/dist/vue.runtime.esm.js'
    }
  }
}
```

## ESLint + Prettier

私が知る限り, フロントエンド開発においてはリンターに ESLint を, コードフォーマッターに Prettier を用いるという構成が現在のスタンダードのようです. 私も長いものに巻かれてエディタに甘えようと思い, この構成を取ることにしました.

TypeScript の場合 TSLint というリンターもあるようですが, TSLint チームは今後 ESLint に寄っていくというような記事が今年の 1 月に出たり:

  - [The future of TypeScript on ESLint](https://eslint.org/blog/2019/01/future-typescript-eslint)

していたので ESLint を使おうと判断しました. また折しも今年の 2 月後半には 2019 年中に TSLint が deprecated となる旨がアナウンスされました:

  - [TSLint in 2019](https://medium.com/palantir/tslint-in-2019-1a144c2317a9)

  - [Roadmap: TSLint -> ESLint #4534](https://github.com/palantir/tslint/issues/4534)

さて, ESLint と Prettier を組み合わせるために Prettier の[公式ドキュメント](https://prettier.io/docs/en/eslint.html)を見ていきましょう. するとまず ESLint から Prettier を実行してくれるプラグインであるところの [eslint-plugin-prettier](https://github.com/prettier/eslint-plugin-prettier) が必要と書かれています. リポジトリの README を見ると `.eslintrc.json` に

```json:.eslintrc.json
{
  "plugins": ["prettier"],
  "rules": {
    "prettier/prettier": "error"
  }
}
```

と書けば良いようです.

さらに ESLint 側のコードフォーマットに関わるルールを無効にしてくれる [eslint-config-prettier](https://github.com/prettier/eslint-config-prettier) なるものもあります. こちらの README にもいろいろと書いてありますが, eslint-plugin-prettier 側の README とも合わせて読むと, 結局これら 2 つをインストールした上で `.eslintrc.json` の `"extends"` 配列の最後に `"plugin:prettier/recommended"` を指定すれば良いことがわかります.

その上でさらにいくつか追加のプラグインも紹介されており, それらにも eslint-config-prettier が対応していることが示されています. 今回の構成だと私は

  - [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/eslint-plugin)

  - [eslint-plugin-vue](https://github.com/vuejs/eslint-plugin-vue)

を使ったほうが良さそうです.

@typescript-eslint/eslint-plugin の README を読むと, [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/parser) をインストールすることが求められています.

ただし eslint-plugin-vue の[ユーザーガイド](https://eslint.vuejs.org/user-guide/#how-to-use-custom-parser)には, eslint-plugin-vue はパーサとして vue-eslint-parser を用いるので, カスタムパーサを使いたいときは `parser` オプションではなく `parserOptions.parser` オプションで指定せよとの記述があります.

長々と見てきましたがこれで必要なものが揃いそうなので, すべてインストールして

```bash
npm install --save-dev eslint prettier eslint-plugin-prettier eslint-config-prettier @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-plugin-vue
```

すべての設定を融合させましょう:

```json:.eslintrc.json
{
  "extends": [
    "eslint:recommended",
    "plugin:vue/recommended",
    "plugin:@typescript-eslint/recommended",
    "plugin:prettier/recommended",
    "prettier/@typescript-eslint",
    "prettier/vue"
  ],
  "plugins": [
    "@typescript-eslint",
    "prettier",
    "vue"
  ],
  "parser": "vue-eslint-parser",
  "parserOptions": {
    "parser": "@typescript-eslint/parser",
    "project": "./tsconfig.json",
    "extraFileExtensions": [".vue"]
  },
  "rules": {
    "prettier/prettier": "error"
  }
}
```

### VSCode の拡張機能

上記の eslint-plugin-vue のユーザーガイドには [Editor integrations](https://eslint.vuejs.org/user-guide/#editor-integrations) という項があり, VSCode で ESLint 拡張機能を `.vue` ファイルに対しても適用するための設定が書かれています.
また Vetur プラグインを使っている場合は `"vetur.validation.template": false` を設定せよとあります.

私はそれに加えてファイル保存時に ESLint の auto fix をかけるよう設定したので, `.vscode/settings.json` は以下のようになりました:


```json:.vscode/settings.json
{
  "eslint.validate": [
      "javascript",
      "javascriptreact",
      { "language": "vue", "autoFix": true },
      { "language": "typescript", "autoFix": true }
  ],
  "eslint.autoFixOnSave": true,
  "vetur.validation.template": false
}
```

ファイル保存時に自動整形がかかる体験は最高で, コードフォーマッティングに関する些事を人間が気にしなくてよくなり最高.
デフォルトの `printWidth: 80` だけは少し窮屈すぎたのでそこだけ広げましたが, その上で prettier が改行すると判断したのならそれは正しいのです.

(とはいえテトリミノの回転を配列で定義して, 見やすいように手で整形したのに prettier が押しつぶそうとしてくる箇所だけは disabled にしましたが...)

## 実装フェーズ

ようやく開発環境が整ったので, テトリスの実装に入ることができます.

「『小さく、自己完結的で、（多くの場合）再利用可能なコンポーネント』を組み合わせることで、大規模アプリケーションを構築する」[^Composing-with-Components]のが Vue のスタイルなので, 必要なコンポーネントについて考えると

  - メインとなる盤面 (ミノが落ちたり回転したり消えたりするところ)

  - NEXT ミノをプレビューできる部分

  - ホールド中のミノを表示する部分

  - 現在のレベルと得点を表示する部分

ぐらいがあれば良さそうです.

するとおおまかな構成は以下のようになるでしょう:

```dist/index.html
<!doctype html>
<html>
  <head>
  </head>
  <body>
    <div id="app"></div>
    <script src="./build.js"></script>
  </body>
</html>
```

```src/index.ts
import Vue from "vue"
import TetrisComponent from "components/Tetris.vue"

new Vue({
  el: "#app",
  render: h => h(TetrisComponent)
})
```

```src/components/Tetris.vue
<template>
  <div>
    <div id="tetris-component">
      <hold class="inline-block" />
      <play-field class="inline-block" />
      <div class="inline-block">
        <next-preview />
        <level-score />
      </div>
    </div>
  </div>
</template>

<script lang="ts">
import { Vue, Component } from "vue-property-decorator"
import Hold from "components/Hold.vue"
import PlayField from "components/PlayField.vue"
import NextPreview from "components/NextPreview.vue"
import LevelScore from "components/LevelScore.vue"

@Component({
  components: {
    Hold,
    PlayField,
    NextPreview,
    LevelScore
  }
})
export default class TetrisComponent extends Vue {}
</script>

<style>
#tetris-component {
  text-align: center;
}
.inline-block {
  display: inline-block;
  vertical-align: top;
}
</style>
```

### テトリス実装の基本概念

テトリスのロジック自体は素朴な実装にしました.

#### 盤面

盤面として幅 (10 + 2) * 高さ (20 + 2) の 2 次元配列を 2 つ用意し, 1 つは色情報を, もう 1 つはブロックが埋まっているか否かの情報を格納するのに使います.

幅と高さを +2 しているのは外壁, いわゆる番兵というやつです. 色と埋まりの配列を分けずにもっと統一的に扱っても良いかもしれませんが, そこは好みの問題だと思います.

#### ミノ

テトリスの主役であるところのミノについては, ミノの型を定義して `export default interface Tetromino` し, すべてのミノの名前と色と回転パターンを記述した `Tetrominos: Tetromino[]` を定義しておきます:

```src/Tetrominos.ts
import Tetromino from "typings/Tetromino"
export const Tetrominos: Tetromino[] = [
  {
    name: "I",
    color: "red",
    blocks: [
      [ [1, 1, 1, 1] ],
      [
        [1],
        [1],
        [1],
        [1]
      ]
    ]
  },

  {
    name: "O",
    color: "gold",
    blocks: [
      [
        [1, 1],
        [1, 1]
      ]
    ]
  },
  .
  .
  .
]
```

この `Tetrominos` のインデックスでミノの種類を表現できるというわけです.

#### 操作

ミノを移動させたり回転させたりといった操作を行うときは単純に, その操作後のミノが既に埋まっているマスとぶつからないかどうかを検査し, ぶつからなければ操作可能, ぶつかるなら操作不可能とすれば良さそうです.

ただし `moveDown()` だけは特別で, それ以上ミノを下に動かせないということはミノが接地したということ, すなわち現在のターンが終了したということを示します. なので揃ったラインを消すことを始めとした様々な終了処理を行う必要が出てくるでしょう.

#### ラインの消去

テトリスというゲームの命 (?) であるところのライン消去機能を作るには, [Array.prototype.splice()](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/splice) がいい感じに使えるでしょう.
ミノを接地した結果すべての列が埋まったラインが出来たならば, 上から順番にそのラインの `index` について `splice(index, 1)` すれば良いのです. そして上記の 2 次元配列の先頭には空っぽの `unitLine` を都度 `splice(1, 0, unitLine)` して詰めてあげれば良いでしょう (index が 1 なのは一番上の 0 行目が壁だからです).

#### ホールド

ホールドとはつまり現在の `tetrominoIndex` と `holdTetrominoIndex` とを交換するという操作です. ただし初回だけはホールドされているミノが無いので, NEXT のミノを盤面に呼ぶ必要があるでしょう. なので `holdTetrominoIndex` の初期値は -1 とか (インデックスとしてありえない値) にしておいて, それで初回の判定をします.

またホールドは 1 ターンにつき 1 回しかできないので, `isHoldTetrominoUsed` のような変数を用意して, ホールドしてからミノが接地するまでは `isHoldTetrominoUsed = true` としてロックしておく等の処理が必要です.

### 余談: `Math.floor(Math.random() * 7)` を捨てる

出現するミノをランダムにするために `Math.floor(Math.random() * 7)` の値を使おうと思う方がいるかもしれませんが, やめたほうが良いです.

代わりに 「**7 種類のミノが 1 つずつ入った集合の中から, 空っぽになるまで 1 つずつランダムに取り出す**」ことを繰り返すようにしてください.

実際にやってみるとわかるのですが, ナイーブな `Math.floor(Math.random() * 7)` ではとても遊べるゲームになりません. 動作確認用くらいにとどめておくべきです. これは重要なポイントなのでぜひ覚えておいてください.

今回私は Fisher–Yates アルゴリズムというシャッフルアルゴリズムを使いました:

```Algorhythms.ts
// Fisher–Yates shuffle algorithm
export const shuffle = function(array: number[]): number[] {
  for (let i: number = array.length - 1; i > 0; i--) {
    const j: number = Math.floor(Math.random() * (i + 1))
    ;[array[i], array[j]] = [array[j], array[i]]
  }
  return array
}
```

```TetrominoIndices.ts
import { Tetrominos } from "Tetrominos"
import { shuffle } from "Algorhythms"

// ...
  nextTetrominoIndicesSet: number[] = shuffle(Array.from(Array(Tetrominos.length).keys()))
// ...
```

## Vuex

さてこのまま `props` や `$emit` や `methods` を駆使してコードを書いていっても良いのですが (実際私も最初はそうしていました), だんだんと状態変数が増えてきて管理が辛くなっていきます.
そこで途中から Vuex による状態管理に移行することにしました (今からやり直すなら最初から Vuex を使うと思います).

Vuex や Flux 自体についてはほかに良い記事がたくさんあると思うのであえて私がここで拙い解説を書くことはしませんが, Vuex の[公式ドキュメント](https://vuex.vuejs.org/ja/)を読んでいけば

  - 状態 (`state`) は `Store` と呼ばれるグローバルシングルトンの中に保持する

  - 状態を変更する唯一の方法は `mutation` をコミットすることである

  - `mutation` は同期的な処理だけを行う

  - 非同期処理を行ったり複数の `mutation` をコミットしたりするためには `action` を定義し, その `action` を `dispatch` することで実行する

  - 状態は `getter` で取り出す

ことが基本のスタイルであると読めます.

さらに Vuex で TypeScript を型安全に使うために [vuex-module-decorators](https://github.com/championswimmer/vuex-module-decorators) を利用しました.
これについては以下の記事が大変参考になりました:

  - [vuex + typescriptをvuex-module-decoratorsで無敵になる](https://qiita.com/tsrnk/items/fd95c3d8013d0795a260)

コンポーネントの中で `this.$store` と書かなくてよくなったり mutation 名や action 名を文字列で一字一句間違えずに書かずに済むようになったりというのはこの記事のとおりです.

### コーディングスタイル

Vuex の公式ドキュメントには「[ミューテーション・タイプに定数を使用する](https://vuex.vuejs.org/ja/guide/mutations.html#%E3%83%9F%E3%83%A5%E3%83%BC%E3%83%86%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%83%BB%E3%82%BF%E3%82%A4%E3%83%97%E3%81%AB%E5%AE%9A%E6%95%B0%E3%82%92%E4%BD%BF%E7%94%A8%E3%81%99%E3%82%8B)」というオプションが提示されており, 上記の記事でもそのスタイルだったので, 今回は私もそれに則ることにしました.

また vuex-module-decorators を使うと VuexModule を TypeScript の class として書くことになり, 各プロパティやメソッドにアクセス指定子をつけることができます.
私は Vuex Store が外部 (Store を利用する Vue のコンポーネント) に公開すべきなのは `getter` と `action` だけであるとドキュメントからエスパーにより読み取った (?) ので, `state` と `mutation` は `private` にし, `getter` と `action` を `public` としました.

こうなるとテトリスを実装する上での主要なロジックはほとんど Vuex の action となり, Vue のコンポーネントからは `props` が消え去りました. コンポーネントに残ったのは少しの内部変数と `mounted` フック, それに `action` を制御する薄いメソッド, リアクティブな描画のために Store の getter をそのまま返す getter ぐらいです.

## 反省点

### 描画

何も考えずに Canvas 上でゲーム画面を描画するものとしてコードを書き始めてしまったのですが, そうすると例えば

```ts
import { Tetrominos } from "Tetrominos"

// class PlayField extends Vue とか VuexModule とか { 
// ...

  // 適当な値です
  tetrominoIndex: number = 0
  currentX: number = 5
  currentY: number = 1
  rotation: number = 0

  unitWidth: number = 10
  unitHeight: number = 10

  drawTetromino(): void {
    const canvas: HTMLCanvasElement = document.getElementById("canvas") as HTMLCanvasElement
    const context = canvas.getContext("2d")

    context.strokeStyle = "lightgray"
    context.fillStyle = Tetrominos[this.tetrominoIndex].color

    for (const [dy, row] of Tetrominos[this.tetrominoIndex].blocks[this.rotation].entries()) {
      for (const [dx, blockElement] of row.entries()) {
        if (blockElement != 0) {
          context.fillRect(
            (this.currentX + dx) * this.unitWidth,
            (this.currentY + dy) * this.unitHeight,
            this.unitWidth,
            this.unitHeight
          )
          context.strokeRect(
            (this.currentX + dx) * this.unitWidth,
            (this.currentY + dy) * this.unitHeight,
            this.unitWidth,
            this.unitHeight
          )
        }
      }
    }
  }

// ...
// }
```

的なコードを書くことになり, `fillRect()` がいかにも手続き的です.
だいぶ後になってから気がつきましたが, せっかく属性に値を bind できるのだから SVG で描画したほうが良かったかもしれません. こんど何か図形を描画して動かすものを作るときは SVG の利用を検討しようと思います.

## そういう感じです

このテトリスを作っている間に Nintendo Switch で Tetris 99[^tetris-99] が配信開始されました. この Tetris 99 は Nintendo Switch Online に加入していれば無料で遊べるし, 現代的な要素ももりもり詰まっています.
私はなるべくクラシックなテトリスに近いものを作って遊ぼうというモチベーションがあったから良かったものの, もし現代的テトリスを目指して作っていたら, べつに Tetris 99 とかいう良くできたゲームがあるしわざわざ俺がちまちま作んなくてもいいじゃん...という気持ちになって途中で心が折れていたかもしれません. そういう意味では運良く開発できたなあと思っています.

そういうわけですので皆さんも Tetris 99 をやりましょう.

[^ctwc-2018-final]: https://www.youtube.com/watch?v=L_UPHsGR6fM とか

[^Composing-with-Components]: https://jp.vuejs.org/v2/guide/index.html#%E3%82%B3%E3%83%B3%E3%83%9D%E3%83%BC%E3%83%8D%E3%83%B3%E3%83%88%E3%81%AB%E3%82%88%E3%82%8B%E6%A7%8B%E6%88%90

[^tetris-99]: https://www.nintendo.co.jp/switch/arzna/index.html