Joe Reese
Published © GPL3+

Alexa, Begin Self Destruct Sequence!

AS a lifelong Star Trek fan I've always wanted to command my own Star Ship, and now, thanks to Amazon I can have the next best thing.

BeginnerProtip2 hours4,773
Alexa, Begin Self Destruct Sequence!

Things used in this project

Hardware components

Amazon Echo
Amazon Alexa Amazon Echo
×1

Software apps and online services

AWS Lambda
Amazon Web Services AWS Lambda
Alexa Skills Kit
Amazon Alexa Alexa Skills Kit

Story

Read more

Code

index.js

JavaScript
Star Trek trivia game source code.
'use strict';

/**
 * When editing your questions pay attention to your punctuation. Make sure you use question marks or periods.
 * Make sure the first answer is the correct one. Set at least 4 answers, any extras will be shuffled in.
 */
var questions = [
    {
        "What is the principle of Starfleet’s Prime Directive?": [
            "Non-intervention",
            "Diplomacy and nonviolence",
            "Providing assistance to those in need",
            "Exploration",
            "Scientific study"
        ]
    },
    {
        "What is the first rule of the Ferengi Rules of Aquisition?": [
            "Once you have their money, never give it back.",
            "Never allow family to stand in the way of opportunity.",
            "Never confuse wisdom with luck.",
            "Take joy from profit, and profit from joy."
        ]
    },
    {
        "What was Kirk's first Enterprise ship registered as?": [
            "NCC-1701",
            "NCC-1701 A",
            "NCC-1701 B",
            "NCC-1701 C"
        ]
    },
    {
        "What was Picard's Borg Name?": [
            "Locutus of Borg",
            "6 of 12",
            "Locust of borg",
            "Larry",
            "Emissary"
        ]
    },
    {
        "WWhat is the Name of Benjamin Sisco's ship?": [
            "The Defiant",
            "The Pegasus",
            "The Genesis",
            "The Rodenberry"
        ]
    },
    {
        "What species is Neelix?": [
            "Talaxian",
            "Tarquin",
            "Tellarite",
            "Platonians"
        ]
    },
    {
        "Who was the discraced scientist who created Lieutenant Commander Data?": [
            "Noonien Soong",
            "Tsung Tsu",
            "Cara Sorenson",
            "Michel Foucault"
        ]
    },
    {
        "Who lead the Klingon High Counsel following the death of K'mpec?": [
            "Gowron",
            "Warf",
            "Duras",
            "Kahless"
        ]
    },
    {
        "What Admiral led the Federation defense against the Borg?": [
            "Admiral Hanson",
            "Admiral Nogura",
            "Admiral Haftel",
            "Admiral Janeway",
            "Admiral Sulu"
        ]
    },
    {
        "Which phaser types were more characteristic of rifles?": [
            "Type Three Phasers",
            "Type Two Phasers",
            "Type One Phasers",
            "Type Two Disrupters"
        ]
    },
    {
        "Who was the captain in the pilot episode of the original series?": [
            "Christopher Pike",
            "James Kirk",
            "Jonathon Archer",
            "Michael Dorn"
        ]
    },
    {
        "Where did Captain Janeway chase the Marquis ship ?": [
            "The Badlands",
            "Earth",
            "The Delta Quadrant",
            "Vulcan"
        ]
    },
    {
        "What is the Name of Picard's Brother ?": [
            "Robert",
            "Michel",
            "Jean Marc",
            "Luc"
        ]
    },
    {
        "Including Rudolph, how many reindeer hooves are there?": [
            "36",
            "24",
            "16",
            "8"
        ]
    },
    {
        "Where was Picard Born?": [
            "LaBarre, France",
            "Nice, France",
            "La Bastille, France",
            "Naples, Italy"
        ]
    },
    {
        "Who protected the Ocampa?": [
            "The Caretaker",
            "Q",
            "Burl",
            "The Overlord"
        ]
    },
    {
        "James T Kirk - What does the 'T' stand for?": [
            "Tiberius",
            "Thomas",
            "Titus",
            "Tadius"
        ]
    },
    {
        "What sport did Captain Sisko play?": [
            "Baseball",
            "Basketball",
            "Water Polo",
            "Parchisi Squares"
        ]
    },
    {
        "Why did Spock believe that Captain Kirk was dead?": [
            "He killed him",
            "Killed by the Ferengi",
            "Lost in the Delta Quadrant",
            "He disappeared in transportation"
        ]
    },
    {
        "USS Enterprise Bartender Guinan, played by Whoopi Goldberg, is a member of what Long-lived species?": [
            "El-Aurian",
            "Denobulan",
            "Ferengi",
            "Human"
        ]
    },
    {
        "What is the name of the Klingon home world?": [
            "Qo'noS",
            "Klingonia",
            "Tak Mao",
            "Ka Pla"
        ]
    },
    {
        "Whick of the following was not a founding species of the united federation of plannets?": [
            "Betazoids",
            "Vulcans",
            "Andorians",
            "Tellarites"
        ]
    },
    {
        "What was seven of nine's given first name, before she was assimilated by the Borg?": [
            "Annika",
            "Alice",
            "Deanna",
            "Beverly"
        ]
    },
    {
        "What is the purpose of the Vulcan ritual of Kolinahr?": [
            "Purging of Emotions",
            "Sexual release and mating",
            "The transfer of one's consciousness into another body",
            "The union of two minds"
        ]
    },
    {
        "What is the name of the alien who killed Lietenant Tasha Yar?": [
            "Armus",
            "Khan",
            "Locutus",
            "Barnabus"
        ]
    },
    {
        "What title was Captain Benjamin Sisko given by the Bajorans?": [
            "The Emissary",
            "The prophet",
            "The Chosen One",
            "The all knowing"
        ]
    },
    {
        "The United Federation of Plannets was formally incorporated at an Interplanetray conference, Where was it held?": [
            "Babel",
            "Vulcan",
            "Rigel",
            "San Francisco, Terra"
        ]
    },
    {
        "What was the name of the play that Dr' Crusher wrote in a fist full of Datas?": [
            "Something fro Breakfast",
            "One in four",
            "Family is forever",
            "Who ate my hat?"
        ]
    },
    {
        "What was the name of the computer system built and programmed by the progeniotors of planet Aldea?": [
            "The custodian",
            "The Master",
            "The all knowing",
            "The oracle"
        ]
    },
    {
        "Who considered himself to be the Grandfather of Data?": [
            "Dr. Ira Graves",
            "Q",
            "Dr. Noonien Soong",
            "Dr. Soran"
        ]
    }
];

