alexaでゲームスキルを作ってみた


これまでにいくつかスキルを出していますが、
今回新たに利用した機能や発見についてご紹介できればと思います。

# どんなスキル?
今回作ったのは[交通量調査の達人](https://www.amazon.co.jp/dp/B07QZWTGQC/)です。

「単位上等!爆走数取○」（通称：○取団）というめちゃイケのコーナーをご存知ですか？
暴走族に扮したメンバーが、順番にお題を出しながら数と単位を答えていき、間違ったらゲームオーバー（力士に襲われる）になるというゲームです。

これにインスパイアされて作ったのが本スキル「交通量調査」です。
街角でよく、交通量調査のバイトさんがカウンターを持って座ってらっしゃいますよね。
道路で車を数えていたり、大型ショッピングモールでは人を数えていたりするのをみたことがあると思います。
このスキルでは、いろんなものが通っていくので、それを単位をつけながらカウントしていくというゲームです。

どのようなスキルかイメージが湧かない？是非一度遊んでみてください。

# スキルの実装

## Intent
主に２つのインテントから構成されています。

### QuestionIntent: 問題の出題
*QuestionIntent* では、アプリと一緒にデプロイされている問題一覧（json）からランダムに一つを選び、ユーザーに問題を出します。
また、後述するAnswerIntentで回答チェックできるように、sessionAttributeに出題した内容とその答えを保存しています。

```ts
const QuestionHandler = {
  canHandle(handlerInput: Alexa.HandlerInput) {
    const request = handlerInput.requestEnvelope.request;
    return request.type === 'IntentRequest'
      && request.intent.name === 'QuestionIntent';
  },
  async handle(handlerInput: Alexa.HandlerInput) {
    // 問題をランダムを選択
    const random = Math.floor( Math.random() * questionList.length );
    const question = questionList[random];

    // 数をインクリメントする
    let attributes = handlerInput.attributesManager.getSessionAttributes();
    const nextCounter = !attributes.counter ? 1 : attributes.counter + 1

    // 正解をセッションに保存
    handlerInput.attributesManager.setSessionAttributes({counter: nextCounter, question: question});

    var speechOutput = question.item;
    return handlerInput.responseBuilder
      .speak(speechOutput)
      .reprompt(REPROMPT)
      .getResponse();
  },
```

### AnswerIntent: ユーザー回答のチェック

*AnswerIntent* では、sessionAttributeに保存されている答えと、ユーザーからの応答をチェックします。
正解の場合は、次の問題を出すために、QuestionIntentに遷移させ、間違っている場合はそこでゲームを終了させます。

また、*AnswerIntent* では、 {number} と {unit} の二つのslotを定義しています。

|slotName | type | 備考 |
|---|---|---|
|number | AMAZON.NUMBER | 数字の部分を読み取る |
|unit | カスタムスロットタイプ | 自前で単位一覧を定義し、単位部分を読み取る|

```ts
const AnswerHandler = {
  canHandle(handlerInput: Alexa.HandlerInput) {
    const request = handlerInput.requestEnvelope.request;
    return request.type === 'IntentRequest'
      && request.intent.name === 'AnswerIntent';
  },
  async handle(handlerInput: Alexa.HandlerInput) {
    const request = (handlerInput.requestEnvelope.request as IntentRequest)

    // 正解をsessionAttributeから取得
    let attributes = handlerInput.attributesManager.getSessionAttributes()

    // ユーザーからの回答
    const slots = request.intent.slots;
    const counter = slots['number'].value;
    const unit = slots['unit'].resolutions.resolutionsPerAuthority[0].values[0].value.name;
    let answer = {
      counter: Number(counter),
      unit: unit
    };

    // 回答のチェック
    if (checkAnswer(attributes, answer)) {
      // 正解の場合、さらに質問を出すために、質問をだすintentに遷移
      return QuestionHandler.handle(handlerInput)

    } else {
      // 間違った場合
      return handlerInput.responseBuilder
        .speak(incorrect(attributes))
        .withShouldEndSession(true)
        .getResponse();
    }
  }
};

```


# 工夫ポイント
## スロットと問題の管理
「交通量調査」では、新しい問題で、新しい単位が増えた場合は、スロットも追加する必要があり、二重管理になっています。
現在は、問題数65問、単位数36個という状況で、手動で一個ずつ管理するのが少し大変でした。
そこで、問題のデータ構造を下記のようなjsonにし、コマンド一発でcsvファイルを生成し、
alexa developer consoleの「スロットタイプの値を一括編集」でcsv形式のデータからimportするようにしています。

```bash
$ cat data/question/data.json
[
  {"item":"りんご", "unit":[["こ","個"]]},
  {"item":"たたみ", "unit":[["じょう","畳"],["まい","枚"]]},
  {"item":"おさら", "unit":[["まい","枚"]]},
  ...
]
```

```bash
$ cat data/question/data.json | jq '.[].unit[] | [.[0],"",.[1]] | @csv ' -r | sort | uniq |  sed -e 's/"//g' > data/question/data.csv

$ head data/question/data.csv
こ,,個
じょう,,畳
まい,,枚
...
```

# 発見
## 濁音、半濁音は自動で認識してくれる
例えば、「イカ」という問題が出された場合、単位は「杯」です。
- 数が２の場合は、二杯（に**は**い）
- 数が３の場合は、三杯（さん**ば**い）
- 数が１０の場合は、十杯（じゅっ**ぱ**い）
で数によって濁音や半濁音がついたりします。

この濁音、半濁音も{unit} slot に全部登録しないといけないのかなーと思ってたのですが、
{unit} slotの同義語（オプション）に漢字で登録しておくと、よしなに認識してくれるようです。

# 困ったこと

## 一人（ひとり）、二人（ふたり）が認識されない
問題で、人物名（例えば、夏目漱石）を用意しており、この場合は○人が正解です。
しかし、日本語の場合は、三人（さんにん）以上であれば問題ないのですが、一人（ひとり）、二人（ふたり）だとうまくslotに当てはめてくれないので、序盤に人物が出た場合はそこで詰みになります。
（ひとひと、ふたひとと答えれば実は正解したりするのですが）

## サウンドキットの音量調整ができない
https://developer.amazon.com/ja/docs/custom-skills/ask-soundlibrary.html

```
<audio src='soundbank://soundlibrary/animals/amzn_sfx_bear_groan_roar_01'/>
```

公式でたくさんの音源が用意されているので、ちょっと使ってみよーと思い使っています。
しかし、困ったことに、サウンドキットの音量がちょっと小さかったので、
音量あげる方法ないかなーと思い調べてみたのですが、なさそうなので諦めたので、何か方法があれば是非知りたいです。
（自分でmp3作ればいいのでしょうが、タグの属性でできたら幸せ）


# その他
## 審査でのありがたい指摘
- スキルの説明文に `「馬」が通った場合は、二匹` と書いてたら、`二頭ですよ`とツッコミをいただきました。毎回のチェックありがとうございます。

```スキルの説明.txt

このゲームは、通っていくもの（読み上げられるもの）を単位をつけて数えてゆくゲームです。

例えば、初めに「車」が通った場合は「一台」、
次に「馬」が通った場合は、「二頭」というように
数をカウントアップしながら、適切な単位をつけて答えます。

数や単位を間違えるとゲームオーバーです。

一人で記録を目指してもよいですし、複数人で順番に答えて遊ぶこともできます。
```