An Alexa Skill in Javascript – Part 5

Finally, the last post in the series.  Here, we will show Alexa how to Roll Some Dice with a modifier so that we can get our skill working properly.

We’re going to do this in two steps:

  1. Create a function that will take the diceCount, plusMinus and modifier parameters and return the results as speech text.
  2. We’ll modify the RollSomeDiceIntentHandler to pull those parameters from the Request and call the function we created in the first step.

Create a Function that can Roll Some Dice

To contain our function, we’ll create a new JavaScript file in the lambda\custom folder of our project.  Right-click on the lambda\custom folder and select New File from the menu.  Enter rollSomeDiceFunction.js as the file name.

In the new file, let’s stub out our function.  We need to export the function to be able to access it from the other file, index.js.  It will accept three parameters.  Like so:

exports.rollSomeDice = function rollSomeDice(diceCountParm, 
plusMinusParm, modifierParm) {

}

Next, we need to verify that the diceCount Slot parameter (diceCountParm) has been provided (i.e. is not null):

if (diceCountParm == null) {
    speechOutput = "Sorry, I did not hear that.  Please say something like roll 2 dice.";
    return (speechOutput);
}

Then, we need to convert the string parameters into numbers.  Slot values are always provided as strings.  We need to verify that the diceCount and modifier contain numeric values and parse the strings to actual numbers.

var diceCount = (isNaN(diceCountParm)) ? null :
    parseInt(diceCountParm);
const diceSize = 6;
var modifier = (isNaN(modifierParm)) ? 0 :
    parseInt(modifierParm);

Now, we need to check again that diceCount is not null:

if (diceCount == null) {
    speechOutput = "Sorry, I did not hear that.  Please say something like roll 2 dice.";
    return (speechOutput);
}

Next, we’re going to set up an array of possible dice rolls so we can keep track of what we roll:

var buckets = [0, 0, 0, 0, 0, 0, 0];
var bucketSlots = ['zero', 'one', 'two', 'three', 'four', 'five', 'six'];

And then we can finally roll the dice:

