Challenge Alexa to a battle of pi knowledge!
AWS LambdaTo bestow Alexa with the knowlege of pi...
- Go to the AWS Portal
- Create a Lambda function
- Type Alexa into the filter and select alexa-skills-color-expert
- For the trigger click on the dotted box on the left and choose Alexa Skills Kit
- On the next screen, name the function, pick Node.js 4.3 for the runtime, paste in the code from the lambda.js file in GitHub (or copy it from below), pick lambda_basic_execution for the Execution Policy then continue onward
'use strict';
var questions = [
{
"What does Pi represent?": [
"The ratio of a circle's circumference to its diameter",
"A circle's diameter over it's radius",
"The ratio of a circle's diameter to its circumference",
"A sweet treat"
]
}
, {
"Which number comes next, 3.14159265?": [
"4",
"9",
"3",
"7"
]
}
, {
"On what date is Pi approximation day?": [
"July 22nd",
"Pi day the 13th",
"August 12th",
"March 15th"
]
}
, {
"How many Pi's are in a Tau?": [
"2",
"Pi",
"10",
"1000"
]
}
, {
"The Chinese mathematician Zu Chongzhi, around 480 AD and using a 12,288-sided polygon, calculated Pi accurately to how many places?": [
"7",
"2",
"27",
"103"
]
}
, {
"The digits of Pi were calculated to a new record in the fall of 1999. Approximately how many places was it calculated to?": [
"206 billion",
"Pi places",
"809 million",
"4.3 trillion"
]
}
, {
"Piphilology is a term meaning what?": [
"The practice of memorizing the digits of Pi",
"The philosophical study of Pi's value",
"The tautology of utilizing circular arguments",
"Eating"
]
}
, {
"Which of these formulae utilize Pi?": [
"All of them",
"Heisenberg's uncertainty principle",
"Earth's gravitational acceleration",
"Euler's buckling formula"
]
}
, {
"What type of number is Pi?": [
"Transcendental",
"Rational",
"Irrational",
"Natural"
]
}
, {
"The infinite series 1 over 1 squared plus 1 over 2 squared plus 1 over 3 squared ... plus 1 over lodge logic error detect ted ... equates to what what?": [
"Pi squared over 6",
"1 over Pi",
"e to the Pi power",
"The natural log of pi"
]
}
, {
"How many degrees are in Pi radians?": [
"180",
"360",
"90",
"98.6"
]
}
, {
"Pi is Greek for which letter?": [
"p",
"g",
"m",
"t"
]
}
, {
"Which number appears most in Pi's first 6 billion digits?": [
"1",
"6",
"0",
"9"
]
}
, {
"What is the cosine of 2 Pi?": [
"1",
"0",
"-1",
"Why does Pi need a cosigner?"
]
}
, {
"Which of these is false?": [
"A person has memorized more than 130,000 digits of Pi",
"The first recorded value of Pi was from the Babylonians",
"The first 31 digits of Pi do not contain 0",
"A calculation for Pi appears in the Bible"
]
}
];
exports.handler = function (event, context) {
try {
console.log("event.session.application.applicationId=" + event.session.application.applicationId);
//if (event.session.application.applicationId !== "YOUR_SKILL_ID") {
//context.fail("Invalid Application ID");
//}
if (event.session.new) {
onSessionStarted({requestId: event.request.requestId}, event.session);
}
if (event.request.type === "LaunchRequest") {
onLaunch(event.request,
event.session,
function callback(sessionAttributes, speechletResponse) {
context.succeed(buildResponse(sessionAttributes, speechletResponse));
});
} else if (event.request.type === "IntentRequest") {
onIntent(event.request,
event.session,
function callback(sessionAttributes, speechletResponse) {
context.succeed(buildResponse(sessionAttributes, speechletResponse));
});
} else if (event.request.type === "SessionEndedRequest") {
onSessionEnded(event.request, event.session);
context.succeed();
}
} catch (e) {
context.fail("Exception: " + e);
}
};
/**
* Called when the session starts.
*/
function onSessionStarted(sessionStartedRequest, session) {
console.log("onSessionStarted requestId=" + sessionStartedRequest.requestId
+ ", sessionId=" + session.sessionId);
// add any session init logic here
}
/**
* Called when the user invokes the skill without specifying what they want.
*/
function onLaunch(launchRequest, session, callback) {
console.log("onLaunch requestId=" + launchRequest.requestId
+ ", sessionId=" + session.sessionId);
getWelcomeResponse(callback);
}
/**
* Called when the user specifies an intent for this skill.
*/
function onIntent(intentRequest, session, callback) {
console.log("onIntent requestId=" + intentRequest.requestId
+ ", sessionId=" + session.sessionId);
var intent = intentRequest.intent,
intentName = intentRequest.intent.name;
// handle yes/no intent after the user has been prompted
if (session.attributes && session.attributes.userPromptedToContinue) {
delete session.attributes.userPromptedToContinue;
if ("AMAZON.NoIntent" === intentName) {
handleFinishSessionRequest(session, callback);
} else if ("AMAZON.YesIntent" === intentName) {
handleRepeatRequest(session, callback);
}
}
// dispatch custom intents to handlers here
if ("AnswerIntent" === intentName) {
handleAnswerRequest(intent, session, callback);
} else if ("AnswerOnlyIntent" === intentName) {
handleAnswerRequest(intent, session, callback);
} else if ("DontKnowIntent" === intentName) {
handleAnswerRequest(intent, session, callback);
} else if ("AMAZON.YesIntent" === intentName) {
handleAnswerRequest(intent, session, callback);
} else if ("AMAZON.NoIntent" === intentName) {
handleAnswerRequest(intent, session, callback);
} else if ("AMAZON.StartOverIntent" === intentName) {
getWelcomeResponse(callback);
} else if ("AMAZON.RepeatIntent" === intentName) {
handleRepeatRequest(session, callback);
} else if ("AMAZON.HelpIntent" === intentName) {
handleGetHelpRequest(session, callback);
} else if ("AMAZON.StopIntent" === intentName) {
handleFinishSessionRequest(session, callback);
} else if ("AMAZON.CancelIntent" === intentName) {
handleFinishSessionRequest(session, callback);
} else {
throw "Invalid intent";
}
}
/**
* Called when the user ends the session.
* Is not called when the skill returns shouldEndSession=true.
*/
function onSessionEnded(sessionEndedRequest, session) {
console.log("onSessionEnded requestId=" + sessionEndedRequest.requestId
+ ", sessionId=" + session.sessionId);
// Add any cleanup logic here
}
// ------- Skill specific business logic -------
var ANSWER_COUNT = 4;
var GAME_LENGTH = 5;
var CARD_TITLE = "Pi Quiz"; // Be sure to change this for your skill.
function getWelcomeResponse(callback) {
var speechOutput = "Welcome to Pi Quiz. I will ask you " + GAME_LENGTH.toString()
+ " questions, try to get as many right as you can. Just say the number of the answer. Let's begin. ",
shouldEndSession = false,
gameQuestions = populateGameQuestions(),
correctAnswerIndex = Math.floor(Math.random() * (ANSWER_COUNT)), // Generate a random index for the correct answer, from 0 to 3
roundAnswers = populateRoundAnswers(gameQuestions, 0, correctAnswerIndex),
currentQuestionIndex = 0,
spokenQuestion = Object.keys(questions[gameQuestions[currentQuestionIndex]])[0],
repromptText = "Question 1. " + spokenQuestion + " ",
i;
for (i = 0; i < ANSWER_COUNT; i++) {
repromptText += (i + 1).toString() + ". " + roundAnswers[i] + ". ";
}
speechOutput += repromptText;
var sessionAttributes = {
"speechOutput": repromptText,
"repromptText": repromptText,
"currentQuestionIndex": currentQuestionIndex,
"correctAnswerIndex": correctAnswerIndex + 1,
"questions": gameQuestions,
"score": 0,
"correctAnswerText": questions[gameQuestions[currentQuestionIndex]][Object.keys(questions[gameQuestions[currentQuestionIndex]])[0]][0]
};
callback(sessionAttributes,
buildSpeechletResponse(CARD_TITLE, speechOutput, repromptText, shouldEndSession));
}
function populateGameQuestions() {
var gameQuestions = [];
var indexList = [];
var index = questions.length;
if (GAME_LENGTH > index) {
throw "Invalid Game Length.";
}
for (var i = 0; i < questions.length; i++) {
indexList.push(i);
}
// Pick GAME_LENGTH random questions from the list to ask the user, make sure there are no repeats.
for (var j = 0; j < GAME_LENGTH; j++) {
var rand = Math.floor(Math.random() * index);
index -= 1;
var temp = indexList[index];
indexList[index] = indexList[rand];
indexList[rand] = temp;
gameQuestions.push(indexList[index]);
}
return gameQuestions;
}
function populateRoundAnswers(gameQuestionIndexes, correctAnswerIndex, correctAnswerTargetLocation) {
// Get the answers for a given question, and place the correct answer at the spot marked by the
// correctAnswerTargetLocation variable. Note that you can have as many answers as you want but
// only ANSWER_COUNT will be selected.
var answers = [],
answersCopy = questions[gameQuestionIndexes[correctAnswerIndex]][Object.keys(questions[gameQuestionIndexes[correctAnswerIndex]])[0]],
temp, i;
var index = answersCopy.length;
if (index < ANSWER_COUNT) {
throw "Not enough answers for question.";
}
// Shuffle the answers, excluding the first element.
for (var j = 1; j < answersCopy.length; j++) {
var rand = Math.floor(Math.random() * (index - 1)) + 1;
index -= 1;
temp = answersCopy[index];
answersCopy[index] = answersCopy[rand];
answersCopy[rand] = temp;
}
// Swap the correct answer into the target location
for (i = 0; i < ANSWER_COUNT; i++) {
answers[i] = answersCopy[i];
}
temp = answers[0];
answers[0] = answers[correctAnswerTargetLocation];
answers[correctAnswerTargetLocation] = temp;
return answers;
}
function handleAnswerRequest(intent, session, callback) {
var speechOutput = "";
var sessionAttributes = {};
var gameInProgress = session.attributes && session.attributes.questions;
var answerSlotValid = isAnswerSlotValid(intent);
var userGaveUp = intent.name === "DontKnowIntent";
if (!gameInProgress) {
// If the user responded with an answer but there is no game in progress, ask the user
// if they want to start a new game. Set a flag to track that we've prompted the user.
sessionAttributes.userPromptedToContinue = true;
speechOutput = "There is no game in progress. Do you want to start a new game? ";
callback(sessionAttributes,
buildSpeechletResponse(CARD_TITLE, speechOutput, speechOutput, false));
} else if (!answerSlotValid && !userGaveUp) {
// If the user provided answer isn't a number > 0 and < ANSWER_COUNT,
// return an error message to the user. Remember to guide the user into providing correct values.
var reprompt = session.attributes.speechOutput;
speechOutput = "Your answer must be a number between 1 and " + ANSWER_COUNT + ". " + reprompt;
callback(session.attributes,
buildSpeechletResponse(CARD_TITLE, speechOutput, reprompt, false));
} else {
var gameQuestions = session.attributes.questions,
correctAnswerIndex = parseInt(session.attributes.correctAnswerIndex),
currentScore = parseInt(session.attributes.score),
currentQuestionIndex = parseInt(session.attributes.currentQuestionIndex),
correctAnswerText = session.attributes.correctAnswerText;
var speechOutputAnalysis = "";
if (answerSlotValid && parseInt(intent.slots.Answer.value) == correctAnswerIndex) {
currentScore++;
speechOutputAnalysis = "correct. ";
} else {
if (!userGaveUp) {
speechOutputAnalysis = "wrong. "
}
speechOutputAnalysis += "The correct answer is " + correctAnswerIndex + ": " + correctAnswerText + ". ";
}
// if currentQuestionIndex is 4, we've reached 5 questions (zero-indexed) and can exit the game session
if (currentQuestionIndex == GAME_LENGTH - 1) {
speechOutput = userGaveUp ? "" : "That answer is ";
speechOutput += speechOutputAnalysis + "You got " + currentScore.toString() + " out of "
+ GAME_LENGTH.toString() + " questions correct. Thank you for playing!";
callback(session.attributes,
buildSpeechletResponse(CARD_TITLE, speechOutput, "", true));
} else {
currentQuestionIndex += 1;
var spokenQuestion = Object.keys(questions[gameQuestions[currentQuestionIndex]])[0];
// Generate a random index for the correct answer, from 0 to 3
correctAnswerIndex = Math.floor(Math.random() * (ANSWER_COUNT));
var roundAnswers = populateRoundAnswers(gameQuestions, currentQuestionIndex, correctAnswerIndex),
questionIndexForSpeech = currentQuestionIndex + 1,
repromptText = "Question " + questionIndexForSpeech.toString() + ". " + spokenQuestion + " ";
for (var i = 0; i < ANSWER_COUNT; i++) {
repromptText += (i + 1).toString() + ". " + roundAnswers[i] + ". "
}
speechOutput += userGaveUp ? "" : "That answer is ";
speechOutput += speechOutputAnalysis + "Your score is " + currentScore.toString() + ". " + repromptText;
sessionAttributes = {
"speechOutput": repromptText,
"repromptText": repromptText,
"currentQuestionIndex": currentQuestionIndex,
"correctAnswerIndex": correctAnswerIndex + 1,
"questions": gameQuestions,
"score": currentScore,
"correctAnswerText": questions[gameQuestions[currentQuestionIndex]][Object.keys(questions[gameQuestions[currentQuestionIndex]])[0]][0]
};
callback(sessionAttributes,
buildSpeechletResponse(CARD_TITLE, speechOutput, repromptText, false));
}
}
}
function handleRepeatRequest(session, callback) {
if (!session.attributes || !session.attributes.speechOutput) {
getWelcomeResponse(callback);
} else {
callback(session.attributes,
buildSpeechletResponseWithoutCard(session.attributes.speechOutput, session.attributes.repromptText, false));
}
}
function handleGetHelpRequest(session, callback) {
session.attributes.userPromptedToContinue = true;
var speechOutput = "I will ask you " + GAME_LENGTH + " multiple choice questions. Respond with the number of the answer. "
+ "For example, say one, two, three, or four. To start a new game at any time, say, start game. "
+ "To repeat the last question, say, repeat. "
+ "Would you like to keep playing?",
repromptText = "To give an answer to a question, respond with the number of the answer . "
+ "Would you like to keep playing?";
var shouldEndSession = false;
callback(session.attributes,
buildSpeechletResponseWithoutCard(speechOutput, repromptText, shouldEndSession));
}
function handleFinishSessionRequest(session, callback) {
callback(session.attributes,
buildSpeechletResponseWithoutCard("Good bye!", "", true));
}
function isAnswerSlotValid(intent) {
var answerSlotFilled = intent.slots && intent.slots.Answer && intent.slots.Answer.value;
var answerSlotIsInt = answerSlotFilled && !isNaN(parseInt(intent.slots.Answer.value));
return answerSlotIsInt && parseInt(intent.slots.Answer.value) < (ANSWER_COUNT + 1) && parseInt(intent.slots.Answer.value) > 0;
}
// ------- Helper functions to build responses -------
function buildSpeechletResponse(title, output, repromptText, shouldEndSession) {
return {
outputSpeech: {
type: "PlainText",
text: output
},
card: {
type: "Simple",
title: title,
content: output
},
reprompt: {
outputSpeech: {
type: "PlainText",
text: repromptText
}
},
shouldEndSession: shouldEndSession
};
}
function buildSpeechletResponseWithoutCard(output, repromptText, shouldEndSession) {
return {
outputSpeech: {
type: "PlainText",
text: output
},
reprompt: {
outputSpeech: {
type: "PlainText",
text: repromptText
}
},
shouldEndSession: shouldEndSession
};
}
function buildResponse(sessionAttributes, speechletResponse) {
return {
version: "1.0",
sessionAttributes: sessionAttributes,
response: speechletResponse
};
}
- Once the function is created, find the ARN in the upper right, it should look like arn:aws:lambda:us-east-1:{UniqueID}:function:{FunctionName}. Save it for later
- Keep the tab open, we'll need the Skill Id in a bit
Now that we have the backend set up to handle requests, we'll need to configure the frontend.
- Go to the Amazon developer portal
- Click the Alexa tab
- Choose the Alexa Skills Kit
- Click Add a New Skill
- For Skill Information set the name to one of your choosing and give "pi quiz" for the invocation name
- Under the Interaction Model set the Intent Schema to
{
"intents": [
{
"intent": "AnswerIntent",
"slots": [
{
"name": "Answer",
"type": "LIST_OF_ANSWERS"
}
]
},
{
"intent": "AnswerOnlyIntent",
"slots": [
{
"name": "Answer",
"type": "LIST_OF_ANSWERS"
}
]
},
{
"intent": "DontKnowIntent"
},
{
"intent": "AMAZON.StartOverIntent"
},
{
"intent": "AMAZON.RepeatIntent"
},
{
"intent": "AMAZON.HelpIntent"
},
{
"intent": "AMAZON.YesIntent"
},
{
"intent": "AMAZON.NoIntent"
},
{
"intent": "AMAZON.StopIntent"
},
{
"intent": "AMAZON.CancelIntent"
}
]
}
create a Custom Slot Type named LIST_OF_ANSWERS with the values
1
2
3
4
and for sample utterances put
AnswerIntent the answer is {Answer}
AnswerIntent my answer is {Answer}
AnswerIntent is it {Answer}
AnswerIntent {Answer} is my answer
AnswerOnlyIntent {Answer}
AMAZON.StartOverIntent start game
AMAZON.StartOverIntent new game
AMAZON.StartOverIntent start
AMAZON.StartOverIntent start new game
DontKnowIntent i don't know
DontKnowIntent don't know
DontKnowIntent skip
DontKnowIntent i don't know that
DontKnowIntent who knows
DontKnowIntent i don't know this question
DontKnowIntent i don't know that one
DontKnowIntent dunno
- In the Configuration tab select AWS Lambda ARN and choose North America, then paste in the ARN from your lambda function.
- Now we can go back to the Lambda function and bind it to this skill. Towards the top of the page, in small font, you should see an ID like amzn1.echo-sdk-ams.app.{UniqueID} Copy it. Back in the lamba function go to line 151 in the code and replace the placeholder id with the skill id
- For the Publishing Information tab select Games, Trivia & Accessories for the Category and Knowledge & Trivia for the Sub Category. After some basic testing instructions and skill descriptions put "Alexa, ask pi quiz to start game. ", "My answer is 3. " and "I don't know that one. " for the Example Phrases. Upload some icons (I highly recommend vectr for online vector graphics) and then it's on to Privacy.
- For Privacy and Compliance say No to everything and check the box for Export Compliance.
- Finally, you can hit that sweet, sweet Submit for Certification button. With any luck it will be listed on the store for all to enjoy.
Congratulations! Now you're Alexa enabled device is ready for Pi Day!
Comments