【Swift × Node.js】サーバーレスアーキテクチャでアプリにガチャを実装してみた

# 概要

近年のソーシャルゲームでは欠かせない要素である **ガチャ**。
ガチャを引くのが１番の楽しみというユーザーも多く、最早アプリのメインコンテンツとも言えます。
今回はそんなガチャを引くだけのアプリを作ってみました。

ソースコードも載せてあるので、ソーシャルゲームを作ってみたい方やSwiftの勉強をしている方の参考になればと思います。

# アプリ紹介
Today's Lunchというアプリを作ってみました。
「今日のご飯なにたべよう」と迷った時にガチャで決めようというアプリです。
食べ物の中には超激レアなアイテムもあるのでコンプリートを目指して楽しむこともできます。

<img width="375" alt="Simulator Screen Shot 2016.11.22 0.24.07.png" src="https://qiita-image-store.s3.amazonaws.com/0/63169/0e9f9678-7885-9d2d-e6f5-1fb5e2d94bdb.png">


# ソース
[github](https://github.com/fuku2014/todays-lunch)

# データの用意
まずはガチャで出現するItemのデータを定義します。
jsonファイルで以下のようなデータを作成しました。

```json:lunch.json
{
    "foods": [ 
        {
            "name": "おにぎり", 
            "rate": 50, 
            "star": "☆"
        }, 
        {
            "name": "ハンバーガー", 
            "rate": 25, 
            "star": "☆☆"
        }, 
        {
            "name": "コロッケ定食", 
            "rate": 15, 
            "star": "☆☆☆"
        }, 
        {
            "name": "から揚げ定食", 
            "rate": 7, 
            "star": "☆☆☆☆"
        },
        {
            "name": "焼肉ランチ", 
            "rate": 3, 
            "star": "☆☆☆☆☆"
        }
    ]
}
```

itemには名前と確率とレアリティである☆が定義してあります。
一般的なソーシャルゲームのガチャもこれくらいの確率ですね。
⭐️5が出たらラッキー!

# 構成

クライアント側はiOS向けに作ったのでSwift3.0です。

通常ガチャのロジックはクライアント側ではなくサーバー側で実装することが多いと思うので、サーバーサイドのアプリケーションとそれを実行するためのサーバーも必要になります。
しかし、自分でサーバーを用意するとなるとサーバー自体のインフラ料金はもちろん、サーバーの構築や運用にも膨大な工数を取られてしまいます。

そんなお悩みを解決するために、今回は2016/10/25にリリースされたばかりの[ニフティクラウドスクリプト](http://cloud.nifty.com/service/script.htm)というサービスを使ってみました。

## ニフティクラウドスクリプトとは？
このサービスはサーバーサイドのスクリプトをクラウド上で実行できるサービスです。
すぐにNode.jsの実行環境が手に入るので、[*サーバー不要*] [*環境構築不要*]  [*運用不要*] と大変便利です。

さらに通常のサーバーを用意するのと比べて低コストで運用できるメリットもあります。
例としてニフティクラウドのIaaSで運用した場合とコストを比較してみました。

* ガチャが月に10,000回実行された場合

|                                      |              月額コスト  |     
|:-----------------|------------------:|
| [ニフティクラウド mediumuサーバー ](http://cloud.nifty.com/price/)      |              24,200円 |          
| [ニフティクラウドスクリプト](http://cloud.nifty.com/price/ep.htm#script)         |            1,000円 + 300円 | 
※ スクリプトの処理時間は1回0.１秒としてます。

ニフティクラウドスクリプトは完全重量課金なので、実行回数と処理時間に応じて課金されます。
つまり、通常のサーバーのようにアプリが人気でなくて売り上げがないのにサーバーの料金を取られてしまうということもないので安心です。
逆にスクリプトがたくさん実行されて金額がかかるということは、それだけアプリの売上も上がっているってことですね。
その場合も自分でサーバーのスペックをあげたりスケーリングする必要もないので便利です。

## ニフティクラウドスクリプトへ登録する

[クイックスタート](http://cloud.nifty.com/guide/script/quickstart.htm)を参考に[ニフティクラウドスクリプト](http://cloud.nifty.com/service/script.htm)への登録を完了させます。
以上で準備は完了です、いよいよニフティクラウドスクリプトを使ってガチャを実行されるプログラムを書いていきます。

# Node.jsでガチャをプログラミングする

こちらが完成形のソースになります。

```js:script.js
const URL = 'https://lunch.jp-east-2.os.cloud.nifty.com/lunch.json';

let http = require('https');
module.exports = (req, res) => {
  http.get(URL, (result) => {
    let body = '';
    result.setEncoding('utf8');

    result.on('data', (chunk) => {
      body += chunk;
    });

    result.on('end', (result) => {
      data = JSON.parse(body);
      let max = 0;
      data.foods.forEach(d => {
        max += d["rate"];
      });

      let rate = Math.floor(Math.random() * max);
      for (var d of data.foods) {
        max -= d["rate"];
        if (max <= rate) {
          res.send(JSON.stringify(d));
        }
      }
    });
  });
}
```

## ポイント
* まずは先ほど用意したガチャで出現するItemデータのjsonを読み込む
* 今回はデータを[ニフティクラウドオブジェクトストレージ](http://cloud.nifty.com/service/obj_storage.htm)に置いてるのでそこから取得
* その後jsonデータに定義されている確率の累計を求め、乱数を生成してマッチするアイテムを返す

早速このプログラムをニフティクラウドスクリプトに登録し、ためにしコントロールパネルから実行してみます。
![script.png](https://qiita-image-store.s3.amazonaws.com/0/63169/1eb64629-7821-63ed-12a9-41b4d0be9107.png)

⭐️1のカツ丼が出ました。

# swiftからニフティクラウドスクリプトを呼び出す

ニフティクラウドスクリプトを使ってガチャのプログラミングが完了したので、今度はクライアントサイドとなるiOSアプリを作っていきます。

ニフティクラウドスクリプトは[API](http://cloud.nifty.com/api/script/)を公開しているので単純にswiftからこれをリクエストしてあげればOKです。

一見難しそうに見える[APIの認証](http://cloud.nifty.com/api/script/start.htm)なのですが、実はこれAWSの認証とまったく同じ仕様になっているようです。
ですので、[aws-sdk-ios](https://github.com/aws/aws-sdk-ios)を使って簡単にリクエストできます。

## aws-sdk-iosを使ってAPIをリクエストする

認証部分はAWSCoreにあるので、CocoaPodsなどでこれをダウンロードしてきます。

```:Podfile
latform :ios, '8.0'

target 'todaysLunch' do
  pod 'AWSCore'
end
```
aws-sdk-iosはObjective-Cで書かれているので、swiftから呼べるようにBridgeHeaderファイルを作成しておきます。

```:BridgeHeader.h
#import "AWSCore.h"
```

準備が整ったので、実際にニフティクラウドスクリプトのAPIをリクエストしてみます。

```swift:Script.swift
class Script: NSObject, XMLParserDelegate {
    
    var endpoint: URL
    var version: String
    var credentialsProvider: AWSStaticCredentialsProvider
    
    var currentElementName = ""
    var responseData       = ""
    
    init(endpoint: String, version: String, accessKey: String, secretKey: String){
        self.endpoint = URL(string: String(format: "%@/%@",endpoint,version))!
        self.version = version
        self.credentialsProvider = AWSStaticCredentialsProvider(accessKey: accessKey,secretKey: secretKey)
    }
    
    func executeScript(scriptIdentifier: String, method: String, query: String = "null", body: String  = "null", header: String = "null", callback: @escaping (String, String?) -> Void) {
        let action   = "ExecuteScript"
        let httpBody = String(format: "ScriptIdentifier=%@&Method=%@&Query=%@&Header=%@&Body=%@",scriptIdentifier,method,query,header,body)
        let req = makeReqest(body: httpBody, action: action)
        let task = URLSession.shared.dataTask(with: req as URLRequest, completionHandler: {data, response, error in
            if error == nil {
                let parser = XMLParser(data: data! as Data)
                parser.delegate = self
                parser.parse()
                callback(self.responseData as String, nil)
            } else {
                callback("", error!.localizedDescription)
            }
        })
        task.resume()
    }
    
    func makeReqest(body: String, action: String) -> NSMutableURLRequest {
        let req: NSMutableURLRequest = NSMutableURLRequest(url: self.endpoint)
        let date = Date()
        let formatter = DateFormatter()
        formatter.dateFormat = AWSDateISO8601DateFormat2
        
        req.httpMethod = "POST"
        req.httpBody = body.data(using: String.Encoding.utf8)
        req.setValue(formatter.string(from: date), forHTTPHeaderField:"X-Amz-Date")
        req.addValue(String(format: "%@.%@",self.version,action), forHTTPHeaderField: "X-Amz-Target")
        req.addValue("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", forHTTPHeaderField: "Accept")
        req.addValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
        
        let signer: AWSSignatureV4Signer = AWSSignatureV4Signer(
            credentialsProvider: self.credentialsProvider,
            endpoint: AWSEndpoint.init(region: AWSRegionType.usEast1, service: AWSServiceType.lambda, url: self.endpoint))
        signer.interceptRequest(req)
        return req
    }
    
    func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String]) {
        currentElementName = elementName
    }
    
    func parser(_ parser: XMLParser, foundCharacters string: String) {
        if currentElementName == "ResponseData" {
            responseData = string
        }
    }
}
```

以下のように呼び出します。

```swift:
let script = Script(endpoint:  "https://script.api.cloud.nifty.com",
                    version:   "2015-09-01",
                    accessKey: MY_ACCESS_KEY,
                    secretKey: MY_SECRET_KEY)
        
script.executeScript(scriptIdentifier: "hoge.js", method: "GET") { (response, error) -> Void in

  if error != nil {
      print(error)
  }

  let json = try! JSONSerialization.jsonObject(with: response.data(using: String.Encoding.utf8)!) as! [String: AnyObject]
  print(json)
}
```

###  ポイント

* ACCESS_KEYとSECRET_KEYはニフティクラウドのコントロールパネルから取得した自分のキーを使う
* aws-sdk-iosのAWSSignatureV4Signerクラスを使ってリクエストを実行するだけ
* APIのレスポンスとしてXMLが返されるので「ResponseData」を取得すれば完了

# まとめ

swiftからスクリプトを呼び出すことができれば、その結果を画面に表示させてあげるだけでアプリは完成です！

<img width="414" alt="Simulator Screen Shot 2016.11.22 0.45.17.png" src="https://qiita-image-store.s3.amazonaws.com/0/63169/66b30379-3958-44bd-47c3-f3154142dfed.png">


ソースの全体は[github](https://github.com/fuku2014/todays-lunch)にもあげてありますのでこちらも参照ください。

いかがでしたでしょうか？
普通に作ったら結構大変なiOSとサーバーサイドの連携アプリがこんな簡単に作れてしまいました。
やっぱり自分でサーバーを用意しなくていいのは楽でいいですね。

[ニフティクラウドスクリプト](http://cloud.nifty.com/api/script/)はとても便利なサービスだと思うので、皆さんもぜひ使ってみてください。

以上、「[NIFTY Advent Calendar 2016](http://qiita.com/advent-calendar/2016/nifty)」の1日目の記事として、ニフティクラウドスクリプトを実際のアプリで使ってみた話を記載してみました。