for (var i = 0; i < diceCount; i++) {
    let roll = 0;
    roll = Math.floor(Math.random() * diceSize) + 1;

And if the user supplied a plusMinus Slot value (in plusMinusParm) and a modifier value, then we need to add or subtract the modifier to/from the dice roll:

if (plusMinusParm != null && modifier != 0) {
    // add or subtract the modifier to each roll
    if (plusMinusParm == 'plus') {
        roll += modifier;
    } else {
        roll -= modifier;
    }
}

Next, we need to put the roll in the correct bucket.  Note that because of the modifier, we could roll less than 1 or more than 6, so we need to handle those values too:

if (roll < 1) {     buckets[0]++; } else if (roll > 5) {
    buckets[6]++;
} else {
    buckets[roll]++;
}

Now, we’ve completed the for loop that loops over the diceCount and we can start putting together the speech phrases that we want Alexa to say in her response.  First, we’ll tackle the buckets so she can say “I rolled 3 ones, 2 twos, a four and 3 sixes.”  Note:  This code snippet uses apostrophes ( ‘ ‘ ) and backward tick marks ( ` ` ) to create the right strings.

var bucketsPhrase = 'I rolled ';
for (let i = 0; i < 7; i++) {     if (buckets[i] > 1) {
        if (i < 6) {
            bucketsPhrase += `${buckets[i]} ${bucketSlots[i]}s, `;
        } else {
            bucketsPhrase += `and ${buckets[i]} ${bucketSlots[i]}es `;
        }
    } else if (buckets[i] == 1) {
        if (i < 6) {
            bucketsPhrase += `a ${bucketSlots[i]}, `;
        } else {
            bucketsPhrase += `and a ${bucketSlots[i]} `;
        }
    }
}

The above code handles the fact that six is the only plural that ends in ‘es’.  And that six is the last number in the set, so put an ‘and’ in front of it.  It also differentiates between ‘a six’ and ‘two sixes’.

The next phrase to put together is the entire speech that we want Alexa to say.  We have two options:  with modifier and without, so the code looks like this:

if (modifier == 0 && target == null) {
    speechOutput = `You said roll ${diceCount} d ${diceSize}. ${bucketsPhrase}.`;
} else if (target == null) {
    speechOutput = `You said roll ${diceCount} d ${diceSize} ${plusMinusParm} ${modifier}. ${bucketsPhrase}.`;
}

Lastly, we return the speechOutput to Alexa:

return (speechOutput);

The Finished Function

Putting it all together, we get the following:

exports.rollSomeDice = function rollSomeDice(diceCountParm, plusMinusParm, modifierParm) {

    var speechOutput = "";
    if (diceCountParm == null) {
        speechOutput = "Sorry, I did not hear that.  Please say something like roll 2 dice.";
        return (speechOutput);
    }
    var diceCount = (isNaN(diceCountParm)) ? null :
        parseInt(diceCountParm);
    const diceSize = 6;
    var modifier = (isNaN(modifierParm)) ? 0 :
        parseInt(modifierParm);

    if (diceCount == null) {
        speechOutput = "Sorry, I did not hear the number of dice to roll.  Please say something like roll 2 dice.";
        return (speechOutput);
    }

    var buckets = [0, 0, 0, 0, 0, 0, 0];
    var bucketSlots = ['zero', 'one', 'two', 'three', 'four', 'five', 'six'];

    for (var i = 0; i < diceCount; i++) {
        let roll = 0;
        roll = Math.floor(Math.random() * diceSize) + 1;

        if (plusMinusParm != null && modifier != 0) {
            // add or subtract the modifier to each roll
            if (plusMinusParm == 'plus') {
                roll += modifier;
            } else {
                roll -= modifier;
            }
        }

        if (roll < 1) {             buckets[0]++;         } else if (roll > 5) {
            buckets[6]++;
        } else {
            buckets[roll]++;
        }
    }

    var bucketsPhrase = 'I rolled ';
    for (let i = 0; i < 7; i++) {         if (buckets[i] > 1) {
            if (i < 6) {
                bucketsPhrase += `${buckets[i]} ${bucketSlots[i]}s, `;
            } else {
                bucketsPhrase += `and ${buckets[i]} ${bucketSlots[i]}es `;
            }
        } else if (buckets[i] == 1) {
            if (i < 6) {
                bucketsPhrase += `a ${bucketSlots[i]}, `;
            } else {
                bucketsPhrase += `and a ${bucketSlots[i]} `;
            }
        }
    }

    if (modifier == 0) {
        speechOutput = `You said roll ${diceCount} dice. ${bucketsPhrase}.`;
    } else if (target == null) {
        speechOutput = `You said roll ${diceCount} dice ${plusMinusParm} ${modifier}. ${bucketsPhrase}.`;
    }

    return (speechOutput);
}

Modify the RollSomeDiceIntentHandler

Now that we have a function that can roll some dice, we need to wire it up to the RollSomeDiceIntentHandler we stubbed out in the basic template code in index.js.

Open index.js, right at the top we’re going to import the function we just created above, like so:

const Alexa = require('ask-sdk-core');
const i18n = require('i18next');
const sprintf = require('i18next-sprintf-postprocessor');
const diceRoller = require("./rollSomeDiceFunction.js");

Next, find the RollSomeDiceIntentHandler.  We’re going to replace the red, bold text in this function:

const RollSomeDiceIntentHandler = {
    canHandle(handlerInput) {
        return handlerInput.requestEnvelope.request.type === 'IntentRequest' &&
            handlerInput.requestEnvelope.request.intent.name === 'RollSomeDiceIntent';
    },
    handle(handlerInput) {

        const speechText = 'Hello World!';

        return handlerInput.responseBuilder
            .speak(speechText)
            .withSimpleCard('Roll Some Dice', speechText)
            .getResponse();
    },
};

First, delete the line const speechText = ‘Hello World!’;

Next, we need to pull the Slot values out of the request:

var diceCount = handlerInput.requestEnvelope.request.intent.slots["diceCount"].value;
var plusMinusSlot = handlerInput.requestEnvelope.request.intent.slots["plusMinus"];
var plusMinus = '';
if (plusMinusSlot.resolutions) {
    plusMinus = plusMinusSlot.resolutions.resolutionsPerAuthority[0].values[0].value.name;
}
var modifier = handlerInput.requestEnvelope.request.intent.slots["modifier"].value;

Next, we need to make sure the user supplied values for the Slots.  If not, we will assign a default value:

if (!(diceCount)) {
    diceCount = '1';
}
if (!(plusMinus)) {
    plusMinus = 'plus';
}
if (!(modifier)) {
    modifier = '0';
}

Next, we need to get the speech from the RollSomeDice() function we created earlier.

const sessionAttributes = handlerInput.attributesManager.getSessionAttributes();
sessionAttributes.speakOutput = diceRoller.rollSomeDice(diceCount, plusMinus, modifier) + " ... Now, what shall I roll?";
sessionAttributes.repromptSpeech = 'What shall I roll?';

Lastly, we need to return the speech to Alexa:

return handlerInput.responseBuilder
    .speak(sessionAttributes.speakOutput)
    .reprompt(sessionAttributes.repromptSpeech)
    .withSimpleCard('Roll Some Dice', sessionAttributes.speakOutput)
    .getResponse();

The Completed RollSomeDiceIntentHandler

Putting it all together, we get:

const RollSomeDiceIntentHandler = {
    canHandle(handlerInput) {
        return handlerInput.requestEnvelope.request.type === 'IntentRequest' &&
            handlerInput.requestEnvelope.request.intent.name === 'RollSomeDiceIntent';
    },
    handle(handlerInput) {

        var diceCount = handlerInput.requestEnvelope.request.intent.slots["diceCount"].value;
        var plusMinusSlot = handlerInput.requestEnvelope.request.intent.slots["plusMinus"];
        var plusMinus = '';
        if (plusMinusSlot.resolutions) {
            plusMinus = plusMinusSlot.resolutions.resolutionsPerAuthority[0].values[0].value.name;
        }
        var modifier = handlerInput.requestEnvelope.request.intent.slots["modifier"].value;

        if (!(diceCount)) {
            diceCount = '1';
        }
        if (!(plusMinus)) {
            plusMinus = 'plus';
        }
        if (!(modifier)) {
            modifier = '0';
        }

        const sessionAttributes = handlerInput.attributesManager.getSessionAttributes();
        sessionAttributes.speakOutput = diceRoller.rollSomeDice(diceCount, plusMinus, modifier) + " ... Now, what shall I roll?";
        sessionAttributes.repromptSpeech = 'What shall I roll?';

        return handlerInput.responseBuilder
            .speak(sessionAttributes.speakOutput)
            .reprompt(sessionAttributes.repromptSpeech)
            .withSimpleCard('Roll Some Dice', sessionAttributes.speakOutput)
            .getResponse();
    },
};

Publish the Code to Alexa and AWS Lambda

Now that the RollSomeDice() function and the RollSomeDiceIntentHandler are written, we can publish the skill to Alexa and AWS Lambda.

In VS Code, open the Command Palette (View | Command Palette…).  Type ASK.  Select Deploy the skill.

snip_20181106045852

Wait a moment while the ASK initializes, then select the default profile:

snip_20181106045953

Then select “All”:

snip_20181106050011

Now, it will try, and probably fail, to deploy. If it succeeds, great.  If it fails, you probably need to change the working directory in the Terminal window at the bottom right of VS Code.  This is done with the CD command, like so:

snip_20181106050219

Once you’ve changed the directory to the one with your skill in it, press the up arrow on the keyboard a couple of times to get the “ask deploy” command back.  Then press enter.

If all goes well, a few moments later, your skill will be deployed:

snip_20181106050943

Now that your skill is deployed, we can switch over to the Alexa Developer Console to test it.  If you still have your template project open from earlier, click “Your Skills” in the top left corner.  Then you will see your deployed skill at the top of the list:

snip_20181106051315

Click the Edit link on the far right.  On the Build tab, you should see a little popup saying your build was successful:

snip_20181106051456

Click on the Test tab.  If it’s not enabled, enable testing for this skill:

snip_20181106051713

In the Alexa Simulator, type in “start Roll Some Dice and roll 2 dice”.  Press enter.

snip_20181106051938

Alexa should say that she rolled some dice and list the results:

snip_20181109075156

I just realized, there’s one last step:  Submitting Your Skill for Amazon Approval.  This will be covered in my next blog entry.

An Alexa Skill in Javascript – Part 4

So far we’ve defined, gathered and installed the requisite tools in Part 1.  We’ve defined the Skill we’re creating in Part 2.  And, we’ve created the Intents for our skill in Part 3.

Now, we will code the Lambda function that is the brains of our skill in this entry, Part 4.

What is a Lambda Function?

Amazon Web Services (AWS) calls the functions you create in their cloud “Lambda Functions.”  For the purposes of Alexa skills, these functions are the brains behind the skill.  Alexa takes care of the speech part of the skill, and figures out how to interpret the Alexa User’s spoken words and which intent to choose and slots to fill based on those words, but you then have to make your Alexa skill actually do something to fulfill the intent you created on the Alexa side of your Skill development process.  In my case, that means writing a function that can roll some dice and add or subtract and specified modifier to the rolls.

Lambda functions can be written in a variety of languages.  I’ve blogged before about using C# to do this.  (Note:  Using C# is now SOOO much easier as the Lambda Function tools in AWS have been updated to use .Net Core as a standard platform.)  Today, I am using JavaScript (node.js).  Other options include Python, Java and Go.

A Little More Cleanup First

In our VS Code project, in the \lambda\custom folder is a file named package.json.  We need to correct the name of our package here.  My skill is called “Roll Some Dice” so I’ll have to name my code package “roll-some-dice”.  Note the lower case and replacing spaces with dashes.

{
  "name": "hello-world",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",

I’m going to simply replace “hello-world” with “roll-some-dice” and save and close the file.

{
  "name": "roll-some-dice",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",

In that same folder, is a file named package-lock.json.  We need to make the same edit to that file.  So:

{
  "name": "hello-world",
  "version": "1.0.0",
  "lockfileVersion": 1,

Becomes:

{
  "name": "roll-some-dice",
  "version": "1.0.0",
  "lockfileVersion": 1,

One More Thing Before We Can Code

In order to roll a dice, we need a random number generator.  This can be found in the node.js module named i18next.  We will also need the module named i18next-sprintf-postprocessor.  To get these, we will use the npm package manager.

On the left side of the VS Code screen, right click on the “custom” folder in the basic skill template and choose “Open in Terminal”:

snip_20181105201412

In the bottom right of the VS Code screen is the Terminal panel.  When you get to the command prompt:

snip_20181105202241

Type in the commands “npm install i18next” and then “npm install i18next-sprintf-postprocessor“:

snip_20181105203203

Once completed, you should have a couple of new folders in the “custom\node_modules” folder of your project:

snip_20181105203632

Finally, the Brains of the Operation (index.js)

Since we just added the modules above to the project, let’s also add them to index.js, then I’ll discuss what’s in there and what we need to do.

Include the i18next Modules in index.js

At the top of the index.js file, we need to include the i18next modules.  This is done by adding new constants for them below the Alexa constant, like so:

const Alexa = require('ask-sdk-core');
const i18n = require('i18next');
const sprintf = require('i18next-sprintf-postprocessor');

A Brief Tour of index.js

After the constants declared at the top of the file to include the required modules, there are several “Handler” functions.  The first is the LaunchRequestHandler:

const LaunchRequestHandler = {
  canHandle(handlerInput) {
    return handlerInput.requestEnvelope.request.type === 'LaunchRequest';
  },
  handle(handlerInput) {
    const speechText = 'Welcome to the Alexa Skills Kit, you can say hello!';
    return handlerInput.responseBuilder
      .speak(speechText)
      .reprompt(speechText)
      .withSimpleCard('Hello World', speechText)
      .getResponse();
  },
};

Each of the Handler functions handles one specific request from the user to Alexa.  In this case, it the LaunchRequestHandler can handle requests with a type of “LaunchRequest”.  When this Handler is fired, it builds a response that is sent back to the user.  This response includes some speech (what Alexa will say), a reprompt (what she will say to remind the user what to do, and a Card (to be displayed on the Echo Show and other devices with screens).

The other Handler functions are set up to handle the different intents of the skill:

  • HelloWorldIntentHandler  –  The main intent handler
  • HelpIntentHandler  –  Handles the case where the user asks Alexa for help
  • CancelAndStopIntentHandler  –  Handles Alexa, Stop and Alexa, Cancel\
  • SessionEndedRequestHandler  –  Handles the final request sent to the skill when the session ends
  • ErrorHandler  –  Handles any error situations

Next, we get to the lines that export the various Handlers so that the Alexa SkillBuilder knows which functions to call:

const skillBuilder = Alexa.SkillBuilders.custom();
exports.handler = skillBuilder
  .addRequestHandlers(
    LaunchRequestHandler,
    HelloWorldIntentHandler,
    HelpIntentHandler,
    CancelAndStopIntentHandler,
    SessionEndedRequestHandler
  )
  .addErrorHandlers(ErrorHandler)
  .lambda();

What We Need to Change

First, we will change the LaunchRequestHandler, as shown in bold, blue text below:

const LaunchRequestHandler = {
  canHandle(handlerInput) {
    return handlerInput.requestEnvelope.request.type === 'LaunchRequest';
  },
  handle(handlerInput) {
    const speechText = 'Welcome to Roll Some Dice.  You can say, Roll 2 Dice, or Roll 4 Dice plus 1!';
    return handlerInput.responseBuilder
      .speak(speechText)
      .reprompt(speechText)
      .withSimpleCard('Roll Some Dice', speechText)
      .getResponse();
  },
};

Next, we will fix the Card lines in all the other intents so that this:

.withSimpleCard('Hello World', speechText)

becomes:

.withSimpleCard('Roll Some Dice', speechText)

Next, we will change the Help Speech Text in the HelpIntentHandler to:

const speechText = 'You can say roll 2 dice, roll 3 dice plus 1, roll 6 dice minus 1 or any number of dice with any modifier.';

Next, we will completely change the HelloWorldIntentHandler.  We will do this in stages.  First, we need to change the name of the Handler and its corresponding entry in the SkillBuilder at the bottom of the file, like so:

const RollSomeDiceIntentHandler = {
  canHandle(handlerInput) {
    return handlerInput.requestEnvelope.request.type === 'IntentRequest'
      && handlerInput.requestEnvelope.request.intent.name === 'RollSomeDiceIntent';
  },
  handle(handlerInput) {
    var speechText = 'Hello World!';
    return handlerInput.responseBuilder
      .speak(speechText)
      .withSimpleCard('Roll Some Dice', speechText)
      .getResponse();
  },
};

...

const skillBuilder = Alexa.SkillBuilders.custom();
exports.handler = skillBuilder
  .addRequestHandlers(
    LaunchRequestHandler,
    RollSomeDiceIntentHandler,
    HelpIntentHandler,
    CancelAndStopIntentHandler,
    SessionEndedRequestHandler
  )
  .addErrorHandlers(ErrorHandler)
  .lambda();

Now, we have a Handler for our Intent that doesn’t actually do what it needs to do.
But, we do have a Handler.  At this point, we can deploy and test our Skill.

Deploying Your Skill to Alexa Developer Console and Amazon Web Services

It turns out, that with the ASK in VS Code, deploying our Skill to Alexa and Amazon is quite easy.  Open the Command Palette (View | Command Palette…).  Type ASK.  Select Deploy the skill.

snip_20181106045852

Wait a moment while the ASK initializes, then select the default profile:

snip_20181106045953

Then select “All”:

snip_20181106050011

Now, it will try, and probably fail, to deploy. If it succeeds, great.  If it fails, you probably need to change the working directory in the Terminal window at the bottom right of VS Code.  This is done with the CD command, like so:

snip_20181106050219

Once you’ve changed the directory to the one with your skill in it, press the up arrow on the keyboard a couple of times to get the “ask deploy” command back.  Then press enter.

If all goes well, a few moments later, your skill will be deployed:

snip_20181106050943

Now  that your skill is deployed, we can switch over to the Alexa Developer Console to test it.  If you still have your template project open from earlier, click “Your Skills” in the top left corner.  Then you will see your newly deployed skill at the top of the list:

snip_20181106051315

Click the Edit link on the far right.  On the Build tab, you should see a little popup saying your build was successful:

snip_20181106051456

Click on the Test tab.  If it’s not enabled, enable testing for this skill:

snip_20181106051713

In the Alexa Simulator, type in “start Roll Some Dice and roll 2 dice”.  Press enter.

snip_20181106051938

Alexa should say, “Hello World”.  (Remember, we haven’t actually told her how to roll some dice yet.)

snip_20181106052130

Notice the JSON Output from your Skill code in index.js:

snip_20181106052146

And scroll down to see the Card visual that would be shown on the Echo Show:

snip_20181106052221

That’s it for now.  In the final post in this series, I will teach Alexa to roll dice with a modifier and report the results.