Alexa course, Skill from "Add Persistence to Your Skill"


#1

Hi everyone,

I’ve some doubtys testing my Alexa Skill, “flashcards”: when is going to ask the question to the end user, “the question” is susbtituted by “undefined”; so apparently it’s not getting the question from the function AskQuestion…

–> Here is your first question. undefined

Any tip? someone could help me out?

thank you very much!

maxplay


#2

I’ve got the same issue :frowning:
Did you manage to sort it?


#3

hello, i found this post:

https://discuss.codecademy.com/t/6-string-interpolation-with-session-attributes-instruction-error/240578/2

and then i changed my code, but when testing the skill with the “Service Simulator”, i get this error:

“The remote endpoint could not be called, or the response it returned was invalid.”

so, if you manage to find out, let me know.

thanks


#4

Ok, i think it’s working right now:

'use strict';

var Alexa = require('alexa-sdk');

var flashcardsDictionary = [
    {
      question: 'How do you find the length of a string?',
      rubyAnswer: 'length',
      pythonAnswer: 'Len',
      javascriptAnswer: 'length'
    },
    {
      question: 'How do you print to the console or terminal?',
      rubyAnswer: 'puts',
      pythonAnswer: 'print',
      javascriptAnswer:'console.log'
    },
    {
       question:'Are the boolean terms true and false capitalized?',
       rubyAnswer: 'no',
       pythonAnswer: 'yes',
       javascriptAnswer: 'no'
     }];

var DECK_LENGTH = flashcardsDictionary.length;

var handlers = {

  // Open Codecademy Flashcards
  'LaunchRequest': function() {
    if(Object.keys(this.attributes).length === 0) {
        this.attributes.flashcards = {
          'currentLanguage': '',
          'languages': {
            'ruby': {
              'numberCorrect': 0,
              'currentFlashcardIndex': 0
            },
            'python': {
              'numberCorrect': 0,
              'currentFlashcardIndex': 0
            },
            'javascript': {
              'numberCorrect': 0,
              'currentFlashcardIndex': 0
            }
          }
        };

        this.response
            .listen('Welcome to Flashcards. Do you want to test your knowledge' +
            'in Ruby, Python, or Javascript?',
            'Which language would you like to practice?');

      } else {
        var currentLanguage = this.attributes.flashcards.currentLanguage.toLowerCase();
        var numberCorrect = this.attributes.flashcards.languages[currentLanguage].numberCorrect;
        var currentFlashcardIndex = this.attributes.flashcards.languages[currentLanguage].currentFlashcardIndex;

        this.response
            .listen('Welcome back to Flashcards. You are currently working on ' +
            currentLanguage + '. You\'re on question ' + currentFlashcardIndex +
            ' and have answered ' + numberCorrect + ' correctly.' +
            ' Do you want to test your knowledge in Ruby, Python, or Javascript?',
            'Which language would you like to practice?');

      }
    this.emit(':responseReady');
  },

  'SetMyLanguageIntent': function() {
    this.attributes.flashcards.currentLanguage = this.event.request.intent.slots.languages.value;
    var currentLanguage = this.attributes.flashcards.currentLanguage.toLowerCase();
    var currentFlashcardIndex = this.attributes.flashcards.languages[currentLanguage].currentFlashcardIndex;
    var message = '';
    
    if (currentFlashcardIndex < DECK_LENGTH) {
		message += 'Okay, I will ask you some questions about ' +
            currentLanguage + '. Here is your first question. ' + 
            askQuestion(this.attributes);
		this.response.speak(message).listen(askQuestion(this.attributes));
	} else {
		message += 'Sorry, there are no more questions for the ' + currentLanguage + ' language. Please, select another language.';
		this.response.speak(message).listen('Which language would you like to practice?');
	}

    this.emit(':responseReady');
  },

  // User gives an answer
  'AnswerIntent': function() {
    var currentLanguage = this.attributes.flashcards.currentLanguage.toLowerCase();
    var currentFlashcardIndex = this.attributes.flashcards.languages[currentLanguage].currentFlashcardIndex;
    var userAnswer = this.event.request.intent.slots.answer.value;
    var languageAnswer = currentLanguage.toLowerCase() + 'Answer';
    var correctAnswer = flashcardsDictionary[currentFlashcardIndex][languageAnswer];
	var numberCorrect = 0;
	var message = '';
	
	this.attributes.flashcards.languages[currentLanguage].currentFlashcardIndex++;

    if (userAnswer == correctAnswer){
        this.attributes.flashcards.languages[currentLanguage].numberCorrect++;
        numberCorrect = this.attributes.flashcards.languages[currentLanguage].numberCorrect;
		
		message = 'Nice job! The correct answer is ' + correctAnswer + '. You ' +
			'have gotten ' + numberCorrect + ' out of ' + DECK_LENGTH + ' ' +
			currentLanguage + ' questions correct. ';
			
		if (this.attributes.flashcards.languages[currentLanguage].currentFlashcardIndex < DECK_LENGTH) {
			message += 'Here is your next question. ' + askQuestion(this.attributes);
			this.response.speak(message).listen(askQuestion(this.attributes));
		} else {
			message += 'That was the last question. Thank you and let\'s play again soon.';
			this.response.speak(message);
		}
    } else {
        numberCorrect = this.attributes.flashcards.languages[currentLanguage].numberCorrect;
      
		message = 'Sorry, the correct answer is ' + correctAnswer + '. You ' +
			'have gotten ' + numberCorrect + ' out of ' + DECK_LENGTH + ' ' +
			currentLanguage + ' questions correct. ';

		if (this.attributes.flashcards.languages[currentLanguage].currentFlashcardIndex < DECK_LENGTH) {
			message += 'Here is your next question. ' + askQuestion(this.attributes);
			this.response.speak(message).listen(askQuestion(this.attributes));
		} else {
			message += 'That was the last question. Thank you and let\'s play again soon.';
			this.response.speak(message);
		}
    }

    this.emit(':responseReady');
  },

  // Stop
  'AMAZON.StopIntent': function() {
      this.response.speak('Ok, let\'s play again soon.');
      this.emit(':responseReady');
  },

  // Cancel
  'AMAZON.CancelIntent': function() {
      this.response.speak('Ok, let\'s play again soon.');
      this.emit(':responseReady');
  },

  // Save state
  'SessionEndedRequest': function() {
    console.log('session ended!');
    this.emit(':saveState', true);
  }

};

