0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Alexaスキルアワード公式ハッカソン東京(2018/06/30-07/01)

Last updated at Posted at 2019-04-03
1 / 7

Alexaスキルアワード公式ハッカソン東京

2018.06.30-07/01 @御茶ノ水
connpassサイト


■イベント概要(connpassより引用)

『Amazonでは多くのユーザーにとってさらに魅力的なスキルが提供され、Alexaを通じテクノロジーと人がもっとつながりあう未来が来る事を期待し、Alexaスキルアワード2018を開催することとなりました。
※開催期間:6/13(水)〜8/27(月)
今回のハッカソンはチームビルディングやアイデアソンから始まり、2日間でスキル開発完了を目指します。
2日目の最後には懇親会と参加者全員によるスキル体験会を通して、Alexaスキルの開発者同士の交流の場を設けたいと思います。』


■スケジュール

時間 コンテンツ
1日目
9:30 Opening
10:00 インプット、サポート紹介
11:00 アイデアソン
12:30 ランチ
14:00 アイデア発表
14:30 ハッキングスタート
20:00 一日目終了
2日目
9:30 Opening
10:00 ハッキングタイム
12:00 ランチ
15:30 作業終了
16:00 各作品プレゼン
17:30 タッチ&トライ
18:00 懇親会

■支援サポーター

  • (Twilio)[https://twilio.kddi-web.com/]
  • (Kintone)[https://kintone.cybozu.com/jp/]
  • (nifity cloud mobile backend)[http://mb.cloud.nifty.com/]

■コーディングメモ

基本手順はこのHP

【Skill作成の大きな流れ】

AlexaSkillのAmazon開発者ポータルにSkill名等の基本設定を実施
→ AWSからLamdaの設定(AlexaSkillとの紐付けを実施)
→ AlexaSkillからもLamdaの紐付けを実施
→ Lamdaに処理をコーディング
→ インスタント及びスロットをLamdaの処理に合わせて作成
→ AlexaSkillの設定をいじったらbildする
→ テスト環境もしくは実機でtest

【参考にしたサイト】

【詰まったところ】

  • スロット:音声ワードを格納する変数みたいなもの。
  • インテント:ユーザーの音声によるリクエストを満たすアクションを表す。
  • emit()は1つのfunction(インテント)に1つまで
  • functionを呼び出す方法は2パターン
     「音声入力(キーワード)で呼び出す」、「別のfunctionから呼び出す」
  • global変数を多く定義するのは良くないが、関数間出渡す方法が分からない
  • statesを指定できるが、何故かうまくいかない・・・(カウントで何とかする)
  • 音声入力のワード認識が難しい。。。(amazon.personにしたとき地名を言うと、人の名前に近いワードで認識される)
  • エラーハンドリングの考慮が難しい。(音声入力だけに間違え方、認識の仕方が無限・・・)
  • タイミングや長いワードへの対応に考慮が必要
  • 読み上げ文章を漢字で書くと、読み方を間違えたり、ひらがなにすると変な発音になる。
  • bildに時間がかかるのと、debugがピンポイントでできない(技術不足)のでtry&errorに疲れる・・・

■成果物

【概要】

子供向け物語り作成Skill『ストーリーメーカー』
子供が2人の登場人物と舞台をAlexaに答えるだけで、物語を作成してくれる。
作成された物語を読んでいく中で、応対形式でストーリーを分岐させる参加型物語。
 ex)応援することで相手を倒すことができる。

【プレゼン用】ストーリーメイカーforKIDS (1).jpg
【プレゼン用】ストーリーメイカーforKIDS (1)2.jpg
【プレゼン用】ストーリーメイカーforKIDS (1)3.jpg

【実演】

Google_Chrome_-_developer_amazon_com_-_Alexa_Skills_Kit_Developer_Console_-_Screen_Shot_2018-07-01_22_37_13.png

【構成】

Google_Chrome_-_ap-northeast-1_console_aws_amazon_com_-_Lambda_Management_Console_-_Screen_Shot_2018-07-01_22_37_34.png

【ソースコード】

index.js

'use strict';

const Alexa = require('alexa-sdk');

const APP_ID = undefined;  // TODO replace with your app ID (OPTIONAL).

// global変数を定義
var character = [];     //inputワード配列(キャラクター)
var location;           //inputワード変数(場所)
var count = 1;          //応答回数を記録する変数
var fightVoltage = 0;   //頑張れカウント変数
var storyContent = [];  //物語の格納配列
var chooseStory = true; //trueの場合は桃太郎のはなしを、falseの場合はお金持ちの話をする

// メイン処理
const handlers = {
    //スキル応答(初期動作)
    'LaunchRequest': function () {

        if(count==1 || count == 2){     //登場人物二人を登録
          var speechOutput = 'このスキルでは、君たちから聞いた登場人物や場所の名前から、'
                  + '短い物語を作るよ。';
          var speechOutput2 = '登場人物' + count + 'の名前を教えてくれるかな。' ;
          if(count == 1){
              this.emit(':ask', speechOutput + speechOutput2);
          }
          this.emit(':ask', speechOutput2);
        }else if(count==3){             //場所を登録
          var speechOutput = '登場人物の次はお話の舞台となる場所の名前を教えてくれるかな。';
          this.emit(':ask', speechOutput);
        }else if(count == 4){           //最終確認
          var speechOutput = character[1] + 'と' + character[2] + 'が' + location + 'で活躍するお話を作るね。';
          var speechOutput2 = '今から作るから少し間ってね。'
                            + 'うーーーーーーーーーーーーーーん。     '
                            + "<audio src='https://s3.amazonaws.com/ask-soundlibrary/scifi/amzn_sfx_scifi_engines_on_02.mp3'/>"
                            + 'できたよ。これから作ったお話をするから静かに聞いててね。みんな準備はいいかな?';
          this.emit(':ask', speechOutput + speechOutput2);
        }else{
          this.emit('FallbackIntent');
        }
    },

    //温泉入力
    'InputAdIntent':function(){
        if(count==1 || count == 2){
            character[count] = this.event.request.intent.slots.Input.value;
        }else{
            location = this.event.request.intent.slots.Input.value;
        }
        this.emit('InputCheck');
    },

    // 入力内容の確認
    'InputCheck':function(){
        if(count==1 || count==2){
            this.emit(':ask', character[count] + 'であってる?')
        }
        else{
            this.emit(':ask', location + 'であってる?')
        }
    },

    //物語の作成
    'ReadStory':function(){
        if (count == 5) {

            //物語をランダムに指定。
            if((Math.floor(Math.random() * 10) % 2) == 0){
                chooseStory = true;
            }else{
                chooseStory = false;
            }
            if(chooseStory == true){    //物語1
                //起(起承転結)
                storyContent.push('むかしむかしあるところに、おじいさんとおばあさんが住んでいました。'
                    + "おばあさんが川で洗濯していると、どんぶらこどんぶらこと大きな桃が流れてきました"
                    + "<audio src='https://s3.amazonaws.com/ask-soundlibrary/nature/amzn_sfx_small_stream_02.mp3'/>。"
                    + "驚いたおばあさんが桃をふたつにわると、中から元気な男の子が飛び出してきました。"
                    + "<audio src='https://s3.amazonaws.com/ask-soundlibrary/human/amzn_sfx_baby_big_cry_01.mp3'/>'"
                    + 'おじいさんとおばあさんはその男の子に'
                    + character[1]
                    + 'と名づけ、うちで育てることにしました。');
                //承(起承転結)
                storyContent.push('おじいさん、おばあさんと'
                    + character[1]
                    + 'が仲良く暮らしていると、'
                    + location
                    + 'で'
                    + character[2]
                    +'が悪さをしているという噂が飛び込んできました。そこで'
                    + character[1]
                    + 'は、'
                    + character[2]
                    + '退治にでかけることになりました。途中、おばあさんが作ってくれたきびだんごで'
                    + "サル。<audio src='https://s3.amazonaws.com/ask-soundlibrary/animals/amzn_sfx_monkey_chimp_01.mp3'/>"
                    + "イヌ。<audio src='https://s3.amazonaws.com/ask-soundlibrary/animals/amzn_sfx_dog_med_bark_growl_01.mp3'/>"
                    + "キジ。<audio src='https://s3.amazonaws.com/ask-soundlibrary/animals/amzn_sfx_raven_caw_1x_01.mp3'/>"
                    + 'を仲間にしました。');
                //転(起承転結)
                storyContent.push(location
                    + 'に到着した'
                    + character[1]
                    + 'と仲間たち。'
                    + "<prosody pitch='x-low'>「がおーーー」</prosody>"
                    + "<prosody pitch='x-high'>「な、なんて強さだ...みんな僕に元気を分けてくれっ...!!」</prosody>"
                    + 'みんな'
                    + character[1]
                    + 'を応援してあげてくれる?');
                const speechOut = storyContent[0] + storyContent[1] + storyContent[2];
                this.emit(':ask',speechOut);
            }else{      //物語2
                //起(起承転結)
                storyContent.push("むかしむかし、"
                    + location
                    + "に、一人のおおがねもち。"
                    + character[1] + "がいました。"
                    + " この人は大金持ちですが、とてもけちでした。"
                    + "  貧しい人がたずねて行っても、決して助けてはくれません。"
                    + "<prosody pitch='x-low'>「何て、けちな男だ」</prosody>"
                    + location
                    + "の人たちは、みんな"
                    + character[1]
                    + "の悪口を言いました。さて、"
                    + location
                    + "に、"
                    + character[2]
                    + "がいました。");

                //承(起承転結)
                storyContent.push("この"
                    + character[2]
                    + "はとても親切で、貧しい人を見ると必ず、お金や食べ物を与えて助けてあげたのです。"
                    + "  ある日の事、"
                    + character[1]
                    + "が急な病気で死んでしまいました。 ");
                //転(起承転結)
                storyContent.push(location
                    + "のせわやくは、"
                    + character[1]
                    + "を"
                    + location
                    + "のお墓の一番はしに埋めました。"
                    + "<audio src='https://s3.amazonaws.com/ask-soundlibrary/musical/amzn_sfx_church_bell_1x_05.mp3'/>"
                    + "しかし葬式には、"
                    + location
                    + "の人たちは誰ひとり行きませんでした。"
                    + " それから何日かして、一人の貧しい人が"
                    + character[2]
                    + "を尋ねました。"
                    + "<prosody pitch='x-high'>「何か、めぐんでください」</prosody>。"
                    + "みなさんならどうしまする? なにかあげる?なにもあげない?");
                const speechOut = storyContent[0] + storyContent[1] + storyContent[2];
                this.emit(':ask',speechOut);
            }
            
        }else if(count == 6){
            if(chooseStory == true){
                storyContent.push('それじゃあ、一緒に「頑張れ」っていってね。'
                    + 'せーの、がんばれー!」');
                const speechOut = storyContent[3];
                this.emit(':ask',speechOut);
            }else{
                const speechOut = storyContent[3];
                this.emit(':tell',speechOut);
                count = 1;
              character[1] = null;
              character[2] = null;
              location = null;
              fightVoltage = 1;
              chooseStory = null;
              for (var i = 0; i < storyContent.length; i++) {
                storyContent[i] = null;
              }
            }
        }else if(count == 7){
            if(chooseStory == true){
                storyContent.push("<prosody pitch='x-high'>「みんな、ありがとう!うおおおおおお」</prosody>"
                    + "<prosody pitch='x-low'>「ぐへーーーやられたーーー」</prosody>。"
                    + character[2]
                    + 'を倒しました。'
                    + character[2]
                    + 'をどうしましょう?みんなどうする?仲良くする?たべちゃう?');
                const speechOut = storyContent[4];
                this.emit(':ask',speechOut);
            }else{
            }
        }else if(count == 8){
            var speechOut = storyContent[5]
            this.emit(':tell',speechOut);
            count = 1;
            character[1] = null;
            character[2] = null;
            location = null;
            fightVoltage = 1;
            chooseStory = null;
            for (var i = 0; i < storyContent.length; i++) {
              storyContent[i] = null;
            }
        }
    },

    'AgreeIntent':function(){
        count++;
        if (count < 5 ){
            this.emit('LaunchRequest');
        } else{
            this.emit('ReadStory');
        }
    },

    'DisagreeIntent':function(){
      this.emit('FallbackIntent');
    },

    'JudgeFriend':function(){
        count++;
       storyContent.push(character[2]
                    + 'は、すっかり改心して悪さをしなくなりました。そして'
                    + character[2]
                    + 'と'
                    + character[1]
                    + "は、今日も仲良く鬼ごっこをして遊んでいるようですよ。めでたし、めでたし。"
                    + "<audio src='https://s3.amazonaws.com/ask-soundlibrary/human/amzn_sfx_large_crowd_cheer_02.mp3'/>");
        this.emit('ReadStory');
    },

    'JudgeEat':function(){
      count++;
      storyContent.push(character[1]
                      + 'は'
                      + character[2]
  //                    + 'を火鍋に入れ、しゃぶしゃぶにしました。'
                      + "<audio src='https://s3.amazonaws.com/ask-soundlibrary/foley/amzn_sfx_large_fire_crackling_01.mp3'/>"
                      + '意外と美味しかったので、'
                      + character[1]
                      + 'は'
                      + character[2]
                      + 'を探し求め旅を始めました。めでたし、めでたし。');

      this.emit('ReadStory');
    },

    'JudgeFight':function(){
      if(fightVoltage == 1){
        fightVoltage++;
        this.emit(':ask','みんなもっと応援してあげて');
      }else if(fightVoltage > 1 && fightVoltage < 4){
        fightVoltage++;
        this.emit(':ask','もっと');
      }else{
        count++;
        this.emit('ReadStory');
      }
    },

    'JudgeGet':function(){
      count=6;
      storyContent.push(character[2]
                      + "は貧しい人にごはんをわけてあげました。"
                      + "貧しい人は、不満そうにいいました。"
                      + "<prosody pitch='x-high'>「どうしてですか? あなたはいつも、お金をめぐんでくれたのに」</prosody>。"
                      + "すると"
                      + character[2]
                      + "は、こんな事を話し出しました。"
                      + " <prosody pitch='low'>「この前に亡くなったお金持ちのご主人が、お金を持って来て、わたしにこんな事を頼んだんだ。 『このお金を、貧しい人にあげてくれ。ただし、誰のお金かは、決して言わないように』と」</prosody>。"
                      + "この話は、すぐに"
                      + location
                      + "中に広まりました。"
                      + " そしてみんなは、すぐにお墓へ飛んでいきました。"
                      + " そして、今まで悪口を言っていた事を、心からあやまったのです。");
      this.emit('ReadStory');
    },

    'JudgeNoGet':function(){
      count=6;
      storyContent.push("すると、"
                      + character[2]
                      + "がこう言ったのです。"
                      + " <prosody pitch='low'>「あげる物なんて、何もないよ」</prosody>"
                      + "それを聞いた貧しい人は、びっくりしました。"
                      + " <prosody pitch='x-high'>「どうしてですか? あなたはいつも、わたしたちを助けてくれたのに」。</prosody>"
                      + "すると"
                      + character[2]
                      + "は、こんな事を話し出しました。"
                      + " <prosody pitch='low'>「この前に亡くなったお金持ちのご主人が、お金を持って来て、わたしにこんな事を頼んだんだ。 『このお金を、貧しい人にあげてくれ。ただし、誰のお金かは、決して言わないように』と」。</prosody>"
                      + "この話は、すぐに"
                      + location
                      + "中に広まりました。"
                      + "そしてみんなは、すぐにお墓へ飛んでいきました。"
                      + "そして、今まで悪口を言っていた事を、心からあやまったのです。");
        this.emit('ReadStory');
    },

    //エラーインテント
    'FallbackIntent':function(){
      this.emit(':ask', 'ゴメンね。うまく聞き取れなかった。もう一度お願いできる?')
    },

    'AMAZON.HelpIntent': function () {
        this.emit('LaunchRequest');
    },
    'AMAZON.CancelIntent': function () {
        count = 1;
        character[1] = null;
        character[2] = null;
        location = null;
        fightVoltage = 1;
        chooseStory = null;
        for (var i = 0; i < storyContent.length; i++) {
          storyContent[i] = null;
        }
        this.emit(':tell', 'じゃあね');
    },
    'AMAZON.StopIntent': function () {
        count = 1;
        character[1] = null;
        character[2] = null;
        location = null;
        fightVoltage = 1;
        chooseStory = null;
        for (var i = 0; i < storyContent.length; i++) {
          storyContent[i] = null;
        }
        this.emit(':tell', 'じゃあね');
    },
};

exports.handler = function (event, context) {
    const alexa = Alexa.handler(event, context);
    alexa.APP_ID = APP_ID;
    // To enable string internationalization (i18n) features, set a resources object.
    alexa.registerHandlers(handlers);
    alexa.execute();
};

json.js

{
    "interactionModel": {
        "languageModel": {
            "invocationName": "ストーリーメーカー",
            "intents": [
                {
                    "name": "AMAZON.CancelIntent",
                    "samples": []
                },
                {
                    "name": "AMAZON.HelpIntent",
                    "samples": []
                },
                {
                    "name": "AMAZON.StopIntent",
                    "samples": []
                },
                {
                    "name": "LaunchRequest",
                    "slots": [],
                    "samples": [
                        "ストーリーメーカーをお願い",
                        "ストーリーメーカーを起動",
                        "ストーリーメーカーを開いて",
                        "ストーリーメーカーを起動して"
                    ]
                },
                {
                    "name": "InputAdIntent",
                    "slots": [
                        {
                            "name": "Input",
                            "type": "AMAZON.Person"
                        }
                    ],
                    "samples": [
                        "{Input}",
                        "{Input} が登場",
                        "{Input} を登場 ",
                        "{Input} を出して"
                    ]
                },
                {
                    "name": "InputCheck",
                    "slots": [],
                    "samples": []
                },
                {
                    "name": "StartGame",
                    "slots": [],
                    "samples": []
                },
                {
                    "name": "AgreeIntent",
                    "slots": [],
                    "samples": [
                        "うん",
                        "いい",
                        "そう",
                        "あってる"
                    ]
                },
                {
                    "name": "DisagreeIntent",
                    "slots": [],
                    "samples": [
                        "いいえ",
                        "あってない",
                        "ちがう",
                        "まちがってる"
                    ]
                },
                {
                    "name": "JudgeFriend",
                    "slots": [],
                    "samples": [
                        "仲良くする"
                    ]
                },
                {
                    "name": "JudgeEat",
                    "slots": [],
                    "samples": [
                        "食う",
                        "喰う",
                        "食べちゃう",
                        "食べる"
                    ]
                },
                {
                    "name": "JudgeFight",
                    "slots": [],
                    "samples": [
                        "がんばれ",
                        "頑張れー",
                        "頑張れ"
                    ]
                },
                {
                    "name": "JudgeGet",
                    "slots": [],
                    "samples": [
                        "わけてあげる",
                        "提供する",
                        "プレゼントする",
                        "あげる"
                    ]
                },
                {
                    "name": "JudgeNoGet",
                    "slots": [],
                    "samples": [
                        "わけてあげない",
                        "提供しない",
                        "プレゼントしない",
                        "いじわるする",
                        "あげない"
                    ]
                }
            ],
            "types": []
        }
    }
}

今後の課題

  • まだ現状2エピソードしかないので、拡充したい。
  • コードが煩雑なので、整理。
  • エラーコードが上手く処理で来ていないので、修正
  • 子供が使いやすい(しゃべる言葉を誘導する、騒いでも大丈夫)などが考慮できてないので、改善。
  • そもそもNode.jsがお初なので、もっと勉強して使いこなしたい。
    などなど課題は山済み。
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?