├── .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 |
--------------------------------------------------------------------------------