exports.handler = function(event, context, callback){
    var alexa = Alexa.handler(event, context, callback);
    alexa.dynamoDBTableName = 'CodecademyFlashcards';
    alexa.registerHandlers(handlers);
    alexa.execute();
};

function askQuestion(attributes) {
    var currentLanguage = attributes.flashcards.currentLanguage.toLowerCase();
    var currentFlashcardIndex = attributes.flashcards.languages[currentLanguage].currentFlashcardIndex;
    var currentQuestion = flashcardsDictionary[currentFlashcardIndex].question;	
	
    return 'In ' + currentLanguage +', ' + currentQuestion;
}

thanks @scriptsolver10453


#5

When testing with the online web “Service Simulator”, i get an error answering the 2nd question, so, i’m not sure if the code is 100% correct, i didn’t have the time to make a full test yet.


#6

Also, i added a few more Utterances and Answers to the interaction model …because this question:

‘Are the boolean terms true and false capitalized?’

may be answered like ‘Yes’ or ‘No’, so now the lambda code “anylanguageAnswer” matches the “Answers” from the interaction model.

hope it helps


#7

Although this thread may be a little old, maybe someone who just recently is just starting out, and who tried to use the code from the lesson, and who found that it didn’t work (i.e., like me yesterday) might find this update helpful.

Here’s how I’ve reworked the Lambda code so as to actually perform the functionality as described in the Codecademy Build Your First Alexa Skill, “Add Persistence to Your Skill” course, the Session Attributes lesson, in the Create the Lambda Function exercise.

Having said all that, I could never have gotten to this point had it not been for the excellent work by maxibelino. Only by examining your code could I figure out what was wrong with the Codecademy code. Tip of the hat to you, my friend!

