Find the Button is a simple game that provides a surprising amount of fun! In this game, one player hides an Echo Button and another goes and finds it. It is especially fun for little ones! My two year old son loves running around, figuring out where the button is from the blinking lights.
To play, all you have to do is press the button once when you've hidden or found it. Each action is given thirty seconds, in an effort to increase the speed and pace of the game.
After launching the skill and starting up, the whole game loop can be operated through with only the button! No voice required! You can of course ask for help at any time.
Also, since this is a skill that doesn't explicitly depend upon the colors of the button for playing, you can disable and enable the button lights at any time. I included this feature for those who may have sensitivities to flashing lights, though after I did I realized that it also greatly increases the challenge of finding the button.
ImplementationI used Typescript and the ask-sdk v2 to develop my skill, and I host my code on AWS Lambda.
I like to compartmentalize my code as much as possible, so my handler index file is always fairly simple:
import { SkillBuilders } from 'ask-sdk';import { RequestHandlers, ErrorHandlers } from './handlers';import { RequestInterceptors } from './request-interceptors';import { ResponseInterceptors } from './response-interceptors';const skillBuilder = SkillBuilders.custom();export const handler = skillBuilder .addRequestInterceptors(...RequestInterceptors) .addRequestHandlers(...RequestHandlers) .addErrorHandlers(...ErrorHandlers) .addResponseInterceptors(...ResponseInterceptors) .lambda();
I have index files for the Handlers and Interceptors, so I'm able to import all my handlers at the same time like this. Here's the RequestHandlers index file:
import { RollCallStateRequestHandlers } from './RollCallState';import { GenericRequestHandlers, GenericErrorHandlers } from './Stateless';import { HideButtonStateRequestHandlers } from './HideButtonState';import { FindButtonStateRequestHandlers } from './FindButtonState';import { GameOverStateRequestHandlers } from './GameOverState';import { LaunchStateRequestHandlers } from './LaunchState';export const RequestHandlers = [ ...LaunchStateRequestHandlers, ...RollCallStateRequestHandlers, ...HideButtonStateRequestHandlers, ...FindButtonStateRequestHandlers, ...GameOverStateRequestHandlers, ...GenericRequestHandlers];export const ErrorHandlers = [ ...GenericErrorHandlers];
I assign all possible intents to an execution State, and then I separate them into folders that way. Here's the structure of my handlers dir:
Each State has handlers that require specific execution depending on the state. For example, the AMAZON.HelpIntent handler (a required handler for certification) needs to say different things depending on where you are in the skill, and always needs to end with a question. Since the base behavior is always the same (the only thing that's guaranteed to change between different states' help handlers is what Alexa says), I created a generic HelpIntentHandler class that I extend and only pass the state into. Since I sort all my speech messages by state name, the handler is able to give a response simply by passing messages[this.state].Help() to the speak call:
import { HandlerInput, ResponseBuilder } from "ask-sdk";import { MyRequestHandler } from "./MyRequestHandler.class";import { GadgetController } from "../../utils";export class HelpIntentHandler extends MyRequestHandler { constructor(private state: string) { super(); } public canHandle(handlerInput: HandlerInput): boolean { const request = handlerInput.requestEnvelope.request; const state = handlerInput.attributesManager.getSessionAttributes().state; return state === this.state && request.type === 'IntentRequest' && request.intent.name === 'AMAZON.HelpIntent'; } protected updateSessionAttributes(handlerInput: HandlerInput) { this.setSessionAttribute('hasBeenAskedAQuestion', true); this.removeSessionAttribute('inputHandlerId'); this.removeSessionAttribute('handlerStartTime'); this.saveSessionAttributes(); } protected createResponse(handlerInput: HandlerInput): ResponseBuilder { const messages = this.getRequestAttribute('messages');; const flashingDisabled = this.getSessionAttribute('flashingDisabled'); const buttonId = this.getSessionAttribute('buttonId'); let response = handlerInput.responseBuilder .speak(messages[this.state].Help()) .addDirective(GadgetController.stopButtonDownAnimation()) .addDirective(GadgetController.stopButtonUpAnimation()) .addDirective(GadgetController.stopDefaultAnimation()) .withShouldEndSession(false); if (!flashingDisabled) { response = response .addDirective(GadgetController.createButtonDownAnimation(undefined, 'FF0000')) .addDirective(GadgetController.createButtonUpAnimation(undefined, 'FF0000')) .addDirective(GadgetController.createHelpAnimation(buttonId)); } return response; }}
You may notice that HelpIntentHandler extends a class named MyRequestHandler. This is a class that I created that encapsulates behavior common to all handlers, including setting up the methods on this that you may have noticed I'm calling in the Help handler, setSessionAttrribute, removeSessionAttribute, getRequestAttribute, etc. These methods, and others that i've written, allow me to work with the attributesManager included on handlerInput in a more convenient fashion.
I also wrote utility classes, GadgetController and GameEngine, to help ease interactions the two button APIs, as they are not intuitive. I assume Amazon will be releasing button API utility classes like this at some point; for now, I'm proud of and happy to use what I've built.
All in all, with these awesome tools, adding an AMAZON.HelpIntent handler to one of your states is as simple as the following:
import { HelpIntentHandler } from "../classes/HelpIntentHandler.class";import { STATES } from "../../data/states";export class RollCallHelpHandler extends HelpIntentHandler { constructor() { super(STATES.RollCall); }}
No typing out the canHandle and handle methods, no having to remember 'What are the request properties I should check for for a Help intent handler?'. Just add this simple class, add it to your handler definitions file, add the message you need it to say for your state's message, and you're done.
In addition to the cool stuff I've mentioned above, I've built a lot on top of these tools, including InputEventHandler to handle inputHandler events (buttonUp, buttonDown, timedOut, and any custom event you define), and many more. If you'd like to know more about these tools, or have some ideas to contribute, please let me know at bmontgomdev@gmail.com
ConclusionI think that's it for this write-up. There is another skill I worked on quite a bit for this competition (~80 hours or so..) but wasn't able to finish in time. It's called Button Brawl, and will be released sometime in the next few months. Look forward to it!
Thanks for reading! For a demo of the skill in action, you can check out the video linked below:
Comments