├── .github └── PULL_REQUEST_TEMPLATE.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── NOTICE ├── README.md ├── Step 0 - Initialize Riddle Game ├── .ask │ └── config ├── README.md ├── lambda │ └── custom │ │ ├── index.js │ │ ├── package.json │ │ └── riddle_objects.js ├── models │ └── en-US.json └── skill.json ├── Step 1 - Add Advanced Voice Design ├── .ask │ └── config ├── README.md ├── lambda │ └── custom │ │ ├── index.js │ │ ├── package.json │ │ └── riddle_objects.js ├── models │ └── en-US.json └── skill.json ├── Step 2 - Add ISP ├── .ask │ └── config ├── README.md ├── lambda │ └── custom │ │ ├── index.js │ │ ├── package.json │ │ └── riddle_objects.js ├── models │ └── en-US.json └── skill.json └── Step 3 - Add APL ├── .ask └── config ├── README.md ├── imgs_of_apl_screens ├── finished_landscape.png ├── finished_round.png ├── hint_landscape.png ├── hint_round.png ├── launchrequest_landscape.png ├── launchrequest_round.png ├── riddle_landscape.png └── riddle_round.png ├── lambda └── custom │ ├── finishedgame.json │ ├── hint.json │ ├── index.js │ ├── launchrequest.json │ ├── package.json │ ├── riddle.json │ └── riddle_objects.js ├── models └── en-US.json └── skill.json /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | *Issue #, if available:* 2 | 3 | *Description of changes:* 4 | 5 | 6 | By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice. 7 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check [existing open](https://github.com/alexa-labs/skill-sample-nodejs-level-up-riddles/issues), or [recently closed](https://github.com/alexa-labs/skill-sample-nodejs-level-up-riddles/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aclosed%20), issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *master* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 41 | 42 | 43 | ## Finding contributions to work on 44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any ['help wanted'](https://github.com/alexa-labs/skill-sample-nodejs-level-up-riddles/labels/help%20wanted) issues is a great place to start. 45 | 46 | 47 | ## Code of Conduct 48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 50 | opensource-codeofconduct@amazon.com with any additional questions or comments. 51 | 52 | 53 | ## Security issue notifications 54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 55 | 56 | 57 | ## Licensing 58 | 59 | See the [LICENSE](https://github.com/alexa-labs/skill-sample-nodejs-level-up-riddles/blob/master/LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 60 | 61 | We may ask you to sign a [Contributor License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger changes. 62 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Amazon Software License 1.0 2 | 3 | This Amazon Software License ("License") governs your use, reproduction, and 4 | distribution of the accompanying software as specified below. 5 | 6 | 1. Definitions 7 | 8 | "Licensor" means any person or entity that distributes its Work. 9 | 10 | "Software" means the original work of authorship made available under this 11 | License. 12 | 13 | "Work" means the Software and any additions to or derivative works of the 14 | Software that are made available under this License. 15 | 16 | The terms "reproduce," "reproduction," "derivative works," and 17 | "distribution" have the meaning as provided under U.S. copyright law; 18 | provided, however, that for the purposes of this License, derivative works 19 | shall not include works that remain separable from, or merely link (or bind 20 | by name) to the interfaces of, the Work. 21 | 22 | Works, including the Software, are "made available" under this License by 23 | including in or with the Work either (a) a copyright notice referencing the 24 | applicability of this License to the Work, or (b) a copy of this License. 25 | 26 | 2. License Grants 27 | 28 | 2.1 Copyright Grant. Subject to the terms and conditions of this License, 29 | each Licensor grants to you a perpetual, worldwide, non-exclusive, 30 | royalty-free, copyright license to reproduce, prepare derivative works of, 31 | publicly display, publicly perform, sublicense and distribute its Work and 32 | any resulting derivative works in any form. 33 | 34 | 2.2 Patent Grant. Subject to the terms and conditions of this License, each 35 | Licensor grants to you a perpetual, worldwide, non-exclusive, royalty-free 36 | patent license to make, have made, use, sell, offer for sale, import, and 37 | otherwise transfer its Work, in whole or in part. The foregoing license 38 | applies only to the patent claims licensable by Licensor that would be 39 | infringed by Licensor's Work (or portion thereof) individually and 40 | excluding any combinations with any other materials or technology. 41 | 42 | 3. Limitations 43 | 44 | 3.1 Redistribution. You may reproduce or distribute the Work only if 45 | (a) you do so under this License, (b) you include a complete copy of this 46 | License with your distribution, and (c) you retain without modification 47 | any copyright, patent, trademark, or attribution notices that are present 48 | in the Work. 49 | 50 | 3.2 Derivative Works. You may specify that additional or different terms 51 | apply to the use, reproduction, and distribution of your derivative works 52 | of the Work ("Your Terms") only if (a) Your Terms provide that the use 53 | limitation in Section 3.3 applies to your derivative works, and (b) you 54 | identify the specific derivative works that are subject to Your Terms. 55 | Notwithstanding Your Terms, this License (including the redistribution 56 | requirements in Section 3.1) will continue to apply to the Work itself. 57 | 58 | 3.3 Use Limitation. The Work and any derivative works thereof only may be 59 | used or intended for use with the web services, computing platforms or 60 | applications provided by Amazon.com, Inc. or its affiliates, including 61 | Amazon Web Services, Inc. 62 | 63 | 3.4 Patent Claims. If you bring or threaten to bring a patent claim against 64 | any Licensor (including any claim, cross-claim or counterclaim in a 65 | lawsuit) to enforce any patents that you allege are infringed by any Work, 66 | then your rights under this License from such Licensor (including the 67 | grants in Sections 2.1 and 2.2) will terminate immediately. 68 | 69 | 3.5 Trademarks. This License does not grant any rights to use any 70 | Licensor's or its affiliates' names, logos, or trademarks, except as 71 | necessary to reproduce the notices described in this License. 72 | 73 | 3.6 Termination. If you violate any term of this License, then your rights 74 | under this License (including the grants in Sections 2.1 and 2.2) will 75 | terminate immediately. 76 | 77 | 4. Disclaimer of Warranty. 78 | 79 | THE WORK IS PROVIDED "AS IS" WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 80 | EITHER EXPRESS OR IMPLIED, INCLUDING WARRANTIES OR CONDITIONS OF 81 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE OR 82 | NON-INFRINGEMENT. YOU BEAR THE RISK OF UNDERTAKING ANY ACTIVITIES UNDER 83 | THIS LICENSE. SOME STATES' CONSUMER LAWS DO NOT ALLOW EXCLUSION OF AN 84 | IMPLIED WARRANTY, SO THIS DISCLAIMER MAY NOT APPLY TO YOU. 85 | 86 | 5. Limitation of Liability. 87 | 88 | EXCEPT AS PROHIBITED BY APPLICABLE LAW, IN NO EVENT AND UNDER NO LEGAL 89 | THEORY, WHETHER IN TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE 90 | SHALL ANY LICENSOR BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY DIRECT, 91 | INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR 92 | RELATED TO THIS LICENSE, THE USE OR INABILITY TO USE THE WORK (INCLUDING 93 | BUT NOT LIMITED TO LOSS OF GOODWILL, BUSINESS INTERRUPTION, LOST PROFITS 94 | OR DATA, COMPUTER FAILURE OR MALFUNCTION, OR ANY OTHER COMM ERCIAL DAMAGES 95 | OR LOSSES), EVEN IF THE LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF 96 | SUCH DAMAGES. 97 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Level Up Riddles 2 | Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Riddle Game Workshop 2 | 3 | Riddle Game Workshop was built to showcase advanced skill building techniques. A user opens the skill and selects to play with easy, medium, or hard riddles. After their selection, Alexa gives them 5 riddles per commanded category. A user goes through trying to answer the riddles, to which Alexa responds with celebration if correct, the correct answer if incorrect. At the end, Alexa totals the number of correct riddles they answered. 4 | 5 | ## Overview 6 | 7 | This workshop is split into 4 sections. Each section builds off the previous, and gets more advanced per step: 8 | 9 | - [**Step 0 - Initialize Riddle Game**](https://github.com/alexa-labs/skill-sample-nodejs-level-up-riddles/tree/master/Step%200%20-%20Initialize%20Riddle%20Game): You will create and configure the Riddle Game skill using the Alexa Skills Kit SDK in NodeJS and AWS Lambda. When launched, this Alexa skill will have the customer interact with a riddle game that features a simple voice interaction and using session attributes. 10 | 11 | - [**Step 1 - Add Advanced Voice Design**](https://github.com/alexa-labs/skill-sample-nodejs-level-up-riddles/tree/master/Step%201%20-%20Add%20Advanced%20Voice%20Design): At the beginning of the skill, you will prompt the user not only for the difficulty level, but also optionally their name, favorite color, and how many questions they'd like to be asked in the game. 12 | 13 | - [**Step 2 - Add In-Skill Purchasing**](https://github.com/alexa-labs/skill-sample-nodejs-level-up-riddles/tree/master/Step%202%20-%20Add%20ISP): A customer can now buy the ability to ask for hints within the game. There are 3 hints per question. 14 | 15 | - [**Step 3 - Add Displays with the Alexa Presentation Language**](https://github.com/alexa-labs/skill-sample-nodejs-level-up-riddles/tree/master/Step%203%20-%20Add%20APL): With APL, you can develop visual templates for skills formatted anyway you'd like. This integration allows customers to interact with their multimodal devices and the skill. 16 | 17 | ## License 18 | 19 | This library is licensed under the Amazon Software License. 20 | -------------------------------------------------------------------------------- /Step 0 - Initialize Riddle Game/.ask/config: -------------------------------------------------------------------------------- 1 | { 2 | "deploy_settings": { 3 | "default": { 4 | "skill_id": "", 5 | "was_cloned": false, 6 | "merge": { 7 | } 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Step 0 - Initialize Riddle Game/README.md: -------------------------------------------------------------------------------- 1 | # Initialize Riddle Game 2 | 3 | In this section of the workshop, you will create and configure a skill using the Alexa Skills Kit SDK in NodeJS and AWS Lambda. When launched, this Alexa skill will have the customer interact with a Riddle Game skill that features a simple voice interaction. 4 | 5 | ## Objectives 6 | 7 | After completing this workshop, you will be able to: 8 | 9 | - Create an Amazon Developer account. 10 | - Create and configure a new skill using the Alexa Skills Kit and AWS Lambda 11 | - Create and configure Intents, Sample Utterances, and Slots 12 | - Test a skill using Lambda and an Echo device. 13 | 14 | ## Prerequisites 15 | 16 | This lab requires: 17 | 18 | - Access to a notebook computer with Wi-Fi, running Microsoft Windows, Mac OSX, or Linux (Ubuntu, SuSE, or RedHat). 19 | - An Internet browser suchas Chrome, Firefox, or IE9 (previous versions of Internet Explorer are not supported). 20 | 21 | ## Goal: Completing a voice-only Riddle Game. 22 | Alexa is the voice service that powers Amazon Echo. Alexa provides capabilities, called skills, which enable customers to interact with devices using voice (answer questions, play music, and more). 23 | 24 | The Alexa Skills Kit (ASK) is a collection of self-service APIs, tools, documentation, and code samples that make it easy for you to develop your own Alexa skills, which you can then publish. ASK supports simple command-oriented skills, such as "Alexa, ask Greeter to say hello world" as well as sophisticated multi-command dialogs and parameter passing, such as "Alexa, what is this weekend's weather forecast?" The Alexa Skills Kit is a low-friction way to learn to build for voice. 25 | 26 | This task will walk you through creating a simple skill that quizzes the customer through Easy, Medium and Hard riddles. Through this you will use the Alexa skills kit to learn the fundamentals of building a voice user experience. 27 | 28 | ### Task 0.1: Create an Account on developer.amazon.com (or Sign In) 29 | 30 | 1. Navigate to the Amazon Developer Portal at[https://developer.amazon.com/alexa](https://developer.amazon.com/alexa). 31 | 2. Click **Sign In** in the upper right to create a free account. 32 | 33 | ### Task 0.2: Create the Riddle Game Workshop Skill 34 | 35 | 1. When signed in, click **Your Alexa Dashboards** in the upper right. 36 | 2. Choose **Get Started** under Alexa Skills Kit. Alexa Skills Kit will enable you to add new skills to Alexa. (The other option, Alexa Voice Services, is what you use if you want to put Alexa onto other devices such as a Raspberry Pi.) 37 | 3. To start the process of creating a skill, click the **Create Skill** button on the right. 38 | 39 | ### Task 0.3: Skill Information 40 | 41 | 1. Skill Name:enter **Riddle Game Workshop**. 42 | 2. Skill Type: Select **Custom Interaction Model**. 43 | 3. Language: Select **English (U.S.).** 44 | 4. Invocation Name: **riddle game workshop**. This will be the name that you will use to start your skill (eg.,"Alexa, Open _[hello world__]_".) The invocation name you choose needs to be more than one word and not contain a brand name. Remember the invocation name for future use in this lab. 45 | 5. Click **Create Skill**. 46 | 6. Select the **Start from scratch** template. 47 | 7. Click **Choose**. 48 | 49 | ### Task 0.4: Interaction Model 50 | 51 | 1. In the navigation menu on the left, choose **JSON Editor**. 52 | 2. **Copy** the JSON from [the en-US language model](https://github.com/alexa-labs/skill-sample-nodejs-level-up-riddles/blob/master/Step%200%20-%20Initialize%20Riddle%20Game/models/en-US.json). 53 | 54 | Each of these JSON fields are **Intents**. Intents represent what your skill can do, they are an action Alexa will take. To prompt Alexa for the action, a user would say an **Utterance**. In the case of the **CancelIntent** , the **Utterance** a user would say to perform the cancel action would be "cancel riddles game workshop". 55 | 56 | This skill has two customer intents: `PlayGameIntent` and `AnswerRiddleIntent`. The `PlayGameIntent` starts the gameplay according to the level the user specifies. The `AnswerRiddleIntent` accepts the customer's answer to the riddle, and outputs the next riddle in the series or ends the game. 57 | 58 | Some of the utterances include **Slots**. These are items that are variable to what the user says. In the context of this skill, there are two custom slot types. The first is `levelType` which defines a level a user could select. The second is `answerType`, which defines the correct answers a customer could say to the give riddle. Each slot has synonyms associated to it, which are resolved in the skill code through **Entity Resolution**. 59 | 60 | 3. Click the **Save Model** button. This will start the process of creating your interaction (If you did not make changes in the Code Editor the **Save Model** button is gray). 61 | 4. Click on **Build Model.** 62 | 8. We're now done with the Interaction Model. Choose **Enpoints** in the left menu. 63 | 64 | ### Task 0.5: Configuration 65 | 66 | Your skill needs to be connected to an endpoint that will perform your skill logic. We will be using AWS Lambda for this lab. We will create the Riddle Game Workshop skill Lambda function, copy its ARN (Amazon Resource Name), and paste it into your skill's configuration page. 67 | 68 | 1. In a new browser tab, go to [http://aws.amazon.com](http://aws.amazon.com/) 69 | 2. **Sign in** to the management console. 70 | 3. From the region selector in the upper right, be sure that you're in the **US East (N. Virginia)** region. 71 | 4. From the Services menu on the left of the top menu bar, choose **Services | Compute | Lambda**. 72 | 5. Click the orange **Create Function** button in the upper right. 73 | 6. Assure that **Author from scratch** is toggled. 74 | 7. Name: **riddleGameWorkshop** 75 | 8. Runtime: NodeJS 8.10 76 | 9. Role: **Create a new role from one or more template(s)** 77 | 10. Role name: **riddleGameWorkshopRole** 78 | 11. Policy templates: **Simple microservice permissions** 79 | 12. Click the orange **Create function** in the lower right. 80 | 81 | The Lambda function for your skill has now been created. Now you need to attach your skill to it. 82 | 83 | 14. In the **Designer** view, under **Add Triggers** , select **Alexa Skills Kit** 84 | 15. In the upper-right corner of the page, **copy your ARN**. Copy everything except "ARN-". It will look like this: 85 | `arn:aws:lambda:us-east-1:123456789012:function:riddleGameWorkshop` 86 | 16. Now **switch browser tabs back to your skill** in the developer portal. You should be on the configuration page. (If you closed the browser tab, here's how to get back: Go to [http://developer.amazon.com](http://developer.amazon.com/), sign in, click Alexa, click Alexa Skills Kit, click on your skill name, click on configuration from the left-hand menu). 87 | 17. Select **Endpoint** from the left menu. 88 | 18. For the service endpoint type, choose the **AWS Lambda ARN (Amazon Resource Name)** radio button. 89 | 19. **Paste your Lambda ARN** into the Default text field. 90 | 20. Click **Save Endpoints**. 91 | 21. Copy your skill ID. 92 | 22. Navigate back to your **Lambda function tab**. **Click** on the **Alexa Skills Kit** trigger that we previously added in the **Designer** view (it should say "Configuration Required" underneath). 93 | 23. Scroll down to the **Configure Triggers** view. 94 | 24. Skill id verification: **Enabled** 95 | 25. **Paste** your skill id. 96 | 26. Click **Add.** 97 | 27. Click **Save**. 98 | 99 | Next, we will upload the Riddle Game skill code into Lambda. You should now see details of your riddleGameWorkshop Lambda function that includes your function's ARN in the upper right and the Configuration view of your function. 100 | 101 | 28. Click on the **riddleGameWorkshop** part of the tree in the **Designer** view. 102 | 29. Scroll down to see the **Function code** view. 103 | 30. Code Entry Type: **Upload a .zip file** 104 | 31. Ensure **Node.js 8.10** is selected for **Runtime** 105 | 32. Handler: **index.handler**. 106 | 33. Function Package: **Upload** a .zip of the contents in the lambda/custom folder of this section of the repo. 107 | - Clone this repo locally 108 | - Navigate to 0 - Initialize Riddle Game/lambda/custom 109 | - Zip the contents of this folder 110 | - Upload the .zip 111 | 34. Click the **Save** button in the top of the page. This will upload your function code into the Lambda container. 112 | 113 | After the Save is complete, you may or may not see your code editor inline: if your function code becomes large, this view will not be available after uploading, but will still run. It is important to keep a local copy of your skill for this reason. 114 | 115 | ### Task 0.6: Test your voice interaction 116 | 117 | We'll now test your skill in the Developer Portal. You can also optionally test your skill in AWS Lambda using the JSON Input from the testing console. 118 | 119 | 1. Switch browser tabs to **the developer portal** (If you closed the browser tab, here's how to get back: Go to [http://developer.amazon.com](http://developer.amazon.com/), sign in, click Alexa, click Alexa Skills Kit, click on your skill name, click on configuration from the left-hand menu). 120 | 2. Scroll to the top of the page and click **Test**. 121 | 3. Switch **Test is disabled for this skill** to Development. 122 | 4. In **Alexa Simulator** tab, under **Type or click…**, type "open riddle game workshop" 123 | 5. You should hear and see Alexa respond with the message in your LaunchRequest. 124 | 125 | 126 | 127 | ### Congratulations! You have finished Task 0! 128 | 129 | 130 | ## License 131 | 132 | This library is licensed under the Amazon Software License. 133 | -------------------------------------------------------------------------------- /Step 0 - Initialize Riddle Game/lambda/custom/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable func-names */ 2 | /* eslint-disable no-console */ 3 | 4 | const Alexa = require('ask-sdk-core'); 5 | const RIDDLES = require("./riddle_objects"); 6 | 7 | const LaunchRequestHandler = { 8 | canHandle(handlerInput) { 9 | return handlerInput.requestEnvelope.request.type === 'LaunchRequest'; 10 | }, 11 | handle(handlerInput) { 12 | const speechText = "Welcome to Level Up Riddles! " 13 | + "I will give you 5 riddles. Would you like to start with easy, medium, or hard riddles?"; 14 | 15 | return handlerInput.responseBuilder 16 | .speak(speechText) 17 | .reprompt(speechText) 18 | .withSimpleCard('Level Up Riddles', speechText) 19 | .getResponse(); 20 | }, 21 | }; 22 | 23 | const PlayGameIntentHandler = { 24 | canHandle(handlerInput) { 25 | return handlerInput.requestEnvelope.request.type === 'IntentRequest' 26 | && handlerInput.requestEnvelope.request.intent.name === 'PlayGameIntent'; 27 | }, 28 | handle(handlerInput) { 29 | const sessionAttributes = handlerInput.attributesManager.getSessionAttributes(); 30 | const request = handlerInput.requestEnvelope.request; 31 | 32 | // Get the level the customer selected: 'easy', 'med', 'hard' 33 | const spokenLevel = request.intent.slots.level.value; 34 | if (spokenLevel) { 35 | sessionAttributes.currentLevel = spokenLevel; 36 | } else { 37 | sessionAttributes.currentLevel = 'easy'; 38 | } 39 | 40 | // Reset variables to 0 to start the new game 41 | sessionAttributes.correctCount = 0; 42 | sessionAttributes.currentIndex = 0; 43 | 44 | // Get the first riddle according to that level 45 | sessionAttributes.currentRiddle = RIDDLES.LEVELS[sessionAttributes.currentLevel][sessionAttributes.currentIndex]; 46 | 47 | sessionAttributes.speechText = "Lets play with " 48 | + sessionAttributes.currentLevel + " riddles! " 49 | + " First riddle: " + sessionAttributes.currentRiddle.question; 50 | 51 | handlerInput.attributesManager.setSessionAttributes(sessionAttributes); 52 | 53 | return handlerInput.responseBuilder 54 | .speak(sessionAttributes.speechText) 55 | .reprompt(sessionAttributes.speechText) 56 | .withSimpleCard('Level Up Riddles', sessionAttributes.speechText) 57 | .getResponse(); 58 | } 59 | }; 60 | 61 | const AnswerRiddleIntentHandler = { 62 | canHandle(handlerInput) { 63 | return handlerInput.requestEnvelope.request.type === 'IntentRequest' 64 | && handlerInput.requestEnvelope.request.intent.name === 'AnswerRiddleIntent'; 65 | }, 66 | handle(handlerInput) { 67 | const sessionAttributes = handlerInput.attributesManager.getSessionAttributes(); 68 | 69 | console.log(JSON.stringify(handlerInput.requestEnvelope.request.intent.slots)); 70 | 71 | // Determine if the customer said the correct answer for the current riddle 72 | const spokenAnswer = handlerInput.requestEnvelope.request.intent.slots.answer.value; 73 | sessionAttributes.speechText = ""; 74 | if (spokenAnswer == sessionAttributes.currentRiddle.answer) { 75 | sessionAttributes.speechText += sessionAttributes.currentRiddle.answer + " is correct! You got it right! " 76 | sessionAttributes.correctCount += 1; 77 | sessionAttributes.correct = "Correct! "; 78 | } else { 79 | sessionAttributes.speechText += "Oops, that was wrong. The correct answer is " + sessionAttributes.currentRiddle.answer + ". "; 80 | sessionAttributes.correct = "Incorrect! "; 81 | } 82 | 83 | // Move on to the next question 84 | sessionAttributes.currentIndex += 1; 85 | 86 | // If the customer has gone through all 5 riddles, report the score 87 | if (sessionAttributes.currentIndex == RIDDLES.LEVELS[sessionAttributes.currentLevel].length) { 88 | sessionAttributes.speechText += 89 | "You have completed all of the riddles on this level! " 90 | + "Your correct answer count is " 91 | + sessionAttributes.correctCount 92 | + ". To play another level, say easy, medium, or hard. "; 93 | 94 | // Reset variables to start a new game 95 | sessionAttributes.currentLevel = ""; 96 | sessionAttributes.currentRiddle = {}; 97 | sessionAttributes.currentIndex = 0; 98 | } else { 99 | sessionAttributes.currentRiddle = RIDDLES.LEVELS[sessionAttributes.currentLevel][sessionAttributes.currentIndex]; 100 | sessionAttributes.speechText += "Next riddle: " + sessionAttributes.currentRiddle.question; 101 | } 102 | 103 | handlerInput.attributesManager.setSessionAttributes(sessionAttributes); 104 | 105 | return handlerInput.responseBuilder 106 | .speak(sessionAttributes.speechText) 107 | .reprompt(sessionAttributes.speechText) 108 | .withSimpleCard('Level Up Riddles', sessionAttributes.speechText) 109 | .getResponse(); 110 | } 111 | }; 112 | 113 | const HelpIntentHandler = { 114 | canHandle(handlerInput) { 115 | return handlerInput.requestEnvelope.request.type === 'IntentRequest' 116 | && handlerInput.requestEnvelope.request.intent.name === 'AMAZON.HelpIntent'; 117 | }, 118 | handle(handlerInput) { 119 | const speechText = "I will give you 5 riddles. Would you like to start with easy, medium, or hard riddles?" 120 | 121 | return handlerInput.responseBuilder 122 | .speak(speechText) 123 | .reprompt(speechText) 124 | .withSimpleCard('Level Up Riddles', speechText) 125 | .getResponse(); 126 | } 127 | }; 128 | 129 | const CancelAndStopIntentHandler = { 130 | canHandle(handlerInput) { 131 | return handlerInput.requestEnvelope.request.type === 'IntentRequest' 132 | && (handlerInput.requestEnvelope.request.intent.name === 'AMAZON.CancelIntent' 133 | || handlerInput.requestEnvelope.request.intent.name === 'AMAZON.StopIntent'); 134 | }, 135 | handle(handlerInput) { 136 | const speechText = 'Goodbye!'; 137 | 138 | return handlerInput.responseBuilder 139 | .speak(speechText) 140 | .withSimpleCard('Level Up Riddles', speechText) 141 | .getResponse(); 142 | } 143 | }; 144 | 145 | const SessionEndedRequestHandler = { 146 | canHandle(handlerInput) { 147 | return handlerInput.requestEnvelope.request.type === 'SessionEndedRequest'; 148 | }, 149 | handle(handlerInput) { 150 | console.log(`Session ended with reason: ${handlerInput.requestEnvelope.request.reason}`); 151 | console.log("Error in request " + JSON.stringify(handlerInput.requestEnvelope.request)); 152 | 153 | return handlerInput.responseBuilder.getResponse(); 154 | } 155 | }; 156 | 157 | const ErrorHandler = { 158 | canHandle() { 159 | return true; 160 | }, 161 | handle(handlerInput, error) { 162 | console.log(`Error handled: ${error.message}`); 163 | 164 | return handlerInput.responseBuilder 165 | .speak('Sorry, I can\'t understand the command. Please say again.') 166 | .reprompt('Sorry, I can\'t understand the command. Please say again.') 167 | .getResponse(); 168 | } 169 | }; 170 | 171 | const skillBuilder = Alexa.SkillBuilders.custom(); 172 | 173 | exports.handler = skillBuilder 174 | .addRequestHandlers( 175 | LaunchRequestHandler, 176 | PlayGameIntentHandler, 177 | AnswerRiddleIntentHandler, 178 | HelpIntentHandler, 179 | CancelAndStopIntentHandler, 180 | SessionEndedRequestHandler 181 | ) 182 | .withApiClient(new Alexa.DefaultApiClient()) 183 | .addErrorHandlers(ErrorHandler) 184 | .lambda(); 185 | 186 | 187 | 188 | 189 | -------------------------------------------------------------------------------- /Step 0 - Initialize Riddle Game/lambda/custom/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hello-world", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "ask-sdk": "^2.0.0", 13 | "ask-sdk-core": "^2.0.0", 14 | "ask-sdk-model": "^1.0.0" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Step 0 - Initialize Riddle Game/lambda/custom/riddle_objects.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | LEVELS : { 5 | easy : [ 6 | { 7 | question : "I run all day and never walk, I tell you something, but I do not talk!", 8 | answer : "clock" 9 | }, 10 | { 11 | question : "When you look at my face it is easy to see, You're looking at you when you're looking at me!", 12 | answer : "mirror" 13 | }, 14 | { 15 | question : "What is full of holes but holds water?", 16 | answer : "sponge" 17 | }, 18 | { 19 | question : "What has four fingers and one thumb but is not alive?", 20 | answer : "glove" 21 | }, 22 | { 23 | question : "Soft as a petal that falls from a tree, the more I dry the wetter I'll be!", 24 | answer : "towel" 25 | } 26 | ], 27 | medium : [ 28 | { 29 | question : "I can be quick and then I'm deadly, I am a rock, shell and bone medley.", 30 | answer : "sand" 31 | }, 32 | { 33 | question : "When I point up it's bright, but when I point down it's dark. What am I?", 34 | answer : "light switch" 35 | }, 36 | { 37 | question : "Lighter than what I am made of, More of me is hidden Than is seen.", 38 | answer : "iceberg" 39 | }, 40 | { 41 | question : "I am more useful when I am broken. What am I?", 42 | answer : "egg" 43 | }, 44 | { 45 | question : "I'm the part of the bird that's not in the sky. I can swim in the ocean and yet remain dry. What am I?", 46 | answer : "shadow" 47 | } 48 | ], 49 | hard : [ 50 | { 51 | question : "The more you take the more you leave behind?", 52 | answer : "foot steps" 53 | }, 54 | { 55 | question : "What is black when you buy it, red when you use it, and gray when you throw it away?", 56 | answer : "charcoal" 57 | }, 58 | { 59 | question : "Mountains will crumble and temples will fall, and no man can survive its endless call. What is it?", 60 | answer : "time" 61 | }, 62 | { 63 | question : "I can be cracked, I can be made. I can be told, I can be played. What am I?", 64 | answer : "joke" 65 | }, 66 | { 67 | question : "Lovely and round, I shine with pale light, grown in the darkness, A lady's delight. What am I?", 68 | answer : "pearl" 69 | } 70 | ] 71 | } 72 | }; 73 | 74 | -------------------------------------------------------------------------------- /Step 0 - Initialize Riddle Game/models/en-US.json: -------------------------------------------------------------------------------- 1 | { 2 | "interactionModel": { 3 | "languageModel": { 4 | "invocationName": "riddle game workshop", 5 | "intents": [ 6 | { 7 | "name": "AMAZON.CancelIntent", 8 | "samples": [] 9 | }, 10 | { 11 | "name": "AMAZON.HelpIntent", 12 | "samples": [] 13 | }, 14 | { 15 | "name": "AMAZON.StopIntent", 16 | "samples": [] 17 | }, 18 | { 19 | "name": "PlayGameIntent", 20 | "slots": [ 21 | { 22 | "name": "level", 23 | "type": "levelType" 24 | } 25 | ], 26 | "samples": [ 27 | "how about {level} riddles", 28 | "lets do some {level} riddles", 29 | "{level} ones", 30 | "lets do some {level} ones", 31 | "the {level} riddles", 32 | "the {level} ones", 33 | "{level} level riddles", 34 | "i want {level} riddles", 35 | "i want to play {level}", 36 | "{level} riddles", 37 | "{level}" 38 | ] 39 | }, 40 | { 41 | "name": "AnswerRiddleIntent", 42 | "slots": [ 43 | { 44 | "name": "answer", 45 | "type": "answerType" 46 | } 47 | ], 48 | "samples": [ 49 | "i think the answer is {answer} ", 50 | "my answer is {answer}", 51 | "your {answer}", 52 | "my {answer}", 53 | "the {answer}", 54 | "an {answer}", 55 | "a {answer}", 56 | "the answer to the riddle is {answer}", 57 | "the answer is {answer}", 58 | "{answer} is the answer", 59 | "i think it is {answer}", 60 | "{answer}" 61 | ] 62 | }, 63 | { 64 | "name": "AMAZON.MoreIntent", 65 | "samples": [] 66 | }, 67 | { 68 | "name": "AMAZON.NavigateHomeIntent", 69 | "samples": [] 70 | }, 71 | { 72 | "name": "AMAZON.NavigateSettingsIntent", 73 | "samples": [] 74 | }, 75 | { 76 | "name": "AMAZON.NextIntent", 77 | "samples": [] 78 | }, 79 | { 80 | "name": "AMAZON.PageUpIntent", 81 | "samples": [] 82 | }, 83 | { 84 | "name": "AMAZON.PageDownIntent", 85 | "samples": [] 86 | }, 87 | { 88 | "name": "AMAZON.PreviousIntent", 89 | "samples": [] 90 | }, 91 | { 92 | "name": "AMAZON.ScrollRightIntent", 93 | "samples": [] 94 | }, 95 | { 96 | "name": "AMAZON.ScrollDownIntent", 97 | "samples": [] 98 | }, 99 | { 100 | "name": "AMAZON.ScrollLeftIntent", 101 | "samples": [] 102 | }, 103 | { 104 | "name": "AMAZON.ScrollUpIntent", 105 | "samples": [] 106 | } 107 | ], 108 | "types": [ 109 | { 110 | "name": "levelType", 111 | "values": [ 112 | { 113 | "name": { 114 | "value": "easy", 115 | "synonyms": [ 116 | "children", 117 | "child", 118 | "kids", 119 | "kid", 120 | "for kids", 121 | "easy peasy", 122 | "really easy" 123 | ] 124 | } 125 | }, 126 | { 127 | "name": { 128 | "value": "hard", 129 | "synonyms": [ 130 | "advanced", 131 | "hardest", 132 | "harder", 133 | "really hard", 134 | "challenging", 135 | "difficult" 136 | ] 137 | } 138 | }, 139 | { 140 | "name": { 141 | "value": "medium", 142 | "synonyms": [ 143 | "med" 144 | ] 145 | } 146 | } 147 | ] 148 | }, 149 | { 150 | "name": "answerType", 151 | "values": [ 152 | { 153 | "name": { 154 | "value": "heart" 155 | } 156 | }, 157 | { 158 | "name": { 159 | "value": "leg", 160 | "synonyms": [ 161 | "legs" 162 | ] 163 | } 164 | }, 165 | { 166 | "name": { 167 | "value": "water" 168 | } 169 | }, 170 | { 171 | "name": { 172 | "value": "icicle" 173 | } 174 | }, 175 | { 176 | "name": { 177 | "value": "scissors", 178 | "synonyms": [ 179 | "shears" 180 | ] 181 | } 182 | }, 183 | { 184 | "name": { 185 | "value": "pride" 186 | } 187 | }, 188 | { 189 | "name": { 190 | "value": "volcano" 191 | } 192 | }, 193 | { 194 | "name": { 195 | "value": "counterfeit", 196 | "synonyms": [ 197 | "phony money", 198 | "counterfeit money", 199 | "fake money" 200 | ] 201 | } 202 | }, 203 | { 204 | "name": { 205 | "value": "tomorrow" 206 | } 207 | }, 208 | { 209 | "name": { 210 | "value": "key" 211 | } 212 | }, 213 | { 214 | "name": { 215 | "value": "blink" 216 | } 217 | }, 218 | { 219 | "name": { 220 | "value": "snail" 221 | } 222 | }, 223 | { 224 | "name": { 225 | "value": "ship", 226 | "synonyms": [ 227 | "yacht ", 228 | "boat" 229 | ] 230 | } 231 | }, 232 | { 233 | "name": { 234 | "value": "echo" 235 | } 236 | }, 237 | { 238 | "name": { 239 | "value": "candle" 240 | } 241 | }, 242 | { 243 | "name": { 244 | "value": "hour glass", 245 | "synonyms": [ 246 | "sand timer", 247 | "hourglass", 248 | "timer" 249 | ] 250 | } 251 | }, 252 | { 253 | "name": { 254 | "value": "wheel" 255 | } 256 | }, 257 | { 258 | "name": { 259 | "value": "calendar" 260 | } 261 | }, 262 | { 263 | "name": { 264 | "value": "blue" 265 | } 266 | }, 267 | { 268 | "name": { 269 | "value": "corn" 270 | } 271 | }, 272 | { 273 | "name": { 274 | "value": "finger nails", 275 | "synonyms": [ 276 | "nails", 277 | "fingernails" 278 | ] 279 | } 280 | }, 281 | { 282 | "name": { 283 | "value": "name" 284 | } 285 | }, 286 | { 287 | "name": { 288 | "value": "piano", 289 | "synonyms": [ 290 | "keyboard" 291 | ] 292 | } 293 | }, 294 | { 295 | "name": { 296 | "value": "sunshine", 297 | "synonyms": [ 298 | "sunlight", 299 | "sun light", 300 | "sun" 301 | ] 302 | } 303 | }, 304 | { 305 | "name": { 306 | "value": "carpet" 307 | } 308 | }, 309 | { 310 | "name": { 311 | "value": "turtle" 312 | } 313 | }, 314 | { 315 | "name": { 316 | "value": "river" 317 | } 318 | }, 319 | { 320 | "name": { 321 | "value": "wind" 322 | } 323 | }, 324 | { 325 | "name": { 326 | "value": "hole", 327 | "synonyms": [ 328 | "holes", 329 | "whole" 330 | ] 331 | } 332 | }, 333 | { 334 | "name": { 335 | "value": "onion" 336 | } 337 | }, 338 | { 339 | "name": { 340 | "value": "newspaper" 341 | } 342 | }, 343 | { 344 | "name": { 345 | "value": "chalkboard", 346 | "synonyms": [ 347 | "blackboard", 348 | "chalk board", 349 | "board", 350 | "white board", 351 | "chalk wall", 352 | "black board" 353 | ] 354 | } 355 | }, 356 | { 357 | "name": { 358 | "value": "phone", 359 | "synonyms": [ 360 | "cell phone", 361 | "telephone" 362 | ] 363 | } 364 | }, 365 | { 366 | "name": { 367 | "value": "darkness", 368 | "synonyms": [ 369 | "dark" 370 | ] 371 | } 372 | }, 373 | { 374 | "name": { 375 | "value": "match" 376 | } 377 | }, 378 | { 379 | "name": { 380 | "value": "stamp" 381 | } 382 | }, 383 | { 384 | "name": { 385 | "value": "rainbow" 386 | } 387 | }, 388 | { 389 | "name": { 390 | "value": "stars", 391 | "synonyms": [ 392 | "star" 393 | ] 394 | } 395 | }, 396 | { 397 | "name": { 398 | "value": "yardstick", 399 | "synonyms": [ 400 | "yard stick", 401 | "ruler", 402 | "yard ruler" 403 | ] 404 | } 405 | }, 406 | { 407 | "name": { 408 | "value": "salt" 409 | } 410 | }, 411 | { 412 | "name": { 413 | "value": "breath", 414 | "synonyms": [ 415 | "breathe" 416 | ] 417 | } 418 | }, 419 | { 420 | "name": { 421 | "value": "tempurature" 422 | } 423 | }, 424 | { 425 | "name": { 426 | "value": "cold" 427 | } 428 | }, 429 | { 430 | "name": { 431 | "value": "alphabet" 432 | } 433 | }, 434 | { 435 | "name": { 436 | "value": "umbrella" 437 | } 438 | }, 439 | { 440 | "name": { 441 | "value": "pearl" 442 | } 443 | }, 444 | { 445 | "name": { 446 | "value": "joke" 447 | } 448 | }, 449 | { 450 | "name": { 451 | "value": "time" 452 | } 453 | }, 454 | { 455 | "name": { 456 | "value": "charcoal" 457 | } 458 | }, 459 | { 460 | "name": { 461 | "value": "foot steps", 462 | "synonyms": [ 463 | "feet marks", 464 | "feet prints", 465 | "foot marks", 466 | "footsteps" 467 | ] 468 | } 469 | }, 470 | { 471 | "name": { 472 | "value": "shadow" 473 | } 474 | }, 475 | { 476 | "name": { 477 | "value": "egg" 478 | } 479 | }, 480 | { 481 | "name": { 482 | "value": "iceberg" 483 | } 484 | }, 485 | { 486 | "name": { 487 | "value": "light switch", 488 | "synonyms": [ 489 | "switch" 490 | ] 491 | } 492 | }, 493 | { 494 | "name": { 495 | "value": "sand" 496 | } 497 | }, 498 | { 499 | "name": { 500 | "value": "towel" 501 | } 502 | }, 503 | { 504 | "name": { 505 | "value": "glove" 506 | } 507 | }, 508 | { 509 | "name": { 510 | "value": "sponge" 511 | } 512 | }, 513 | { 514 | "name": { 515 | "value": "mirror" 516 | } 517 | }, 518 | { 519 | "name": { 520 | "value": "clock" 521 | } 522 | } 523 | ] 524 | } 525 | ] 526 | } 527 | } 528 | } -------------------------------------------------------------------------------- /Step 0 - Initialize Riddle Game/skill.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest": { 3 | "publishingInformation": { 4 | "locales": { 5 | "en-US": { 6 | "summary": "Sample Short Description", 7 | "examplePhrases": [ 8 | "Alexa open hello world", 9 | "Alexa tell hello world hello", 10 | "Alexa ask hello world say hello" 11 | ], 12 | "name": "Riddle Game Workshop", 13 | "description": "Sample Full Description" 14 | } 15 | }, 16 | "isAvailableWorldwide": true, 17 | "testingInstructions": "Sample Testing Instructions.", 18 | "category": "EDUCATION_AND_REFERENCE", 19 | "distributionCountries": [] 20 | }, 21 | "apis": { 22 | "custom": { 23 | "endpoint": { 24 | "sourceDir": "lambda/custom", 25 | "uri": "ask-level-up-riddles" 26 | } 27 | } 28 | }, 29 | "manifestVersion": "1.0" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Step 1 - Add Advanced Voice Design/.ask/config: -------------------------------------------------------------------------------- 1 | { 2 | "deploy_settings": { 3 | "default": { 4 | "skill_id": "", 5 | "was_cloned": false, 6 | "merge": { 7 | } 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Step 1 - Add Advanced Voice Design/README.md: -------------------------------------------------------------------------------- 1 | # Add Advanced Voice Design 2 | 3 | In this section of the workshop, you will incorporate Auto slot delegation into your skill. Auto delegation promotes a more conversational VUI design. When launched, this Alexa skill will have the customer interact with a Riddle Game skill that requests them to provide both the level type and how many questions they'd like to answer. 4 | 5 | ## Objectives 6 | 7 | After completing this workshop, you will be able to: 8 | 9 | - Configure Intents, Sample Utterances, and Slots 10 | - Update your Lambda service code to be able to handle auto delegation 11 | 12 | ## Prerequisites 13 | 14 | This lab requires: 15 | 16 | - Access to a notebook computer with Wi-Fi, running Microsoft Windows, Mac OSX, or Linux (Ubuntu, SuSE, or RedHat). 17 | - An Internet browser suchas Chrome, Firefox, or IE9 (previous versions of Internet Explorer are not supported). 18 | - Having completed **[Step 0: Initialize Riddle Game](https://github.com/alexa-labs/skill-sample-nodejs-level-up-riddles/tree/master/Step%200%20-%20Initialize%20Riddle%20Game)** 19 | 20 | ## Goal: Handling more complex dialog in your skill. 21 | Real conversations are dynamic, moving between topics and ideas fluidly. To create conversational Alexa skills, design for flexibility and responsiveness. Skills should be able to handle variations of conversation, conditional collection of data, and switching context mid-conversation. Auto Delegation and Dialog management makes these natural interactions possible. 22 | 23 | To read about Dialog Management, go [here](https://build.amazonalexadev.com/alexa-skill-dialog-management-guide-ww.html). 24 | 25 | ### Task 1.1: Update your Interfaces 26 | In order to achieve our more advanced conversational experience, we need to incorporate Auto Delegation into our skill. 27 | 28 | 1. Navigate to the Amazon Developer Portal at[https://developer.amazon.com/alexa](https://developer.amazon.com/alexa). 29 | 2. Click **Sign In** in the upper right. 30 | 3. When signed in, click **Your Alexa Dashboards** in the upper right. 31 | 4. Choose your **Riddle Game Workshop** skill. 32 | 5. Click on **Interfaces** on the left menu. 33 | 6. Assure that **Auto Delegation** is toggled. Note: it may already be toggled as it is enabled by default. 34 | 35 | ### Task 1.2: Use Auto Delegation in your Interaction Model 36 | When a customer launches the Riddle Game Workshop skill, we want to update our opening message to request that they provide what level they want to source riddles from, along with how many riddles they would like to play with, their name, and favorite color. 37 | 38 | As the skill stands, we already have the `level` slot in the `PlayGameIntent`. In this task, we want to make that the required slot, and add optional slots of `riddleNum`, `name`, and `color`. 39 | 40 | 1. Select the **PlayGameIntent** in the left menu, under Interaction Model. This is the intent logically follows the LaunchRequest, where Alexa will prompt the user for the `level` slot to be filled. 41 | 2. Scroll down to **Intent Slots**. In row 2, toggle your mouse into _Create a new slot_ and type "riddleNum". 42 | 3. Hit the **+** icon or _Enter_. 43 | 4. Repeat the same process for "name" and "color". 44 | 5. Next to each new slot, there is a dropdown menu to _Select a slot type_. For "riddleNum", select **AMAZON.NUMBER**. 45 | 3. For "name", select **AMAZON.US_FIRST_NAME**. 46 | 4. For "color", select **AMAZON.Color**. 47 | 5. Scroll up to **Sample Utterances**. Add each of the following utterances individually: 48 | 49 | ``` 50 | let's start 51 | i want to play 52 | i want {riddleNum} riddles 53 | give me {riddleNum} riddles 54 | my name is {name} 55 | my name is {name} and my favorite color is {color} 56 | i am {name} 57 | i am {name} and i like {color} 58 | my favorite color is {color} 59 | {riddleNum} of the {level} riddles 60 | i like {color} 61 | i like {color} and {level} riddles 62 | my favorite color is {color} and {riddleNum} {level} 63 | i want {riddleNum} {level} riddles 64 | give me {riddleNum} {level} riddles 65 | my name is {name} and i want {level} riddles 66 | my name is {name} and i want {riddleNum} {level} riddles 67 | my name is {name} and my favorite color is {color} and i want {riddleNum} {level} riddles 68 | give me {riddleNum} {level} 69 | {name} and {color} and i want {riddleNum} {level} riddles 70 | {name} {color} {riddleNum} level {level} 71 | ``` 72 | Each of these utterances shows a varying combination of what a customer could say to initiate the gameplay, on top of the utterances we already have trained in our skill. Now, we need to make sure that out of each of these, the customer will at least fill the `level` slot. 73 | 74 | 5. Under **Intent Slots**, click on "level". 75 | 6. Toggle "Is this slot required to fulfill the intent" under **Slot Filling**. 76 | 77 | You will see two fields appear: **Alexa speech prompts** and **User utterances**. The former is what Alexa will say to prompt the user to fill the `level` slot. The latter is what the user might say in response to Alexa's prompt. 78 | 79 | 7. Add the following to **Alexa speech prompts**. 80 | 81 | ``` 82 | Do you want easy, medium, or hard riddles? 83 | Hi {name}, do you want easy, medium or hard riddles? 84 | Which category would you like your {riddleNum} riddles to be sourced from, easy, medium, or hard? 85 | Nice to meet you {name}. You want to play through {riddleNum} riddles. Would you like those to be easy, medium, or hard? 86 | ``` 87 | Notice how you can incorporate slots that the customer has potentially filled within your speech prompt. 88 | 89 | 8. Add the following to **User utterances**. 90 | 91 | ``` 92 | {level} 93 | level {level} 94 | {level} riddles 95 | {riddleNum} {level} riddles 96 | {riddleNum} {level} 97 | {riddleNum} of the {level} riddles 98 | ``` 99 | 9. Now navigate back to your `PlayGameIntent`. 100 | 10. You will notice under **Dialog Delegation Strategy** that "fallback to skill setting" is selected. Select **enable auto delegation**. 101 | 11. Scroll up, and click **Save Model**. 102 | 12. Once it is done being saved, click **Build Model**. 103 | 104 | ### Task 1.3: Update your Skill Lambda 105 | 106 | At this point in your development lifecycle, I recommend updating your code locally as it could start to get large. The code editor in AWS Lambda may not show your code depending on its size. With each iteration of your skill code, you can [**Upload a .zip** into Lambda](https://github.com/alexa-labs/skill-sample-nodejs-level-up-riddles/tree/master/Step%200%20-%20Initialize%20Riddle%20Game#uploadzip). 107 | 108 | 1. Open **index.js** 109 | 2. In the `handle` of your `LaunchRequestHanler`, update the `speechText` to also request a name, favorite color, and number of riddles. 110 | 111 | ``` 112 | const speechText = "Welcome to Level Up Riddles! Before we get started, I need a few pieces of information." 113 | + " What is your name and favorite color?" 114 | + " How many riddles would you like to play with?" 115 | + " Would you like to start with easy, medium, or hard riddles?"; 116 | ``` 117 | 118 | 3. Now we need to store those slot values as variables in our code, and use them within our skill logic. Navigate to `PlayGameIntentHandler`. 119 | In the `handle` of `PlayGameIntentHandler`, you will see that we are already getting the value of the `level` slot and defaulting to `'easy'` if it isn't filled. Now that we have marked `level` as required, we no longer need that condition. 120 | 4. Delete `const spokenLevel = ... if(spokenLevel) {...} else {...}`. 121 | 5. Insert the following line: 122 | 123 | ``` 124 | sessionAttributes.currentLevel = request.intent.slots.level.value; 125 | ``` 126 | 127 | We need to check and see if the customer provided a `name`, `color`. 128 | 129 | 6. Insert the following conditions: 130 | 131 | ``` 132 | // Store the slot values for name and color 133 | if (request.intent.slots.name.value) { 134 | sessionAttributes.name = request.intent.slots.name.value; 135 | } 136 | 137 | if (request.intent.slots.color.value) { 138 | sessionAttributes.color = request.intent.slots.color.value; 139 | } 140 | ``` 141 | Finally, we need to check if the customer has provided a value for `riddleNum`. If they have, we need to assure that the number does not exceed 5, and that the game only lists off that number of riddles. 142 | 143 | 6. Insert the following condition: 144 | 145 | ``` 146 | // Check if the slot value for riddleNum is filled and <5, otherwise default to 5 147 | const riddleNum = request.intent.slots.riddleNum.value; 148 | if (riddleNum) { 149 | sessionAttributes.totalRids = riddleNum <= 5 ? riddleNum : 5; 150 | } else { 151 | sessionAttributes.totalRids = 5; 152 | } 153 | 154 | ``` 155 | 156 | Now we need to update the `AnswerRiddleIntentHandler` to incorporate what the user potentially provided for `riddleNum`. 157 | 158 | 7. Navigate to `AnswerRiddleIntentHandler`. 159 | 8. In `handle`, find the line `if (sessionAttributes.currentIndex == RIDDLES.LEVELS[sessionAttributes.currentLevel].length)` 160 | 9. Update the condition to instead read: 161 | 162 | ``` 163 | // If the customer has gone through all riddles, report the score 164 | if (sessionAttributes.currentIndex == sessionAttributes.totalRids) { 165 | ``` 166 | 167 | 10. Finally, we need to reset the `totalRids` attribute to 5 if this condition is true, indicating that the game is over and the customer can refill the `riddleNum` slot. Add the following line within the condition: 168 | 169 | ``` 170 | sessionAttributes.totalRids = 5; 171 | ``` 172 | 173 | 11. **Upload your code** to your Lambda function and click the **Save** button in the top of the page. This will upload your function code into the Lambda container. 174 | 175 | After the Save is complete, you may or may not be able your code editor inline. 176 | 177 | ### Task 1.4: Test your voice interaction 178 | 179 | We'll now test your skill in the Developer Portal. You can also optionally test your skill in AWS Lambda using the JSON Input from the testing console. 180 | 181 | 1. Navigate to the **Test** tab of the Developer Portal. 182 | 2. In **Alexa Simulator** tab, under **Type or click…**, type "open riddle game workshop" 183 | 3. You should hear and see Alexa respond with the message in your LaunchRequest. Now type "i want three easy riddles". 184 | 4. Walk through the game and assure that only 3 riddles are asked. 185 | 186 | 187 | ### Congratulations! You have finished Task 1! 188 | 189 | 190 | ## License 191 | 192 | This library is licensed under the Amazon Software License. 193 | -------------------------------------------------------------------------------- /Step 1 - Add Advanced Voice Design/lambda/custom/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable func-names */ 2 | /* eslint-disable no-console */ 3 | 4 | const Alexa = require('ask-sdk-core'); 5 | const RIDDLES = require("./riddle_objects"); 6 | 7 | const LaunchRequestHandler = { 8 | canHandle(handlerInput) { 9 | return handlerInput.requestEnvelope.request.type === 'LaunchRequest'; 10 | }, 11 | handle(handlerInput) { 12 | const speechText = "Welcome to Level Up Riddles! Before we get started, I need a few pieces of information." 13 | + " What is your name and favorite color?" 14 | + " How many riddles would you like to play with?" 15 | + " Would you like to start with easy, medium, or hard riddles?"; 16 | 17 | return handlerInput.responseBuilder 18 | .speak(speechText) 19 | .reprompt(speechText) 20 | .withSimpleCard('Level Up Riddles', speechText) 21 | .getResponse(); 22 | }, 23 | }; 24 | 25 | const PlayGameIntentHandler = { 26 | canHandle(handlerInput) { 27 | return handlerInput.requestEnvelope.request.type === 'IntentRequest' 28 | && handlerInput.requestEnvelope.request.intent.name === 'PlayGameIntent'; 29 | }, 30 | handle(handlerInput) { 31 | const sessionAttributes = handlerInput.attributesManager.getSessionAttributes(); 32 | const request = handlerInput.requestEnvelope.request; 33 | 34 | // Get the level the customer selected: 'easy', 'med', 'hard' 35 | sessionAttributes.currentLevel = request.intent.slots.level.value; 36 | 37 | // Store the slot values for name and color 38 | if (request.intent.slots.name.value) { 39 | sessionAttributes.name = request.intent.slots.name.value; 40 | } 41 | 42 | if (request.intent.slots.color.value) { 43 | sessionAttributes.color = request.intent.slots.color.value; 44 | } 45 | 46 | // Check if the slot value for riddleNum is filled and <5, otherwise default to 5 47 | const riddleNum = request.intent.slots.riddleNum.value; 48 | if (riddleNum) { 49 | sessionAttributes.totalRids = riddleNum <= 5 ? riddleNum : 5; 50 | } else { 51 | sessionAttributes.totalRids = 5; 52 | } 53 | 54 | // Reset variables to 0 to start the new game 55 | sessionAttributes.correctCount = 0; 56 | sessionAttributes.currentIndex = 0; 57 | 58 | // Get the first riddle according to that level 59 | sessionAttributes.currentRiddle = RIDDLES.LEVELS[sessionAttributes.currentLevel][sessionAttributes.currentIndex]; 60 | 61 | sessionAttributes.speechText = "Lets play with " 62 | + sessionAttributes.currentLevel + " riddles! " 63 | + " First riddle: " + sessionAttributes.currentRiddle.question; 64 | 65 | handlerInput.attributesManager.setSessionAttributes(sessionAttributes); 66 | 67 | return handlerInput.responseBuilder 68 | .speak(sessionAttributes.speechText) 69 | .reprompt(sessionAttributes.speechText) 70 | .withSimpleCard('Level Up Riddles', sessionAttributes.speechText) 71 | .getResponse(); 72 | } 73 | }; 74 | 75 | const AnswerRiddleIntentHandler = { 76 | canHandle(handlerInput) { 77 | return handlerInput.requestEnvelope.request.type === 'IntentRequest' 78 | && handlerInput.requestEnvelope.request.intent.name === 'AnswerRiddleIntent'; 79 | }, 80 | handle(handlerInput) { 81 | const sessionAttributes = handlerInput.attributesManager.getSessionAttributes(); 82 | 83 | console.log(JSON.stringify(handlerInput.requestEnvelope.request.intent.slots)); 84 | 85 | // Determine if the customer said the correct answer for the current riddle 86 | const spokenAnswer = handlerInput.requestEnvelope.request.intent.slots.answer.value; 87 | sessionAttributes.speechText = ""; 88 | if (spokenAnswer == sessionAttributes.currentRiddle.answer) { 89 | sessionAttributes.speechText += sessionAttributes.currentRiddle.answer + " is correct! You got it right! " 90 | sessionAttributes.correctCount += 1; 91 | sessionAttributes.correct = "Correct! "; 92 | } else { 93 | sessionAttributes.speechText += "Oops, that was wrong. The correct answer is " + sessionAttributes.currentRiddle.answer + ". "; 94 | sessionAttributes.correct = "Incorrect! "; 95 | } 96 | 97 | // Move on to the next question 98 | sessionAttributes.currentIndex += 1; 99 | 100 | // If the customer has gone through all riddles, report the score 101 | if (sessionAttributes.currentIndex == sessionAttributes.totalRids) { 102 | sessionAttributes.speechText += 103 | "You have completed all of the riddles on this level! " 104 | + "Your correct answer count is " 105 | + sessionAttributes.correctCount 106 | + ". To play another level, say easy, medium, or hard. "; 107 | 108 | // Reset variables to start a new game 109 | sessionAttributes.currentLevel = ""; 110 | sessionAttributes.currentRiddle = {}; 111 | sessionAttributes.currentIndex = 0; 112 | sessionAttributes.totalRids = 5; 113 | } else { 114 | sessionAttributes.currentRiddle = RIDDLES.LEVELS[sessionAttributes.currentLevel][sessionAttributes.currentIndex]; 115 | sessionAttributes.speechText += "Next riddle: " + sessionAttributes.currentRiddle.question; 116 | } 117 | 118 | handlerInput.attributesManager.setSessionAttributes(sessionAttributes); 119 | 120 | return handlerInput.responseBuilder 121 | .speak(sessionAttributes.speechText) 122 | .reprompt(sessionAttributes.speechText) 123 | .withSimpleCard('Level Up Riddles', sessionAttributes.speechText) 124 | .getResponse(); 125 | } 126 | }; 127 | 128 | const HelpIntentHandler = { 129 | canHandle(handlerInput) { 130 | return handlerInput.requestEnvelope.request.type === 'IntentRequest' 131 | && handlerInput.requestEnvelope.request.intent.name === 'AMAZON.HelpIntent'; 132 | }, 133 | handle(handlerInput) { 134 | const speechText = "I will give you 5 riddles. Would you like to start with easy, medium, or hard riddles?" 135 | 136 | return handlerInput.responseBuilder 137 | .speak(speechText) 138 | .reprompt(speechText) 139 | .withSimpleCard('Level Up Riddles', speechText) 140 | .getResponse(); 141 | } 142 | }; 143 | 144 | const CancelAndStopIntentHandler = { 145 | canHandle(handlerInput) { 146 | return handlerInput.requestEnvelope.request.type === 'IntentRequest' 147 | && (handlerInput.requestEnvelope.request.intent.name === 'AMAZON.CancelIntent' 148 | || handlerInput.requestEnvelope.request.intent.name === 'AMAZON.StopIntent'); 149 | }, 150 | handle(handlerInput) { 151 | const speechText = 'Goodbye!'; 152 | 153 | return handlerInput.responseBuilder 154 | .speak(speechText) 155 | .withSimpleCard('Level Up Riddles', speechText) 156 | .getResponse(); 157 | } 158 | }; 159 | 160 | const SessionEndedRequestHandler = { 161 | canHandle(handlerInput) { 162 | return handlerInput.requestEnvelope.request.type === 'SessionEndedRequest'; 163 | }, 164 | handle(handlerInput) { 165 | console.log(`Session ended with reason: ${handlerInput.requestEnvelope.request.reason}`); 166 | console.log("Error in request " + JSON.stringify(handlerInput.requestEnvelope.request)); 167 | 168 | return handlerInput.responseBuilder.getResponse(); 169 | } 170 | }; 171 | 172 | const ErrorHandler = { 173 | canHandle() { 174 | return true; 175 | }, 176 | handle(handlerInput, error) { 177 | console.log(`Error handled: ${error.message}`); 178 | 179 | return handlerInput.responseBuilder 180 | .speak('Sorry, I can\'t understand the command. Please say again.') 181 | .reprompt('Sorry, I can\'t understand the command. Please say again.') 182 | .getResponse(); 183 | } 184 | }; 185 | 186 | const skillBuilder = Alexa.SkillBuilders.custom(); 187 | 188 | exports.handler = skillBuilder 189 | .addRequestHandlers( 190 | LaunchRequestHandler, 191 | PlayGameIntentHandler, 192 | AnswerRiddleIntentHandler, 193 | HelpIntentHandler, 194 | CancelAndStopIntentHandler, 195 | SessionEndedRequestHandler 196 | ) 197 | .withApiClient(new Alexa.DefaultApiClient()) 198 | .addErrorHandlers(ErrorHandler) 199 | .lambda(); 200 | 201 | 202 | 203 | 204 | -------------------------------------------------------------------------------- /Step 1 - Add Advanced Voice Design/lambda/custom/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hello-world", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "ask-sdk": "^2.0.0", 13 | "ask-sdk-core": "^2.0.0", 14 | "ask-sdk-model": "^1.0.0" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Step 1 - Add Advanced Voice Design/lambda/custom/riddle_objects.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | LEVELS : { 5 | easy : [ 6 | { 7 | question : "I run all day and never walk, I tell you something, but I do not talk!", 8 | answer : "clock" 9 | }, 10 | { 11 | question : "When you look at my face it is easy to see, You're looking at you when you're looking at me!", 12 | answer : "mirror" 13 | }, 14 | { 15 | question : "What is full of holes but holds water?", 16 | answer : "sponge" 17 | }, 18 | { 19 | question : "What has four fingers and one thumb but is not alive?", 20 | answer : "glove" 21 | }, 22 | { 23 | question : "Soft as a petal that falls from a tree, the more I dry the wetter I'll be!", 24 | answer : "towel" 25 | } 26 | ], 27 | medium : [ 28 | { 29 | question : "I can be quick and then I'm deadly, I am a rock, shell and bone medley.", 30 | answer : "sand" 31 | }, 32 | { 33 | question : "When I point up it's bright, but when I point down it's dark. What am I?", 34 | answer : "light switch" 35 | }, 36 | { 37 | question : "Lighter than what I am made of, More of me is hidden Than is seen.", 38 | answer : "iceberg" 39 | }, 40 | { 41 | question : "I am more useful when I am broken. What am I?", 42 | answer : "egg" 43 | }, 44 | { 45 | question : "I'm the part of the bird that's not in the sky. I can swim in the ocean and yet remain dry. What am I?", 46 | answer : "shadow" 47 | } 48 | ], 49 | hard : [ 50 | { 51 | question : "The more you take the more you leave behind?", 52 | answer : "foot steps" 53 | }, 54 | { 55 | question : "What is black when you buy it, red when you use it, and gray when you throw it away?", 56 | answer : "charcoal" 57 | }, 58 | { 59 | question : "Mountains will crumble and temples will fall, and no man can survive its endless call. What is it?", 60 | answer : "time" 61 | }, 62 | { 63 | question : "I can be cracked, I can be made. I can be told, I can be played. What am I?", 64 | answer : "joke" 65 | }, 66 | { 67 | question : "Lovely and round, I shine with pale light, grown in the darkness, A lady's delight. What am I?", 68 | answer : "pearl" 69 | } 70 | ] 71 | } 72 | }; 73 | 74 | -------------------------------------------------------------------------------- /Step 1 - Add Advanced Voice Design/models/en-US.json: -------------------------------------------------------------------------------- 1 | { 2 | "interactionModel": { 3 | "languageModel": { 4 | "invocationName": "riddle game workshop advanced voice", 5 | "intents": [ 6 | { 7 | "name": "AMAZON.CancelIntent", 8 | "samples": [] 9 | }, 10 | { 11 | "name": "AMAZON.HelpIntent", 12 | "samples": [] 13 | }, 14 | { 15 | "name": "AMAZON.StopIntent", 16 | "samples": [] 17 | }, 18 | { 19 | "name": "PlayGameIntent", 20 | "slots": [ 21 | { 22 | "name": "level", 23 | "type": "levelType", 24 | "samples": [ 25 | "{riddleNum} {level}", 26 | "{riddleNum} {level} riddles", 27 | "{level} riddles", 28 | "level {level}", 29 | "{level}" 30 | ] 31 | }, 32 | { 33 | "name": "riddleNum", 34 | "type": "AMAZON.NUMBER" 35 | }, 36 | { 37 | "name": "name", 38 | "type": "AMAZON.US_FIRST_NAME" 39 | }, 40 | { 41 | "name": "color", 42 | "type": "AMAZON.Color" 43 | } 44 | ], 45 | "samples": [ 46 | "{riddleNum} of the {level} riddles", 47 | "{name} {color} {riddleNum} level {level}", 48 | "{name} and {color} and i want {riddleNum} {level} riddles", 49 | "give me {riddleNum} {level}", 50 | "my favorite color is {color} and {riddleNum} {level}", 51 | "i like {color} and {level} riddles", 52 | "let's start", 53 | "i want to play", 54 | "i want {riddleNum} riddles", 55 | "give me {riddleNum} riddles", 56 | "my name is {name}", 57 | "my name is {name} and my favorite color is {color}", 58 | "i am {name}", 59 | "i am {name} and i like {color}", 60 | "my favorite color is {color}", 61 | "i like {color}", 62 | "i want {riddleNum} {level} riddles", 63 | "give me {riddleNum} {level} riddles", 64 | "my name is {name} and i want {level} riddles", 65 | "my name is {name} and i want {riddleNum} {level} riddles", 66 | "my name is {name} and my favorite color is {color} and i want {riddleNum} {level} riddles", 67 | "how about {level} riddles", 68 | "lets do some {level} riddles", 69 | "{level} ones", 70 | "lets do some {level} ones", 71 | "the {level} riddles", 72 | "the {level} ones", 73 | "{level} level riddles", 74 | "i want {level} riddles", 75 | "i want to play {level}", 76 | "{level} riddles", 77 | "{level}" 78 | ] 79 | }, 80 | { 81 | "name": "AnswerRiddleIntent", 82 | "slots": [ 83 | { 84 | "name": "answer", 85 | "type": "answerType" 86 | } 87 | ], 88 | "samples": [ 89 | "i think the answer is {answer} ", 90 | "my answer is {answer}", 91 | "your {answer}", 92 | "my {answer}", 93 | "the {answer}", 94 | "an {answer}", 95 | "a {answer}", 96 | "the answer to the riddle is {answer}", 97 | "the answer is {answer}", 98 | "{answer} is the answer", 99 | "i think it is {answer}", 100 | "{answer}" 101 | ] 102 | }, 103 | { 104 | "name": "AMAZON.MoreIntent", 105 | "samples": [] 106 | }, 107 | { 108 | "name": "AMAZON.NavigateHomeIntent", 109 | "samples": [] 110 | }, 111 | { 112 | "name": "AMAZON.NavigateSettingsIntent", 113 | "samples": [] 114 | }, 115 | { 116 | "name": "AMAZON.NextIntent", 117 | "samples": [] 118 | }, 119 | { 120 | "name": "AMAZON.PageUpIntent", 121 | "samples": [] 122 | }, 123 | { 124 | "name": "AMAZON.PageDownIntent", 125 | "samples": [] 126 | }, 127 | { 128 | "name": "AMAZON.PreviousIntent", 129 | "samples": [] 130 | }, 131 | { 132 | "name": "AMAZON.ScrollRightIntent", 133 | "samples": [] 134 | }, 135 | { 136 | "name": "AMAZON.ScrollDownIntent", 137 | "samples": [] 138 | }, 139 | { 140 | "name": "AMAZON.ScrollLeftIntent", 141 | "samples": [] 142 | }, 143 | { 144 | "name": "AMAZON.ScrollUpIntent", 145 | "samples": [] 146 | } 147 | ], 148 | "types": [ 149 | { 150 | "name": "levelType", 151 | "values": [ 152 | { 153 | "name": { 154 | "value": "easy", 155 | "synonyms": [ 156 | "children", 157 | "child", 158 | "kids", 159 | "kid", 160 | "for kids", 161 | "easy peasy", 162 | "really easy" 163 | ] 164 | } 165 | }, 166 | { 167 | "name": { 168 | "value": "hard", 169 | "synonyms": [ 170 | "advanced", 171 | "hardest", 172 | "harder", 173 | "really hard", 174 | "challenging", 175 | "difficult" 176 | ] 177 | } 178 | }, 179 | { 180 | "name": { 181 | "value": "medium", 182 | "synonyms": [ 183 | "med" 184 | ] 185 | } 186 | } 187 | ] 188 | }, 189 | { 190 | "name": "answerType", 191 | "values": [ 192 | { 193 | "name": { 194 | "value": "heart" 195 | } 196 | }, 197 | { 198 | "name": { 199 | "value": "leg", 200 | "synonyms": [ 201 | "legs" 202 | ] 203 | } 204 | }, 205 | { 206 | "name": { 207 | "value": "water" 208 | } 209 | }, 210 | { 211 | "name": { 212 | "value": "icicle" 213 | } 214 | }, 215 | { 216 | "name": { 217 | "value": "scissors", 218 | "synonyms": [ 219 | "shears" 220 | ] 221 | } 222 | }, 223 | { 224 | "name": { 225 | "value": "pride" 226 | } 227 | }, 228 | { 229 | "name": { 230 | "value": "volcano" 231 | } 232 | }, 233 | { 234 | "name": { 235 | "value": "counterfeit", 236 | "synonyms": [ 237 | "phony money", 238 | "counterfeit money", 239 | "fake money" 240 | ] 241 | } 242 | }, 243 | { 244 | "name": { 245 | "value": "tomorrow" 246 | } 247 | }, 248 | { 249 | "name": { 250 | "value": "key" 251 | } 252 | }, 253 | { 254 | "name": { 255 | "value": "blink" 256 | } 257 | }, 258 | { 259 | "name": { 260 | "value": "snail" 261 | } 262 | }, 263 | { 264 | "name": { 265 | "value": "ship", 266 | "synonyms": [ 267 | "yacht ", 268 | "boat" 269 | ] 270 | } 271 | }, 272 | { 273 | "name": { 274 | "value": "echo" 275 | } 276 | }, 277 | { 278 | "name": { 279 | "value": "candle" 280 | } 281 | }, 282 | { 283 | "name": { 284 | "value": "hour glass", 285 | "synonyms": [ 286 | "sand timer", 287 | "hourglass", 288 | "timer" 289 | ] 290 | } 291 | }, 292 | { 293 | "name": { 294 | "value": "wheel" 295 | } 296 | }, 297 | { 298 | "name": { 299 | "value": "calendar" 300 | } 301 | }, 302 | { 303 | "name": { 304 | "value": "blue" 305 | } 306 | }, 307 | { 308 | "name": { 309 | "value": "corn" 310 | } 311 | }, 312 | { 313 | "name": { 314 | "value": "finger nails", 315 | "synonyms": [ 316 | "nails", 317 | "fingernails" 318 | ] 319 | } 320 | }, 321 | { 322 | "name": { 323 | "value": "name" 324 | } 325 | }, 326 | { 327 | "name": { 328 | "value": "piano", 329 | "synonyms": [ 330 | "keyboard" 331 | ] 332 | } 333 | }, 334 | { 335 | "name": { 336 | "value": "sunshine", 337 | "synonyms": [ 338 | "sunlight", 339 | "sun light", 340 | "sun" 341 | ] 342 | } 343 | }, 344 | { 345 | "name": { 346 | "value": "carpet" 347 | } 348 | }, 349 | { 350 | "name": { 351 | "value": "turtle" 352 | } 353 | }, 354 | { 355 | "name": { 356 | "value": "river" 357 | } 358 | }, 359 | { 360 | "name": { 361 | "value": "wind" 362 | } 363 | }, 364 | { 365 | "name": { 366 | "value": "hole", 367 | "synonyms": [ 368 | "holes", 369 | "whole" 370 | ] 371 | } 372 | }, 373 | { 374 | "name": { 375 | "value": "onion" 376 | } 377 | }, 378 | { 379 | "name": { 380 | "value": "newspaper" 381 | } 382 | }, 383 | { 384 | "name": { 385 | "value": "chalkboard", 386 | "synonyms": [ 387 | "blackboard", 388 | "chalk board", 389 | "board", 390 | "white board", 391 | "chalk wall", 392 | "black board" 393 | ] 394 | } 395 | }, 396 | { 397 | "name": { 398 | "value": "phone", 399 | "synonyms": [ 400 | "cell phone", 401 | "telephone" 402 | ] 403 | } 404 | }, 405 | { 406 | "name": { 407 | "value": "darkness", 408 | "synonyms": [ 409 | "dark" 410 | ] 411 | } 412 | }, 413 | { 414 | "name": { 415 | "value": "match" 416 | } 417 | }, 418 | { 419 | "name": { 420 | "value": "stamp" 421 | } 422 | }, 423 | { 424 | "name": { 425 | "value": "rainbow" 426 | } 427 | }, 428 | { 429 | "name": { 430 | "value": "stars", 431 | "synonyms": [ 432 | "star" 433 | ] 434 | } 435 | }, 436 | { 437 | "name": { 438 | "value": "yardstick", 439 | "synonyms": [ 440 | "yard stick", 441 | "ruler", 442 | "yard ruler" 443 | ] 444 | } 445 | }, 446 | { 447 | "name": { 448 | "value": "salt" 449 | } 450 | }, 451 | { 452 | "name": { 453 | "value": "breath", 454 | "synonyms": [ 455 | "breathe" 456 | ] 457 | } 458 | }, 459 | { 460 | "name": { 461 | "value": "tempurature" 462 | } 463 | }, 464 | { 465 | "name": { 466 | "value": "cold" 467 | } 468 | }, 469 | { 470 | "name": { 471 | "value": "alphabet" 472 | } 473 | }, 474 | { 475 | "name": { 476 | "value": "umbrella" 477 | } 478 | }, 479 | { 480 | "name": { 481 | "value": "pearl" 482 | } 483 | }, 484 | { 485 | "name": { 486 | "value": "joke" 487 | } 488 | }, 489 | { 490 | "name": { 491 | "value": "time" 492 | } 493 | }, 494 | { 495 | "name": { 496 | "value": "charcoal" 497 | } 498 | }, 499 | { 500 | "name": { 501 | "value": "foot steps", 502 | "synonyms": [ 503 | "feet marks", 504 | "feet prints", 505 | "foot marks", 506 | "footsteps" 507 | ] 508 | } 509 | }, 510 | { 511 | "name": { 512 | "value": "shadow" 513 | } 514 | }, 515 | { 516 | "name": { 517 | "value": "egg" 518 | } 519 | }, 520 | { 521 | "name": { 522 | "value": "iceberg" 523 | } 524 | }, 525 | { 526 | "name": { 527 | "value": "light switch", 528 | "synonyms": [ 529 | "switch" 530 | ] 531 | } 532 | }, 533 | { 534 | "name": { 535 | "value": "sand" 536 | } 537 | }, 538 | { 539 | "name": { 540 | "value": "towel" 541 | } 542 | }, 543 | { 544 | "name": { 545 | "value": "glove" 546 | } 547 | }, 548 | { 549 | "name": { 550 | "value": "sponge" 551 | } 552 | }, 553 | { 554 | "name": { 555 | "value": "mirror" 556 | } 557 | }, 558 | { 559 | "name": { 560 | "value": "clock" 561 | } 562 | } 563 | ] 564 | } 565 | ] 566 | }, 567 | "dialog": { 568 | "intents": [ 569 | { 570 | "name": "PlayGameIntent", 571 | "delegationStrategy": "ALWAYS", 572 | "confirmationRequired": false, 573 | "prompts": {}, 574 | "slots": [ 575 | { 576 | "name": "level", 577 | "type": "levelType", 578 | "confirmationRequired": false, 579 | "elicitationRequired": true, 580 | "prompts": { 581 | "elicitation": "Elicit.Slot.1456697663557.1392510278084" 582 | } 583 | }, 584 | { 585 | "name": "riddleNum", 586 | "type": "AMAZON.NUMBER", 587 | "confirmationRequired": false, 588 | "elicitationRequired": false, 589 | "prompts": {} 590 | }, 591 | { 592 | "name": "name", 593 | "type": "AMAZON.US_FIRST_NAME", 594 | "confirmationRequired": false, 595 | "elicitationRequired": false, 596 | "prompts": {} 597 | }, 598 | { 599 | "name": "color", 600 | "type": "AMAZON.Color", 601 | "confirmationRequired": false, 602 | "elicitationRequired": false, 603 | "prompts": {} 604 | } 605 | ] 606 | } 607 | ], 608 | "delegationStrategy": "ALWAYS" 609 | }, 610 | "prompts": [ 611 | { 612 | "id": "Elicit.Slot.1456697663557.1392510278084", 613 | "variations": [ 614 | { 615 | "type": "PlainText", 616 | "value": "Nice to meet you {name} . You want to play through {riddleNum} riddles. Would you like those to be easy, medium, or hard?" 617 | }, 618 | { 619 | "type": "PlainText", 620 | "value": "Which category would you like your {riddleNum} riddles to be sourced from, easy, medium, or hard?" 621 | }, 622 | { 623 | "type": "PlainText", 624 | "value": "Hi {name}, do you want easy, medium or hard riddles?" 625 | }, 626 | { 627 | "type": "PlainText", 628 | "value": "Do you want easy, medium, or hard riddles?" 629 | } 630 | ] 631 | } 632 | ] 633 | } 634 | } -------------------------------------------------------------------------------- /Step 1 - Add Advanced Voice Design/skill.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest": { 3 | "publishingInformation": { 4 | "locales": { 5 | "en-US": { 6 | "summary": "Sample Short Description", 7 | "examplePhrases": [ 8 | "Alexa open hello world", 9 | "Alexa tell hello world hello", 10 | "Alexa ask hello world say hello" 11 | ], 12 | "name": "Riddle Game Workshop Advanced Voice", 13 | "description": "Sample Full Description" 14 | } 15 | }, 16 | "isAvailableWorldwide": true, 17 | "testingInstructions": "Sample Testing Instructions.", 18 | "category": "EDUCATION_AND_REFERENCE", 19 | "distributionCountries": [] 20 | }, 21 | "apis": { 22 | "custom": { 23 | "endpoint": { 24 | "sourceDir": "lambda/custom", 25 | "uri": "ask-level-up-riddles" 26 | } 27 | } 28 | }, 29 | "manifestVersion": "1.0" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Step 2 - Add ISP/.ask/config: -------------------------------------------------------------------------------- 1 | { 2 | "deploy_settings": { 3 | "default": { 4 | "skill_id": "", 5 | "was_cloned": false, 6 | "merge": { 7 | } 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Step 2 - Add ISP/README.md: -------------------------------------------------------------------------------- 1 | # Add In-Skill Purchasing 2 | 3 | In this section of the workshop, you will incorporate in-skill purchasing (ISP) into your skill. When developers and content creators build delightful skills with compelling content, customers win. With in-skill purchasing, you can sell premium content to enrich your Alexa skill experience. 4 | 5 | When a customer plays through your Riddle Game, they have the option to purchase the ability to ask for a hint. When the customer successfully completes the in-skill purchase, they can ask for up to three hints per riddle. 6 | 7 | ## Objectives 8 | 9 | After completing this workshop, you will be able to: 10 | 11 | - Set up an ISP entitlement in the developer console 12 | - Configure your interaction model to handle ISP 13 | - Update your Lambda service code to be able to handle the various requests from the purchase flow 14 | 15 | ## Prerequisites 16 | 17 | This lab requires: 18 | 19 | - Access to a notebook computer with Wi-Fi, running Microsoft Windows, Mac OSX, or Linux (Ubuntu, SuSE, or RedHat). 20 | - An Internet browser suchas Chrome, Firefox, or IE9 (previous versions of Internet Explorer are not supported). 21 | - Having completed **[Step 0: Initialize Riddle Game](https://github.com/alexa-labs/skill-sample-nodejs-level-up-riddles/tree/master/Step%200%20-%20Initialize%20Riddle%20Game)** 22 | - Having completed **[Step 1: Add Advanced Voice Design](https://github.com/alexa-labs/skill-sample-nodejs-level-up-riddles/tree/master/Step%201%20-%20Add%20Advanced%20Voice%20Design)** 23 | 24 | ## Goal: Integrating Premium Features into your skill 25 | ISP supports one-time purchases for entitlements that unlock access to features or content in your skill, subscriptions that offer access to premium features or content for a period of time, and consumables which can be purchased, depleted and purchased again. 26 | 27 | You can define your premium offering and price, and we handle the voice-first purchasing flow. We also provide self-service tools to manage your in-skill products, and optimize their sales performance over time. Today, you can make money through both ISP and Alexa Developer Rewards. This feature is available for Alexa skills in the US. 28 | 29 | ### Task 2.1: Build the Premium Feature into your skill 30 | Before we start to integrate a formal ISP flow into our skill, we need to build the premium feature into the skill. This will mean creating an intent for when the customer asks for a hint in the game. 31 | 32 | 1. Navigate to the Amazon Developer Portal at[https://developer.amazon.com/alexa](https://developer.amazon.com/alexa). 33 | 2. Click **Sign In** in the upper right. 34 | 3. When signed in, click **Your Alexa Dashboards** in the upper right. 35 | 4. Choose your **Riddle Game Workshop** skill. 36 | 5. In the left-hand menu, click the **+ Add** icon to add an intent 37 | 6. Create a custom intent called "HintIntent" 38 | 7. Enter the following sample utterances for HintIntent: 39 | 40 | ``` 41 | give me a hint 42 | i need a hint 43 | tell me a hint 44 | hint 45 | i need another hint 46 | i need a clue 47 | i want a clue 48 | clue 49 | give me a clue 50 | tell me a clue 51 | i need another clue 52 | ``` 53 | 54 | 8. **Save** and **Build** your interaction model. 55 | 56 | This has updated our interaction model to be able to understand when the user requests for a hint. Now we need to be able to handle this in our service code. Once this is done, we will put this ability behind an ISP flow. 57 | 58 | 9. Navigate to your service code. 59 | 10. Update the `riddle_objects.js` file to add a `hints` array to each JSON object (you can copy it directly from [here](https://github.com/alexa-labs/skill-sample-nodejs-level-up-riddles/blob/master/Step%201%20-%20Add%20Advanced%20Voice%20Design/lambda/custom/riddle_objects.js)). 60 | 11. In your `index.js`, add a handler for the HintIntent to read off all requested hints for the current question: 61 | 62 | ``` 63 | const HintIntentHandler = { 64 | canHandle(handlerInput) { 65 | return handlerInput.requestEnvelope.request.type === 'IntentRequest' 66 | && handlerInput.requestEnvelope.request.intent.name === 'HintIntent'; 67 | }, 68 | handle(handlerInput) { 69 | const sessionAttributes = handlerInput.attributesManager.getSessionAttributes(); 70 | const index = sessionAttributes.currentHintIndex; 71 | 72 | // Read all hints the customer has asked for thus far 73 | let speechText = "Okay, here are your hints: "; 74 | let i = 0; 75 | while (i <= index) { 76 | speechText += sessionAttributes.currentRiddle.hints[i] + ", "; 77 | i++; 78 | } 79 | speechText += ". Here is your question again: " 80 | + sessionAttributes.currentRiddle.question; 81 | 82 | // Update the current hint index, maximum of 3 hints per riddle 83 | sessionAttributes.currentHintIndex = index == 2 ? 2 : (index + 1); 84 | handlerInput.attributesManager.setSessionAttributes(sessionAttributes); 85 | 86 | return handlerInput.responseBuilder 87 | .speak(speechText) 88 | .reprompt(sessionAttributes.currentRiddle.question) 89 | .withSimpleCard('Level Up Riddles', speechText) 90 | .getResponse(); 91 | } 92 | }; 93 | ``` 94 | It is important to note, in this code we keep track of a new session attribute: `sessionAttributes.currentHintIndex`. We need to initialize this attribute at the start of the game play. 95 | 96 | 12. Initialize `sessionAttributes.currentHintIndex` to 0 in `PlayGameIntent`, and for each new question in `AnswerRiddleIntent`. 97 | 13. Finally, add `HintIntentHandler` to your **RequestHandler** builder. 98 | 14. **Upload your code** to your Lambda function and click the **Save** button in the top of the page. This will upload your function code into the Lambda container. 99 | 100 | After the Save is complete, you may or may not be able your code editor inline. 101 | 102 | ### Task 2.2: Test that the Premium Feature works in your skill 103 | At this point we should test that hints work within your Riddle Game skill. You can test your skill in the Developer Portal or in Lambda using the JSON Input from the testing console. 104 | 105 | 1. Navigate to the **Test** tab of the Developer Portal. 106 | 2. In **Alexa Simulator** tab, under **Type or click…**, type "open riddle game workshop" 107 | 3. You should hear and see Alexa respond with the message in your LaunchRequest. Now type "i want three easy riddles". 108 | 4. When Alexa is done reading off the first riddle, respond with "i need a hint". Assure you get an appropriate response. 109 | 110 | ### Task 2.3: Add In-Skill Purchasing into your interaction model 111 | Now we are going to add In-Skill purchasing into our skill. This will allow a customer to pay for a premium feature within your skill. You can integrate ISP into your skill through the Developer Portal or via the ASK-CLI. 112 | 113 | 1. Navigate to the **Build** tab of the Developer Portal. 114 | 2. Scroll down on the left-menu and select **IN-SKILL PRODUCTS** 115 | 3. You are now in the ISP management portal. Click the blue **Create In-Skill Product** button. 116 | 4. Type `hint_pack` in **Reference Name** field. 117 | 5. Assure that **One-Time Purchase** is selected as the product type. 118 | 6. Click the blue **Create In-Skill Product** button. 119 | 7. Fill all the metadata fields for `hint_pack` as follows: 120 | - **Display Name:** Hint Pack 121 | - **One Sentence Description:** Hint pack for the Riddle Game skill 122 | - **Detailed Description:** Unlock the hint pack for the Riddle Games skill to be able to request hints within the game. 123 | - **Example Phrases:** Tell me a hint, Give me a hint, I need a hint 124 | - **Icons:** Use the [Alexa Icon Builder](https://developer.amazon.com/docs/tools/icon-builder.html) to make the icons for your product, and upload each size appropriately 125 | - **Keywords:** hint, hints, clue, clues 126 | - **Purchase Prompt Description:** The hint pack includes three hints to help you solve each riddle in Riddle Game. 127 | - **Purchase confirmation description:** Purchase the hint pack? 128 | - **Privacy policy URL:** [https://privacy.com](https://privacy.com) 129 | 8. Click the **Save and Continue** button. 130 | 9. **Select a price** for your product (default is $0.99). 131 | 10. Assure the **Release Date** set to today. 132 | 11. Set the **Tax Category** for your product to "Information Services". 133 | 12. Click the **Save and Continue** button. 134 | 13. For **Testing Instructions**, insert your example phrases: Tell me a hint, Give me a hint, I need a hint. 135 | 14. Click the **Save and Finish** button. 136 | 15. You will see a prompt - “Link hint_pack to your skill Premium Facts Skill?” - Click on **Link to skill**. 137 | 138 | ### Task 2.4: Add ISP into your service 139 | Now that we can recognize ISP requests and responses within our skill, we need to handle it within our skill. 140 | 141 | 1. Firstly, we need two helper functions to let us know how to define an Entitlement within our skill. **Add** the following helper functions to `index.js`: 142 | 143 | ``` 144 | function isProduct(product) { 145 | return product && product.length > 0; 146 | } 147 | 148 | function isEntitled(product) { 149 | return isProduct(product) && product[0].entitled == 'ENTITLED'; 150 | } 151 | ``` 152 | 153 | 2. **Navigate** to our `HintIntentHandler` in `index.js`. 154 | 3. At the top of the `handle` function, **insert the following lines of code:** 155 | 156 | ``` 157 | handle(handlerInput) { 158 | const locale = handlerInput.requestEnvelope.request.locale; 159 | const ms = handlerInput.serviceClientFactory.getMonetizationServiceClient(); 160 | ``` 161 | The first line grabs the customer's local, and the second begins the monetization flow. 162 | 163 | 4. Wrap the logic we wrote in Task 2.1 in the following return statement: 164 | 165 | ``` 166 | return ms.getInSkillProducts(locale).then(function(res) { 167 | // Task 2.1 logic here 168 | }); 169 | ``` 170 | This statement will return an appropriate response depending on whether the customer has already made a purchase or not. 171 | 172 | 5. Now we need to determine what product a customer could purchase at this point in the skill, and if they have purchased it. **Insert the following code:** 173 | 174 | ``` 175 | return ms.getInSkillProducts(locale).then(function(res) { 176 | var product = res.inSkillProducts.filter(record => record.referenceName == 'hint_pack'); 177 | 178 | if (isEntitled(product)) { 179 | // Task 2.1 logic here 180 | } else { 181 | 182 | } 183 | ``` 184 | The customer will be pushed into the `else` statement if they have not purchased `hint_pack`. It will enter the ISP flow and ask if the user would like to complete a purchase. 185 | 186 | 6. **Insert the following code** into the `else` statement: 187 | 188 | ``` 189 | const upsellMessage = "You don't currently own the hint pack. Want to learn more about it?"; 190 | 191 | return handlerInput.responseBuilder 192 | .addDirective({ 193 | 'type': 'Connections.SendRequest', 194 | 'name': 'Upsell', 195 | 'payload': { 196 | 'InSkillProduct': { 197 | 'productId': product[0].productId 198 | }, 199 | 'upsellMessage': upsellMessage 200 | }, 201 | 'token': 'correlationToken' 202 | }) 203 | .getResponse(); 204 | ``` 205 | 206 | In this case, we are sending a directive with the name `Upsell`. This is indicating we are entering the ISP flow with the directive to tell the customer more information about the product before they purchase. We also need to create a similar intent for when the customer requests to buy something directly. 207 | 208 | 7. **Create a custom intent** named "BuyIntent" for your interaction model in the Developer Portal with the following utterances 209 | 210 | ``` 211 | buy 212 | buy hints 213 | i want to buy hints 214 | buy hints for my game 215 | buy me hints 216 | purchase hints 217 | purchase clues 218 | buy clues 219 | buy clues for my game 220 | ``` 221 | 222 | 8. In `index.js`, **create a handler** for `BuyIntent` that can send a directive of type `Connections.SendRequest` with the name `Buy` to buy the `hint_pack` directly: 223 | 224 | ``` 225 | const BuyIntentHandler = { 226 | canHandle(handlerInput) { 227 | return handlerInput.requestEnvelope.request.type === 'IntentRequest' && 228 | handlerInput.requestEnvelope.request.intent.name === 'BuyIntent'; 229 | }, 230 | handle(handlerInput) { 231 | // Inform the user about what products are available for purchase 232 | const locale = handlerInput.requestEnvelope.request.locale; 233 | const ms = handlerInput.serviceClientFactory.getMonetizationServiceClient(); 234 | 235 | return ms.getInSkillProducts(locale).then(function(res) { 236 | let product = res.inSkillProducts.filter(record => record.referenceName == "hint_pack"); 237 | 238 | return handlerInput.responseBuilder 239 | .addDirective({ 240 | 'type': 'Connections.SendRequest', 241 | 'name': 'Buy', 242 | 'payload': { 243 | 'InSkillProduct': { 244 | 'productId': product[0].productId 245 | } 246 | }, 247 | 'token': 'correlationToken' 248 | }) 249 | .getResponse(); 250 | }); 251 | } 252 | }; 253 | ``` 254 | Once either the `Buy` or `Upsell` directive is sent, the customer has the option to ask for more information, or step out of the purchasing flow. We need to incorporate handlers for these steps. 255 | 256 | 9. First, let's handle the `Upsell` request. **Create** an `UpsellResponseHandler` that matches the following: 257 | 258 | ``` 259 | const UpsellResponseHandler = { 260 | canHandle(handlerInput) { 261 | return handlerInput.requestEnvelope.request.type === "Connections.Response" && 262 | handlerInput.requestEnvelope.request.name === "Upsell"; 263 | }, 264 | handle(handlerInput) { 265 | if (handlerInput.requestEnvelope.request.status.code == 200) { 266 | let speechOutput = ""; 267 | let reprompt = ""; 268 | 269 | if (handlerInput.requestEnvelope.request.payload.purchaseResult == 'ACCEPTED') { 270 | speechOutput = "You can now ask for hints in your game! To get a hint, say, i want a hint."; 271 | reprompt = "Let's play a new game with hints! Would you like to start with easy, medium, or hard riddles?"; 272 | } else if (handlerInput.requestEnvelope.request.payload.purchaseResult == 'DECLINED') { 273 | speechOutput = "Okay. I can't offer you any hints at this time. "; 274 | reprompt = "Let's play a game. Would you like to start with easy, medium, or hard riddles?"; 275 | } 276 | 277 | return handlerInput.responseBuilder 278 | .speak(speechOutput + reprompt) 279 | .reprompt(reprompt) 280 | .getResponse(); 281 | } else { 282 | // Something has failed with the connection. 283 | console.log('Connections.Response indicated failure. error:' + handlerInput.requestEnvelope.request.status.message); 284 | return handlerInput.responseBuilder 285 | .speak("There was an error handling your purchase request. Please try again or contact us for help.") 286 | .getResponse(); 287 | } 288 | } 289 | }; 290 | ``` 291 | Notice how the returned response is just a normal speech response, indicating we are leaving the ISP flow. 292 | 293 | Our `BuyResponseHandler` will look very similar. The major difference between this and the `UpsellResponseHandler` is that we need to formulate our response in the context of getting more product information than just what was read in the Upsell. 294 | 295 | 10. Now let's handle the `Buy` request. **Create** an `BuyResponseHandler` that matches the following: 296 | 297 | ``` 298 | const BuyResponseHandler = { 299 | canHandle(handlerInput) { 300 | return handlerInput.requestEnvelope.request.type === "Connections.Response" && 301 | handlerInput.requestEnvelope.request.name === "Buy"; 302 | }, 303 | handle(handlerInput) { 304 | const locale = handlerInput.requestEnvelope.request.locale; 305 | const ms = handlerInput.serviceClientFactory.getMonetizationServiceClient(); 306 | const productId = handlerInput.requestEnvelope.request.payload.productId; 307 | 308 | return ms.getInSkillProducts(locale).then(function(res) { 309 | let product = res.inSkillProducts.filter(record => record.productId == productId); 310 | let speechOutput = ""; 311 | let reprompt = ""; 312 | 313 | if (handlerInput.requestEnvelope.request.status.code == 200) { 314 | if (handlerInput.requestEnvelope.request.payload.purchaseResult == 'ACCEPTED') { 315 | speechOutput = "You can now ask for hints in your game! To get a hint, say, i want a hint."; 316 | reprompt = "Let's play a new game with hints! Would you like to start with easy, medium, or hard riddles?"; 317 | } else if (handlerInput.requestEnvelope.request.payload.purchaseResult == 'DECLINED') { 318 | speechOutput = "Thanks for your interest in the " + product[0].name + ". "; 319 | reprompt = "Let's play a game. Would you like to start with easy, medium, or hard riddles?"; 320 | } 321 | 322 | return handlerInput.responseBuilder 323 | .speak(speechOutput + reprompt) 324 | .reprompt(reprompt) 325 | .getResponse(); 326 | } else { 327 | // Something has failed with the connection. 328 | console.log('Connections.Response indicated failure. error:' + handlerInput.requestEnvelope.request.status.message); 329 | return handlerInput.responseBuilder 330 | .speak("There was an error handling your purchase request. Please try again or contact us for help.") 331 | .getResponse(); 332 | } 333 | }); 334 | } 335 | }; 336 | ``` 337 | 338 | We have now finished our intent handers for the ISP flow within our skill. We need to add these intents to our skill request handler. 339 | 340 | 11. **Add** `BuyIntentHandler`, `UpsellResponseHandler`, and `BuyResponseHandler` to your **RequestHandler** builder. 341 | 12. **Upload your code** to your Lambda function and click the **Save** button in the top of the page. This will upload your function code into the Lambda container. 342 | 343 | After the Save is complete, you may or may not be able your code editor inline. 344 | 345 | ### Task 2.5: Test that the ISP works in your skill 346 | 347 | We will now test our skill again to assure that the ISP flow works and that our hints are accessible ONLY after a purchase flow is successful. As always, you can test your skill in the Developer Portal or in Lambda using the JSON Input from the testing console. 348 | 349 | 1. Navigate to the **Test** tab of the Developer Portal. 350 | 2. In **Alexa Simulator** tab, under **Type or click…**, type "open riddle game workshop" 351 | 3. You should hear and see Alexa respond with the message in your LaunchRequest. Now type "i want three easy riddles". 352 | 4. When Alexa is done reading off the first riddle, respond with "i need a hint". **Assure that she asks you if you want to purchase hints.** 353 | 5. She should respond with "You don't currently own the hint pack. Want to learn more about it?". Type "yes". 354 | 6. She should now read your **entitlement description**. Respond "yes" to buy it. 355 | 7. Now that you have purchased a hint pack, we can start a new game with hints! Say "i want three easy riddles" and assure you can ask for hints throughout the game. 356 | 357 | 358 | ### Congratulations! You have finished Task 2! 359 | 360 | 361 | ## License 362 | 363 | This library is licensed under the Amazon Software License. 364 | -------------------------------------------------------------------------------- /Step 2 - Add ISP/lambda/custom/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable func-names */ 2 | /* eslint-disable no-console */ 3 | 4 | const Alexa = require('ask-sdk-core'); 5 | const RIDDLES = require("./riddle_objects"); 6 | 7 | const LaunchRequestHandler = { 8 | canHandle(handlerInput) { 9 | return handlerInput.requestEnvelope.request.type === 'LaunchRequest'; 10 | }, 11 | handle(handlerInput) { 12 | const speechText = "Welcome to Level Up Riddles! Before we get started, I need a few pieces of information." 13 | + " What is your name and favorite color?" 14 | + " How many riddles would you like to play with?" 15 | + " Would you like to start with easy, medium, or hard riddles?"; 16 | 17 | return handlerInput.responseBuilder 18 | .speak(speechText) 19 | .reprompt(speechText) 20 | .withSimpleCard('Level Up Riddles', speechText) 21 | .getResponse(); 22 | }, 23 | }; 24 | 25 | const PlayGameIntentHandler = { 26 | canHandle(handlerInput) { 27 | return handlerInput.requestEnvelope.request.type === 'IntentRequest' 28 | && handlerInput.requestEnvelope.request.intent.name === 'PlayGameIntent'; 29 | }, 30 | handle(handlerInput) { 31 | const sessionAttributes = handlerInput.attributesManager.getSessionAttributes(); 32 | const request = handlerInput.requestEnvelope.request; 33 | 34 | // Get the level the customer selected: 'easy', 'med', 'hard' 35 | sessionAttributes.currentLevel = request.intent.slots.level.value; 36 | 37 | // Store the slot values for name and color 38 | if (request.intent.slots.name.value) { 39 | sessionAttributes.name = request.intent.slots.name.value; 40 | } 41 | 42 | if (request.intent.slots.color.value) { 43 | sessionAttributes.color = request.intent.slots.color.value; 44 | } 45 | 46 | // Check if the slot value for riddleNum is filled and <5, otherwise default to 5 47 | const riddleNum = request.intent.slots.riddleNum.value; 48 | if (riddleNum) { 49 | sessionAttributes.totalRids = riddleNum <= 5 ? riddleNum : 5; 50 | } else { 51 | sessionAttributes.totalRids = 5; 52 | } 53 | 54 | // Reset variables to 0 to start the new game 55 | sessionAttributes.correctCount = 0; 56 | sessionAttributes.currentIndex = 0; 57 | sessionAttributes.currentHintIndex = 0; 58 | 59 | // Get the first riddle according to that level 60 | sessionAttributes.currentRiddle = RIDDLES.LEVELS[sessionAttributes.currentLevel][sessionAttributes.currentIndex]; 61 | 62 | sessionAttributes.speechText = "Lets play with " 63 | + sessionAttributes.currentLevel + " riddles! " 64 | + " First riddle: " + sessionAttributes.currentRiddle.question; 65 | 66 | handlerInput.attributesManager.setSessionAttributes(sessionAttributes); 67 | 68 | return handlerInput.responseBuilder 69 | .speak(sessionAttributes.speechText) 70 | .reprompt(sessionAttributes.speechText) 71 | .withSimpleCard('Level Up Riddles', sessionAttributes.speechText) 72 | .getResponse(); 73 | } 74 | }; 75 | 76 | const AnswerRiddleIntentHandler = { 77 | canHandle(handlerInput) { 78 | return handlerInput.requestEnvelope.request.type === 'IntentRequest' 79 | && handlerInput.requestEnvelope.request.intent.name === 'AnswerRiddleIntent'; 80 | }, 81 | handle(handlerInput) { 82 | const sessionAttributes = handlerInput.attributesManager.getSessionAttributes(); 83 | 84 | console.log(JSON.stringify(handlerInput.requestEnvelope.request.intent.slots)); 85 | 86 | // Determine if the customer said the correct answer for the current riddle 87 | const spokenAnswer = handlerInput.requestEnvelope.request.intent.slots.answer.value; 88 | sessionAttributes.speechText = ""; 89 | if (spokenAnswer == sessionAttributes.currentRiddle.answer) { 90 | sessionAttributes.speechText += sessionAttributes.currentRiddle.answer + " is correct! You got it right! " 91 | sessionAttributes.correctCount += 1; 92 | sessionAttributes.correct = "Correct! "; 93 | } else { 94 | sessionAttributes.speechText += "Oops, that was wrong. The correct answer is " + sessionAttributes.currentRiddle.answer + ". "; 95 | sessionAttributes.correct = "Incorrect! "; 96 | } 97 | 98 | // Move on to the next question 99 | sessionAttributes.currentIndex += 1; 100 | 101 | // If the customer has gone through all riddles, report the score 102 | if (sessionAttributes.currentIndex == sessionAttributes.totalRids) { 103 | sessionAttributes.speechText += 104 | "You have completed all of the riddles on this level! " 105 | + "Your correct answer count is " 106 | + sessionAttributes.correctCount 107 | + ". To play another level, say easy, medium, or hard. "; 108 | 109 | // Reset variables to start a new game 110 | sessionAttributes.currentLevel = ""; 111 | sessionAttributes.currentRiddle = {}; 112 | sessionAttributes.currentIndex = 0; 113 | sessionAttributes.totalRids = 5; 114 | } else { 115 | sessionAttributes.currentRiddle = RIDDLES.LEVELS[sessionAttributes.currentLevel][sessionAttributes.currentIndex]; 116 | sessionAttributes.speechText += "Next riddle: " + sessionAttributes.currentRiddle.question; 117 | } 118 | 119 | // Reset hint index for the new question 120 | sessionAttributes.currentHintIndex = 0; 121 | 122 | handlerInput.attributesManager.setSessionAttributes(sessionAttributes); 123 | 124 | return handlerInput.responseBuilder 125 | .speak(sessionAttributes.speechText) 126 | .reprompt(sessionAttributes.speechText) 127 | .withSimpleCard('Level Up Riddles', sessionAttributes.speechText) 128 | .getResponse(); 129 | } 130 | }; 131 | 132 | 133 | 134 | const HelpIntentHandler = { 135 | canHandle(handlerInput) { 136 | return handlerInput.requestEnvelope.request.type === 'IntentRequest' 137 | && handlerInput.requestEnvelope.request.intent.name === 'AMAZON.HelpIntent'; 138 | }, 139 | handle(handlerInput) { 140 | const speechText = "I will give you 5 riddles. Would you like to start with easy, medium, or hard riddles?" 141 | 142 | return handlerInput.responseBuilder 143 | .speak(speechText) 144 | .reprompt(speechText) 145 | .withSimpleCard('Level Up Riddles', speechText) 146 | .getResponse(); 147 | } 148 | }; 149 | 150 | const CancelAndStopIntentHandler = { 151 | canHandle(handlerInput) { 152 | return handlerInput.requestEnvelope.request.type === 'IntentRequest' 153 | && (handlerInput.requestEnvelope.request.intent.name === 'AMAZON.CancelIntent' 154 | || handlerInput.requestEnvelope.request.intent.name === 'AMAZON.StopIntent'); 155 | }, 156 | handle(handlerInput) { 157 | const speechText = 'Goodbye!'; 158 | 159 | return handlerInput.responseBuilder 160 | .speak(speechText) 161 | .withSimpleCard('Level Up Riddles', speechText) 162 | .getResponse(); 163 | } 164 | }; 165 | 166 | const SessionEndedRequestHandler = { 167 | canHandle(handlerInput) { 168 | return handlerInput.requestEnvelope.request.type === 'SessionEndedRequest'; 169 | }, 170 | handle(handlerInput) { 171 | console.log(`Session ended with reason: ${handlerInput.requestEnvelope.request.reason}`); 172 | console.log("Error in request " + JSON.stringify(handlerInput.requestEnvelope.request)); 173 | 174 | return handlerInput.responseBuilder.getResponse(); 175 | } 176 | }; 177 | 178 | const ErrorHandler = { 179 | canHandle() { 180 | return true; 181 | }, 182 | handle(handlerInput, error) { 183 | console.log(`Error handled: ${error.message}`); 184 | 185 | return handlerInput.responseBuilder 186 | .speak('Sorry, I can\'t understand the command. Please say again.') 187 | .reprompt('Sorry, I can\'t understand the command. Please say again.') 188 | .getResponse(); 189 | } 190 | }; 191 | 192 | //---------------------------------------------------------------------- 193 | //----------------------------ISP HANDLERS------------------------------ 194 | //---------------------------------------------------------------------- 195 | 196 | const HintIntentHandler = { 197 | canHandle(handlerInput) { 198 | return handlerInput.requestEnvelope.request.type === 'IntentRequest' 199 | && handlerInput.requestEnvelope.request.intent.name === 'HintIntent'; 200 | }, 201 | handle(handlerInput) { 202 | const locale = handlerInput.requestEnvelope.request.locale; 203 | const ms = handlerInput.serviceClientFactory.getMonetizationServiceClient(); 204 | 205 | // Determine if the customer has purchased the hint_pack 206 | return ms.getInSkillProducts(locale).then(function(res) { 207 | var product = res.inSkillProducts.filter(record => record.referenceName == 'hint_pack'); 208 | 209 | if (isEntitled(product)) { 210 | const sessionAttributes = handlerInput.attributesManager.getSessionAttributes(); 211 | const index = sessionAttributes.currentHintIndex; 212 | 213 | // Read all hints the customer has asked for thus far 214 | let speechText = "Okay, here are your hints: "; 215 | let i = 0; 216 | while (i <= index) { 217 | speechText += sessionAttributes.currentRiddle.hints[i] + ", "; 218 | i++; 219 | } 220 | speechText += ". Here is your question again: " 221 | + sessionAttributes.currentRiddle.question; 222 | 223 | // Update the current hint index, maximum of 3 hints per riddle 224 | sessionAttributes.currentHintIndex = index == 2 ? 2 : (index + 1); 225 | handlerInput.attributesManager.setSessionAttributes(sessionAttributes); 226 | 227 | return handlerInput.responseBuilder 228 | .speak(speechText) 229 | .reprompt(sessionAttributes.currentRiddle.question) 230 | .withSimpleCard('Level Up Riddles', speechText) 231 | .getResponse(); 232 | } else { 233 | const upsellMessage = "You don't currently own the hint pack. Want to learn more about it?"; 234 | 235 | return handlerInput.responseBuilder 236 | .addDirective({ 237 | 'type': 'Connections.SendRequest', 238 | 'name': 'Upsell', 239 | 'payload': { 240 | 'InSkillProduct': { 241 | 'productId': product[0].productId 242 | }, 243 | 'upsellMessage': upsellMessage 244 | }, 245 | 'token': 'correlationToken' 246 | }) 247 | .getResponse(); 248 | } 249 | }); 250 | } 251 | }; 252 | 253 | const BuyIntentHandler = { 254 | canHandle(handlerInput) { 255 | return handlerInput.requestEnvelope.request.type === 'IntentRequest' && 256 | handlerInput.requestEnvelope.request.intent.name === 'BuyIntent'; 257 | }, 258 | handle(handlerInput) { 259 | // Inform the user about what products are available for purchase 260 | const locale = handlerInput.requestEnvelope.request.locale; 261 | const ms = handlerInput.serviceClientFactory.getMonetizationServiceClient(); 262 | 263 | return ms.getInSkillProducts(locale).then(function(res) { 264 | let product = res.inSkillProducts.filter(record => record.referenceName == "hint_pack"); 265 | 266 | return handlerInput.responseBuilder 267 | .addDirective({ 268 | 'type': 'Connections.SendRequest', 269 | 'name': 'Buy', 270 | 'payload': { 271 | 'InSkillProduct': { 272 | 'productId': product[0].productId 273 | } 274 | }, 275 | 'token': 'correlationToken' 276 | }) 277 | .getResponse(); 278 | }); 279 | } 280 | }; 281 | 282 | const UpsellResponseHandler = { 283 | canHandle(handlerInput) { 284 | return handlerInput.requestEnvelope.request.type === "Connections.Response" && 285 | handlerInput.requestEnvelope.request.name === "Upsell"; 286 | }, 287 | handle(handlerInput) { 288 | if (handlerInput.requestEnvelope.request.status.code == 200) { 289 | let speechOutput = ""; 290 | let reprompt = ""; 291 | 292 | if (handlerInput.requestEnvelope.request.payload.purchaseResult == 'ACCEPTED') { 293 | speechOutput = "You can now ask for hints in your game! To get a hint, say, i want a hint. "; 294 | reprompt = "Let's play a new game with hints! Would you like to start with easy, medium, or hard riddles?"; 295 | } else if (handlerInput.requestEnvelope.request.payload.purchaseResult == 'DECLINED') { 296 | speechOutput = "Okay. I can't offer you any hints at this time. "; 297 | reprompt = "Let's play a game. Would you like to start with easy, medium, or hard riddles?"; 298 | } 299 | 300 | return handlerInput.responseBuilder 301 | .speak(speechOutput + reprompt) 302 | .reprompt(reprompt) 303 | .getResponse(); 304 | } else { 305 | // Something has failed with the connection. 306 | console.log('Connections.Response indicated failure. error:' + handlerInput.requestEnvelope.request.status.message); 307 | return handlerInput.responseBuilder 308 | .speak("There was an error handling your purchase request. Please try again or contact us for help.") 309 | .getResponse(); 310 | } 311 | } 312 | }; 313 | 314 | const BuyResponseHandler = { 315 | canHandle(handlerInput) { 316 | return handlerInput.requestEnvelope.request.type === "Connections.Response" && 317 | handlerInput.requestEnvelope.request.name === "Buy"; 318 | }, 319 | handle(handlerInput) { 320 | const locale = handlerInput.requestEnvelope.request.locale; 321 | const ms = handlerInput.serviceClientFactory.getMonetizationServiceClient(); 322 | const productId = handlerInput.requestEnvelope.request.payload.productId; 323 | 324 | return ms.getInSkillProducts(locale).then(function(res) { 325 | let product = res.inSkillProducts.filter(record => record.productId == productId); 326 | let speechOutput = ""; 327 | let reprompt = ""; 328 | 329 | if (handlerInput.requestEnvelope.request.status.code == 200) { 330 | if (handlerInput.requestEnvelope.request.payload.purchaseResult == 'ACCEPTED') { 331 | speechOutput = "You can now ask for hints in your game! To get a hint, say, i want a hint. "; 332 | reprompt = "Let's play a new game with hints! Would you like to start with easy, medium, or hard riddles?"; 333 | } else if (handlerInput.requestEnvelope.request.payload.purchaseResult == 'DECLINED') { 334 | speechOutput = "Thanks for your interest in the " + product[0].name + ". "; 335 | reprompt = "Let's play a game. Would you like to start with easy, medium, or hard riddles?"; 336 | } 337 | 338 | return handlerInput.responseBuilder 339 | .speak(speechOutput + reprompt) 340 | .reprompt(reprompt) 341 | .getResponse(); 342 | } else { 343 | // Something has failed with the connection. 344 | console.log('Connections.Response indicated failure. error:' + handlerInput.requestEnvelope.request.status.message); 345 | return handlerInput.responseBuilder 346 | .speak("There was an error handling your purchase request. Please try again or contact us for help.") 347 | .getResponse(); 348 | } 349 | }); 350 | } 351 | }; 352 | 353 | function isProduct(product) { 354 | return product && product.length > 0; 355 | } 356 | 357 | function isEntitled(product) { 358 | return isProduct(product) && product[0].entitled == 'ENTITLED'; 359 | } 360 | 361 | 362 | const skillBuilder = Alexa.SkillBuilders.custom(); 363 | 364 | exports.handler = skillBuilder 365 | .addRequestHandlers( 366 | LaunchRequestHandler, 367 | PlayGameIntentHandler, 368 | AnswerRiddleIntentHandler, 369 | HelpIntentHandler, 370 | HintIntentHandler, 371 | BuyIntentHandler, 372 | UpsellResponseHandler, 373 | BuyResponseHandler, 374 | CancelAndStopIntentHandler, 375 | SessionEndedRequestHandler 376 | ) 377 | .withApiClient(new Alexa.DefaultApiClient()) 378 | .addErrorHandlers(ErrorHandler) 379 | .lambda(); 380 | 381 | 382 | 383 | 384 | -------------------------------------------------------------------------------- /Step 2 - Add ISP/lambda/custom/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hello-world", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "ask-sdk": "^2.0.0", 13 | "ask-sdk-core": "^2.0.0", 14 | "ask-sdk-model": "^1.0.0" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Step 2 - Add ISP/lambda/custom/riddle_objects.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | LEVELS : { 5 | easy : [ 6 | { 7 | question : "I run all day and never walk, I tell you something, but I do not talk!", 8 | answer : "clock", 9 | hints : [ 10 | "I have two hands!", 11 | "You can find me on a table, on a wall, on your wrist, or on your phone!", 12 | "Check on me to see the time of day." 13 | ] 14 | }, 15 | { 16 | question : "When you look at my face it is easy to see, You're looking at you when you're looking at me!", 17 | answer : "mirror", 18 | hints : [ 19 | "I am known for reflection!", 20 | "I usually live above a sink in a bathroom!", 21 | "Wave at yourself when you look at me!" 22 | ] 23 | }, 24 | { 25 | question : "What is full of holes but holds water?", 26 | answer : "sponge", 27 | hints : [ 28 | "Wash dishes with me!", 29 | "I am typically yellow.", 30 | "A famous version of me is square pants" 31 | ] 32 | }, 33 | { 34 | question : "What has four fingers and one thumb but is not alive?", 35 | answer : "glove", 36 | hints : [ 37 | "Not a hand, but warms it up!", 38 | "Wear me in the winter or when gardening.", 39 | "I usually come in a pair" 40 | ] 41 | }, 42 | { 43 | question : "Soft as a petal that falls from a tree, the more I dry the wetter I'll be!", 44 | answer : "towel", 45 | hints : [ 46 | "I am with you at the beach, when you work out, or in your bathroom!", 47 | "See you after a nice swim!", 48 | "Use me when you are done in the shower." 49 | ] 50 | } 51 | ], 52 | medium : [ 53 | { 54 | question : "I can be quick and then I'm deadly, I am a rock, shell and bone medley.", 55 | answer : "sand", 56 | hints : [ 57 | "If I was made into a man, I'd make people dream", 58 | "I gather in my millions by ocean, sea and stream.", 59 | "Use me to sculpt a castle." 60 | ] 61 | }, 62 | { 63 | question : "When I point up it's bright, but when I point down it's dark. What am I?", 64 | answer : "light switch", 65 | hints : [ 66 | "where there is a room with light, there I am", 67 | "I have two states", 68 | "I need power to function" 69 | ] 70 | }, 71 | { 72 | question : "Lighter than what I am made of, More of me is hidden Than is seen.", 73 | answer : "iceberg", 74 | hints : [ 75 | "It is cold in here", 76 | "The titanic should look out", 77 | "You can put pieces of me in a drink to cool down" 78 | ] 79 | }, 80 | { 81 | question : "I am more useful when I am broken. What am I?", 82 | answer : "egg", 83 | hints : [ 84 | "I am very fragile", 85 | "I am a future chicken", 86 | "I go great with bacon" 87 | ] 88 | }, 89 | { 90 | question : "I'm the part of the bird that's not in the sky. I can swim in the ocean and yet remain dry. What am I?", 91 | answer : "shadow", 92 | hints : [ 93 | "Lights off, I disappear", 94 | "Lights on, I appear", 95 | "The sun and a surface are my companions" 96 | ] 97 | } 98 | ], 99 | hard : [ 100 | { 101 | question : "The more you take the more you leave behind?", 102 | answer : "foot steps", 103 | hints : [ 104 | "I am an imprint to where you have been", 105 | "I am the same size and shape", 106 | "You see me on sand, in rain, or when you step in something" 107 | ] 108 | }, 109 | { 110 | question : "What is black when you buy it, red when you use it, and gray when you throw it away?", 111 | answer : "charcoal", 112 | hints : [ 113 | "Got something to grill? You need me", 114 | "You can mine me in minecraft from burnt wood", 115 | "I am the aftermath of a fire" 116 | ] 117 | }, 118 | { 119 | question : "Mountains will crumble and temples will fall, and no man can survive its endless call. What is it?", 120 | answer : "time", 121 | hints : [ 122 | "Check your watch", 123 | "Check your clock", 124 | "No one is immune" 125 | ] 126 | }, 127 | { 128 | question : "I can be cracked, I can be made. I can be told, I can be played. What am I?", 129 | answer : "joke", 130 | hints : [ 131 | "Some people find me funny", 132 | "Some people find me rude", 133 | "Anyone can take part" 134 | ] 135 | }, 136 | { 137 | question : "Lovely and round, I shine with pale light, grown in the darkness, A lady's delight. What am I?", 138 | answer : "pearl", 139 | hints : [ 140 | "I live in a sealed place", 141 | "A clam is my home", 142 | "I am valuable on a neck or wrist" 143 | ] 144 | } 145 | ] 146 | } 147 | }; 148 | 149 | -------------------------------------------------------------------------------- /Step 2 - Add ISP/skill.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest": { 3 | "publishingInformation": { 4 | "locales": { 5 | "en-US": { 6 | "summary": "Sample Short Description", 7 | "examplePhrases": [ 8 | "Alexa open hello world", 9 | "Alexa tell hello world hello", 10 | "Alexa ask hello world say hello" 11 | ], 12 | "name": "Riddle Game Workshop Skill Purchasing", 13 | "description": "Sample Full Description" 14 | } 15 | }, 16 | "isAvailableWorldwide": true, 17 | "testingInstructions": "Sample Testing Instructions.", 18 | "category": "EDUCATION_AND_REFERENCE", 19 | "distributionCountries": [] 20 | }, 21 | "apis": { 22 | "custom": { 23 | "endpoint": { 24 | "sourceDir": "lambda/custom", 25 | "uri": "ask-level-up-riddles" 26 | } 27 | } 28 | }, 29 | "manifestVersion": "1.0" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Step 3 - Add APL/.ask/config: -------------------------------------------------------------------------------- 1 | { 2 | "deploy_settings": { 3 | "default": { 4 | "skill_id": "", 5 | "was_cloned": false, 6 | "merge": { 7 | } 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Step 3 - Add APL/imgs_of_apl_screens/finished_landscape.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexa-labs/skill-sample-nodejs-level-up-riddles/9e35ea27f9223b16e2e352d870c285a8356032e6/Step 3 - Add APL/imgs_of_apl_screens/finished_landscape.png -------------------------------------------------------------------------------- /Step 3 - Add APL/imgs_of_apl_screens/finished_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexa-labs/skill-sample-nodejs-level-up-riddles/9e35ea27f9223b16e2e352d870c285a8356032e6/Step 3 - Add APL/imgs_of_apl_screens/finished_round.png -------------------------------------------------------------------------------- /Step 3 - Add APL/imgs_of_apl_screens/hint_landscape.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexa-labs/skill-sample-nodejs-level-up-riddles/9e35ea27f9223b16e2e352d870c285a8356032e6/Step 3 - Add APL/imgs_of_apl_screens/hint_landscape.png -------------------------------------------------------------------------------- /Step 3 - Add APL/imgs_of_apl_screens/hint_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexa-labs/skill-sample-nodejs-level-up-riddles/9e35ea27f9223b16e2e352d870c285a8356032e6/Step 3 - Add APL/imgs_of_apl_screens/hint_round.png -------------------------------------------------------------------------------- /Step 3 - Add APL/imgs_of_apl_screens/launchrequest_landscape.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexa-labs/skill-sample-nodejs-level-up-riddles/9e35ea27f9223b16e2e352d870c285a8356032e6/Step 3 - Add APL/imgs_of_apl_screens/launchrequest_landscape.png -------------------------------------------------------------------------------- /Step 3 - Add APL/imgs_of_apl_screens/launchrequest_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexa-labs/skill-sample-nodejs-level-up-riddles/9e35ea27f9223b16e2e352d870c285a8356032e6/Step 3 - Add APL/imgs_of_apl_screens/launchrequest_round.png -------------------------------------------------------------------------------- /Step 3 - Add APL/imgs_of_apl_screens/riddle_landscape.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexa-labs/skill-sample-nodejs-level-up-riddles/9e35ea27f9223b16e2e352d870c285a8356032e6/Step 3 - Add APL/imgs_of_apl_screens/riddle_landscape.png -------------------------------------------------------------------------------- /Step 3 - Add APL/imgs_of_apl_screens/riddle_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexa-labs/skill-sample-nodejs-level-up-riddles/9e35ea27f9223b16e2e352d870c285a8356032e6/Step 3 - Add APL/imgs_of_apl_screens/riddle_round.png -------------------------------------------------------------------------------- /Step 3 - Add APL/lambda/custom/finishedgame.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "APL", 3 | "version": "1.0", 4 | "theme": "dark", 5 | "import": [], 6 | "resources": [ 7 | { 8 | "description": "Colors dark to light", 9 | "colors": { 10 | "myBlack": "#343838", 11 | "myPurple": "#9C0A54", 12 | "myRed": "#FC2D47", 13 | "myOrange": "#FD704B", 14 | "myYellow": "#FDB04F", 15 | "myWhite": "#FFFFFF" 16 | } 17 | } 18 | ], 19 | "styles": {}, 20 | "layouts": { 21 | "HomePageButton": { 22 | "parameters": [ 23 | "title", 24 | "colorPrimary", 25 | "colorSecondary" 26 | ], 27 | "items": [ 28 | { 29 | "type": "TouchWrapper", 30 | "width": "40vw", 31 | "height": "20vh", 32 | "item": { 33 | "type": "Container", 34 | "width": "40vw", 35 | "height": "20vh", 36 | "items": [ 37 | { 38 | "type": "Frame", 39 | "width": "40vw", 40 | "height": "20vh", 41 | "backgroundColor": "${colorSecondary}", 42 | "position": "absolute" 43 | }, 44 | { 45 | "type": "Frame", 46 | "width": "39vw", 47 | "height": "18vh", 48 | "backgroundColor": "${colorPrimary}", 49 | "position": "absolute" 50 | }, 51 | { 52 | "type": "Text", 53 | "text": "${title}", 54 | "color": "@myWhite", 55 | "fontWeight": "900", 56 | "fontSize": "6vw", 57 | "width": "39vw", 58 | "height": "18vh", 59 | "textAlign": "center", 60 | "textAlignVertical": "center" 61 | } 62 | ] 63 | }, 64 | "onPress": { 65 | "type": "SendEvent", 66 | "arguments": [ 67 | "${title}" 68 | ] 69 | } 70 | } 71 | ] 72 | } 73 | }, 74 | "mainTemplate": { 75 | "parameters": [ 76 | "payload" 77 | ], 78 | "items": [ 79 | { 80 | "type": "Container", 81 | "width": "100vw", 82 | "height": "100vh", 83 | "items": [ 84 | { 85 | "type": "Frame", 86 | "width": "100vw", 87 | "height": "100vh", 88 | "backgroundColor": "@myBlack", 89 | "position": "absolute" 90 | }, 91 | { 92 | "when": "${viewport.shape == 'round'}", 93 | "type": "Container", 94 | "width": "100vw", 95 | "height": "100vh", 96 | "items": [ 97 | { 98 | "type": "Text", 99 | "text": "You got ${payload.riddleGameData.properties.numCorrect} correct!", 100 | "color": "@myWhite", 101 | "fontWeight": "900", 102 | "width": "100vw", 103 | "fontSize": "7vh", 104 | "paddingTop": "12vh", 105 | "textAlign": "center" 106 | }, 107 | { 108 | "type": "HomePageButton", 109 | "title": "Easy", 110 | "colorPrimary": "@myYellow", 111 | "colorSecondary": "@myOrange", 112 | "position": "absolute", 113 | "top": "28vh", 114 | "left": "30vw" 115 | }, 116 | { 117 | "type": "HomePageButton", 118 | "title": "Medium", 119 | "colorPrimary": "@myOrange", 120 | "colorSecondary": "@myRed", 121 | "position": "absolute", 122 | "top": "51vh", 123 | "left": "30vw" 124 | }, 125 | { 126 | "type": "HomePageButton", 127 | "title": "Hard", 128 | "colorPrimary": "@myRed", 129 | "colorSecondary": "@myPurple", 130 | "position": "absolute", 131 | "top": "74vh", 132 | "left": "30vw" 133 | } 134 | ] 135 | }, 136 | { 137 | "when": "${viewport.shape != 'round'}", 138 | "type": "Container", 139 | "width": "100vw", 140 | "height": "100vh", 141 | "direction": "row", 142 | "items": [ 143 | { 144 | "type": "Container", 145 | "width": "50vw", 146 | "height": "100vh", 147 | "paddingTop": "15vh", 148 | "items": [ 149 | { 150 | "type": "Text", 151 | "text": "You got", 152 | "color": "@myWhite", 153 | "fontWeight": "500", 154 | "width": "50vw", 155 | "fontSize": "7vh", 156 | "textAlign": "center", 157 | "textAlignVertical": "center" 158 | }, 159 | { 160 | "type": "Text", 161 | "text": "${payload.riddleGameData.properties.numCorrect}", 162 | "color": "@myWhite", 163 | "fontWeight": "900", 164 | "width": "50vw", 165 | "fontSize": "35vh", 166 | "textAlign": "center", 167 | "textAlignVertical": "center" 168 | }, 169 | { 170 | "type": "Text", 171 | "text": "correct!", 172 | "color": "@myWhite", 173 | "fontWeight": "500", 174 | "width": "50vw", 175 | "fontSize": "7vh", 176 | "textAlign": "center", 177 | "textAlignVertical": "center" 178 | } 179 | ] 180 | }, 181 | { 182 | "type": "Container", 183 | "width": "50vw", 184 | "height": "100vh", 185 | "items": [ 186 | { 187 | "type": "HomePageButton", 188 | "title": "Easy", 189 | "colorPrimary": "@myYellow", 190 | "colorSecondary": "@myOrange", 191 | "position": "absolute", 192 | "top": "10vh" 193 | }, 194 | { 195 | "type": "HomePageButton", 196 | "title": "Medium", 197 | "colorPrimary": "@myOrange", 198 | "colorSecondary": "@myRed", 199 | "position": "absolute", 200 | "top": "40vh" 201 | }, 202 | { 203 | "type": "HomePageButton", 204 | "title": "Hard", 205 | "colorPrimary": "@myRed", 206 | "colorSecondary": "@myPurple", 207 | "position": "absolute", 208 | "top": "70vh" 209 | } 210 | ] 211 | } 212 | ] 213 | } 214 | ] 215 | } 216 | ] 217 | } 218 | } -------------------------------------------------------------------------------- /Step 3 - Add APL/lambda/custom/hint.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "APL", 3 | "version": "1.0", 4 | "theme": "dark", 5 | "import": [], 6 | "resources": [ 7 | { 8 | "description": "Colors dark to light", 9 | "colors": { 10 | "myBlack": "#343838", 11 | "myPurple": "#9C0A54", 12 | "myRed": "#FC2D47", 13 | "myOrange": "#FD704B", 14 | "myYellow": "#FDB04F", 15 | "myWhite": "#FFFFFF" 16 | } 17 | } 18 | ], 19 | "styles": {}, 20 | "layouts": {}, 21 | "mainTemplate": { 22 | "parameters": [ 23 | "payload" 24 | ], 25 | "items": [ 26 | { 27 | "type": "Container", 28 | "width": "100vw", 29 | "height": "100vh", 30 | "items": [ 31 | { 32 | "type": "Frame", 33 | "width": "100vw", 34 | "height": "100vh", 35 | "backgroundColor": "@myBlack", 36 | "position": "absolute" 37 | }, 38 | { 39 | "type": "Text", 40 | "text": "${payload.riddleGameData.properties.currentLevel}", 41 | "width": "100vw", 42 | "color": "@myWhite", 43 | "fontWeight": "900", 44 | "fontSize": "5vw", 45 | "paddingTop": "5vh", 46 | "textAlign": "center" 47 | }, 48 | { 49 | "type": "Text", 50 | "text": "Riddle ${payload.riddleGameData.properties.currentQuestionNumber} Hint:", 51 | "width": "100vw", 52 | "color": "@myWhite", 53 | "fontWeight": "300", 54 | "fontSize": "8vw", 55 | "textAlign": "center" 56 | }, 57 | { 58 | "type": "Text", 59 | "text": "${payload.riddleGameData.properties.currentHint}", 60 | "width": "100vw", 61 | "color": "@myWhite", 62 | "fontWeight": "300", 63 | "fontSize": "5vw", 64 | "paddingTop": "5vh", 65 | "paddingLeft": "5vh", 66 | "paddingRight": "5vh", 67 | "textAlign": "center" 68 | }, 69 | { 70 | "when": "${payload.riddleGameData.properties.currentLevel == 'easy'}", 71 | "type": "Frame", 72 | "width": "100vw", 73 | "height": "20vh", 74 | "backgroundColor": "@myYellow", 75 | "position": "absolute", 76 | "top": "80vh" 77 | }, 78 | { 79 | "when": "${payload.riddleGameData.properties.currentLevel == 'medium'}", 80 | "type": "Frame", 81 | "width": "100vw", 82 | "height": "20vh", 83 | "backgroundColor": "@myOrange", 84 | "position": "absolute", 85 | "top": "80vh" 86 | }, 87 | { 88 | "when": "${payload.riddleGameData.properties.currentLevel == 'hard'}", 89 | "type": "Frame", 90 | "width": "100vw", 91 | "height": "20vh", 92 | "backgroundColor": "@myRed", 93 | "position": "absolute", 94 | "top": "80vh" 95 | }, 96 | { 97 | "type": "Text", 98 | "text": "Score: ${payload.riddleGameData.properties.numCorrect} correct", 99 | "width": "100vw", 100 | "fontWeight": "900", 101 | "fontSize": "5vw", 102 | "textAlign": "center", 103 | "position": "absolute", 104 | "top": "85vh" 105 | } 106 | ] 107 | } 108 | ] 109 | } 110 | } -------------------------------------------------------------------------------- /Step 3 - Add APL/lambda/custom/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable func-names */ 2 | /* eslint-disable no-console */ 3 | 4 | const Alexa = require('ask-sdk-core'); 5 | const RIDDLES = require("./riddle_objects"); 6 | 7 | const LaunchRequestHandler = { 8 | canHandle(handlerInput) { 9 | return handlerInput.requestEnvelope.request.type === 'LaunchRequest'; 10 | }, 11 | handle(handlerInput) { 12 | const speechText = "Welcome to Level Up Riddles! Before we get started, I need a few pieces of information." 13 | + " What is your name and favorite color?" 14 | + " How many riddles would you like to play with?" 15 | + " Would you like to start with easy, medium, or hard riddles?"; 16 | 17 | if (supportsAPL(handlerInput)) { 18 | handlerInput.responseBuilder 19 | .addDirective({ 20 | type: 'Alexa.Presentation.APL.RenderDocument', 21 | document: require('./launchrequest.json') 22 | }); 23 | } 24 | return handlerInput.responseBuilder 25 | .speak(speechText) 26 | .reprompt(speechText) 27 | .withSimpleCard('Level Up Riddles', speechText) 28 | .getResponse(); 29 | }, 30 | }; 31 | 32 | const PlayGameIntentHandler = { 33 | canHandle(handlerInput) { 34 | return handlerInput.requestEnvelope.request.type === 'IntentRequest' 35 | && handlerInput.requestEnvelope.request.intent.name === 'PlayGameIntent' 36 | || handlerInput.requestEnvelope.request.type === 'Alexa.Presentation.APL.UserEvent'; 37 | }, 38 | handle(handlerInput) { 39 | const sessionAttributes = handlerInput.attributesManager.getSessionAttributes(); 40 | const request = handlerInput.requestEnvelope.request; 41 | 42 | // Get the level the customer selected: 'easy', 'med', 'hard' 43 | if (request.type === 'Alexa.Presentation.APL.UserEvent') { 44 | if (request.arguments[0] === 'Easy') { 45 | sessionAttributes.currentLevel = "easy" 46 | } else if (request.arguments[0] === 'Medium') { 47 | sessionAttributes.currentLevel = "medium" 48 | } else { 49 | sessionAttributes.currentLevel = "hard" 50 | } 51 | 52 | sessionAttributes.totalRids = 5; 53 | } else { 54 | sessionAttributes.currentLevel = request.intent.slots.level.value; 55 | 56 | // Store the slot values for name and color 57 | if (request.intent.slots.name.value) { 58 | sessionAttributes.name = request.intent.slots.name.value; 59 | } 60 | 61 | if (request.intent.slots.color.value) { 62 | sessionAttributes.color = request.intent.slots.color.value; 63 | } 64 | 65 | // Check if the slot value for riddleNum is filled and <5, otherwise default to 5 66 | const riddleNum = request.intent.slots.riddleNum.value; 67 | if (riddleNum) { 68 | sessionAttributes.totalRids = riddleNum <= 5 ? riddleNum : 5; 69 | } else { 70 | sessionAttributes.totalRids = 5; 71 | } 72 | } 73 | 74 | // Reset variables to 0 to start the new game 75 | sessionAttributes.correctCount = 0; 76 | sessionAttributes.currentIndex = 0; 77 | sessionAttributes.currentHintIndex = 0; 78 | 79 | // Get the first riddle according to that level 80 | sessionAttributes.currentRiddle = RIDDLES.LEVELS[sessionAttributes.currentLevel][sessionAttributes.currentIndex]; 81 | 82 | sessionAttributes.speechText = "Lets play with " 83 | + sessionAttributes.currentLevel + " riddles! " 84 | + " First riddle: "; 85 | 86 | handlerInput.attributesManager.setSessionAttributes(sessionAttributes); 87 | 88 | if (supportsAPL(handlerInput)) { 89 | let data = createDatasource.call(this, sessionAttributes); 90 | handlerInput.responseBuilder 91 | .addDirective({ 92 | type: 'Alexa.Presentation.APL.RenderDocument', 93 | token: 'riddleToken', 94 | document: require('./riddle.json'), 95 | 'datasources': data 96 | }) 97 | .addDirective({ 98 | type: 'Alexa.Presentation.APL.ExecuteCommands', 99 | token: 'riddleToken', 100 | commands: [ 101 | { 102 | type: 'SpeakItem', 103 | componentId: 'riddleComp', 104 | highlightMode: 'line' 105 | } 106 | ] 107 | }); 108 | } else { 109 | sessionAttributes.speechText += sessionAttributes.currentRiddle.question; 110 | } 111 | 112 | return handlerInput.responseBuilder 113 | .speak(sessionAttributes.speechText) 114 | .reprompt(sessionAttributes.speechText + sessionAttributes.currentRiddle.question) 115 | .withSimpleCard('Level Up Riddles', sessionAttributes.speechText) 116 | .getResponse(); 117 | } 118 | }; 119 | 120 | const AnswerRiddleIntentHandler = { 121 | canHandle(handlerInput) { 122 | return handlerInput.requestEnvelope.request.type === 'IntentRequest' 123 | && handlerInput.requestEnvelope.request.intent.name === 'AnswerRiddleIntent'; 124 | }, 125 | handle(handlerInput) { 126 | const sessionAttributes = handlerInput.attributesManager.getSessionAttributes(); 127 | 128 | console.log(JSON.stringify(handlerInput.requestEnvelope.request.intent.slots)); 129 | 130 | // Determine if the customer said the correct answer for the current riddle 131 | const spokenAnswer = handlerInput.requestEnvelope.request.intent.slots.answer.value; 132 | sessionAttributes.speechText = ""; 133 | if (spokenAnswer == sessionAttributes.currentRiddle.answer) { 134 | sessionAttributes.speechText += sessionAttributes.currentRiddle.answer + " is correct! You got it right! " 135 | sessionAttributes.correctCount += 1; 136 | sessionAttributes.correct = "Correct! "; 137 | } else { 138 | sessionAttributes.speechText += "Oops, that was wrong. The correct answer is " + sessionAttributes.currentRiddle.answer + ". "; 139 | sessionAttributes.correct = "Incorrect! "; 140 | } 141 | 142 | // Move on to the next question 143 | sessionAttributes.currentIndex += 1; 144 | 145 | // If the customer has gone through all riddles, report the score 146 | if (sessionAttributes.currentIndex == sessionAttributes.totalRids) { 147 | sessionAttributes.speechText += 148 | "You have completed all of the riddles on this level! " 149 | + "Your correct answer count is " 150 | + sessionAttributes.correctCount 151 | + ". To play another level, say easy, medium, or hard. "; 152 | 153 | if (supportsAPL(handlerInput)) { 154 | let data = createDatasource.call(this, sessionAttributes); 155 | handlerInput.responseBuilder 156 | .addDirective({ 157 | type: 'Alexa.Presentation.APL.RenderDocument', 158 | document: require('./finishedgame.json'), 159 | 'datasources': data 160 | }); 161 | } 162 | 163 | // Reset variables to start a new game 164 | sessionAttributes.currentLevel = ""; 165 | sessionAttributes.currentRiddle = {}; 166 | sessionAttributes.currentIndex = 0; 167 | sessionAttributes.totalRids = 5; 168 | } else { 169 | sessionAttributes.currentRiddle = RIDDLES.LEVELS[sessionAttributes.currentLevel][sessionAttributes.currentIndex]; 170 | sessionAttributes.speechText += "Next riddle: "; 171 | 172 | if (supportsAPL(handlerInput)) { 173 | let data = createDatasource.call(this, sessionAttributes); 174 | 175 | handlerInput.responseBuilder 176 | .addDirective({ 177 | type: 'Alexa.Presentation.APL.RenderDocument', 178 | token: 'riddleToken', 179 | document: require('./riddle.json'), 180 | 'datasources': data 181 | }) 182 | .addDirective({ 183 | type: 'Alexa.Presentation.APL.ExecuteCommands', 184 | token: 'riddleToken', 185 | commands: [ 186 | { 187 | type: 'SpeakItem', 188 | componentId: 'riddleComp', 189 | highlightMode: 'line' 190 | } 191 | ] 192 | }); 193 | } else { 194 | sessionAttributes.speechText += sessionAttributes.currentRiddle.question; 195 | } 196 | } 197 | 198 | // Reset hint index for the new question 199 | sessionAttributes.currentHintIndex = 0; 200 | 201 | handlerInput.attributesManager.setSessionAttributes(sessionAttributes); 202 | 203 | return handlerInput.responseBuilder 204 | .speak(sessionAttributes.speechText) 205 | .reprompt(sessionAttributes.speechText) 206 | .withSimpleCard('Level Up Riddles', sessionAttributes.speechText) 207 | .getResponse(); 208 | } 209 | }; 210 | 211 | const HelpIntentHandler = { 212 | canHandle(handlerInput) { 213 | return handlerInput.requestEnvelope.request.type === 'IntentRequest' 214 | && handlerInput.requestEnvelope.request.intent.name === 'AMAZON.HelpIntent'; 215 | }, 216 | handle(handlerInput) { 217 | const speechText = "I will give you 5 riddles. Would you like to start with easy, medium, or hard riddles?" 218 | 219 | return handlerInput.responseBuilder 220 | .speak(speechText) 221 | .reprompt(speechText) 222 | .withSimpleCard('Level Up Riddles', speechText) 223 | .getResponse(); 224 | } 225 | }; 226 | 227 | const CancelAndStopIntentHandler = { 228 | canHandle(handlerInput) { 229 | return handlerInput.requestEnvelope.request.type === 'IntentRequest' 230 | && (handlerInput.requestEnvelope.request.intent.name === 'AMAZON.CancelIntent' 231 | || handlerInput.requestEnvelope.request.intent.name === 'AMAZON.StopIntent'); 232 | }, 233 | handle(handlerInput) { 234 | const speechText = 'Goodbye!'; 235 | 236 | return handlerInput.responseBuilder 237 | .speak(speechText) 238 | .withSimpleCard('Level Up Riddles', speechText) 239 | .getResponse(); 240 | } 241 | }; 242 | 243 | const SessionEndedRequestHandler = { 244 | canHandle(handlerInput) { 245 | return handlerInput.requestEnvelope.request.type === 'SessionEndedRequest'; 246 | }, 247 | handle(handlerInput) { 248 | console.log(`Session ended with reason: ${handlerInput.requestEnvelope.request.reason}`); 249 | console.log("Error in request " + JSON.stringify(handlerInput.requestEnvelope.request)); 250 | 251 | return handlerInput.responseBuilder.getResponse(); 252 | } 253 | }; 254 | 255 | const ErrorHandler = { 256 | canHandle() { 257 | return true; 258 | }, 259 | handle(handlerInput, error) { 260 | console.log(`Error handled: ${error.message}`); 261 | 262 | return handlerInput.responseBuilder 263 | .speak('Sorry, I can\'t understand the command. Please say again.') 264 | .reprompt('Sorry, I can\'t understand the command. Please say again.') 265 | .getResponse(); 266 | } 267 | }; 268 | 269 | //---------------------------------------------------------------------- 270 | //----------------------------ISP HANDLERS------------------------------ 271 | //---------------------------------------------------------------------- 272 | 273 | const HintIntentHandler = { 274 | canHandle(handlerInput) { 275 | return handlerInput.requestEnvelope.request.type === 'IntentRequest' 276 | && handlerInput.requestEnvelope.request.intent.name === 'HintIntent'; 277 | }, 278 | handle(handlerInput) { 279 | const locale = handlerInput.requestEnvelope.request.locale; 280 | const ms = handlerInput.serviceClientFactory.getMonetizationServiceClient(); 281 | 282 | // Determine if the customer has purchased the hint_pack 283 | return ms.getInSkillProducts(locale).then(function(res) { 284 | var product = res.inSkillProducts.filter(record => record.referenceName == 'hint_pack'); 285 | 286 | if (isEntitled(product)) { 287 | const sessionAttributes = handlerInput.attributesManager.getSessionAttributes(); 288 | const index = sessionAttributes.currentHintIndex; 289 | 290 | // Read all hints the customer has asked for thus far 291 | let speechText = "Okay, here are your hints: "; 292 | let i = 0; 293 | while (i <= index) { 294 | speechText += sessionAttributes.currentRiddle.hints[i] + ", "; 295 | i++; 296 | } 297 | speechText += ". Here is your question again: " 298 | + sessionAttributes.currentRiddle.question; 299 | 300 | // Update the current hint index, maximum of 3 hints per riddle 301 | sessionAttributes.currentHintIndex = index == 2 ? 2 : (index + 1); 302 | handlerInput.attributesManager.setSessionAttributes(sessionAttributes); 303 | 304 | if (supportsAPL(handlerInput)) { 305 | let data = createDatasource.call(this, sessionAttributes); 306 | 307 | handlerInput.responseBuilder 308 | .addDirective({ 309 | type: 'Alexa.Presentation.APL.RenderDocument', 310 | token: 'hintToken', 311 | document: require('./hint.json'), 312 | 'datasources': data 313 | }); 314 | } 315 | 316 | return handlerInput.responseBuilder 317 | .speak(speechText) 318 | .reprompt(sessionAttributes.currentRiddle.question) 319 | .withSimpleCard('Level Up Riddles', speechText) 320 | .getResponse(); 321 | } else { 322 | const upsellMessage = "You don't currently own the hint pack. Want to learn more about it?"; 323 | 324 | return handlerInput.responseBuilder 325 | .addDirective({ 326 | 'type': 'Connections.SendRequest', 327 | 'name': 'Upsell', 328 | 'payload': { 329 | 'InSkillProduct': { 330 | 'productId': product[0].productId 331 | }, 332 | 'upsellMessage': upsellMessage 333 | }, 334 | 'token': 'correlationToken' 335 | }) 336 | .getResponse(); 337 | } 338 | }); 339 | } 340 | }; 341 | 342 | const BuyIntentHandler = { 343 | canHandle(handlerInput) { 344 | return handlerInput.requestEnvelope.request.type === 'IntentRequest' && 345 | handlerInput.requestEnvelope.request.intent.name === 'BuyIntent'; 346 | }, 347 | handle(handlerInput) { 348 | // Inform the user about what products are available for purchase 349 | const locale = handlerInput.requestEnvelope.request.locale; 350 | const ms = handlerInput.serviceClientFactory.getMonetizationServiceClient(); 351 | 352 | return ms.getInSkillProducts(locale).then(function(res) { 353 | let product = res.inSkillProducts.filter(record => record.referenceName == "hint_pack"); 354 | 355 | return handlerInput.responseBuilder 356 | .addDirective({ 357 | 'type': 'Connections.SendRequest', 358 | 'name': 'Buy', 359 | 'payload': { 360 | 'InSkillProduct': { 361 | 'productId': product[0].productId 362 | } 363 | }, 364 | 'token': 'correlationToken' 365 | }) 366 | .getResponse(); 367 | }); 368 | } 369 | }; 370 | 371 | const UpsellResponseHandler = { 372 | canHandle(handlerInput) { 373 | return handlerInput.requestEnvelope.request.type === "Connections.Response" && 374 | handlerInput.requestEnvelope.request.name === "Upsell"; 375 | }, 376 | handle(handlerInput) { 377 | if (handlerInput.requestEnvelope.request.status.code == 200) { 378 | let speechOutput = ""; 379 | let reprompt = ""; 380 | 381 | if (handlerInput.requestEnvelope.request.payload.purchaseResult == 'ACCEPTED') { 382 | speechOutput = "You can now ask for hints in your game! To get a hint, say, i want a hint. "; 383 | reprompt = "Let's play a new game with hints! Would you like to start with easy, medium, or hard riddles?"; 384 | } else if (handlerInput.requestEnvelope.request.payload.purchaseResult == 'DECLINED') { 385 | speechOutput = "Okay. I can't offer you any hints at this time. "; 386 | reprompt = "Let's play a game. Would you like to start with easy, medium, or hard riddles?"; 387 | } 388 | 389 | return handlerInput.responseBuilder 390 | .speak(speechOutput + reprompt) 391 | .reprompt(reprompt) 392 | .getResponse(); 393 | } else { 394 | // Something has failed with the connection. 395 | console.log('Connections.Response indicated failure. error:' + handlerInput.requestEnvelope.request.status.message); 396 | return handlerInput.responseBuilder 397 | .speak("There was an error handling your purchase request. Please try again or contact us for help.") 398 | .getResponse(); 399 | } 400 | } 401 | }; 402 | 403 | const BuyResponseHandler = { 404 | canHandle(handlerInput) { 405 | return handlerInput.requestEnvelope.request.type === "Connections.Response" && 406 | handlerInput.requestEnvelope.request.name === "Buy"; 407 | }, 408 | handle(handlerInput) { 409 | const locale = handlerInput.requestEnvelope.request.locale; 410 | const ms = handlerInput.serviceClientFactory.getMonetizationServiceClient(); 411 | const productId = handlerInput.requestEnvelope.request.payload.productId; 412 | 413 | return ms.getInSkillProducts(locale).then(function(res) { 414 | let product = res.inSkillProducts.filter(record => record.productId == productId); 415 | let speechOutput = ""; 416 | let reprompt = ""; 417 | 418 | if (handlerInput.requestEnvelope.request.status.code == 200) { 419 | if (handlerInput.requestEnvelope.request.payload.purchaseResult == 'ACCEPTED') { 420 | speechOutput = "You can now ask for hints in your game! To get a hint, say, i want a hint. "; 421 | reprompt = "Let's play a new game with hints! Would you like to start with easy, medium, or hard riddles?"; 422 | } else if (handlerInput.requestEnvelope.request.payload.purchaseResult == 'DECLINED') { 423 | speechOutput = "Thanks for your interest in the " + product[0].name + ". "; 424 | reprompt = "Let's play a game. Would you like to start with easy, medium, or hard riddles?"; 425 | } 426 | 427 | return handlerInput.responseBuilder 428 | .speak(speechOutput + reprompt) 429 | .reprompt(reprompt) 430 | .getResponse(); 431 | } else { 432 | // Something has failed with the connection. 433 | console.log('Connections.Response indicated failure. error:' + handlerInput.requestEnvelope.request.status.message); 434 | return handlerInput.responseBuilder 435 | .speak("There was an error handling your purchase request. Please try again or contact us for help.") 436 | .getResponse(); 437 | } 438 | }); 439 | } 440 | }; 441 | 442 | function isProduct(product) { 443 | return product && product.length > 0; 444 | } 445 | 446 | function isEntitled(product) { 447 | return isProduct(product) && product[0].entitled == 'ENTITLED'; 448 | } 449 | 450 | //---------------------------------------------------------------------- 451 | //-----------------------------APL HELPER------------------------------- 452 | //---------------------------------------------------------------------- 453 | 454 | function supportsAPL(handlerInput) { 455 | const supportedInterfaces = 456 | handlerInput.requestEnvelope.context.System.device.supportedInterfaces; 457 | const aplInterface = supportedInterfaces['Alexa.Presentation.APL']; 458 | return aplInterface != null && aplInterface != undefined; 459 | } 460 | 461 | function createDatasource(attributes) { 462 | return { 463 | "riddleGameData": { 464 | "properties": { 465 | "currentQuestionSsml": "" 466 | + attributes.currentRiddle.question 467 | + "", 468 | "currentLevel": attributes.currentLevel, 469 | "currentQuestionNumber": (attributes.currentIndex + 1), 470 | "numCorrect": attributes.correctCount, 471 | "currentHint": attributes.currentRiddle.hints[attributes.currentHintIndex] 472 | }, 473 | "transformers": [ 474 | { 475 | "inputPath": "currentQuestionSsml", 476 | "outputName": "currentQuestionSpeech", 477 | "transformer": "ssmlToSpeech" 478 | }, 479 | { 480 | "inputPath": "currentQuestionSsml", 481 | "outputName": "currentQuestionText", 482 | "transformer": "ssmlToText" 483 | } 484 | ] 485 | } 486 | }; 487 | } 488 | 489 | 490 | const skillBuilder = Alexa.SkillBuilders.custom(); 491 | 492 | exports.handler = skillBuilder 493 | .addRequestHandlers( 494 | LaunchRequestHandler, 495 | PlayGameIntentHandler, 496 | AnswerRiddleIntentHandler, 497 | HelpIntentHandler, 498 | HintIntentHandler, 499 | BuyIntentHandler, 500 | UpsellResponseHandler, 501 | BuyResponseHandler, 502 | CancelAndStopIntentHandler, 503 | SessionEndedRequestHandler 504 | ) 505 | .withApiClient(new Alexa.DefaultApiClient()) 506 | .addErrorHandlers(ErrorHandler) 507 | .lambda(); 508 | 509 | 510 | 511 | 512 | -------------------------------------------------------------------------------- /Step 3 - Add APL/lambda/custom/launchrequest.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "APL", 3 | "version": "1.0", 4 | "theme": "dark", 5 | "import": [], 6 | "resources": [ 7 | { 8 | "description": "Colors dark to light", 9 | "colors": { 10 | "myBlack": "#343838", 11 | "myPurple": "#9C0A54", 12 | "myRed": "#FC2D47", 13 | "myOrange": "#FD704B", 14 | "myYellow": "#FDB04F", 15 | "myWhite": "#FFFFFF" 16 | } 17 | } 18 | ], 19 | "styles": {}, 20 | "layouts": { 21 | "HomePageButton": { 22 | "parameters": [ 23 | "title", 24 | "colorPrimary", 25 | "colorSecondary" 26 | ], 27 | "items": [ 28 | { 29 | "type": "TouchWrapper", 30 | "width": "40vw", 31 | "height": "20vh", 32 | "item": { 33 | "type": "Container", 34 | "width": "40vw", 35 | "height": "20vh", 36 | "items": [ 37 | { 38 | "type": "Frame", 39 | "width": "40vw", 40 | "height": "20vh", 41 | "backgroundColor": "${colorSecondary}", 42 | "position": "absolute" 43 | }, 44 | { 45 | "type": "Frame", 46 | "width": "39vw", 47 | "height": "18vh", 48 | "backgroundColor": "${colorPrimary}", 49 | "position": "absolute" 50 | }, 51 | { 52 | "type": "Text", 53 | "text": "${title}", 54 | "color": "@myWhite", 55 | "fontWeight": "900", 56 | "fontSize": "6vw", 57 | "width": "39vw", 58 | "height": "18vh", 59 | "textAlign": "center", 60 | "textAlignVertical": "center" 61 | } 62 | ] 63 | }, 64 | "onPress": { 65 | "type": "SendEvent", 66 | "arguments": [ 67 | "${title}" 68 | ] 69 | } 70 | } 71 | ] 72 | } 73 | }, 74 | "mainTemplate": { 75 | "parameters": [ 76 | "payload" 77 | ], 78 | "items": [ 79 | { 80 | "type": "Container", 81 | "width": "100vw", 82 | "height": "100vh", 83 | "items": [ 84 | { 85 | "type": "Frame", 86 | "width": "100vw", 87 | "height": "100vh", 88 | "backgroundColor": "@myBlack", 89 | "position": "absolute" 90 | }, 91 | { 92 | "when": "${viewport.shape == 'round'}", 93 | "type": "Container", 94 | "width": "100vw", 95 | "height": "100vh", 96 | "items": [ 97 | { 98 | "type": "Text", 99 | "text": "Riddle Game", 100 | "color": "@myWhite", 101 | "fontWeight": "900", 102 | "width": "100vw", 103 | "fontSize": "10vh", 104 | "paddingTop": "12vh", 105 | "textAlign": "center" 106 | }, 107 | { 108 | "type": "HomePageButton", 109 | "title": "Easy", 110 | "colorPrimary": "@myYellow", 111 | "colorSecondary": "@myOrange", 112 | "position": "absolute", 113 | "top": "28vh", 114 | "left": "30vw" 115 | }, 116 | { 117 | "type": "HomePageButton", 118 | "title": "Medium", 119 | "colorPrimary": "@myOrange", 120 | "colorSecondary": "@myRed", 121 | "position": "absolute", 122 | "top": "51vh", 123 | "left": "30vw" 124 | }, 125 | { 126 | "type": "HomePageButton", 127 | "title": "Hard", 128 | "colorPrimary": "@myRed", 129 | "colorSecondary": "@myPurple", 130 | "position": "absolute", 131 | "top": "74vh", 132 | "left": "30vw" 133 | } 134 | ] 135 | }, 136 | { 137 | "when": "${viewport.shape != 'round'}", 138 | "type": "Container", 139 | "width": "100vw", 140 | "height": "100vh", 141 | "direction": "row", 142 | "items": [ 143 | { 144 | "type": "Text", 145 | "text": "Riddle Game", 146 | "color": "@myWhite", 147 | "fontWeight": "900", 148 | "width": "50vw", 149 | "height": "100vh", 150 | "fontSize": "20vh", 151 | "paddingLeft": "5vw", 152 | "textAlignVertical": "center" 153 | }, 154 | { 155 | "type": "Container", 156 | "width": "50vw", 157 | "height": "100vh", 158 | "items": [ 159 | { 160 | "type": "HomePageButton", 161 | "title": "Easy", 162 | "colorPrimary": "@myYellow", 163 | "colorSecondary": "@myOrange", 164 | "position": "absolute", 165 | "top": "10vh" 166 | }, 167 | { 168 | "type": "HomePageButton", 169 | "title": "Medium", 170 | "colorPrimary": "@myOrange", 171 | "colorSecondary": "@myRed", 172 | "position": "absolute", 173 | "top": "40vh" 174 | }, 175 | { 176 | "type": "HomePageButton", 177 | "title": "Hard", 178 | "colorPrimary": "@myRed", 179 | "colorSecondary": "@myPurple", 180 | "position": "absolute", 181 | "top": "70vh" 182 | } 183 | ] 184 | } 185 | ] 186 | } 187 | ] 188 | } 189 | ] 190 | } 191 | } -------------------------------------------------------------------------------- /Step 3 - Add APL/lambda/custom/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hello-world", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "ask-sdk": "^2.0.0", 13 | "ask-sdk-core": "^2.0.0", 14 | "ask-sdk-model": "^1.0.0" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Step 3 - Add APL/lambda/custom/riddle.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "APL", 3 | "version": "1.0", 4 | "theme": "dark", 5 | "import": [], 6 | "resources": [ 7 | { 8 | "description": "Colors dark to light", 9 | "colors": { 10 | "myBlack": "#343838", 11 | "myPurple": "#9C0A54", 12 | "myRed": "#FC2D47", 13 | "myOrange": "#FD704B", 14 | "myYellow": "#FDB04F", 15 | "myWhite": "#FFFFFF" 16 | } 17 | } 18 | ], 19 | "styles": {}, 20 | "layouts": {}, 21 | "mainTemplate": { 22 | "parameters": [ 23 | "payload" 24 | ], 25 | "items": [ 26 | { 27 | "type": "Container", 28 | "width": "100vw", 29 | "height": "100vh", 30 | "items": [ 31 | { 32 | "when": "${payload.riddleGameData.properties.currentLevel == 'easy'}", 33 | "type": "Frame", 34 | "width": "100vw", 35 | "height": "100vh", 36 | "backgroundColor": "@myYellow", 37 | "position": "absolute" 38 | }, 39 | { 40 | "when": "${payload.riddleGameData.properties.currentLevel == 'medium'}", 41 | "type": "Frame", 42 | "width": "100vw", 43 | "height": "100vh", 44 | "backgroundColor": "@myOrange", 45 | "position": "absolute" 46 | }, 47 | { 48 | "when": "${payload.riddleGameData.properties.currentLevel == 'hard'}", 49 | "type": "Frame", 50 | "width": "100vw", 51 | "height": "100vh", 52 | "backgroundColor": "@myRed", 53 | "position": "absolute" 54 | }, 55 | { 56 | "type": "Text", 57 | "text": "${payload.riddleGameData.properties.currentLevel}", 58 | "width": "100vw", 59 | "color": "@myWhite", 60 | "fontWeight": "900", 61 | "fontSize": "5vw", 62 | "paddingTop": "5vh", 63 | "textAlign": "center" 64 | }, 65 | { 66 | "type": "Text", 67 | "text": "Riddle ${payload.riddleGameData.properties.currentQuestionNumber}:", 68 | "width": "100vw", 69 | "color": "@myWhite", 70 | "fontWeight": "300", 71 | "fontSize": "8vw", 72 | "textAlign": "center" 73 | }, 74 | { 75 | "type": "Text", 76 | "id": "riddleComp", 77 | "text": "${payload.riddleGameData.properties.currentQuestionText}", 78 | "speech": "${payload.riddleGameData.properties.currentQuestionSpeech}", 79 | "width": "100vw", 80 | "color": "@myWhite", 81 | "fontWeight": "300", 82 | "fontSize": "5.5vw", 83 | "paddingTop": "5vh", 84 | "paddingLeft": "5vh", 85 | "paddingRight": "5vh", 86 | "textAlign": "center" 87 | }, 88 | { 89 | "when": "${payload.riddleGameData.properties.currentLevel == 'easy'}", 90 | "type": "Frame", 91 | "width": "100vw", 92 | "height": "20vh", 93 | "backgroundColor": "@myOrange", 94 | "position": "absolute", 95 | "top": "80vh" 96 | }, 97 | { 98 | "when": "${payload.riddleGameData.properties.currentLevel == 'medium'}", 99 | "type": "Frame", 100 | "width": "100vw", 101 | "height": "20vh", 102 | "backgroundColor": "@myRed", 103 | "position": "absolute", 104 | "top": "80vh" 105 | }, 106 | { 107 | "when": "${payload.riddleGameData.properties.currentLevel == 'hard'}", 108 | "type": "Frame", 109 | "width": "100vw", 110 | "height": "20vh", 111 | "backgroundColor": "@myPurple", 112 | "position": "absolute", 113 | "top": "80vh" 114 | }, 115 | { 116 | "type": "Text", 117 | "text": "Score: ${payload.riddleGameData.properties.numCorrect} correct", 118 | "width": "100vw", 119 | "color": "@myWhite", 120 | "fontWeight": "900", 121 | "fontSize": "5vw", 122 | "textAlign": "center", 123 | "position": "absolute", 124 | "top": "85vh" 125 | } 126 | ] 127 | } 128 | ] 129 | } 130 | } -------------------------------------------------------------------------------- /Step 3 - Add APL/lambda/custom/riddle_objects.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | LEVELS : { 5 | easy : [ 6 | { 7 | question : "I run all day and never walk, I tell you something, but I do not talk!", 8 | answer : "clock", 9 | hints : [ 10 | "I have two hands!", 11 | "You can find me on a table, on a wall, on your wrist, or on your phone!", 12 | "Check on me to see the time of day." 13 | ] 14 | }, 15 | { 16 | question : "When you look at my face it is easy to see, You're looking at you when you're looking at me!", 17 | answer : "mirror", 18 | hints : [ 19 | "I am known for reflection!", 20 | "I usually live above a sink in a bathroom!", 21 | "Wave at yourself when you look at me!" 22 | ] 23 | }, 24 | { 25 | question : "What is full of holes but holds water?", 26 | answer : "sponge", 27 | hints : [ 28 | "Wash dishes with me!", 29 | "I am typically yellow.", 30 | "A famous version of me is square pants" 31 | ] 32 | }, 33 | { 34 | question : "What has four fingers and one thumb but is not alive?", 35 | answer : "glove", 36 | hints : [ 37 | "Not a hand, but warms it up!", 38 | "Wear me in the winter or when gardening.", 39 | "I usually come in a pair" 40 | ] 41 | }, 42 | { 43 | question : "Soft as a petal that falls from a tree, the more I dry the wetter I'll be!", 44 | answer : "towel", 45 | hints : [ 46 | "I am with you at the beach, when you work out, or in your bathroom!", 47 | "See you after a nice swim!", 48 | "Use me when you are done in the shower." 49 | ] 50 | } 51 | ], 52 | medium : [ 53 | { 54 | question : "I can be quick and then I'm deadly, I am a rock, shell and bone medley.", 55 | answer : "sand", 56 | hints : [ 57 | "If I was made into a man, I'd make people dream", 58 | "I gather in my millions by ocean, sea and stream.", 59 | "Use me to sculpt a castle." 60 | ] 61 | }, 62 | { 63 | question : "When I point up it's bright, but when I point down it's dark. What am I?", 64 | answer : "light switch", 65 | hints : [ 66 | "where there is a room with light, there I am", 67 | "I have two states", 68 | "I need power to function" 69 | ] 70 | }, 71 | { 72 | question : "Lighter than what I am made of, More of me is hidden Than is seen.", 73 | answer : "iceberg", 74 | hints : [ 75 | "It is cold in here", 76 | "The titanic should look out", 77 | "You can put pieces of me in a drink to cool down" 78 | ] 79 | }, 80 | { 81 | question : "I am more useful when I am broken. What am I?", 82 | answer : "egg", 83 | hints : [ 84 | "I am very fragile", 85 | "I am a future chicken", 86 | "I go great with bacon" 87 | ] 88 | }, 89 | { 90 | question : "I'm the part of the bird that's not in the sky. I can swim in the ocean and yet remain dry. What am I?", 91 | answer : "shadow", 92 | hints : [ 93 | "Lights off, I disappear", 94 | "Lights on, I appear", 95 | "The sun and a surface are my companions" 96 | ] 97 | } 98 | ], 99 | hard : [ 100 | { 101 | question : "The more you take the more you leave behind?", 102 | answer : "foot steps", 103 | hints : [ 104 | "I am an imprint to where you have been", 105 | "I am the same size and shape", 106 | "You see me on sand, in rain, or when you step in something" 107 | ] 108 | }, 109 | { 110 | question : "What is black when you buy it, red when you use it, and gray when you throw it away?", 111 | answer : "charcoal", 112 | hints : [ 113 | "Got something to grill? You need me", 114 | "You can mine me in minecraft from burnt wood", 115 | "I am the aftermath of a fire" 116 | ] 117 | }, 118 | { 119 | question : "Mountains will crumble and temples will fall, and no man can survive its endless call. What is it?", 120 | answer : "time", 121 | hints : [ 122 | "Check your watch", 123 | "Check your clock", 124 | "No one is immune" 125 | ] 126 | }, 127 | { 128 | question : "I can be cracked, I can be made. I can be told, I can be played. What am I?", 129 | answer : "joke", 130 | hints : [ 131 | "Some people find me funny", 132 | "Some people find me rude", 133 | "Anyone can take part" 134 | ] 135 | }, 136 | { 137 | question : "Lovely and round, I shine with pale light, grown in the darkness, A lady's delight. What am I?", 138 | answer : "pearl", 139 | hints : [ 140 | "I live in a sealed place", 141 | "A clam is my home", 142 | "I am valuable on a neck or wrist" 143 | ] 144 | } 145 | ] 146 | } 147 | }; 148 | 149 | -------------------------------------------------------------------------------- /Step 3 - Add APL/models/en-US.json: -------------------------------------------------------------------------------- 1 | { 2 | "interactionModel": { 3 | "languageModel": { 4 | "invocationName": "riddle game workshop presentation", 5 | "intents": [ 6 | { 7 | "name": "AMAZON.CancelIntent", 8 | "samples": [] 9 | }, 10 | { 11 | "name": "AMAZON.HelpIntent", 12 | "samples": [] 13 | }, 14 | { 15 | "name": "AMAZON.StopIntent", 16 | "samples": [] 17 | }, 18 | { 19 | "name": "PlayGameIntent", 20 | "slots": [ 21 | { 22 | "name": "level", 23 | "type": "levelType", 24 | "samples": [ 25 | "{riddleNum} {level}", 26 | "{riddleNum} {level} riddles", 27 | "{level} riddles", 28 | "level {level}", 29 | "{level}" 30 | ] 31 | }, 32 | { 33 | "name": "riddleNum", 34 | "type": "AMAZON.NUMBER" 35 | }, 36 | { 37 | "name": "name", 38 | "type": "AMAZON.US_FIRST_NAME" 39 | }, 40 | { 41 | "name": "color", 42 | "type": "AMAZON.Color" 43 | } 44 | ], 45 | "samples": [ 46 | "{riddleNum} of the {level} riddles", 47 | "{name} {color} {riddleNum} level {level}", 48 | "{name} and {color} and i want {riddleNum} {level} riddles", 49 | "give me {riddleNum} {level}", 50 | "my favorite color is {color} and {riddleNum} {level}", 51 | "i like {color} and {level} riddles", 52 | "let's start", 53 | "i want to play", 54 | "i want {riddleNum} riddles", 55 | "give me {riddleNum} riddles", 56 | "my name is {name}", 57 | "my name is {name} and my favorite color is {color}", 58 | "i am {name}", 59 | "i am {name} and i like {color}", 60 | "my favorite color is {color}", 61 | "i like {color}", 62 | "i want {riddleNum} {level} riddles", 63 | "give me {riddleNum} {level} riddles", 64 | "my name is {name} and i want {level} riddles", 65 | "my name is {name} and i want {riddleNum} {level} riddles", 66 | "my name is {name} and my favorite color is {color} and i want {riddleNum} {level} riddles", 67 | "how about {level} riddles", 68 | "lets do some {level} riddles", 69 | "{level} ones", 70 | "lets do some {level} ones", 71 | "the {level} riddles", 72 | "the {level} ones", 73 | "{level} level riddles", 74 | "i want {level} riddles", 75 | "i want to play {level}", 76 | "{level} riddles", 77 | "{level}" 78 | ] 79 | }, 80 | { 81 | "name": "AnswerRiddleIntent", 82 | "slots": [ 83 | { 84 | "name": "answer", 85 | "type": "answerType" 86 | } 87 | ], 88 | "samples": [ 89 | "i think the answer is {answer} ", 90 | "my answer is {answer}", 91 | "your {answer}", 92 | "my {answer}", 93 | "the {answer}", 94 | "an {answer}", 95 | "a {answer}", 96 | "the answer to the riddle is {answer}", 97 | "the answer is {answer}", 98 | "{answer} is the answer", 99 | "i think it is {answer}", 100 | "{answer}" 101 | ] 102 | }, 103 | { 104 | "name": "AMAZON.MoreIntent", 105 | "samples": [] 106 | }, 107 | { 108 | "name": "AMAZON.NavigateHomeIntent", 109 | "samples": [] 110 | }, 111 | { 112 | "name": "AMAZON.NavigateSettingsIntent", 113 | "samples": [] 114 | }, 115 | { 116 | "name": "AMAZON.NextIntent", 117 | "samples": [] 118 | }, 119 | { 120 | "name": "AMAZON.PageUpIntent", 121 | "samples": [] 122 | }, 123 | { 124 | "name": "AMAZON.PageDownIntent", 125 | "samples": [] 126 | }, 127 | { 128 | "name": "AMAZON.PreviousIntent", 129 | "samples": [] 130 | }, 131 | { 132 | "name": "AMAZON.ScrollRightIntent", 133 | "samples": [] 134 | }, 135 | { 136 | "name": "AMAZON.ScrollDownIntent", 137 | "samples": [] 138 | }, 139 | { 140 | "name": "AMAZON.ScrollLeftIntent", 141 | "samples": [] 142 | }, 143 | { 144 | "name": "AMAZON.ScrollUpIntent", 145 | "samples": [] 146 | }, 147 | { 148 | "name": "HintIntent", 149 | "slots": [], 150 | "samples": [ 151 | "i need another clue", 152 | "i need another hint", 153 | "i need a clue", 154 | "i need a hint", 155 | "give me a hint", 156 | "tell me a hint", 157 | "hint", 158 | "i want a clue", 159 | "clue", 160 | "give me a clue", 161 | "tell me a clue" 162 | ] 163 | }, 164 | { 165 | "name": "BuyIntent", 166 | "slots": [], 167 | "samples": [ 168 | "buy hints", 169 | "i want to buy hints", 170 | "buy hints for my game", 171 | "buy me hints", 172 | "purchase hints", 173 | "purchase clues", 174 | "buy clues", 175 | "buy clues for my game" 176 | ] 177 | } 178 | ], 179 | "types": [ 180 | { 181 | "name": "levelType", 182 | "values": [ 183 | { 184 | "name": { 185 | "value": "easy", 186 | "synonyms": [ 187 | "children", 188 | "child", 189 | "kids", 190 | "kid", 191 | "for kids", 192 | "easy peasy", 193 | "really easy" 194 | ] 195 | } 196 | }, 197 | { 198 | "name": { 199 | "value": "hard", 200 | "synonyms": [ 201 | "advanced", 202 | "hardest", 203 | "harder", 204 | "really hard", 205 | "challenging", 206 | "difficult" 207 | ] 208 | } 209 | }, 210 | { 211 | "name": { 212 | "value": "medium", 213 | "synonyms": [ 214 | "med" 215 | ] 216 | } 217 | } 218 | ] 219 | }, 220 | { 221 | "name": "answerType", 222 | "values": [ 223 | { 224 | "name": { 225 | "value": "heart" 226 | } 227 | }, 228 | { 229 | "name": { 230 | "value": "leg", 231 | "synonyms": [ 232 | "legs" 233 | ] 234 | } 235 | }, 236 | { 237 | "name": { 238 | "value": "water" 239 | } 240 | }, 241 | { 242 | "name": { 243 | "value": "icicle" 244 | } 245 | }, 246 | { 247 | "name": { 248 | "value": "scissors", 249 | "synonyms": [ 250 | "shears" 251 | ] 252 | } 253 | }, 254 | { 255 | "name": { 256 | "value": "pride" 257 | } 258 | }, 259 | { 260 | "name": { 261 | "value": "volcano" 262 | } 263 | }, 264 | { 265 | "name": { 266 | "value": "counterfeit", 267 | "synonyms": [ 268 | "phony money", 269 | "counterfeit money", 270 | "fake money" 271 | ] 272 | } 273 | }, 274 | { 275 | "name": { 276 | "value": "tomorrow" 277 | } 278 | }, 279 | { 280 | "name": { 281 | "value": "key" 282 | } 283 | }, 284 | { 285 | "name": { 286 | "value": "blink" 287 | } 288 | }, 289 | { 290 | "name": { 291 | "value": "snail" 292 | } 293 | }, 294 | { 295 | "name": { 296 | "value": "ship", 297 | "synonyms": [ 298 | "yacht ", 299 | "boat" 300 | ] 301 | } 302 | }, 303 | { 304 | "name": { 305 | "value": "echo" 306 | } 307 | }, 308 | { 309 | "name": { 310 | "value": "candle" 311 | } 312 | }, 313 | { 314 | "name": { 315 | "value": "hour glass", 316 | "synonyms": [ 317 | "sand timer", 318 | "hourglass", 319 | "timer" 320 | ] 321 | } 322 | }, 323 | { 324 | "name": { 325 | "value": "wheel" 326 | } 327 | }, 328 | { 329 | "name": { 330 | "value": "calendar" 331 | } 332 | }, 333 | { 334 | "name": { 335 | "value": "blue" 336 | } 337 | }, 338 | { 339 | "name": { 340 | "value": "corn" 341 | } 342 | }, 343 | { 344 | "name": { 345 | "value": "finger nails", 346 | "synonyms": [ 347 | "nails", 348 | "fingernails" 349 | ] 350 | } 351 | }, 352 | { 353 | "name": { 354 | "value": "name" 355 | } 356 | }, 357 | { 358 | "name": { 359 | "value": "piano", 360 | "synonyms": [ 361 | "keyboard" 362 | ] 363 | } 364 | }, 365 | { 366 | "name": { 367 | "value": "sunshine", 368 | "synonyms": [ 369 | "sunlight", 370 | "sun light", 371 | "sun" 372 | ] 373 | } 374 | }, 375 | { 376 | "name": { 377 | "value": "carpet" 378 | } 379 | }, 380 | { 381 | "name": { 382 | "value": "turtle" 383 | } 384 | }, 385 | { 386 | "name": { 387 | "value": "river" 388 | } 389 | }, 390 | { 391 | "name": { 392 | "value": "wind" 393 | } 394 | }, 395 | { 396 | "name": { 397 | "value": "hole", 398 | "synonyms": [ 399 | "holes", 400 | "whole" 401 | ] 402 | } 403 | }, 404 | { 405 | "name": { 406 | "value": "onion" 407 | } 408 | }, 409 | { 410 | "name": { 411 | "value": "newspaper" 412 | } 413 | }, 414 | { 415 | "name": { 416 | "value": "chalkboard", 417 | "synonyms": [ 418 | "blackboard", 419 | "chalk board", 420 | "board", 421 | "white board", 422 | "chalk wall", 423 | "black board" 424 | ] 425 | } 426 | }, 427 | { 428 | "name": { 429 | "value": "phone", 430 | "synonyms": [ 431 | "cell phone", 432 | "telephone" 433 | ] 434 | } 435 | }, 436 | { 437 | "name": { 438 | "value": "darkness", 439 | "synonyms": [ 440 | "dark" 441 | ] 442 | } 443 | }, 444 | { 445 | "name": { 446 | "value": "match" 447 | } 448 | }, 449 | { 450 | "name": { 451 | "value": "stamp" 452 | } 453 | }, 454 | { 455 | "name": { 456 | "value": "rainbow" 457 | } 458 | }, 459 | { 460 | "name": { 461 | "value": "stars", 462 | "synonyms": [ 463 | "star" 464 | ] 465 | } 466 | }, 467 | { 468 | "name": { 469 | "value": "yardstick", 470 | "synonyms": [ 471 | "yard stick", 472 | "ruler", 473 | "yard ruler" 474 | ] 475 | } 476 | }, 477 | { 478 | "name": { 479 | "value": "salt" 480 | } 481 | }, 482 | { 483 | "name": { 484 | "value": "breath", 485 | "synonyms": [ 486 | "breathe" 487 | ] 488 | } 489 | }, 490 | { 491 | "name": { 492 | "value": "tempurature" 493 | } 494 | }, 495 | { 496 | "name": { 497 | "value": "cold" 498 | } 499 | }, 500 | { 501 | "name": { 502 | "value": "alphabet" 503 | } 504 | }, 505 | { 506 | "name": { 507 | "value": "umbrella" 508 | } 509 | }, 510 | { 511 | "name": { 512 | "value": "pearl" 513 | } 514 | }, 515 | { 516 | "name": { 517 | "value": "joke" 518 | } 519 | }, 520 | { 521 | "name": { 522 | "value": "time" 523 | } 524 | }, 525 | { 526 | "name": { 527 | "value": "charcoal" 528 | } 529 | }, 530 | { 531 | "name": { 532 | "value": "foot steps", 533 | "synonyms": [ 534 | "feet marks", 535 | "feet prints", 536 | "foot marks", 537 | "footsteps" 538 | ] 539 | } 540 | }, 541 | { 542 | "name": { 543 | "value": "shadow" 544 | } 545 | }, 546 | { 547 | "name": { 548 | "value": "egg" 549 | } 550 | }, 551 | { 552 | "name": { 553 | "value": "iceberg" 554 | } 555 | }, 556 | { 557 | "name": { 558 | "value": "light switch", 559 | "synonyms": [ 560 | "switch" 561 | ] 562 | } 563 | }, 564 | { 565 | "name": { 566 | "value": "sand" 567 | } 568 | }, 569 | { 570 | "name": { 571 | "value": "towel" 572 | } 573 | }, 574 | { 575 | "name": { 576 | "value": "glove" 577 | } 578 | }, 579 | { 580 | "name": { 581 | "value": "sponge" 582 | } 583 | }, 584 | { 585 | "name": { 586 | "value": "mirror" 587 | } 588 | }, 589 | { 590 | "name": { 591 | "value": "clock" 592 | } 593 | } 594 | ] 595 | } 596 | ] 597 | }, 598 | "dialog": { 599 | "intents": [ 600 | { 601 | "name": "PlayGameIntent", 602 | "delegationStrategy": "ALWAYS", 603 | "confirmationRequired": false, 604 | "prompts": {}, 605 | "slots": [ 606 | { 607 | "name": "level", 608 | "type": "levelType", 609 | "confirmationRequired": false, 610 | "elicitationRequired": true, 611 | "prompts": { 612 | "elicitation": "Elicit.Slot.1456697663557.1392510278084" 613 | } 614 | }, 615 | { 616 | "name": "riddleNum", 617 | "type": "AMAZON.NUMBER", 618 | "confirmationRequired": false, 619 | "elicitationRequired": false, 620 | "prompts": {} 621 | }, 622 | { 623 | "name": "name", 624 | "type": "AMAZON.US_FIRST_NAME", 625 | "confirmationRequired": false, 626 | "elicitationRequired": false, 627 | "prompts": {} 628 | }, 629 | { 630 | "name": "color", 631 | "type": "AMAZON.Color", 632 | "confirmationRequired": false, 633 | "elicitationRequired": false, 634 | "prompts": {} 635 | } 636 | ] 637 | } 638 | ], 639 | "delegationStrategy": "ALWAYS" 640 | }, 641 | "prompts": [ 642 | { 643 | "id": "Elicit.Slot.1456697663557.1392510278084", 644 | "variations": [ 645 | { 646 | "type": "PlainText", 647 | "value": "Nice to meet you {name} . You want to play through {riddleNum} riddles. Would you like those to be easy, medium, or hard?" 648 | }, 649 | { 650 | "type": "PlainText", 651 | "value": "Which category would you like your {riddleNum} riddles to be sourced from, easy, medium, or hard?" 652 | }, 653 | { 654 | "type": "PlainText", 655 | "value": "Hi {name}, do you want easy, medium or hard riddles?" 656 | }, 657 | { 658 | "type": "PlainText", 659 | "value": "Do you want easy, medium, or hard riddles?" 660 | } 661 | ] 662 | } 663 | ] 664 | } 665 | } -------------------------------------------------------------------------------- /Step 3 - Add APL/skill.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest": { 3 | "publishingInformation": { 4 | "locales": { 5 | "en-US": { 6 | "summary": "Sample Short Description", 7 | "examplePhrases": [ 8 | "Alexa open hello world", 9 | "Alexa tell hello world hello", 10 | "Alexa ask hello world say hello" 11 | ], 12 | "name": "Riddle Game Workshop Presentation", 13 | "description": "Sample Full Description" 14 | } 15 | }, 16 | "isAvailableWorldwide": true, 17 | "testingInstructions": "Sample Testing Instructions.", 18 | "category": "EDUCATION_AND_REFERENCE", 19 | "distributionCountries": [] 20 | }, 21 | "apis": { 22 | "custom": { 23 | "endpoint": { 24 | "sourceDir": "lambda/custom", 25 | "uri": "ask-level-up-riddles" 26 | }, 27 | "interfaces": [ 28 | { 29 | "type": "ALEXA_PRESENTATION_APL" 30 | } 31 | ] 32 | } 33 | }, 34 | "manifestVersion": "1.0" 35 | } 36 | } 37 | --------------------------------------------------------------------------------