In this reworking, I have:

  • taken AskQuestion out of the handlers section and made it a function call
  • changed how AskQuestion is called now that it’s in a different scope
  • added maxibelino’s excellent handling of the case of when we’re at the end of the deck,
  • and also fixed some typos,

In all cases where I changed from Codecademy code, I’ve added // comments at the top of the block or at the end of the line.

One thing I haven’t done is to add maxibelino’s separate handling of languages (e.g., currentLanguages, see: then next lessons about persistance.). Well actually I did, but I backed it out to make this example look as much like the Codecademy code as possible in the hopes of helping someone else who is just starting.

Oh, and I never could get Alexa to understand my answer the the Python question about length being “len”. I guess it’s my accent or something.

'use strict';

var Alexa = require('alexa-sdk');

var flashcardsDictionary = [
    {
      question: 'how do you find the length of a string?',
      rubyAnswer: 'length',
      pythonAnswer: 'len', //TYPO: corrected capitalization, probably unimportant
      javascriptAnswer: 'length'
    },
    {
      question: 'how do you print to the console or terminal?',
      rubyAnswer: 'puts',
      pythonAnswer: 'print',
      javascriptAnswer:'console.log'
    },
    {
       question:'are boolean terms capitalized or not capitalized?',
       rubyAnswer: 'not capitalized',
       pythonAnswer: 'capitalized',
       javascriptAnswer: 'not capitalized'
     }];

var DECK_LENGTH = flashcardsDictionary.length;

var handlers = {
  // Open Codecademy Flashcards
  'LaunchRequest': function() {
    this.attributes['language'] = '';
    this.attributes['numberCorrect'] = 0;
    this.attributes['currentFlashcardIndex'] = 0;

    this.response
        .speak('Welcome to Flashcards. In this session, do you want to test' + //CORRECTION: listen and speak reversed
        ' your knowledge in Ruby, Python, or Javascript?').listen(
        'Which language would you like to practice?');
    this.emit(':responseReady');
  },

  'SetMyLanguageIntent': function() {
    this.attributes['language'] = this.event.request.intent.slots.languages.value;
    var language = this.attributes['language'];

    this.response
        .speak('Okay, I will ask you some questions about ' +
        language + '. Here is your first question. ' + 
                AskQuestion(this.attributes)   ).listen(AskQuestion(this.attributes)); //CHANGED: definition of function call
    this.emit(':responseReady');
  },

  // User gives an answer
  'AnswerIntent': function() {
    var userAnswer = this.event.request.intent.slots.answer.value;
    var language = this.attributes['language'];
    var languageAnswer = language + 'Answer';
    var correctAnswer = flashcardsDictionary[this.attributes['currentFlashcardIndex']][languageAnswer];
    this.attributes['currentFlashcardIndex']++; //MOVED: from after "if (userAnswer === correctAnswer)...else..." construct


    if (userAnswer === correctAnswer) {
      this.attributes['numberCorrect']++;
      var numberCorrect = this.attributes['numberCorrect'];

// CHANGED: added test to give different response if we're at the end of the deck
//      this.response
//          .speak('Nice job! The correct answer is ' + correctAnswer + '. You ' +
//            'have gotten ' + numberCorrect + ' out of ' + DECK_LENGTH + ' ' +
//            language + ' questions correct.' + this.AskQuestion)
//          .listen(this.AskQuestion);
      if (this.attributes['currentFlashcardIndex'] < DECK_LENGTH) {
          this.response
              .speak('Nice job! The correct answer is ' + correctAnswer + '. You ' +
                'have gotten ' + numberCorrect + ' out of ' + DECK_LENGTH + ' ' +
                language + ' questions correct. ' + AskQuestion(this.attributes)) //CHANGED: definition of function call
              .listen(AskQuestion(this.attributes)); //CHANGED: definition of function call
      } else {
          this.response
              .speak('Nice job! The correct answer is ' + correctAnswer + '. You ' +
                'have gotten ' + numberCorrect + ' out of ' + DECK_LENGTH + ' ' +
                language + ' questions correct. That was the last question. Thank you and let\'s play again soon.' )
      }
    } else {
      var numberCorrect = this.attributes['numberCorrect'];
      
// CHANGED: added test to give different response if we're at the end of the deck
//     this.response
//          .speak('Sorry, the correct answer is ' + correctAnswer + '. You ' +
//          'have gotten ' + numberCorrect + ' out of ' + DECK_LENGTH + ' ' +
//          language + ' questions correct. Here is your next question.' + 
//                 this.AskQuestion).listen(this.AskQuestion);
     if (this.attributes['currentFlashcardIndex'] < DECK_LENGTH) {
          this.response
              .speak('Sorry, the correct answer is ' + correctAnswer + '. You ' +
                'have gotten ' + numberCorrect + ' out of ' + DECK_LENGTH + ' ' +
                language + ' questions correct. ' + AskQuestion(this.attributes)) //CHANGED: definition of function call
              .listen(AskQuestion(this.attributes)); //CHANGED: definition of function call
      } else {
          this.response
              .speak('Sorry, the correct answer is ' + correctAnswer + '. You ' +
                'have gotten ' + numberCorrect + ' out of ' + DECK_LENGTH + ' ' +
                language + ' questions correct. That was the last question. Thank you and let\'s play again soon.' )
      }
    }

//    this.attributes['currentFlashcardIndex']++; //MOVED: to before "if (userAnswer === correctAnswer)...else..." construct

    this.emit(':responseReady');
  },
  
// MOVED, CHANGED: this function was moved out of handlers and placed at bottom of index.js
//  // Test my {language} knowledge
//  'AskQuestion': function() {
//    var language = this.attributes['language'];
//    var currentQuestion = flashcardsDictionary[this.attributes['currentFlashcardIndex']].question;
//
//    return 'In ' + language +', ' + currentQuestion;
//  },

  // Stop
  'AMAZON.StopIntent': function() {
      this.response.speak('I heard the Stop Intent. Ok, let\'s play again soon.');
      this.emit(':responseReady');
  },

  // Cancel
  'AMAZON.CancelIntent': function() {
      this.response.speak('I heard the Cancel intent. Ok, let\'s play again soon.');
      this.emit(':responseReady');
  }
};

