リモートカードゲームを気軽に遊べるランダムマッチアプリを開発した話

## はじめに
私の趣味の一つに、トレーディングカードゲーム（TCG）があります。
昨今の外出自粛要請による影響は、TCG 界隈においても例外に漏れず、対面で TCG を遊ぶ機会のほとんどがなくなってしまいました。
この記事では、少しでも TCG 界隈の停滞感をなくすために開発した Web アプリの紹介と開発手順のメモ書きを~~まとめています。~~鋭意作成中です。
![スクリーンショット 2020-05-15 12.57.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/240213/82be8a03-4855-07b1-b747-bb3e2d032c7e.png)

### 対象読者
- IT 分野以外の趣味で IT を活用したい人
- Twitter アカウントによる認証を用いた Web アプリをつくりたい人
- WebRTC（ビデオ通話など）を活用した Web アプリをつくりたい人
- Nuxt.js/TypeScript の開発に興味がある人

## モチベーション
TCG は大前提として対面で遊ぶことを想定したアナログゲームですが、学生以下をメインターゲット層としたものも多く、歳を重ねるにつれて身の周りから TCG を一緒に遊ぶ友達がだんだんと減っていくものです（悲しい）。
他方、TCG を専門に取り扱うカードショップなどでは、店舗大会や公認自主イベントと呼ばれるような、誰でも気軽に参加できるイベントが定期的に開催され、**友達や知り合いがいなくても TCG を楽しめる環境**が整っていました。

しかしながら、昨今の外出自粛要請により、カードショップでのイベントは全面的に中止に。
このような状況を受けてか、TCG 界隈では、知り合い同士で Skype や Discord などのビデオ通話アプリを用いた**リモートカードゲーム**の環境を積極的に取り入れつつあります。
私自身、今回の騒動以前から何度かリモートカードゲームを遊んだことがありますが、以下のような課題があると感じています。
![スクリーンショット 2020-05-15 12.55.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/240213/60981c07-5237-a3ba-20d9-626ae07f1876.png)

- 知り合いの中に、都合のつく人がなかなか見つからない
- とはいえ、事前に日程調整しておく（待ち合わせ時間を決めておく）のは面倒
- かといって、知り合い以外の募集に自分から声をかける勇気もない
- 相手が見つかったとて、Skype やら Discord やらを使い分け、都度連絡先を登録するのも面倒

