Search code examples
node.jsalexa-skills-kitalexa-slot

AnswerHandler and PlayerCountHandler mixed up


I'm building an Alexa buttons skill using this template from Amazon for a trivia game. In the game, Alexa asks questions and users respond with an answer from the slot type "answers", which has the following values from the template:

fox,
wolf,
cat,
etc.

I want users to be able to answer trivia questions with a number, instead of a word.

I copied the interaction model and connected a Lambda function to my Alexa skill (as per the readme). When I test it without changing any of the code, it works fine. The dialog goes like this:

  • Alexa: Welcome to Beginning Addition. This game supports up to 4 players. How many players are there?
  • User: two
  • Alexa: Ok. Players, press your buttons now, so I'll know which buttons you will be using.

Debugging info from the device log shows that Alexa thinks the User is responding with PlayerCount intent, which is correct:

"request": {
    "type": "IntentRequest",
    "intent": {
        "name": "PlayerCount",
        "confirmationStatus": "NONE",
        "slots": {
            "players": {
                "name": "players",
                "value": "2",
                "confirmationStatus": "NONE"
            }
        }
    }
}

When I change the slot type of AnswerQuestionIntent and AnswerOnlyIntent to "AMAZON.NUMBER" (instead of the slot type "answers"), the dialog goes like this:

  • Alexa: Welcome to Beginning Addition. This game supports up to 4 players. How many players are there?
  • User: two
  • Alexa: Sorry, I didn't get that. Please say again!

(note: if the user responds with anything else, Alexa will reprompt with "Welcome to Better with Buttons Trivia. This game supports up to 4 players. How many players are there?")

Looking at debugging info from the Device Log, I can see that Alexa thinks that the intent is AnswerOnlyIntent, instead of PlayerCount:

 "request": {
    "type": "IntentRequest",
    "intent": {
        "name": "AnswerOnlyIntent",
        "confirmationStatus": "NONE",
        "slots": {
            "answers": {
                "name": "answers",
                "value": "2",
                "confirmationStatus": "NONE"
            }
        }
    }
}

Why does Alexa think that the user wants AnswerOnlyIntent?

According to the CloudWatch logs, it isn't the AnswerHandler which is handling the response, but the Global DefaultHandler, which is set up to reprompt the user. AnswerHandler is supposed to only run in the following conditions:

return requestEnvelope.request.type === 'IntentRequest' &&
        (requestEnvelope.request.intent.name === 'AnswerQuestionIntent' ||
          requestEnvelope.request.intent.name === 'AnswerOnlyIntent') &&
          validPlayerCount && 
          (sessionAttributes.STATE === settings.STATE.BUTTON_GAME_STATE ||
          sessionAttributes.STATE === settings.STATE.BUTTONLESS_GAME_STATE);

PlayerCountHandler returns canHandle if the following is true:

return requestEnvelope.request.type === 'IntentRequest' &&
        (requestEnvelope.request.intent.name === 'PlayerCount' ||
        requestEnvelope.request.intent.name === 'PlayerCountOnly') &&
        attributesManager.getSessionAttributes().STATE === settings.STATE.START_GAME_STATE;

Solution

  • This is happening because both your AnswerOnlyIntent, AnswerQuestionIntent and PlayerCount all has number slots. They get mapped when users say a number, which is as expected. A good idea is to have just one intent which takes number input from the user. Ex: a NumberInputIntent. And Alexa will map it whenever users say a number. This way you can always expect this intent to be mapped when there is a number involved.

    Use sessionAttributes to keep a track of what you are excepting from the user like
    STATE = "playerCount" or STATE = "answer".

    You can use two different handlers with these canHandle() conditions.

    return requestEnvelope.request.type === 'IntentRequest' 
           && requestEnvelope.request.intent.name === 'NumberInputIntent' 
           && validPlayerCount 
           && sessionAttributes.STATE === 'answer' 
           && <add your extra conditions>
    

    and for player count

    return requestEnvelope.request.type === 'IntentRequest' 
           && requestEnvelope.request.intent.name === 'NumberInputIntent' 
           && validPlayerCount 
           && sessionAttributes.STATE === 'playerCount' 
           && <add your extra conditions>
    

    The idea is simple, use only one intent for all inputs of that particular kind. For number inputs, just use one intent which maps a number.