// Route the incoming request based on type (LaunchRequest, IntentRequest,
// etc.) The JSON body of the request is provided in the event parameter.
exports.handler = function (event, context) {
    try {
        console.log("event.session.application.applicationId=" + event.session.application.applicationId);

        /**
         * Uncomment this if statement and populate with your skill's application ID to
         * prevent someone else from configuring a skill that sends requests to this function.
         */

//     if (event.session.application.applicationId !== "amzn1.echo-sdk-ams.app.05aecccb3-1461-48fb-a008-822ddrt6b516") {
//         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(intent, session, callback);
        } else if ("AMAZON.YesIntent" === intentName) {
            handleRepeatRequest(intent, 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(intent, session, callback);
    } else if ("AMAZON.HelpIntent" === intentName) {
        handleGetHelpRequest(intent, session, callback);
    } else if ("AMAZON.StopIntent" === intentName) {
        handleFinishSessionRequest(intent, session, callback);
    } else if ("AMAZON.CancelIntent" === intentName) {
        handleFinishSessionRequest(intent, 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 = "Reindeer Games"; // Be sure to change this for your skill.

function getWelcomeResponse(callback) {
    var sessionAttributes = {},
        speechOutput = "Reindeer Games. 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, j;

    for (i = 0; i < ANSWER_COUNT; i++) {
        repromptText += (i+1).toString() + ". " + roundAnswers[i] + ". "
    }
    speechOutput += repromptText;
    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;

        var 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;
        var 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(intent, session, callback) {
    // Repeat the previous speechOutput and repromptText from the session attributes if available
    // else start a new game session
    if (!session.attributes || !session.attributes.speechOutput) {
        getWelcomeResponse(callback);
    } else {
        callback(session.attributes,
            buildSpeechletResponseWithoutCard(session.attributes.speechOutput, session.attributes.repromptText, false));
    }
}

function handleGetHelpRequest(intent, session, callback) {
    // Provide a help prompt for the user, explaining how the game is played. Then, continue the game
    // if there is one in progress, or provide the option to start another one.

    // Set a flag to track that we're in the Help state.
    session.attributes.userPromptedToContinue = true;

    // Do not edit the help dialogue. This has been created by the Alexa team to demonstrate best practices.

    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(intent, session, callback) {
    // End the session with a "Good bye!" if the user wants to quit the game
    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
    };
}

Intent Schema

JavaScript
Intents for Alexa Skill
{
  "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"
    }
  ]
}

Sample Utterances

JavaScript
Phrases used to interact with the application
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

Credits

Joe Reese

Joe Reese

6 projects • 18 followers
I love playing with electronics, and building useful devices. I have a Bachelors in software development and a Masters in IT security.

Comments

Add projectSign up / Login