/
index.js
executable file
·384 lines (311 loc) · 15.7 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
/**
* 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": "Do you like working with people", "yes": 2, "no": 3 },
{ "node": 2, "message": "Do you like caring for others", "yes": 4, "no": 5 },
{ "node": 3, "message": "Would you like to work during the day", "yes": 6, "no": 7 },
{ "node": 4, "message": "Can you stand the sight of blood", "yes": 8, "no": 9 },
{ "node": 5, "message": "Is money the most important thing in your life", "yes": 10, "no": 11 },
{ "node": 6, "message": "Do you want to work with animals", "yes": 12, "no": 13 },
{ "node": 7, "message": "Are you active", "yes": 14, "no": 15 },
// Answers & descriptions
{ "node": 8, "message": "Doctor", "yes": 0, "no": 0, "description": "A physician or medical doctor is a professional who practices medicine." },
{ "node": 9, "message": "Teacher", "yes": 0, "no": 0, "description": "In education, teachers facilitate student learning, often in a school or academy or perhaps in another environment such as outdoors."},
{ "node": 10, "message": "Sales person", "yes": 0, "no": 0 , "description": "A salesman is someone who works in sales, with the main function of selling products or services to others."},
{ "node": 11, "message": "Artist", "yes": 0, "no": 0 , "description": "An artist is a person engaged in one or more of any of a broad spectrum of activities related to creating art, practicing the arts, and, or demonstrating an art."},
{ "node": 12, "message": "Zookeeper", "yes": 0, "no": 0 , "description": "A zookeeper is a person who manages zoo animals that are kept in captivity for conservation or to be displayed to the public, and are usually responsible for the feeding and daily care of the animals."},
{ "node": 13, "message": "Software engineer", "yes": 0, "no": 0 , "description": "A software engineer is a person who applies the principles of software engineering to the design, development, maintenance, testing, and evaluation of the software and systems that make computers or anything containing software work."},
{ "node": 14, "message": "Security Guard", "yes": 0, "no": 0 , "description": "A security guard is a private person who is paid to protect an organization's assets from various hazards such as criminal activity, by utilizing preventative measures. "},
{ "node": 15, "message": "Lighthouse keeper", "yes": 0, "no": 0 , "description": "A lighthouse keeper is the person responsible for tending and caring for a lighthouse, particularly the light and lens in the days when oil lamps and clockwork mechanisms were used."},
];
// 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 decision tree, are you ready to play?";
// 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 = "Say 'tell me more' to hear a short description for this profession, or do you want to play again?";
// this is the help message during the setup at the beginning of the game
var helpMessage = "I will ask you some questions that will identify what you would be best at. 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 = decisionMessage + ' ' + 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;
}
};