exports.handler = function(event, context, callback){
    var alexa = Alexa.handler(event, context);
    alexa.registerHandlers(handlers);
    alexa.execute();
};

// MOVED, CHANGED: this function was moved out of handlers and placed at bottom of index.js
function AskQuestion(attributes) {
  var language = attributes['language'];
  var currentQuestion = flashcardsDictionary[attributes['currentFlashcardIndex']].question;
  return 'In ' + language +', ' + currentQuestion;
}


In case it should ever change, this is the code that I started from, copied from Exercise 8, Create the Lambda Function, accessed on 2 March 2018. This code was my starting point:

'use strict';

var Alexa = require('alexa-sdk');

var flashcardsDictionary = [
    {
      question: 'how do you find the length of a string?',
      rubyAnswer: 'length',
      pythonAnswer: 'Len',
      javascriptAnswer: 'length'
    },
    {
      question: 'how do you print to the console or terminal?',
      rubyAnswer: 'puts',
      pythonAnswer: 'print',
      javascriptAnswer:'console.log'
    },
    {
       question:'are boolean terms capitalized or not capitalized?',
       rubyAnswer: 'not capitalized',
       pythonAnswer: 'capitalized',
       javascriptAnswer: 'not capitalized'
     }];

var DECK_LENGTH = flashcardsDictionary.length;

