Neste item será desenvolvido uma Skill responsável por realizar perguntas e respostas de um determinado assunto. Desse modo, iremos construir do zero, um Quiz - jogo de perguntas e respostas -, com o tema: eletrônica analógica.
Para isso, abra a página do console de desenvolvimento Alexa, clicando aqui , ou através do link https://developer.amazon.com/alexa/console/ask. Nessa página, clique no botão Create Skill. Em Skill name, escreva Quiz de Eletrônica Analógica; em Default Language, selecione Portuguese (BR), se já não estiver marcado; selecione o modelo Custom e o método de hospedagem do backend como sendo Alexa-Hosted (Node.js). As imagens a seguir exemplificam esse passos iniciais.
Feito isso, clique no botão Create Skill, localizado no canto superior direito. Na
próxima tela, deixe selecionado o template padrão, para uma Hello World Skill, e clique no botão Continue with template, como na imagem abaixo.
Nesse ponto a Skill inicial está sendo criada, sendo necessário alguns minutos para
a conclusão.
Com o projeto inicial da Skill criado, primeiramente vamos definir o nome de invocação para Skill, para isso, selecione o item Invocation, disponível no menu lateral esquerdo. No campo Skill Invocation Name, escreva: quiz de eletrônica analógica, esse será o nome utilizado todas as vezes que desejarmos chamar a Skill através da Alexa. Feito isso, clique no botão Save Model e após, no botão Build Model, para que a alteração seja salva e o modelo seja reconstruído; lembre-se sempre se clicar nesses botões ao realizar alguma alteração em sua Skill.
Agora é necessário criarmos as intenções que serão utilizadas pela nossa Skill, além de apagar a intenção HelloWorldIntent, já que não a utilizaremos. Para deletar essa intent, simplesmente clique a lixeira, em seu lado direito, como na imagem a seguir:
Uma janela de confirmação irá aparecer, clique em Delete Intent. Após isso, é hora de criarmos as intents que serão úteis em nossa aplicação. Clique no botão Add, na frente no nome intents (4). Escreva no campo Create custom intent o nome: QuizIntent. Essa intenção será responsável por gerenciar as perguntas do Quiz. Clique no botão Create custom intent. Essa etapa é representada na imagem a seguir:
Após criada a intent, devemos definir quais expressões irão chamar essa intenção. Como essa será a nossa intent principal, devemos utilizar frases que serão responsáveis por iniciar o jogo. Assim, no campo Sample Utterances, escreva as seguintes sentenças, sempre pressionando a tecla Enter, após a inserção de cada uma delas:
● começar quiz ● começar jogo ● jogar
Desse modo, sua tela deve se semelhante com a seguinte:
Feito isso, clique em Save Model e a seguir em Build Model. Agora vamos criar a intenção responsável por gerenciar as respostas das perguntas do quiz. Assim, realizando os mesmos passos anteriores, crie uma intent, com o nome: AnswerIntent. Após isso, ainda não defina nenhuma sentença, pois será necessário a criação de um novo tipo de variável, um novo Slot Type. Para isso, clique no item Add, em frente ao item de menu Slot Type, disponível no menu lateral esquerdo. Escreva: AnswerSlot, como nome do Slot Type. Após, clique no botão Create custom slot type. Como na imagem abaixo:
Esse passo de criação de um Slot Type customizado é importante, uma vez que não
existe um slot padrão que possua os valores que iremos utilizar para as respostas das perguntas do quiz. Utilizaremos as letras a,b e c, como respostas das questões, e para isso, é necessário que a Alexa reconheça quando estamos trabalhando com esses valores, através desse slot criado. É importante ressaltar que, sempre que existir na biblioteca
padrão, os Slots que irão ser utilizados, como por exemplo de datas, opte por eles, já que eles possuem maior compatibilidade. Em Slot Values, escreva os seguintes valores, seguidos pelo pressionamento da tecla Enter do teclado, para que o valor seja inserido :
● a. ● b. ● c.
Os valores de seu AnswerSlot, deve ser semelhante a este:
Observe que foi necessário um ponto (.), ao final de cada valor. Isso é necessário para que a Alexa reconheça que estamos trabalhando com letras e não palavras, já que ela poderia entender B como sendo Be, por exemplo.
Agora que já possuímos o Slot Type customizado, é necessário retornar à AnswerIntent, clicando sobre ela , no menu lateral esquerdo. Crie as seguintes expressões:
● {answer}
● alternativa {answer}
Sempre que desejarmos trabalhar com valores ditos variáveis, aqui chamados de slots, devemos utilizar o par de chaves, delimitando o slot. Quando um slot é digitado, uma
janela é mostrada, para que seja criado um novo slot ou utilizado um já existente. Para a primeira expressão criada, devemos criar o slot answer, já que ele ainda não foi definido. Já para a segunda expressão cadastrada, não é necessário. Após o cadastro das sentenças, sua tela deve estar assim:
Embaixo das utterances, estão os slot cadastrados na intent. Nesse caso temos o slot answer. Como na imagem a seguir.
Agora que já possuímos as expressões e o slot em nossa intent, demos definir qual é o tipo do slot criado. Nesse caso, o slot answer é do tipo AnswerSlot, criado anteriormente. Para isso selecione o Slot Type para o slot, na caixa de seleção à direita do nome do slot. Essa etapa é representada pela imagem a seguir.
Feito isso, clique nos botões Save Model e Build Model.
O próximo passo é definir uma intent responsável por gerenciar a troca de
perguntas, para que a Alexa reconheça quando desejarmos ir para a próxima pergunta do
quiz. Para isso, Clique em Add, em Intents, para uma nova intenção ser criada. Escreva:
NextQuestionIntent, como sendo o nome da Intent e clique no botão Create custom intent. Como na imagem abaixo.
As expressões que iremos criar para a intent são as seguintes:
● próxima pergunta ● próxima
● próximo
Após, clique no botão Save Model e Build Model.
Com a fase de construção da Skill concluído, deve-se agora, programar o backend da Skill, para que seja possível receber e responder as requisições. Assim, selecione o item de menu Code, disponível na barra de menu superior.
Com a janela do código aberto, primeiramente devemos apagar o objeto HelloWorldIntentHandler, já que esta Intent não existem mais. O bloco de código a ser deletado é o seguinte: const HelloWorldIntentHandler = { canHandle(handlerInput) { return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest' && Alexa.getIntentName(handlerInput.requestEnvelope) === 'HelloWorldIntent'; }, handle(handlerInput) {
const speakOutput = 'Hello World!';
return handlerInput.responseBuilder
.speak(speakOutput)
//.reprompt('add a reprompt if you want to keep the session open for the user to respond')
.getResponse(); }
};
Após deletado esse bloco de código, é necessário apagar também, a chamada desse
objeto na exportação do handler, ao final do código. Desse modo, localize a linha que
possua o nome do objeto deletado anteriormente e, exclua essa linha. Feito isso, clique no
botão Save e depois Deploy, para que o código seja salvo e compilado. Agora, esse bloco de
código deve parecer-se com o seguinte. exports.handler = Alexa.SkillBuilders.custom() .addRequestHandlers( LaunchRequestHandler, HelpIntentHandler, CancelAndStopIntentHandler, SessionEndedRequestHandler, IntentReflectorHandler, // make sure IntentReflectorHandler is last so it doesn't override your custom intent handlers ) .addErrorHandlers( ErrorHandler, ) .lambda(); Sempre que um novo Handler, ou seja, um objeto que gerencie uma Intent, for
criado, ele deverá ser adicionado no método addRequestHandlers, já que é a partir deste,
que a Alexa encontra os handlers, agindo assim, como um ponto de entrada para a Skill. Antes de criarmos os handlers para as intents definidas anteriormente, vamos criar
um novo arquivo, onde colocaremos as perguntas do nosso quiz e, utilizando o sistema de
módulos do Node.js, importaremos para o index.js, ou seja, para nosso arquivo principal.
Desse modo, na lateral esquerda da página, clique com o botão direito do mouse em cima
da pasta lambda, e selecione Create File. Crie o arquivo questions.js, como na imagem abaixo.
No arquivo de questões, copie o código a seguir:
const questions = [ {
question: 'Quantos diodos possui um retificador em ponte? Letra a, 1. Letra b, 2. Letra c, 4.',
answer: 'c',
answerSpeech: 'Um retificador em ponte possui 4 diodos.'
}, {
question: 'Qual a principal forma de atuação do diodo Zéner? Letra a, de forma direta. Letra b, de forma reversa. Letra c, de forma polar.',
answer: 'b',
answerSpeech: 'A principal forma de atuação do diodo Zéner é de forma reversa.'
}, {
question: 'Como é construído um diodo? Letra a, a partir da junção JK. Letra b, a partir da junção PN. Letra c, a partir da junção S R.',
answer: 'b' ,
de um semicondutor do tipo P com um semicondutor do tipo N.'
}, {
question: 'Qual a mínima tensão nominal, para um diodo de silício polarizado diretamente, necessária para reduzir a região de depleção do diodo e, fazê-lo começar a conduzir corrente elétrica de fato? Letra a, 0.8 Volts. Letra b, 0.6 Volts. Letra c, 0.7 Volts.',
answer: 'c' ,
answerSpeech: 'É necessário uma tensão nominal de no mínimo 0.7 Volts para um diodo de silício e de no mínimo 0.3 Volts para um diodo de
germânio.'
}, {
question: 'Qual a frequência na carga, para um retificador de onda completa? Letra a, a mesma frequência de entrada. Letra b, duas vezes a frequência de entrada. Letra c, três vezes a frequência de entrada.',
answer: 'b' ,
answerSpeech: 'A frequência na carga, para um retificador de onda completa é duas vezes a frequência de entrada.'
}, {
question: `Qual a tensão de pico na carga, para um retificador de onda completa em ponte? Supondo uma tensão de pico do secundário do transformador com sendo V secundário?
Letra a, V secundário, menos a queda de tensão de quatro diodos. Letra b, V secundário, menos a queda de tensão de 1 diodo. Letra c, V secundário, menos a queda de tensão de dois diodos.`,
answer: 'c' ,
answerSpeech: 'A tensão de pico na carga, para um retificador de onda completa em ponte, é V secundário menos a queda de tensão de dois diodos. Já que tanto no semi-ciclo positivo, quanto negativo, dois dos quatro diodos, estão sempre conduzindo.'
}, ]
const amount = questions.length
questions, amount }
Após isso, salve e compile novamente.
No código apresentado para o arquivo de questões, foi definido um array questions,
de objetos, de modo que cada objeto possui três atributos: a pergunta que a Alexa irá
realizar, a resposta para a pergunta e a fala que a Alexa irá realizar após o respondimento
da questão pelo usuário. Também foi definido uma constante amount que armazena o
tamanho do array de perguntas. Esse array possui apenas seis perguntas a título de
simplificação, já que poderia apresentar uma quantidade qualquer de questões. Desse
modo, foi utilizado o module.exports, para exportar um objeto que possui o array de
questões e o tamanho do array, como atributos, a fim de importarmos esse módulo no
arquivo principal, o index.js. Voltando para o arquivo index.js, iremos realizar a importação do arquivo
questions.js, utilizando o require e o operador destructuring, disponível a partir da versão
2016 do JavaScript, logo abaixo da linha: const Alexa = require('ask-sdk-core'); const {questions,amount} = require('./questions'); O operador destructuring é definido por esse par de chaves, de modo que a partir
dele é possível desestruturar o objeto importado através do require(‘.questions’), e desse
modo, recuperar as constantes questions e amount do arquivo requerido, para que seja
possível utilizá-las no arquivo atual. Agora, vamos criar uma função responsável por embaralhar o nosso array de
perguntas. Para que todas as vezes que o usuário abra a Skill, as questões sejam realizadas
em ordens diferentes. Em baixo da linha de código definida anteriormente, crie a seguinte
função, que simplesmente recebe um array e o embaralha: function shuffle(array) { let aux1, aux2; for(let i = amount-1; i > 0; i--) {
aux1 = Math.floor(Math.random() * (i+1)); aux2 = array[i];
array[i] = array[aux1]; array[aux1] = aux2;
} }
Após isso, salve e compile novamente.
Devemos agora, declarar três variáveis que serão utilizadas. Uma variável
responsável por definir a quantidade de perguntas que desejamos que seja realizada, em
cada vez que a Skill for aberta. Uma segunda variável, responsável por armazenar o índice
no array da pergunta que está sendo realizada, para que seja possível iterar sobre ele. E
uma terceira variável, para que seja possível contabilizar a quantidade de perguntas que o
usuário acertou. Assim, logo abaixo da função definida anteriormente, declare as seguintes
constantes: const amountQuestions = 3; let idCurrentQuestion = 0; let score = 0; Salve e compile o código. Agora, vamos modificar algumas linhas nos handlers que já estão criados, para que
a Alexa fale as frases em português. No LaunchRequestHandler, intent utilizada quando a Skill é inicializada, substitua o código, para o seguinte:
const LaunchRequestHandler = { canHandle(handlerInput) { return Alexa.getRequestType(handlerInput.requestEnvelope) === 'LaunchRequest'; }, handle(handlerInput) {
const speakOutput = `Bem vindo ao quiz de eletrônica analógica. Você
será submetido à ${amountQuestions} perguntas. Para começar o jogo você pode dizer coisas como: começar jogo, jogar e começar quiz.`;
shuffle(questions); return handlerInput.responseBuilder .speak(speakOutput) .reprompt(speakOutput) .getResponse(); } };
Dentro um objeto handler, como o LaunchRequestHandler por exemplo, possuímos
dois métodos: canHandle() e o handle(). O método canHandle() é responsável por definir
quando o método handle() será executado; nesse caso, dentro do método canHandle(), é
definido para que o método handle(), seja executado somente se o intent que estiver
fazendo a requisição, for do tipo LaunchRequest. O método handle() que de fato a
manipulação desejada pela intenção. Dentro do método handle(), existe uma constante
com o nome de speakOutput que, utilizando-se de template string, é declarado uma frase
para que Alexa diga. Também nesse método é chamada a função shuffle(), passando o
array de perguntas - para que ele seja embaralhado ao iniciar da Skill -. Ao retorno do
método handle(), existem três funções disponíveis no respondeBuilder: a função speak, para
que a Alexa, fale a frase definida anteriormente; a função reprompt(), para que a sessão
continue aberta, esperando uma resposta do usuário; e a função getResponse(), para uma
resposta em JSON, seja gerada. No handler da Intent Help, é necessário alterar somente a constante speakOutput,
para que seja uma frase em português. Em HelpIntentHandler, localize a linha dessa
variável e substitua-a pela seguinte: const speakOutput = 'Como posso te ajudar?'; Já no handler da Intent CancelAndStop, também é necessário alterar somente a
constante speakOutput, substitua-a pela seguinte: const speakOutput = 'Tchau. Até mais!'; Para os handlers IntentReflectorHandler e ErrorHandler, também altere a constante
speakOutput, substituindo-as respectivamente pelas seguintes: IntentReflectorHandler : const speakOutput = `Você acabou de chamar a intent ${intentName}`; ErrorHandler: const speakOutput = `Me Desculpe, Eu tive problemas para fazer o que você pediu. Por favor, tente novamente.`; Clique nos botões de Salve e Deploy. A partir de agora, iremos implementar os handlers para as nossas Intents
customizadas. O primeiro deles será o handler para a intenção QuizIntent. Logo abaixo do handler LaunchRequestHandler, cole o seguinte código:
const QuizIntentHandler = { canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === 'IntentRequest'
&& handlerInput.requestEnvelope.request.intent.name ===
'QuizIntent'; },
handle(handlerInput) {
const speechText = questions[idCurrentQuestion].question;
return handlerInput.responseBuilder .speak(speechText) .reprompt(speechText) .getResponse(); } };
Salve e compile o código.
Esse handle é responsável por “pegar” a questão disponível na posição idCurrentQuestion, no array de perguntas, e armazená-la na constante speechText, na qual a Alexa perguntará para o usuário. O método repromt garante que a sessão permanecerá aberta até o usuário responder a pergunta.
O próximo handle é responsável por gerenciar a Intent de resposta das questões, o AnswerIntent. Ou seja, quando a Intent que será chamada quando o usuário responder a pergunta disponibilizada pelo handle da Intent QuizIntent. Desse modo, copie o seguinte bloco de código logo abaixo do handler construído anteriormente.
const AnswerIntentHandler = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === 'IntentRequest'
&& (handlerInput.requestEnvelope.request.intent.name ===
'AnswerIntent'); },
handle(handlerInput) {
// armazena os slots que vieram junto com a requisição
const slot = handlerInput.requestEnvelope.request.intent.slots;
// pega o valor do slot com nome 'answer', do objeto slot
const answerSlot = slot['answer'].value.toLowerCase();
let speechText = '';
para ir para a proxima pergunta. Se já foi atingido, fala para ver o rendimento
let speechNext = (idCurrentQuestion + 1 < amountQuestions) ? 'Vá para a próxima pergunta dizendo: próxima.' : 'Você respondeu todas as perguntas disponíveis. Descubra seu rendimento dizendo: próximo.';
// se a resposta da questão for igual ao valor que o usuário falou
if(answerSlot === questions[idCurrentQuestion].answer){ speechText = `Isso mesmo!
${questions[idCurrentQuestion].answerSpeech} ${speechNext}`; score++
}
else {
speechText = `Resposta incorreta.
${questions[idCurrentQuestion].answerSpeech} ${speechNext}`; } return handlerInput.responseBuilder .speak(speechText) .reprompt() .getResponse(); } };
Salve e compile o código.
O próximo e último handle, diz respeito à Intent NextQuestionIntent, para o gerenciamento da próxima pergunta ou para o término do jogo. Copie o seguinte bloco de código logo abaixo do handle anterior:
const NextQuestionIntentHandler = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === 'IntentRequest'
&& (handlerInput.requestEnvelope.request.intent.name ===
'NextQuestionIntent'); },
handle(handlerInput) {
idCurrentQuestion++ // aumenta índice, para ir para a próxima pergunta
if(idCurrentQuestion < amountQuestions) // se ainda não atingiu a quantidade de perguntas, chama o handle QuizIntentHandler, para refazer o
ciclo
return QuizIntentHandler.handle(handlerInput);
else {
idCurrentQuestion = 0; // zera o índice, para um possível próximo jogo
const scorePorcent = (score/amountQuestions) * 100; // calcula a quantidade de acertos em porcentagem
score = 0; // zera a pontuação atual, para um possível próximo jogo
shuffle(questions); // embaralha o array de perguntas, para um possível próximo jogo
let speechResult = '';
if(scorePorcent > 60 && scorePorcent < 80)
speechResult = `Você atingiu a porcentagem de acerto entre 60 e 80 por cento.`;
else if(scorePorcent > 80)
speechResult = 'Parabéns! Você obteve um rendimento igual ou superior à 80 por cento.';
else
speechResult = 'Infelizmente você não chegou aos 60 por cento de acerto.';
return handlerInput.responseBuilder
.speak(`${speechResult} Espero você em outro momento para jogarmos novamente! Até mais!`)
.getResponse(); } } };
Por fim, é necessário inserir os handles que foram adicionados ao código, no addRequestHandlers, como feito anteriormente com a Intent HelloWorld. Vá para o final do arquivo e localize o exports.handle. Adicione as linhas faltantes, para que esse bloco de código fique da seguinte forma:
exports.handler = Alexa.SkillBuilders.custom() .addRequestHandlers( LaunchRequestHandler, QuizIntentHandler, AnswerIntentHandler, NextQuestionIntentHandler,
HelpIntentHandler,
CancelAndStopIntentHandler, SessionEndedRequestHandler,
IntentReflectorHandler, // make sure IntentReflectorHandler is last so it doesn't override your custom intent handlers
) .addErrorHandlers( ErrorHandler, ) .lambda();
Salve e faça o Deploy da aplicação.
Pronto! Feito todos esses passos, nosso Quiz de Eletrônica Analógica está concluído. Se você apresentar algum erro durante a fase de teste, volte ao código apresentado a seguir e verifique se seu index.js ficou semelhante ao apresentado, após todas as etapas:
// This sample demonstrates handling intents from an Alexa skill using the Alexa Skills Kit SDK (v2).
// Please visit https://alexa.design/cookbook for additional examples on implementing slots, dialog management,
// session persistence, api calls, and more.
const Alexa = require('ask-sdk-core');
const {questions,amount} = require('./questions');
function shuffle(array) {
let aux1, aux2;
for(let i = amount-1; i > 0; i--) {
aux1 = Math.floor(Math.random() * (i+1)); aux2 = array[i]; array[i] = array[aux1]; array[aux1] = aux2; } } const amountQuestions = 3; let idCurrentQuestion = 0; let score = 0; const LaunchRequestHandler = { canHandle(handlerInput) { return Alexa.getRequestType(handlerInput.requestEnvelope) ===
'LaunchRequest'; },
handle(handlerInput) {
const speakOutput = `Bem vindo ao quiz de eletrônica analógica. Você
será submetido à ${amountQuestions} perguntas. Para começar o jogo você pode dizer coisas como: começar jogo, jogar e começar quiz.`;
shuffle(questions); return handlerInput.responseBuilder .speak(speakOutput) .reprompt(speakOutput) .getResponse(); } }; const QuizIntentHandler = { canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === 'IntentRequest'
&& handlerInput.requestEnvelope.request.intent.name ===
'QuizIntent'; },
handle(handlerInput) {
const speechText = questions[idCurrentQuestion].question;
return handlerInput.responseBuilder .speak(speechText) .reprompt(speechText) .getResponse(); } }; const AnswerIntentHandler = { canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === 'IntentRequest'
&& (handlerInput.requestEnvelope.request.intent.name ===
'AnswerIntent'); },
handle(handlerInput) {
// armazena os slots que vieram junto com a requisição
const slot = handlerInput.requestEnvelope.request.intent.slots;
// pega o valor do slot com nome 'answer', do objeto slot
const answerSlot = slot['answer'].value.toLowerCase();
let speechText = '';
// Se o valor de perguntas amountQuestions não foi atingido, fala para ir para a proxima pergunta. Se já foi atingido, fala para ver o rendimento
let speechNext = (idCurrentQuestion + 1 < amountQuestions) ? 'Vá para a próxima pergunta dizendo: próxima.' : 'Você respondeu todas as perguntas disponíveis. Descubra seu rendimento dizendo: próximo.';
// se a resposta da questão for igual ao valor que o usuário falou
if(answerSlot === questions[idCurrentQuestion].answer){ speechText = `Isso mesmo!
${questions[idCurrentQuestion].answerSpeech} ${speechNext}`; score++
}
else {
speechText = `Resposta incorreta.
${questions[idCurrentQuestion].answerSpeech} ${speechNext}`; } return handlerInput.responseBuilder .speak(speechText) .reprompt() .getResponse(); } }; const NextQuestionIntentHandler = { canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === 'IntentRequest'
&& (handlerInput.requestEnvelope.request.intent.name ===
'NextQuestionIntent'); },
handle(handlerInput) {
idCurrentQuestion++ // aumenta indice, para ir para a próxima pergunta
if(idCurrentQuestion < amountQuestions) // se ainda não atingiu a quantidade de perguntas, chama o handle QuizIntentHandler, para refazer o