Alexaでサイコロを投げまくる

# はじめてのAlexaスキル
ようやくAmazon echoの招待が来たので早速「Alexaスキルを作ってみよう」と思い立った。サイコロを振るスキルなんか簡単そうだしどうだろう？…
> 「Alexa、サイコロふって」「はい、1がでました！」
> 「Alexa、サイコロ2個ふって」「はい、10がでました！」

えっ？！Echo、できる子…
> 「Alexa、サイコロ３個ふって」「ごめんなさい…」

よしっ、サイコロ何個でも投げてやりますとも！

# 対話モデルをつくる
<img width="350" alt="Paper.アイデア.1.png" src="https://qiita-image-store.s3.amazonaws.com/0/88598/bf14fe67-1b41-c137-6923-bf4e544df468.png">
最初のやり取りでサイコロの個数が指定されたら、それ以降はサイコロの個数を指定しなくてもよいようにセッションを維持するようにしてみる。

## 発話
ユーザからは、サイコロの個数を指定してもらってサイコロを投げるだけなので、発話はすべて CastDiceIntent にマッピングされる。

```
CastDiceIntent さいころ振って
CastDiceIntent {Number} 個振って
CastDiceIntent 振って
CastDiceIntent さいころ投げて
CastDiceIntent {Number} 個投げて
CastDiceIntent 投げて
```

## Intent Schema
CastDiceIntentにはサイコロの個数を示すNumberスロットを設定する。

```json
{
  "intents": [
    {
      "slots": [
        {
          "name": "Number",
          "type": "AMAZON.NUMBER"
        }
      ],
      "intent": "CastDiceIntent"
    },
    {
      "intent": "AMAZON.HelpIntent"
    },
    {
      "intent": "AMAZON.StopIntent"
    },
    {
      "intent": "AMAZON.CancelIntent"
    }
  ]
}
```


# Lambda

CastDiceIntentの中では、サイコロの個数を表すSlotを参照する。２個以上のサイコロが指定されたら、２投目以降はあらためてサイコロの個数を指定しなくてもいいよう、セッションアトリビュートを使ってセッションを維持する。

```
        var number = this.event.request.intent.slots.Number.value; // サイコロの個数
        if (!number) {
            number = 1; // slotに値がなければ1個
        }
        this.attributes['number'] = number;
```
また、２個以上のサイコロが指定されたときには状態をREPEATMODEに遷移し、サイコロ投げを繰り返す。
サイコロが１個の場合はセッションを維持する必要が無いので、応答を返してセッションを終了する。無駄にサイコロをたくさん指定したときもセッションを終了する。

```
        if (number == 1 || number > 10) {
            this.emit(':tell', message); // レスポンスの生成
        } else {
            this.handler.state = states.REPEATMODE;
```

Lambdaのコード全体は以下の通り。

```javascript:index.js
"use strict";
const Alexa = require('alexa-sdk');

const states = {
    REPEATMODE: '_REPEAT'
};

// Lambda関数のメイン処理
exports.handler = function(event, context, callback) {
    var alexa = Alexa.handler(event, context); // Alexa SDKのインスタンス生成
    //alexa.appId = process.env.APP_ID;
    alexa.registerHandlers(handlers, stateHandlers); // ハンドラの登録
    alexa.execute();                  // インスタンスの実行
};

var handlers = {
    // インテントに紐付かないリクエスト
    'LaunchRequest': function () {
    this.emit('AMAZON.HelpIntent'); // AMAZON.HelpIntentの呼び出し
    },
    
    // ヘルプインテント
    'AMAZON.HelpIntent': function () {
        this.emit(':tell', 'サイコロを振ります' +
            'たとえば、サイコロ振ってと言って下さい');
    },
    
    // さいころを投げるインテント
    'CastDiceIntent': function () {
        var number = this.event.request.intent.slots.Number.value; // サイコロの個数
        if (!number) {
            number = 1; // slotに値がなければ1個
        }
        this.attributes['number'] = number;
        
        var message;
        if (number > 10) {
            message = 'そんなにたくさんサイコロ持てないですよ';
        } else {
            message = rollDice(number);
        }
        
        if (number == 1 || number > 10) {
            this.emit(':tell', message); // レスポンスの生成
        } else {
            this.handler.state = states.REPEATMODE;
            this.emit(':ask', message); // レスポンスの生成
        }
        console.log(message);
    }
};

var stateHandlers = Alexa.CreateStateHandler(states.REPEATMODE, {
  'CastDiceIntent': function() {
    var number = this.attributes['number']; // セッションアトリビュート参照
    var message = rollDice(number);
    this.emit(':ask', message);
    console.log(message);
  },
  'Unhandled': function() {
    this.handler.state = '';
    this.attributes['STATE'] = '';
    var message = '終了します';
    this.emit(':tell', message);
    console.log(message);
  }
});

function rollDice(number) {
    var message = '目は';
    for (var i = 0; i < number; i++) {
        var dice = Math.floor(Math.random()*6) + 1;
        if (i > 0) {
            message = message + 'と';
        }
        message = message + dice;
    }
    message = message + 'です';
    return message;
}
```

# サイコロ投げた
これで好きな数だけサイコロを投げられるようになりました！ Alexa標準機能のサイコロと違って、ゾロ目かどうかもわかりますよ。