NCMBスクリプト機能でガチャを実装する

[ニフティクラウドmobile backend (NCMB)](http://mb.cloud.nifty.com/) エヴァンジェリストの小山(@koyhoge)です。

4月4日にNCMBの新機能「[スクリプト](http://mb.cloud.nifty.com/function.htm#script)」が正式リリースになりました。これは NCMB 上で Node.js のモジュールを実行できる機能です。[AWS Lambda](https://aws.amazon.com/jp/lambda/) や [Azure Functions](https://azure.microsoft.com/en-us/services/functions/) などと同じ方向のサービスですね。

* [スクリプト](http://mb.cloud.nifty.com/function.htm#script)

上記のページで使用例の説明として「ガチャ機能」が上げられていますが、どのように実装するかという詳しい解説はないので、それではということでサンプル実装を作ってみました。

今回作成したコードは、GitHub で MIT ライセンスで公開しています。

https://github.com/koyhoge/ncmb-script-gacha

## 準備編

このガチャスクリプトを実行させるためには、いくつかの前準備が必要です。

### データストアの用意

ガチャスクリプトのために、NCMB のデータストアに 3 つのクラスが必要です。

* Items
  * ガチャのアイテム設定を保持するクラスです。
* Log
  * ガチャの排出ログが記録されます。
* Counter
  * どの種類のガチャがいくつ排出されたのか、アイテムごとの合計値が記録されます。

### アイテム設定

ガチャの種類とそれぞれの出現確率の重みづけを Items クラスに設定します。

例えば以下の構成の場合、

| アイテム | 重み |
|---------|------|
|  UR  | 2 |
|  SR  | 9 |
|   R   | 40 |
|   N   |  100 |

Items クラスに以下のレコードを追加します。

| id | UR | SR | R | N |
|----|-----|----|----|----|
|  1  |  2  |  9  |  40  |  100  |

NCMB によって objectId, createDate, updateDate というフィールドが自動で追加されますが、プログラムによって無視されるので心配ありません。

id はそのガチャを表す整数値です。id を変えることで、複数のガチャ設定を保持できるようになっており、スクリプトを呼び出す際にどの設定でガチャを回すのか指定する形になります。

登録されたレコードを NCMB のウェブコンパネで見るとこのようになります。

![fig01.png](https://qiita-image-store.s3.amazonaws.com/0/72656/262e8e92-4af6-6241-2701-f8ce0b0c2c54.png)

ここで注意しなければいけないのは、idや各重みの型が「数値」となっていることです。コンパネの画面からはぱっと見で確認しにくく、コンパネから新しくレコードを入力すると、標準では「文字列」になっているので気をつけて下さい。

### ログ用クラスを作成

ガチャ排出の記録用に Log, Counter の2つのクラスを作成しておきます。レコードは自動で追加されるので空のままで構いません。

### ガチャスクリプトの生成とアップロード

まずは GitHub からリポジトリをクローンします。

```
git clone git@github.com:koyhoge/ncmb-script-gacha.git
```

つぎにリクエストのやり取りをする本体である main.js を修正します。NCMB のアプリケーションキー、クライアントキーを記述する場所があるので、そこを自分のアプリケーションのものに置き換えます。

```javascript:main.js
    var app_key = 'PLEASE PUT YOUR APP KEY HERE';
    var client_key = 'PLEASE PUT YOUR CLIENT KEY HERE';
```

NCMBにアップロードするファイル gacha.js は、複数のファイルに別れたモジュールからできています。現在のスクリプト機能ではアップロードしたモジュールから同じ場所の別モジュールを呼び出すことができないので、すべて連結して一つのファイルにまとめます。

```
cat config.js chooser.js logger.js main.js > gacha.js
```

これで gacha.js ができました。

続いて、NCMBウェブコンパネを使って上記の gacha.js をアップロードします。左側の「スクリプト」メニューを開き、

上部の「アップロード」ボタンを押します。

![fig02.png](https://qiita-image-store.s3.amazonaws.com/0/72656/3287a57d-d93a-9383-ae21-ad497759bf57.png)

するとアップロード用のダイアログが表示されるので、gacha.js をドラッグ&ドロップするか、「ファイルを選択」ボタンを押して gacha.js を選択します。

![fig03.png](https://qiita-image-store.s3.amazonaws.com/0/72656/f6460769-7f60-35d7-b3aa-686f6c2a70e1.png)

するとダイアログが拡張されて、スクリプトの簡易設定項目が出現しますので、以下を変更します。

* 「メソッド」を「GET」に。
* 「ファイルの状態」を「実行可能」に。

![fig04.png](https://qiita-image-store.s3.amazonaws.com/0/72656/a180df9f-00dc-7b7f-a874-eaa06c7a8ffd.png)

「アップロードする」ボタンを押すと、gacha.js がアップロードされて NCMB に登録されます。

![fig05.png](https://qiita-image-store.s3.amazonaws.com/0/72656/fdb87195-158a-64f8-ffce-78e79eb00394.png)

これで準備は完了です。

## NCMBコンパネから実行してみる

それでは早速アップロードした gacha.js を NCMB のコンパネから実行してみましょう。
まずはスクリプトの画面で gacha.js をクリックして選択します。

![fig06.png](https://qiita-image-store.s3.amazonaws.com/0/72656/42af6234-7179-8861-c1e2-fad4ce30d2bf.png)

画面右側にスクリプトの詳細画面が表示されるので、「実行」タブを選択します。表示されたフォームの「Query」項目に、gacha.js に HTTP GET で渡すオプションパラメータを入力します。今回は「item=1&user=ncmbgacha」と入れてみましょう。

![fig07.png](https://qiita-image-store.s3.amazonaws.com/0/72656/74588036-52a4-1ea1-47c7-cb8f8179b44e.png)

「実行」ボタンをクリックすると、NCMB上で gacha.js が実行されます。今回は「R」が結果として返されました。

![fig08.png](https://qiita-image-store.s3.amazonaws.com/0/72656/e2ec58dc-2106-21ca-9978-875fb073744b.png)

データストアの画面に行って、Log と Couter クラスを確認すると、それぞれレコードが追加されていることが分かると思います。

![fig09.png](https://qiita-image-store.s3.amazonaws.com/0/72656/fb72ab00-4933-e290-8011-22fe70eaf7df.png)

## コードの解説

今回作成したガチャプログラムを簡単に解説しておきます。git リポジトリには以下のファイルが登録されています。

* config.js
  * ガチャの設定情報を読み取る Config クラス
* chooser.js
  * 設定値を元に抽選を行う Chooser クラス
* logger.js
  * 抽選結果をログに書き出す Logger クラス
* main.js
  * リクエストのやり取りを行う本体
* nodegacha.js
  * ローカルの Node 環境でガチャ機能を実行するデバッグ用の本体
* call_script.js
  * NCMBにアップロードした gacha.js を実行する Node のサンプル

上記のうち最初の4つを結合させて、アップロードする gacha.js を作成するのはすでに説明した通りです。

## 苦労したところ

今回作成した gacha.js は 200行程度の小さいプログラムですが、それでも開発に関しては苦労した点があります。最後にそれを上げておきます。

### 非同期を徹底しないとハマる

 NCMB の JavaScript SDK が非同期処理を前提に設計されているので、それを呼び出す側も Promise を用いるなどしてすべて非同期に処理をしないと、ある部分は実行されないといったことが起こります。ローカルの Node 処理系では再現されないことも有るので注意。

###  NCMB スクリプトでは独自モジュールは(まだ)扱えない

上記にも書きましたが、NCMB のスクリプト処理系では同一ディレクトリにアップロードした別モジュールを呼び出すことができません。ただ一つの巨大なモジュールにすべてを詰め込むのもメンテナンス的に問題が有るので、今回はそれぞれの機能をモジュールとして別ファイルに分けた上で、それらを単純に結合させたファイルをアップロードするという方法を取りました。これならばローカルの Node 環境でも各モジュールをそのままデバッグできて効率が良いと考えました。

とはいえ同一ディレクトリのモジュール呼び出しは、機能としてあるないとでは便利さが違うので、開発者の方に要望は伝えておきました。そのうち新機能として実装されるかもしれません。

### コンパネから毎回ファイルをアップロードするのが面倒

NCMB のスクリプト機能では、ファイルをアップロードする手段が現在のところウェブコンパネからしかサポートされていません。ファイルのアップロードも SDK や API で公式にサポートされれば、例えば今回のファイルの結合と NCMB へのアップロードを一発でできるようになるのになぁと思いました。

## さいごに

NCMB 側で独自のロジックを実行できるということは、現在流行のキーワードになっている「サーバレス」を実現するための道具がまた一つ増えたということなのですね。このようなロジック実行そのものをホストするサービスがたくさん増えて、運用が楽になる未来が早くこないかと思っています。