var handlers = {
  // Open Codecademy Flashcards
  'LaunchRequest': function() {
    this.attributes['language'] = '';
    this.attributes['numberCorrect'] = 0;
    this.attributes['currentFlashcardIndex'] = 0;

    this.response
        .listen('Welcome to Flashcards. In this session, do you want to test' +
        ' your knowledge in Ruby, Python, or Javascript?').speak(
        'Which language would you like to practice?');
    this.emit(':responseReady');
  },

  'SetMyLanguageIntent': function() {
    this.attributes['language'] = this.event.request.intent.slots.languages.value;
    var language = this.attributes['language'];

    this.response
        .speak('Okay, I will ask you some questions about ' +
        language + '. Here is your first question.' + 
                this.AskQuestion).listen(this.AskQuestion);
    this.emit(':responseReady');
  },

  // User gives an answer
  'AnswerIntent': function() {
    var userAnswer = this.event.request.intent.slots.answer.value;
    var language = this.attributes['language'];
    var languageAnswer = language + 'Answer';
    var correctAnswer = flashcardsDictionary[this.attributes['currentFlashcardIndex']][languageAnswer];


    if (userAnswer === correctAnswer) {
      this.attributes['numberCorrect']++;
      var numberCorrect = this.attributes['numberCorrect'];

      this.response
          .speak('Nice job! The correct answer is ' + correctAnswer + '. You ' +
            'have gotten ' + numberCorrect + ' out of ' + DECK_LENGTH + ' ' +
            language + ' questions correct.' + this.AskQuestion)
          .listen(this.AskQuestion);


    } else {
      var numberCorrect = this.attributes['numberCorrect'];

      this.response
          .speak('Sorry, the correct answer is ' + correctAnswer + '. You ' +
          'have gotten ' + numberCorrect + ' out of ' + DECK_LENGTH + ' ' +
          language + ' questions correct. Here is your next question.' + 
                 this.AskQuestion).listen(this.AskQuestion);
    }

    this.attributes['currentFlashcardIndex']++;

    this.emit(':responseReady');
  },
  
    // Test my {language} knowledge
  'AskQuestion': function() {
    var language = this.attributes['language'];
    var currentQuestion = flashcardsDictionary[this.attributes['currentFlashcardIndex']].question;

    return 'In ' + language +', ' + currentQuestion;
  },

  // Stop
  'AMAZON.StopIntent': function() {
      this.response.speak('Ok, let\'s play again soon.');
      this.emit(':responseReady');
  },

  // Cancel
  'AMAZON.CancelIntent': function() {
      this.response.speak('Ok, let\'s play again soon.');
      this.emit(':responseReady');
  }
};

exports.handler = function(event, context, callback){
    var alexa = Alexa.handler(event, context);
    alexa.registerHandlers(handlers);
    alexa.execute();
};


#8

To test the lambda handler function, you can use following sample JSON triggered through “Test” tab on AWS->lambda->function.

Notice that you can set the value of currentFlashcardIndex and slot “languages” to ruby.

{
“session”: {
“new”: false,
“sessionId”: “amzn1.echo-api.session.[unique-value-here]”,
“attributes”: {
“currentFlashcardIndex”: 0
},
“user”: {
“userId”: “amzn1.ask.account.[unique-value-here]”
},
“application”: {
“applicationId”: “amzn1.ask.skill.[unique-value-here]”
}
},
“version”: “1.0”,
“request”: {
“locale”: “en-US”,
“timestamp”: “2016-10-27T21:06:28Z”,
“type”: “IntentRequest”,
“requestId”: “amzn1.echo-api.request.[unique-value-here]”,
“intent”: {
“slots”: {
“languages”: {
“name”: “languages”,
“value”: “ruby”
}
},
“name”: “SetMyLanguageIntent”
}
},
“context”: {
“AudioPlayer”: {
“playerActivity”: “IDLE”
},
“System”: {
“device”: {
“supportedInterfaces”: {
“AudioPlayer”: {}
}
},
“application”: {
“applicationId”: “amzn1.ask.skill.[unique-value-here]”
},
“user”: {
“userId”: “amzn1.ask.account.[unique-value-here]”
}
}
}
}


#9

I’m having issues with below line. When trying to access the object array:

  var languageAnswer = language + 'Answer';
  var correctAnswer = flashcardsDictionary[this.attributes['currentFlashcardIndex']][languageAnswer];

When I hardcode something like
var correctAnswer = flashcardsDictionary[this.attributes['currentFlashcardIndex']].rubyAnswer;

It will access the object correctly (notice the dot notation) and I also note that within the AskQuestion function the dot notation is also used

var currentQuestion = flashcardsDictionary[this.attributes['currentFlashcardIndex']].question;

Can someone please explain how to use the languageAnswer variable to access the right object within flashcardsDictionary?? @jdkeyes your code isn’t working for this aspect - returning ‘undefined’.

Thanks