Blog

Developing a Custom Skill for an Alexa Game

by on October 23, 2018

In one of our previous articles, we showed how to develop a custom Alexa skill using an example of a Trip Planner app (How To Build A Dialogue Custom Alexa Skill Using JavaScript (Without Lambda). In this article, we will show you a more complex example of the interaction between Alexa and the user. Today, we will build a “Guess My Number” game where Alexa (or technically, our Alexa skill) thinks of a number and the user tries to guess it while the skill suggests whether it is lower or higher.

Here’s a sample dialogue a user may have with Alexa once you implement the skill:
guess-my-number-example1 guess-my-number-example2

In this article, we will omit some details about the user interface of the Amazon Developer Account and assume that you are already familiar with this from the previous article.

Requirements

As before, you will need the following to get started:

Skill Setup

The first step is to set up the skill in the Amazon Developer Account. To do this, go to the Amazon Developer console and create a new skill from the empty template (see the previous article for details).  We called the new Alexa Skill “Guess My Number”, but you are welcome to name it anything you’d like.

Next step is to create a new intent named NumberGuessIntent and an Intent Slot called number with the type Amazon.NUMBER. Make sure to add the Sample Utterances as shown below:
20181009 image 1
Finally, it is important to add all the Built-in Intents listed in the screenshot above as you will need them later. For now, save the model by clicking the Save Model button.

Developing and Configuring an Alexa Skill as a service in Backendless

If you have already installed the SDK for Amazon Alexa, you can skip steps 2 and 3.

  1. Login to Backendless Console and select an app where the custom skill implementation will reside.
  2. Click the Marketplace icon in the vertical toolbar on the left.
  3. Click the All Services section and install the SDK for Amazon AlexaThis will install all the APIs necessary to work with requests from Alexa.
  4. Switch to the Business Logic (Cloud Code) section of Backendless Console and click the CODING tab.
  5. Select JS from the from the language drop-down list and click the Download project icon as shown in the screenshot below:
    20181009 image 2
  6. Extract the files from the downloaded ZIP file and open the downloaded project an IDE of your choice (we recommend IntelliJ IDEA).
  7. Run the following command from the root directory of the project:
    npm i 
  8. Create four files in the /services  directory named as follows:
    guess-my-number.js
    alexa-base.js
    skill-helper.js
    response-builder.js
  9. Open alexa-base.js for editing and copy-paste the following JavaScript code in it:
'use strict'
const SERVICE_NAME = 'AlexaUtils'
class AlexaBase {
 invoke(method, body) {
   return Backendless.CustomServices.invoke(SERVICE_NAME, method, body)
 }
 /**
  * @param {Object} req
  */
 verifyRequest(req) {
   const { headers } = this.request
   const { Signature: signature, SignatureCertChainUrl: signatureCertChainUrl } = headers
   return this.invoke('verifyRequest', { signatureCertChainUrl, signature, req })
 }
 /**
  * @param {Object} request
  * @param {String} intentName
  * @param {String} slotName
  */
 getSlotValue(request, intentName, slotName) {
   return this.invoke('getSlotValue', { request, intentName, slotName })
 }
 /**
  * @param {Object} request
  */
 getIntentName(request) {
   return this.invoke('getIntentName', request)
 }
 /**
  * @param {String} [whatToSay]
  * @param {Boolean} [waitForResponse]
  */
 sendAlexaResponse(whatToSay, waitForResponse) {
   return this.invoke('sendAlexaResponse', { whatToSay, waitForResponse })
 }
 /**
  * @param {Object} request
  */
 isStarted(request) {
   return this.invoke('isStarted', request)
 }
 /**
  * @param {Object} request
  */
 isInProgress(request) {
   return this.invoke('isInProgress', request)
 }
 /**
  * @param {Object} request
  */
 isCompleted(request) {
   return this.invoke('isCompleted', request)
 }
 delegate() {
   return this.sendAlexaResponse(null, true)
 }
 /**
  * @param {Object} request
  */
 getRequestType(request) {
   return this.invoke('getRequestType', request)
 }
 /**
  * @param {Object} request
  * @param {String} speech
  */
 sendProgressiveAlexaResponse(request, speech) {
   return this.invoke('sendProgressiveAlexaResponse', { request, speech })
 }
}
module.exports = AlexaBase

For the explanation of each method see the previous article.

Now, open the file response-builder.js for editing, and enter the following code:

// response-builder
'use strict'
/**
* @typedef {Object} OutputSpeech
* @property {String} type
* @property {String} ssml
*/
/**
* @typedef {Object} Reprompt
* @property {OutputSpeech} outputSpeech
*/
/**
* @typedef {Object} Response
* @property {Boolean} shouldEndSession
* @property {OutputSpeech} [outputSpeech]
* @property {Reprompt} [reprompt]
*/
/**
* @typedef {Object} ResponseObject
* @property {String} version
* @property {Response} response
*/
class ResponseBuilder {
 constructor() {
   this.res = {
     version : '1.0',
     response: { shouldEndSession: false }
   }
 }
 /**
  * Has Alexa say the provided speech to the user
  * @param {String} speechOutput
  * @returns {ResponseBuilder}
  */
 speak(speechOutput) {
   this.res.response.outputSpeech = {
     type: 'SSML',
     ssml: '<speak>'
     + speechOutput
     + '</speak>',
   }
   return this
 }
 /**
  * Has alexa listen for speech from the user. If the user doesn't respond within 8 seconds
  * then has alexa reprompt with the provided reprompt speech
  * @param {String} repromptSpeechOutput
  * @returns {ResponseBuilder}
  */
 reprompt(repromptSpeechOutput) {
   this.res.response.reprompt = {
     outputSpeech: {
       type: 'SSML',
       ssml: '<speak>'
       + repromptSpeechOutput
       + '</speak>',
     },
   }
   return this
 }
 /**
  * Sets shouldEndSession value to null/false/true
  * @param {Boolean} shouldEndSession
  * @return {ResponseBuilder}
  */
 shouldEndSession(shouldEndSession) {
   this.res.response.shouldEndSession = shouldEndSession
   return this
 }
 /**
  * Returns the response object
  * @return {ResponseObject}
  */
 getResponse() {
   return this.res
 }
}
module.exports = ResponseBuilder

Now open the file skill-helper.js for editing, and enter the following code:

// skill-helper.js
'use strict'
const ResponseBuilder = require('./response-builder')
class SkillHelper {
 constructor() {
   this.requestHandlers = []
   this.errorHandlers = []
 }
 /**
  * @private
  * @param {Object} input
  * @param {Object} err
  */
 _processError(input, err) {
   this.requestHandlers.forEach(errorHandler => {
     if (errorHandler.canHandle(input)) {
       return errorHandler.handle(input, err)
     }
   })
 }
 addRequestHandlers(...requestHandlers) {
   this.requestHandlers = requestHandlers
   return this
 }
 addErrorHandlers(...errorHandlers) {
   this.errorHandlers = errorHandlers
   return this
 }
 /**
  * If table name specified, it will be used as persistent store for session attributes
  * @param {String} table
  * @return {SkillHelper}
  */
 withTable(table) {
   this.table = table
   return this
 }
 /**
  * @private
  * @param {Object} req
  */
 _getInput(req) {
   const input = { req }
   const { sessionId } = req.session
   input.responseBuilder = new ResponseBuilder()
   input.getSessionAttributes = () => Backendless.Cache.get(sessionId)
   input.setSessionAttributes = attributes => Backendless.Cache.put(sessionId, attributes)
   input.getPersistentAttributes = async () => {
     const [attributes] = await Backendless.Data.of(this.table).find()
     return attributes || {}
   }
   input.setPersistentAttributes = async attributes => {
     const prevAttributes = await input.getSessionAttributes()
     const newAttributes = Object.assign(prevAttributes, attributes)
     input.setSessionAttributes(newAttributes)
     return Backendless.Data.of(this.table).save(Object.assign(prevAttributes, attributes))
   }
   return input
 }
 /**
  * @param {Object} req
  * @return {ResponseObject}
  */
 async start(req) {
   const input = this._getInput(req)
   for (const handler of this.requestHandlers) {
     if (await handler.canHandle(input)) {
       try {
         return handler.handle(input)
       } catch (err) {
         this._processError(input, err)
       }
     }
   }
 }
}
module.exports = SkillHelper

And finally, open the file guess-my-number.js for editing. In this file you need to declare the guess-my-number service and register it with Backendless:

// guess-my-number.js
'use strict'
const AlexaBase = require('./alexa-base')
const SkillHelper = require('./skill-helper')
const GUESS_MY_NUMBER_INTENT = 'NumberGuessIntent'
class GuessMyNumber extends AlexaBase {
 /**
  * @route POST /
  * @param {Object} req
  * @returns {Promise<ResponseObject>}
  */
 async guessMyNumber(req) {
 }
}
Backendless.ServerCode.addService(GuessMyNumber)

In the code above, you have created a Backendless API service method named guessMyNumber. The method will be accepting Alexa requests and respond with answers.

Amazon requires that Alexa requests must be verified – meaning your code must ensure that requests are indeed sent by Alexa. To achieve this you will use the this.verifyRequest(req) method, which is inherited from the AlexaBase class.

To begin implementing the logic for the guessMyNumber method, define requestType, intentName, and skillHelper, as shown below:

/**
* @route POST /
* @param {Object} req
* @returns {Promise<ResponseObject>}
*/
async guessMyNumber(req) {
 this.verifyRequest(req)
 const requestType = await this.getRequestType(req)
 const intentName = await this.getIntentName(req)
 const skillHelper = new SkillHelper()
}

Next, you need to declare Intent handlers for each of the Intents.  Schematically, an intent handler should look like this:

const IntentNameHandler = {
 /**
  * Method to check if handler can handle intent request
  * @param {Object} input
  * @returns Promise.<Boolean> | Boolean
  */
 canHandle(input) {
 },
 /**
  * Method to handle request if canHandle return true
  * @param {Object} input
  * @returns {ResponseObject} Promise<Response> | Response
  */
 handler(input) {
 }
}

The first handler that you need to declare is the LaunchRequest handler. In this handler, you will set the initial attributes, greet the user, report how many times they have already played, and offer to start the new game. If the user replies “Yes”, the built-in AMAZON.YesIntent will be invoked, and if they reply “No” – the AMAZON.NoIntent will run. Handling of these intents will be done later.

const LaunchRequest = {
 canHandle(input) {
   // launch only if session is new or request type is 'LaunchRequest'
   return input.req.session.new || requestType === 'LaunchRequest'
 },
 async handle(input) {
   const attributes = await input.getPersistentAttributes() || {}
   if (Object.keys(attributes).length === 0) {
     attributes.endedSessionCount = 0
     attributes.gamesPlayed = 0
     attributes.gameState = 'ENDED'
   }
   await input.setSessionAttributes(attributes)
   const speechOutput = `Welcome to Guess My Number.
     You have played ${attributes.gamesPlayed} times. Would you like to play?`
   return input.responseBuilder
     .speak(speechOutput)
     .reprompt('Say yes to start the game or no to quit.')
     .getResponse()
 }
}

The input argument has the following properties and methods (just in case if you decide to explore further):

  • request – Alexa request object
  • getSessionAttributes – returns the attributes set in the session in the format {[key: string]: any}. By default, an empty object is returned.
  • setSessionAttributes – sets in the session attributes in the format {[key: string]: any}. These attributes will exist throughout the session until it ends.
  • getPersistentAttributesreturns the attributes set in the persistent storage in the format {[key: string]: any}. As a repository, a pre-prepared table is used in the Data service.
  • setPersistentAttributes –  sets the attributes in the {[key: string]: any} format to permanent storage. This data is stored independently of the session and will be available at any time. Also sets the attributes in the session.
  • responseBuilder – a set of helper-functions that help to design the response of Alexa. responseBuilder can consist of several elements, so these helpers will remove the need to manually initialize each one.

Now your the first Intent Handler is ready. In case the user decides to end the session, we can use one of the built-in intents such as AMAZON.CancelIntent or AMAZON.StopIntent: and implement the ExitHandler and SessionEndedRequest:

const ExitHandler = {
 canHandle() {
   return requestType === 'IntentRequest'
     && (intentName === 'AMAZON.CancelIntent' || intentName === 'AMAZON.StopIntent')
 },
 handle(input) {
   return input.responseBuilder
     .speak('Thanks for playing!')
     .getResponse()
 }
}
const SessionEndedRequest = {
 canHandle() {
   return requestType === 'SessionEndedRequest'
 },
 handle(input) {
   const { req } = input
   console.error(`Session ended with reason: ${req.request.reason}`)
   if (input.req.request.error) {
     console.error(`Session ended with error: ${req.request.error.message}`)
   }
   return input.responseBuilder.getResponse()
 }
}

The next Intent that you need to declare for a good user experience is AMAZON.HelpIntent. It is needed if the user does not understand how to work with your skill and asks for help.  This is a great place to add instructions on your skill:

const HelpIntent = {
 canHandle() {
   return requestType === 'IntentRequest' && intentName === 'AMAZON.HelpIntent'
 },
 handle(input) {
   const speechOutput = 'I am thinking of a number between zero and one hundred, try to guess it and ' +
     'I will tell you if it is higher or lower.'
   return input.responseBuilder
     .speak(speechOutput)
     .reprompt('Try saying a number.')
     .getResponse()
 }
}

Now you need to process the user’s answer to the question of whether they want to play or not. Possible answers “yes” or “no” will be processed by the built-in intents AMAZON.YesIntent and AMAZON.NoIntent. Below is the handler for the “yes” answer:

const YesIntent = {
 async canHandle(input) {
   let isCurrentlyPlaying = false
   const sessionAttributes = await input.getSessionAttributes()
   if (sessionAttributes.gameState && sessionAttributes.gameState === 'STARTED') {
     isCurrentlyPlaying = true
   }
   return !isCurrentlyPlaying && requestType === 'IntentRequest' && intentName === 'AMAZON.YesIntent'
 },
 async handle(input) {
   const sessionAttributes = await input.getSessionAttributes()
   sessionAttributes.gameState = 'STARTED'
   sessionAttributes.guessNumber = Math.floor(Math.random() * 101)
   await input.setSessionAttributes(sessionAttributes)
   return input.responseBuilder
     .speak('Great! Try saying a number to start the game.')
     .reprompt('Try saying a number.')
     .getResponse()
 },
}

Note that in canHandle, you should add a check to see if the game has already started. This is necessary for those cases when the user for some reason says “Yes” or “No” during the game. You don’t want the game to start over again because of this and the handler is invoked only if the game has not yet started.

Notice that the gameState variable is used in the session attributes. It identifies that the game is already in process. Also, the guessNumber variable contains the actual number which the user is trying to guess. For a more advanced version of this skill, you can have Alexa ask the user at beginning of the game what maximum number to use – for example, the user may want to make the game harder so that Alexa guesses a number between 0 and 1 million.

Another idea to explore is making an Intent for a hint. The user asks for a hint and Alex reports a slightly more minimal search circle for the desired number. 

Let’s move on with the implementation. The next step is for you to handle the case where the user responds “no”. In that case, you need to handle the AMAZON.NoIntent intent. Here, you also need to make sure that the game is not in progress, and in the permanent store we record information – for example, that we had a session, but the game was not continued for some reason (and as a result, the gamesPlayed counter was not updated).

Then, if you receive the “no” response from the user, the shouldEndSession should be invoked, signaling that the session should end:

const NoIntent = {
 async canHandle(input) {
   let isCurrentlyPlaying = false
   const sessionAttributes = await input.getSessionAttributes()
   if (sessionAttributes.gameState && sessionAttributes.gameState === 'STARTED') {
     isCurrentlyPlaying = true
   }
   return !isCurrentlyPlaying && requestType === 'IntentRequest' && intentName === 'AMAZON.NoIntent'
 },
 async handle(input) {
   const sessionAttributes = await input.getSessionAttributes()
   sessionAttributes.endedSessionCount += 1
   sessionAttributes.gameState = 'ENDED'
   await input.setPersistentAttributes(sessionAttributes)
   return input.responseBuilder
     .speak('Ok, see you next time!')
     .shouldEndSession(true)
     .getResponse()
 },
}

The next Intent is NumberGuessIntent. It is the one responsible for the logic and progress of the game itself.  In canHandle, you will check that the game is in progress, and also that the Intent is GUESS_MY_NUMBER_INTENT.

In the body of the handler (the handle function), you get the number the user spoke (available in the input.req.request.intent.slots.number.value variable), and compare it with the number to be guessed. Depending on the result, you will determine the response to be sent back. If the number was guessed correctly, the attributes are stored in the permanent storage and prompt the user to play again.

const NumberGuessIntent = {
 async canHandle(input) {
   let isCurrentlyPlaying = false
   const sessionAttributes = await input.getSessionAttributes()
   if (sessionAttributes.gameState && sessionAttributes.gameState === 'STARTED') {
     isCurrentlyPlaying = true
   }
   return isCurrentlyPlaying && requestType === 'IntentRequest' && intentName === GUESS_MY_NUMBER_INTENT
 },
 async handle(input) {
   const { responseBuilder } = input
   const guessNum = parseInt(input.req.request.intent.slots.number.value, 10)
   const sessionAttributes = await input.getSessionAttributes()
   const targetNum = sessionAttributes.guessNumber
   if (guessNum > targetNum) {
     return responseBuilder
       .speak(`${guessNum} is too high.`)
       .reprompt('Try saying a lower number.')
       .getResponse()
   }
   if (guessNum < targetNum) {
     return responseBuilder
       .speak(`${guessNum} is too low.`)
       .reprompt('Try saying a higher number.')
       .getResponse()
   }
   if (guessNum === targetNum) {
     sessionAttributes.gamesPlayed += 1
     sessionAttributes.gameState = 'ENDED'
     await input.setPersistentAttributes(sessionAttributes)
     return responseBuilder
       .speak(`${guessNum} is correct! Would you like to play a new game?`)
       .reprompt('Say yes to start a new game, or no to end the game.')
       .getResponse()
   }
   return responseBuilder
     .speak('Sorry, I didn\'t get that. Try saying a number.')
     .reprompt('Try saying a number.')
     .getResponse()
 },
}

The basic logic of the skill is almost finished, it only remains to handle errors and any unhandled intents. To do this, add the following  ErrorHandler:

const ErrorHandler = {
 canHandle() {
   return true
 },
 handle(input, error) {
   console.log(`Error handled: ${error.message}`)
   const speech = 'Sorry, I can\'t understand the command. Please say again.'
   return input.responseBuilder
     .speak(speech)
     .reprompt(speech)
     .getResponse()
 },
}

And for all other cases in which the request could not be processed by all previous handlers, add UnhandledIntent:

const UnhandledIntent = {
 canHandle() {
   return true
 },
 handle(input) {
   const outputSpeech = 'Say yes to continue, or no to end the game.'
   return input.responseBuilder
     .speak(outputSpeech)
     .reprompt(outputSpeech)
     .getResponse()
 },
}

Now you need to register all handlers and build skills with skillHelper:

return skillHelper
   .addRequestHandlers(
     LaunchRequest,
     ExitHandler,
     SessionEndedRequest,
     HelpIntent,
     YesIntent,
     NoIntent,
     NumberGuessIntent,
     FallbackHandler,
     UnhandledIntent
   )
   .withTable('GuessNumberGame')
   .addErrorHandlers(ErrorHandler)
   .start(req)

Please note that the order of these handlers is very important because they will be called exactly in the order in which you register them. The addErrorHandlers method, like addRequestHandlers, takes an unlimited number of handlers. Each handler will be called in the order in which you list them.

The final method in the sequence is called start. It is called at the very end and takes the required req parameter.

In order to work with the persistent storage, you must specify the name of the table where the data will be stored. Otherwise, you can not use the get/setPersistentAttributes methods. Make sure to create the GuessNumberGame table in the Backendless Database. The table schema must be configured as shown below:
20181009 image 3

The final version of the service should look like this:

// guess-my-number.js
'use strict'
const AlexaBase = require('./alexa-base')
const SkillHelper = require('./skill-helper')
const GUESS_MY_NUMBER_INTENT = 'NumberGuessIntent'
class GuessMyNumber extends AlexaBase {
 /**
  * @route POST /
  * @param {Object} req
  * @returns {Promise<ResponseObject>}
  */
 async guessMyNumber(req) {
   await this.verifyRequest(req)
   const requestType = await this.getRequestType(req)
   const intentName = await this.getIntentName(req)
   const skillHelper = new SkillHelper()
   const LaunchRequest = {
     canHandle(input) {
       return input.req.session.new || requestType === 'LaunchRequest'
     },
     async handle(input) {
       const attributes = await input.getPersistentAttributes() || {}
       if (Object.keys(attributes).length === 0) {
         attributes.endedSessionCount = 0
         attributes.gamesPlayed = 0
         attributes.gameState = 'ENDED'
       }
       await input.setSessionAttributes(attributes)
       const speechOutput = `Welcome to Guess My Number.
         You have played ${attributes.gamesPlayed} times. Would you like to play?`
       return input.responseBuilder
         .speak(speechOutput)
         .reprompt('Say yes to start the game or no to quit.')
         .getResponse()
     }
   }
   const ExitHandler = {
     canHandle() {
       return requestType === 'IntentRequest'
         && (intentName === 'AMAZON.CancelIntent' || intentName === 'AMAZON.StopIntent')
     },
     handle(input) {
       return input.responseBuilder
         .speak('Thanks for playing!')
         .getResponse()
     }
   }
   const SessionEndedRequest = {
     canHandle() {
       return requestType === 'SessionEndedRequest'
     },
     handle(input) {
       const { req } = input
       console.error(`Session ended with reason: ${req.request.reason}`)
       if (input.req.request.error) {
         console.error(`Session ended with error: ${req.request.error.message}`)
       }
       return input.responseBuilder.getResponse()
     }
   }
   const HelpIntent = {
     canHandle() {
       return requestType === 'IntentRequest' && intentName === 'AMAZON.HelpIntent'
     },
     handle(input) {
       const speechOutput = 'I am thinking of a number between zero and one hundred, try to guess it and ' +
         'I will tell you if it is higher or lower.'
       return input.responseBuilder
         .speak(speechOutput)
         .reprompt('Try saying a number.')
         .getResponse()
     }
   }
   const YesIntent = {
     async canHandle(input) {
       let isCurrentlyPlaying = false
       const sessionAttributes = await input.getSessionAttributes()
       if (sessionAttributes.gameState && sessionAttributes.gameState === 'STARTED') {
         isCurrentlyPlaying = true
       }
       return !isCurrentlyPlaying && requestType === 'IntentRequest' && intentName === 'AMAZON.YesIntent'
     },
     async handle(input) {
       const sessionAttributes = await input.getSessionAttributes()
       sessionAttributes.gameState = 'STARTED'
       sessionAttributes.guessNumber = Math.floor(Math.random() * 101)
       await input.setSessionAttributes(sessionAttributes)
       return input.responseBuilder
         .speak('Great! Try saying a number to start the game.')
         .reprompt('Try saying a number.')
         .getResponse()
     },
   }
   const NoIntent = {
     async canHandle(input) {
       let isCurrentlyPlaying = false
       const sessionAttributes = await input.getSessionAttributes()
       if (sessionAttributes.gameState && sessionAttributes.gameState === 'STARTED') {
         isCurrentlyPlaying = true
       }
       return !isCurrentlyPlaying && requestType === 'IntentRequest' && intentName === 'AMAZON.NoIntent'
     },
     async handle(input) {
       const sessionAttributes = await input.getSessionAttributes()
       sessionAttributes.endedSessionCount += 1
       sessionAttributes.gameState = 'ENDED'
       await input.setPersistentAttributes(sessionAttributes)
       return input.responseBuilder
         .speak('Ok, see you next time!')
         .shouldEndSession(true)
         .getResponse()
     },
   }
   const NumberGuessIntent = {
     async canHandle(input) {
       let isCurrentlyPlaying = false
       const sessionAttributes = await input.getSessionAttributes()
       if (sessionAttributes.gameState && sessionAttributes.gameState === 'STARTED') {
         isCurrentlyPlaying = true
       }
       return isCurrentlyPlaying && requestType === 'IntentRequest' && intentName === GUESS_MY_NUMBER_INTENT
     },
     async handle(input) {
       const { responseBuilder } = input
       const guessNum = parseInt(input.req.request.intent.slots.number.value, 10)
       const sessionAttributes = await input.getSessionAttributes()
       const targetNum = sessionAttributes.guessNumber
       if (guessNum > targetNum) {
         return responseBuilder
           .speak(`${guessNum} is too high.`)
           .reprompt('Try saying a lower number.')
           .getResponse()
       }
       if (guessNum < targetNum) {
         return responseBuilder
           .speak(`${guessNum} is too low.`)
           .reprompt('Try saying a higher number.')
           .getResponse()
       }
       if (guessNum === targetNum) {
         sessionAttributes.gamesPlayed += 1
         sessionAttributes.gameState = 'ENDED'
         await input.setPersistentAttributes(sessionAttributes)
         return responseBuilder
           .speak(`${guessNum} is correct! Would you like to play a new game?`)
           .reprompt('Say yes to start a new game, or no to end the game.')
           .getResponse()
       }
       return responseBuilder
         .speak('Sorry, I didn\'t get that. Try saying a number.')
         .reprompt('Try saying a number.')
         .getResponse()
     },
   }
   const ErrorHandler = {
     canHandle() {
       return true
     },
     handle(input, error) {
       console.log(`Error handled: ${error.message}`)
       const speech = 'Sorry, I can\'t understand the command. Please say again.'
       return input.responseBuilder
         .speak(speech)
         .reprompt(speech)
         .getResponse()
     },
   }
   const UnhandledIntent = {
     canHandle() {
       return true
     },
     handle(input) {
       const outputSpeech = 'Say yes to continue, or no to end the game.'
       return input.responseBuilder
         .speak(outputSpeech)
         .reprompt(outputSpeech)
         .getResponse()
     },
   }
   return skillHelper
     .addRequestHandlers(
       LaunchRequest,
       ExitHandler,
       SessionEndedRequest,
       HelpIntent,
       YesIntent,
       NoIntent,
       NumberGuessIntent,
       UnhandledIntent
     )
     .withTable('GuessNumberGame')
     .addErrorHandlers(ErrorHandler)
     .start(req)
 }
}
Backendless.ServerCode.addService(GuessMyNumber)

The new service is now ready to use! You will need to deploy the code to Backendless with the npm run deploy command,  and install the Endpoint URL in Amazon Developer Console. For more details, see the steps 16-18 from the previous article.

When you are back in the Amazon Developer Console, make sure to click the Build Model button. Once the model is built, you can start testing your skill!  Simply ask Alexa: “Play guess my number”, and the game should start.

In the future, we plan to expand and add new features to our Backendless SDK for Alexa. If there are features that you would like us to add, please do not hesitate to tell us about it, we will be happy to improve the capabilities of the SDK. 

Happy Coding!

If you have any questions about this procedure, please post your questions in our support forum (https://support.backendless.com) or on Slack (http://slack.backendless.com).

Leave a Reply