**ただの面倒くさがりやんけ。**
まあ、とにかくそのような課題の解決を目指す気持ち~~半分、「転活のポートフォリオがわりに使えねえかなぁ」の気持ち半分~~で開発した Web アプリがこの**『[PTCGrand](https://ptcg-rand.web.app)』**です。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">【ゆる宣伝】<a href="https://twitter.com/hashtag/%E3%83%AA%E3%83%A2%E3%83%BC%E3%83%88%E3%83%9D%E3%82%B1%E3%82%AB?src=hash&amp;ref_src=twsrc%5Etfw">#リモートポケカ</a> を気軽に遊べるランダムマッチアプリ「PTCGrand」を開発中です🙌<br>SkypeやDiscordの連絡先交換いらずで、TwitterアカウントがあればOK🙆‍♀️<br>まだ開発途上なのでちょいちょい不具合もありますが、ひとまず動かせるようになったので公開しちゃいます↓<a href="https://t.co/tUvD7lSmf9">https://t.co/tUvD7lSmf9</a> <a href="https://t.co/YWhnuVIwWm">pic.twitter.com/YWhnuVIwWm</a></p>&mdash; てぃら (@tilanosaur) <a href="https://twitter.com/tilanosaur/status/1256919218345226241?ref_src=twsrc%5Etfw">May 3, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>


基本アイディアとして、対戦型オンラインゲームでは一般的な**ランダムマッチング機能**を実装しました。
そのため、たとえ知り合いがいなくても、リアルタイムに対戦相手を募集している人同士でリモートカードゲームを遊ぶことができます。

> - 知り合いの中に、都合のつく人がなかなか見つからない
> - とはいえ、事前に日程調整しておく（待ち合わせ時間を決めておく）のは面倒
> - かといって、知り合い以外の募集に自分から声をかける勇気もない

さらに、**ビデオ通話機能を Web 上で完結**させることで、Skype や Discord の連絡先交換は言わずもがな、アプリのインストールすら不要にしました。
また、この Web アプリを利用するのに複雑な手順があるとかえって面倒になってしまうため、**Twitter アカウントとの連携による認証**を導入し、設定手順も最小化しています。

> - 相手が見つかったとて、Skype やら Discord やらを使い分け、都度連絡先を登録するのも面倒

[特定 TCG プレイヤー向けの紹介記事](https://note.com/tilanosaur/n/nc287588306be) は（別名義で）note に投稿済みなので、興味ある方はそちらも併せてご覧ください。

### フレームワーク と アーキテクチャ
開発した Web アプリの大まかな構成と動作は、以下の通りです。
![スクリーンショット 2020-05-15 12.56.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/240213/83f201d2-aae9-dcba-690a-bc9db5d8c014.png)

#### [Nuxt.js](https://ja.nuxtjs.org/)/Typescript
- SPA (Single Page Application) の生成
- Bootstrap4 ([BootstrapVue](https://bootstrap-vue.org/)) によるデザインレイアウト

<!--
- `@nuxtjs/pwa` モジュールによる PWA (Progressive Web App) 対応
- `nuxt-i18n` モジュールによる多言語対応
-->

#### Firebase
- Nuxt.js で生成した SPA のホスティング ([Firebase Hosting](https://firebase.google.com/docs/hosting?hl=ja))
- Twitter 連携によるユーザー認証 ([Firebase Authentication](https://firebase.google.com/docs/auth?hl=ja))
- データベース機能の提供（Peer 情報と対戦希望の管理） ([Firebase Cloud Firestore](https://firebase.google.com/docs/firestore?hl=ja)）

#### [SkyWay](https://webrtc.ecl.ntt.com/)
- ユーザー間のビデオ通話・データ通信の確立

## 開発手順
重要そうなところやハマったところを中心に、メモ書きを残しています。
~~用意が面倒なので~~スクリーンショットなどを掲載していないため、詳しく知りたい方は、適宜、記事末尾にある参考記事をご覧いただければと思います。

### バージョン
```
$ node -v
v12.16.2
$ npm -v
6.14.4
```

### Nuxt.js プロジェクトの作成
[Nuxt.js 公式サイト](https://ja.nuxtjs.org/guide/installation/)に書かれているとおり、`create-nuxt-app` コマンドでプロジェクトを作成します。
開発言語に TypeScript、UI フレームワークに [BootstrapVue](https://bootstrap-vue.org/) を選択していますが、このあたりは好みかと思います。

Nuxt.js プロジェクトにおける TypeScript 記法は、公式の [Nuxt TypeScript](https://typescript.nuxtjs.org/ja/) ガイドが参考になります。

```
$ npx create-nuxt-app <project-name>

create-nuxt-app v2.15.0
✨  Generating Nuxt.js project in <project-name>
? Project name <project-name>
? Project description <project-description>
? Author name @blachocolat
? Choose programming language TypeScript
? Choose the package manager Npm
? Choose UI framework Bootstrap Vue
? Choose custom server framework None (Recommended)
? Choose the runtime for TypeScript Default
? Choose Nuxt.js modules Progressive Web App (PWA) Support
? Choose linting tools ESLint, Prettier
? Choose test framework Jest
? Choose rendering mode Single Page App
? Choose development tools jsconfig.json (Recommended for VS Code)
```

### Bootstrap テーマ と Web フォントの導入
Nuxt.js プロジェクトの UI フレームワークに Bootstrap を採用する強みの一つは、過去のプロジェクトのテーマを使い回せることかもしれません[^1]。
今回は、以前「[所有しているボードゲームの管理サイト](http://public-files.appspot.com/bgreviews/index.html)」を開発した際に使用した [Bootswatch](https://bootswatch.com/) の Lumen テーマを導入することにしました。

[^1]: 開発を一段落を終えた時点での感想としては、「見た目に大きなこだわりがなければ BootstrapVue ではなく Material Design フレームワークである [Vuetify](https://vuetifyjs.com/ja/) を採用するのが無難そう」という感じです。

`/assets` 以下に `scss/bootswatch.scss` を作成し、Global に読み込む設定を `nuxt.config.js` に追加します。
アプリのテーマ色を設定するため、元の設定値が書かれた `_variables.scss` を `@import` するよりも前に変数 `$primary` を定義することで、実質的に変数を書き換えています[^2]。
CSS セレクタによる Style の適用は、より後に書かれたものが優先されるため、`@import` の後に記述します。

[^2]: `_variables.scss` 内の変数宣言の末尾に付された `!default` は、すでに同名の変数が宣言されていなければ適用する、という意味らしいです。

また、同様に SCSS の変数を書き換える形で、[Google Fonts](https://googlefonts.github.io/japanese/) で公開されている Web フォントを適用しています。

```scss:/assets/scss/bootswatch.scss
$primary: #d44050;
$font-family-sans-serif: 'M PLUS 1p', sans-serif;
$font-family-monospace: 'M PLUS 1p', monospace;

@import '~/node_modules/bootswatch/dist/lumen/_variables';
// $primary: $blue !default;
@import '~/node_modules/bootstrap/scss/bootstrap';
@import '~/node_modules/bootswatch/dist/lumen/_bootswatch';

.b-avatar {
  &.badge-secondary {
    color: $gray-500;
  }
}

...
```

```diff:nuxt.config.js
  export default {
    head: {
      ...
      link: [
        ...
+       { rel: 'stylesheet', type: 'text/css', href: 'https://fonts.googleapis.com/css?family=M+PLUS+1p' }
      ]
    },
    ...
    css: [
      ...
+     '~/assets/scss/bootswatch.scss'
    ],
    ...
  }
```

### 

### Firebase Hosting へのデプロイ
Firebase CLI (`firebase-tools`) をインストールしていなければ、`npm` 経由でインストールします。
インストール完了後、`firebase login` コマンドを実行し、Web ブラウザ経由で Google アカウントを紐づけます。

```
$ npm install -g firebase-tools
$ firebase login
```

作成した Nuxt.js プロジェクトに移動し、Firebase プロジェクトとしての初期設定を行います。
[Firebase 公式ドキュメント](https://firebase.google.com/docs/web/setup?hl=ja)を参考に、あらかじめ Web ブラウザから Firebase プロジェクトを作成しておくとよいでしょう。

ここでは、`? Which Firebase CLI features...` の設問に対して Firebase Hosting のみを選択しています。

```
$ cd /path-to/<project-name>
$ firebase init

...

? Which Firebase CLI features do you want to set up for this folder? Press Space to select features, then Ente
r to confirm your choices. Hosting: Configure and deploy Firebase Hosting sites

...

=== Project Setup

? Please select an option: Use an existing project
? Select a default Firebase project for this directory: <project-name> (<project-name>)
i  Using project <project-name> (<project-name>)

...

=== Hosting Setup

? What do you want to use as your public directory? public
? Configure as a single-page app (rewrite all urls to /index.html)? No

...
```

Firebase Hosting の公開ディレクトリに `/public` を指定したため、それに合わせるように `nuxt.config.js` の出力ディレクトリを変更します。

```diff:nuxt.config.js
  export default {
    ...
+   generate: {
+     dir: 'public'
+   },
    ...
```

以上で、準備が整いました。
下記コマンドで SPA を生成し、Firebase Hosting にデプロイします。

```
$ npm run generate
$ firebase deploy
```

なお、一度公開した SPA を非公開にしたい場合、下記コマンドを実行します。

```
$ firebase hosting:disable
```

## 参考記事
- [Nuxt.js + Firebase Authentication + FireStoreでwebアプリケーションハンズオン (@ririli)](https://qiita.com/ririli/items/d0d3a6ae78c1b6e827fc)
- [Nuxt.js + Vuex + Firebase(Authentication, Cloud Firestore)でTwitterユーザー向けの伝言板を作る (@ysd_marrrr)](https://qiita.com/ysd_marrrr/items/cf7871e7803d7b4919f1)
- [はじめてのvue-property-decorator (nuxtにも対応） (@simochee)](https://qiita.com/simochee/items/e5b77af4aa36bd0f32e5)
- [vuex + typescriptをvuex-module-decoratorsで無敵になる (@tsrnk)](https://qiita.com/tsrnk/items/fd95c3d8013d0795a260)
- [Nuxt.js + Typescript + Vuexする現時点のベストと思う方法 (@suzukenz)](https://qiita.com/suzukenz/items/0b5c0eb5196646599377)
- [0から始める Firestore + Firebase Authentication (@karayok)](https://qiita.com/karayok/items/0996c8f0ea219c284dbd)
- [Nuxt(SPA)+Firebaseでログイン処理を実装する (@pochopocho13)](https://qiita.com/pochopocho13/items/2ebaa66b77d28464561f)
- [Nuxt.jsのプロジェクトでnuxt-i18nを使ってi18n（国際化）対応する方法 (@munieru_jp)](https://qiita.com/munieru_jp/items/d7e9f98b5ab5960e7a93)
- [nuxt-i18nでNuxt.jsの国際化して、英語版と日本語版を用意する (くらげになりたい。)](https://www.memory-lovers.blog/entry/2019/02/19/011253)

## おわりに
せっかくそれなりの形になった Web アプリを開発したので、開発手順のメモ書きをまとめ~~ました。~~たかったのですが途中で力尽きてしまいました。

アプリについては、公開から数日経ったものの肝心のユーザー数はまだまだ少なく、なかなかランダムマッチングが成立していない状況です。
その結果、半ば “待ち合わせ時間を決めておく” のが必要になっており、上であげた課題そのものにぶち当たっています（悲しい）。

~~開発モチベーションの半分が半分なのであれですが、~~個人開発だからこそ、機能をゴリゴリ実装するだけでなく、必要な人のもとに届くための施策を考えていきたいですね。