/**
* This sample demonstrates a simple skill built with the Amazon Alexa Skills Kit.
* The Intent Schema, Custom Slots, and Sample Utterances for this skill, as well as
* testing instructions are located at http://amzn.to/1LzFrj6
*
* For additional samples, visit the Alexa Skills Kit Getting Started guide at
* http://amzn.to/1LGWsLG
*/
var Alexa = require('alexa-sdk');
var states = {
STARTMODE: '_STARTMODE', // Prompt the user to start or restart the game.
ASKMODE: '_ASKMODE', // Alexa is asking user the questions.
DESCRIPTIONMODE: '_DESCRIPTIONMODE' // Alexa is describing the final choice and prompting to start again or quit
};
// Questions
var nodes = [{ "node": 1, "message": "You have just woken up from a nap on a train heading to London. There is a person sitting beside you. Say yes if you want to talk to him. Say no if you want to pretend you are sleeping.", "yes": 2, "no": 3 },
{ "node": 2, "message": "He introduces himself as a detective who has been assigned to this train. Are you the kind of person who can be trusted? Say yes or no.", "no": 4, "yes": 14 },
{ "node": 3, "message": "You fall asleep and wake up to a loud screeching noise and people shouting. Say yes if you continue to sleep or no if you wake up and find out what is going on.", "yes": 5, "no": 6},
{ "node": 4, "message": "I see. It was nice to meet you. Have a nice day. You fall asleep sitting in your chair. You fall asleep and wake up to a loud screeching noise and people shouting. Say yes if you continue to sleep or no if you wake up and find out what is going on.", "yes": 5, "no": 6 },
{ "node": 5, "message": "You wake up as the announcement for arriving to London. The end", "yes": 0, "no": 0 },
{ "node": 6, "message": "You look around your seat. Everyone is sitting around and looking out the window. You are very hungry. Say yes if you want to get up and get some food or no if you want to look out the window", "yes": 7, "no": 8 },
{ "node": 7, "message": "You go to the dining car and get a sandwich. Say yes if you want to eat your food at the dining car. Say no if you want to go back to your seat.", "yes": 9, "no": 10 },
{ "node": 8, "message": "You go to the dining car and get a sandwich. Say yes if you want to eat your food at the dining car. Say no if you want to go back to your seat.", "yes": 9, "no": 10 },
{ "node": 9, "message": "You sit down and enjoy your sandwich. You sit across a strange looking man. Say yes if you want to talk to him. Say no if you want to go back to your seat.", "yes": 11, "no": 10 },
{ "node": 10, "message": "You go back to your seat right as the conductor announces that you have arrived in London. The End.", "yes": 0, "no": 0 },
{ "node": 11, "message": "You say hello to him but he just responds in beeps. he looks deeply in your eyes and you instantly fall asleep. You wake up in a perfectly clean room. The strange man appears beside you. Say yes if you want to scream. Say no if you just want to look at him.", "yes": 5, "no": 12 },
{ "node": 12, "message": "He explains that he’s an alien and he wants you to go to space and live with him. Say yes if you want to go to space with him. Say no if you don’t want to go to space.", "yes": 13, "no": 5 },
{ "node": 13, "message": "You go to space with him and have a wonderful time. The end.", "yes": 0, "no": 0 },
{ "node": 14, "message": "Oh good because there’s been a series of robberies on this train and I could use someone to help me and investigate. Can you help? Say yes or no.", "no": 3, "yes": 15 },
{ "node": 15, "message": "He brings you to a car with nothing but a large wooden box in the middle. He tells you that you need to stay here while he talks to the conductor. He leaves the train car from where you just came. You hear a loud bang. Say yes if you want to leave the car. Say no if you want to stay where you are", "yes": 16, "no": 22 },
{ "node": 16, "message": "You go to the other room and see the detective fighting with the conductor. There is a stick on the floor. Say yes to just watch. Say no if you want to pick up the stick and hit the conductor.", "yes": 17, "no": 21 },
{ "node": 17, "message": "The conductor pushes the detective out of the window. He looks back at you. Say yes if you run away. Say no if you fight the conductor", "yes": 18, "no": 19 },
{ "node": 18, "message": "You get to the dining car before he catches up to you. Say yes if you want to shout for help. Say no if you want to fight him", "yes": 19, "no": 20 },
{ "node": 19, "message": "Everyone turns to see him trying to physically assault you. Cops intervene at the last second and arrest him. You are a hero and win free rides for life. The end.", "yes": 0, "no": 0 },
{ "node": 20, "message": "You throw him out the window. Cops suddenly appear and arrest you. You arrive in London and get thrown in jail. The end.", "yes": 0, "no": 0 },
{ "node": 21, "message": "The conductor falls down. The detective explains that the conductor is an impersonator and is trying to hijack the train. He thanks you for help. He gives you the key to the city. The end.", "yes": 0, "no": 0 },
{ "node": 22, "message": "Three men come into the room and tell you that you shouldn’t be here. Say yes to stay or say no to leave.", "yes": 23, "no": 24 },
{ "node": 23, "message": "They pick you up and throw you off the train. The end.", "yes": 0, "no": 0 },
{ "node": 24, "message": "They let you leave. You find the detective and tell him what happened. He takes out a gun. He tells you to go back to your seat. Say yes if you want to follow him. Say no if you want to go back to your seat.", "yes": 25, "no": 28 },
{ "node": 25, "message": "You stand at the door as the detective handcuffs the men to the wall. He looks around and then goes to the box and opens it. It appears to be a very expensive car. He gets in the car and turns it on. Say yes if you want to shout at him. Say no if you want to write down his license number.", "yes": 26, "no": 27 },
{ "node": 26, "message": "He looks at you and drives away. The end", "yes": 0, "no": 0 },
{ "node": 27, "message": "He drives off the train. You give the number to the police officer when you arrive to London. The detective is arrested and you are a hero. The end.", "yes": 0, "no": 0 },
{ "node": 28, "message": "You go back to your seat and fall asleep. You wake up right as the conductor announces you’ve arrived in London. The End.", "yes": 0, "no": 0 },
];
// this is used for keep track of visted nodes when we test for loops in the tree
var visited;
// These are messages that Alexa says to the user during conversation
// This is the intial welcome message
var welcomeMessage = "Welcome to Adventure on the Train, say yes to start the game. ";
// This is the message that is repeated if the response to the initial welcome message is not heard
var repeatWelcomeMessage = "Say yes to start the game or no to quit.";
// this is the message that is repeated if Alexa does not hear/understand the reponse to the welcome message
var promptToStartMessage = "Say yes to continue, or no to end the game.";
// This is the prompt during the game when Alexa doesnt hear or understand a yes / no reply
var promptToSayYesNo = "Say yes or no to answer the question.";
// This is the response to the user after the final question when Alex decides on what group choice the user should be given
var decisionMessage = "I think you would make a good";
// This is the prompt to ask the user if they would like to hear a short description of thier chosen profession or to play again
var playAgainMessage = "Do you want to play again?";
// this is the help message during the setup at the beginning of the game
var helpMessage = "This a mystery choose your adventure story. Want to start now?";
// This is the goodbye message when the user has asked to quit the game
var goodbyeMessage = "Ok, see you next time!";
var speechNotFoundMessage = "Could not find speech for node";
var nodeNotFoundMessage = "In nodes array could not find node";
var descriptionNotFoundMessage = "Could not find description for node";
var loopsDetectedMessage = "A repeated path was detected on the node tree, please fix before continuing";
var utteranceTellMeMore = "tell me more";
var utterancePlayAgain = "play again";
// the first node that we will use
var START_NODE = 1;
// --------------- Handlers -----------------------
// Called when the session starts.
exports.handler = function (event, context, callback) {
var alexa = Alexa.handler(event, context);
alexa.registerHandlers(newSessionHandler, startGameHandlers, askQuestionHandlers, descriptionHandlers);
alexa.execute();
};
// set state to start up and welcome the user
var newSessionHandler = {
'LaunchRequest': function () {
this.handler.state = states.STARTMODE;
this.emit(':ask', welcomeMessage, repeatWelcomeMessage);
},'AMAZON.HelpIntent': function () {
this.handler.state = states.STARTMODE;
this.emit(':ask', helpMessage, helpMessage);
},
'Unhandled': function () {
this.handler.state = states.STARTMODE;
this.emit(':ask', promptToStartMessage, promptToStartMessage);
}
};
// --------------- Functions that control the skill's behavior -----------------------
// Called at the start of the game, picks and asks first question for the user
var startGameHandlers = Alexa.CreateStateHandler(states.STARTMODE, {
'AMAZON.YesIntent': function () {
// ---------------------------------------------------------------
// check to see if there are any loops in the node tree - this section can be removed in production code
visited = [nodes.length];
var loopFound = helper.debugFunction_walkNode(START_NODE);
if( loopFound === true)
{
// comment out this line if you know that there are no loops in your decision tree
//this.emit(':tell', loopsDetectedMessage);
}
// ---------------------------------------------------------------
// set state to asking questions
this.handler.state = states.ASKMODE;
// ask first question, the response will be handled in the askQuestionHandler
var message = helper.getSpeechForNode(START_NODE);
// record the node we are on
this.attributes.currentNode = START_NODE;
// ask the first question
this.emit(':ask', message, message);
},
'AMAZON.NoIntent': function () {
// Handle No intent.
this.emit(':tell', goodbyeMessage);
},
'AMAZON.StopIntent': function () {
this.emit(':tell', goodbyeMessage);
},
'AMAZON.CancelIntent': function () {
this.emit(':tell', goodbyeMessage);
},
'AMAZON.StartOverIntent': function () {
this.emit(':ask', promptToStartMessage, promptToStartMessage);
},
'AMAZON.HelpIntent': function () {
this.emit(':ask', helpMessage, helpMessage);
},
'Unhandled': function () {
this.emit(':ask', promptToStartMessage, promptToStartMessage);
}
});
// user will have been asked a question when this intent is called. We want to look at their yes/no
// response and then ask another question. If we have asked more than the requested number of questions Alexa will
// make a choice, inform the user and then ask if they want to play again
var askQuestionHandlers = Alexa.CreateStateHandler(states.ASKMODE, {
'AMAZON.YesIntent': function () {
// Handle Yes intent.
helper.yesOrNo(this,'yes');
},
'AMAZON.NoIntent': function () {
// Handle No intent.
helper.yesOrNo(this, 'no');
},
'AMAZON.HelpIntent': function () {
this.emit(':ask', promptToSayYesNo, promptToSayYesNo);
},
'AMAZON.StopIntent': function () {
this.emit(':tell', goodbyeMessage);
},
'AMAZON.CancelIntent': function () {
this.emit(':tell', goodbyeMessage);
},
'AMAZON.StartOverIntent': function () {
// reset the game state to start mode
this.handler.state = states.STARTMODE;
this.emit(':ask', welcomeMessage, repeatWelcomeMessage);
},
'Unhandled': function () {
this.emit(':ask', promptToSayYesNo, promptToSayYesNo);
}
});
// user has heard the final choice and has been asked if they want to hear the description or to play again
var descriptionHandlers = Alexa.CreateStateHandler(states.DESCRIPTIONMODE, {
'AMAZON.YesIntent': function () {
// Handle Yes intent.
// reset the game state to start mode
this.handler.state = states.STARTMODE;
this.emit(':ask', welcomeMessage, repeatWelcomeMessage);
},
'AMAZON.NoIntent': function () {
// Handle No intent.
this.emit(':tell', goodbyeMessage);
},
'AMAZON.HelpIntent': function () {
this.emit(':ask', promptToSayYesNo, promptToSayYesNo);
},
'AMAZON.StopIntent': function () {
this.emit(':tell', goodbyeMessage);
},
'AMAZON.CancelIntent': function () {
this.emit(':tell', goodbyeMessage);
},
'AMAZON.StartOverIntent': function () {
// reset the game state to start mode
this.handler.state = states.STARTMODE;
this.emit(':ask', welcomeMessage, repeatWelcomeMessage);
},
'DescriptionIntent': function () {
//var reply = this.event.request.intent.slots.Description.value;
//console.log('HEARD: ' + reply);
helper.giveDescription(this);
},
'Unhandled': function () {
this.emit(':ask', promptToSayYesNo, promptToSayYesNo);
}
});
// --------------- Helper Functions -----------------------
var helper = {
// gives the user more information on their final choice
giveDescription: function (context) {
// get the speech for the child node
var description = helper.getDescriptionForNode(context.attributes.currentNode);
var message = description + ', ' + repeatWelcomeMessage;
context.emit(':ask', message, message);
},
// logic to provide the responses to the yes or no responses to the main questions
yesOrNo: function (context, reply) {
// this is a question node so we need to see if the user picked yes or no
var nextNodeId = helper.getNextNode(context.attributes.currentNode, reply);
// error in node data
if (nextNodeId == -1)
{
context.handler.state = states.STARTMODE;
// the current node was not found in the nodes array
// this is due to the current node in the nodes array having a yes / no node id for a node that does not exist
context.emit(':tell', nodeNotFoundMessage, nodeNotFoundMessage);
}
// get the speech for the child node
var message = helper.getSpeechForNode(nextNodeId);
// have we made a decision
if (helper.isAnswerNode(nextNodeId) === true) {
// set the game state to description mode
context.handler.state = states.DESCRIPTIONMODE;
// append the play again prompt to the decision and speak it
message = message + ' ,' + playAgainMessage;
}
// set the current node to next node we want to go to
context.attributes.currentNode = nextNodeId;
context.emit(':ask', message, message);
},
// gets the description for the given node id
getDescriptionForNode: function (nodeId) {
for (var i = 0; i < nodes.length; i++) {
if (nodes[i].node == nodeId) {
return nodes[i].description;
}
}
return descriptionNotFoundMessage + nodeId;
},
// returns the speech for the provided node id
getSpeechForNode: function (nodeId) {
for (var i = 0; i < nodes.length; i++) {
if (nodes[i].node == nodeId) {
return nodes[i].message;
}
}
return speechNotFoundMessage + nodeId;
},
// checks to see if this node is an choice node or a decision node
isAnswerNode: function (nodeId) {
for (var i = 0; i < nodes.length; i++) {
if (nodes[i].node == nodeId) {
if (nodes[i].yes === 0 && nodes[i].no === 0) {
return true;
}
}
}
return false;
},
// gets the next node to traverse to based on the yes no response
getNextNode: function (nodeId, yesNo) {
for (var i = 0; i < nodes.length; i++) {
if (nodes[i].node == nodeId) {
if (yesNo == "yes") {
return nodes[i].yes;
}
return nodes[i].no;
}
}
// error condition, didnt find a matching node id. Cause will be a yes / no entry in the array but with no corrosponding array entry
return -1;
},
// Recursively walks the node tree looking for nodes already visited
// This method could be changed if you want to implement another type of checking mechanism
// This should be run on debug builds only not production
// returns false if node tree path does not contain any previously visited nodes, true if it finds one
debugFunction_walkNode: function (nodeId) {
// console.log("Walking node: " + nodeId);
if( helper.isAnswerNode(nodeId) === true) {
// found an answer node - this path to this node does not contain a previously visted node
// so we will return without recursing further
// console.log("Answer node found");
return false;
}
// mark this question node as visited
if( helper.debugFunction_AddToVisited(nodeId) === false)
{
// node was not added to the visited list as it already exists, this indicates a duplicate path in the tree
return true;
}
// console.log("Recursing yes path");
var yesNode = helper.getNextNode(nodeId, "yes");
var duplicatePathHit = helper.debugFunction_walkNode(yesNode);
if( duplicatePathHit === true){
return true;
}
// console.log("Recursing no");
var noNode = helper.getNextNode(nodeId, "no");
duplicatePathHit = helper.debugFunction_walkNode(noNode);
if( duplicatePathHit === true){
return true;
}
// the paths below this node returned no duplicates
return false;
},
// checks to see if this node has previously been visited
// if it has it will be set to 1 in the array and we return false (exists)
// if it hasnt we set it to 1 and return true (added)
debugFunction_AddToVisited: function (nodeId) {
if (visited[nodeId] === 1) {
// node previously added - duplicate exists
// console.log("Node was previously visited - duplicate detected");
return false;
}
// was not found so add it as a visited node
visited[nodeId] = 1;
return true;
}
};
Comments