Alexa Gadget Skill API: Intro to Input Handlers and Roll Call

In a previous post, we set up a basic TypeScript Alexa boilerplate repo to begin Gadget Skill API development. My aim for this and future posts is to create a game on Alexa that my one and a half year old son can play with, while also guiding the readers through the journey of creating their own game. In this post, we create a skill that listens to input events using the Game Engine interface. In particular, we study roll call functionality and the process of developing a somewhat reusable roll call helper to figure out what gadgets are available to the skill. Much of the inspiration to these concepts come from the Alexa trivia game sample, which is unfortunately somewhat difficult to follow.

Introduction to Roll Call and Input Handlers

The idea of a roll call is as follows. Say we have an Alexa device with four paired Echo Button gadgets. Our skill, however, only needs to use two of the four buttons. How do we determine which buttons to use? How do we grab a unique identifier for each button to control it? How do we associate the identifiers with a semantic name such as as Player 2 or Button B? The roll call process allows us to answer these questions. Once completed, we will have the Alexa gadgetIds in our possession. This will allow us to control each device individually and create a compelling game.

To create a roll call we need to generate a GameEngine.StartInputHandler directive and send it as part of a response from our Alexa Skill. This can be a response to the LaunchRequest or to any other request. The directive instructs Alexa gadgets to start recognizing user actions. It can recognize events in a specific pattern, when a user has deviated from a pattern or when a user has completed a portion of a pattern. These units of recognition are called recognizers. The three types just mentioned map to patternRecognizer, deviationRecognizer and progressRecognizer. A patternRecognizer, in the context of a roll call, is one in which each button has one down event. Once we define all of our input handler’s recognizers, we can define custom events. A patternRecognizer can recognize patterns on all or specified gadgetIds. Recognizers are by default set to false. As user input is collected, recognizers are set to true. An event is sent to the skill when a set of recognizer conditions has been met. A developer can specify the list of recognizers that must be true or false, what data to report, a maximum number of invocations and a few other pieces of data. There is also an implicit timed out recognizer that we can utilize in our events.

Conveniently enough, an input handler may include a list of proxies. A proxy is an identifier for a button that can be utilized in a recognizer when a gadgetId is not known. The basic roll call sample input handler in the Alexa documentation shows how we can use proxies and use them in recognizers.


{
  "type": "GameEngine.StartInputHandler",
  "timeout": 10000,
  "proxies": [ "left", "middle", "right" ],
  "recognizers": {
    "all pressed": {
      "type": "match",
      "fuzzy": true,
      "anchor": "start",
      "pattern": [
        {
          "gadgetIds": [ "left" ],
          "action": "down"
        },
        {
          "gadgetIds": [ "middle" ],
          "action": "down"
        },
        {
          "gadgetIds": [ "right" ],
          "action": "down"
        }
      ]
    }
  },
  "events": {
    "complete": {
      "meets": [ "all pressed" ],
      "reports": "matches",
      "shouldEndInputHandler": true
    },
    "failed": {
      "meets": [ "timed out" ],
      "reports": "history",
      "shouldEndInputHandler": true
    }
  }
}

In this example, we expect the user to register three buttons called: left, middle and right. We then define a recognizer composed of a button down event from each button. Whichever button is pressed first will be treated as left, the second as middle and the last one as right. If the first button is pressed twice, the pattern recognizer considers it as two button down event from the left button and continue on its merry way. The fuzzy flag on the recognizer ensures that this doesn’t invalidate the recognizer. Once the recognizer registers true, the complete event is sent to our Alexa Skill and the input handler is unregistered. Note, if the input handler times out after 10 seconds, our Alexa Skill will receive an event called failed.

In the rest of the post, we create a basic Alexa Skill that utilizes this roll call object to register three buttons. Once we have the button gadgetIds we exit the skill. Of note is that the structure of the input handler is very flexible. We could easily add three extra events to call our skill when each individual button is pressed. For example, we create a recognizer leftButtonDown that only recognizes the left button’s down event and declare an event called leftButtonPressed that is invoked when the leftButtonDown recognizer becomes true. The input handler stays active as we set shouldEndInputHandler to false and limit the event to 1 invocation.


"leftButtonPressed": {
  "meets": ["left"],
  "reports: "matches",
  "shouldEndInputHandler: false,
  "maximumInvocations: 1
}

This approach lets us assign semantic names to each button. For example, our skill could say Please press the button for player 1. Once an event comes in, the button is assigned to player1. The skill can then say Please press the button for player 2, and so on.

Getting Ready For a Basic Roll Call Skill

Since I am aiming to develop a single player game, we create a roll call helper that can register between 1 and 4 buttons. Let’s go ahead and see this in action. We are starting with a TypeScript Alexa Skill Boilerplate created in another post.

To create reusable roll call functionality, we must write code that generates the right input handler and can process each message from the Alexa Skills Kit correctly. We will be using the skill’s SessionAttributes to store the roll call state. At the end, we will also store the discovered buttons.

For functionality to be implemented further down the post, we add the AMAZON.YesIntent and AMAZON.NoIntent intents to models/en-US.json. We also assign the invocation name to be something friendlier. Any Alexa skill needs at least one custom intent, so, for now, we leave the HelloIntent in there.


{
  "interactionModel": {
    "languageModel": {
      "invocationName": "my games",
      "types": [],
      "intents": [
        {
          "name": "AMAZON.CancelIntent",
          "samples": []
        },
        {
          "name": "AMAZON.HelpIntent",
          "samples": []
        },
        {
          "name": "AMAZON.StopIntent",
          "samples": []
        },
        {
          "name": "AMAZON.YesIntent",
          "samples": []
        },
        {
          "name": "AMAZON.NoIntent",
          "samples": []
        },
        {
          "name": "HelloIntent",
          "samples": [
            "hello",
            "say hello",
            "say hello world"
          ]
        }
      ]
    }
  }
}

We also add a LocalizedStrings module, so we can have one place with all of our speech strings. The code uses the i18next Node package. The Alexa Skill Kit SDK for Node doesn’t include any direction on how to accomplish localization, though some samples like Trivia, use i18n. The topic is beyond the scope of this blog post.


import * as i18next from "i18next";

export interface ILocalizationResult {
    speech: string;
    reprompt: string;
}

export module LocalizedStrings {

    export function donotunderstand(): ILocalizationResult {
        return {
            speech: i.t("donotunderstand_speech"),
            reprompt: i.t("donotunderstand_reprompt")
        };
    }

    export function welcome(): ILocalizationResult {
        return {
            speech: i.t("welcome_speech"),
            reprompt: i.t("welcome_reprompt")
        };
    }

    export function goodbye(): ILocalizationResult {
        return {
            speech: i.t("goodbye"),
            reprompt: ""
        };
    }
}

const i = i18next.init({
    lng: "en",
    debug: true,
    resources: {
        en: {
            translation: {
                "donotunderstand_speech": "I'm sorry, I didn't quite catch that.",
                "donotunderstand_reprompt": "Sorry, I didn't understand.",
                "goodbye": "Ok, good bye.",
                "welcome_speech": "Hello. Welcome to the games sample. Do you want to play a game?",
                "welcome_reprompt": "Do you want to play a game?"
            }
        }
    }
});

We add more strings to this as we go along.

We modify the LaunchHandler, so that we ask the user if they want to play a game first. If they say yes, we begin the roll call. Otherwise, we leave the skill.


export class LaunchHandler implements RequestHandler {
    canHandle(handlerInput: HandlerInput): boolean {
        const request = handlerInput.requestEnvelope.request;
        return request.type === "LaunchRequest";
    }

    handle(handlerInput: HandlerInput): Response {
        const sessionAttr = handlerInput.attributesManager.getSessionAttributes();
        sessionAttr.inLaunch = true;
        handlerInput.attributesManager.setSessionAttributes(sessionAttr);

        const resp = LocalizedStrings.welcome();

        return handlerInput.responseBuilder
            .speak(resp.speech)
            .reprompt(resp.reprompt).getResponse();
    }
}

Note, we set the inLaunch session attribute to true. We add an InLaunchStateHandler to handle the requests when we are in this state.


export class InLaunchStateHandler implements RequestHandler {
    canHandle(handlerInput: HandlerInput): boolean {
        const sessionAttr = handlerInput.attributesManager.getSessionAttributes();
        return sessionAttr.inLaunch;
    }

    handle(handlerInput: HandlerInput): Response {
        const sessionAttr = handlerInput.attributesManager.getSessionAttributes();
        sessionAttr.inLaunch = false;
        handlerInput.attributesManager.setSessionAttributes(sessionAttr);

        const req = handlerInput.requestEnvelope.request as IntentRequest;
        if (req) {
            if (req.intent.name === "AMAZON.YesIntent") {
                // proceed to roll call
                return RollCall.initialize(handlerInput);
            } else if (req.intent.name === "AMAZON.NoIntent") {
                // exit
                return handlerInput.responseBuilder
                    .speak(LocalizedStrings.goodbye().speech)
                    .getResponse();
            }
        }

        const donotresp = LocalizedStrings.donotunderstand();
        return handlerInput.responseBuilder
            .speak(donotresp.speech)
            .reprompt(donotresp.reprompt)
            .getResponse();
    }
}

A few things of note. If the handler receives a AMAZON.NoIntent, the skill exits. If the handler receives a AMAZON.YesIntent, we initialize a new roll call. The implication is that the RollCall.initialize function returns a Response object. Presumably, this will send the input handler JSON we discussed in the previous section. For now, we simply send some speech and set a boolean flag on the SessionAttributes.


export module RollCall {
    export function initialize(handlerInput: HandlerInput): Response {
        const sessionAttr = handlerInput.attributesManager.getSessionAttributes();
        sessionAttr.inRollcall = true;
        handlerInput.attributesManager.setSessionAttributes(sessionAttr);

        return handlerInput.responseBuilder
            .speak("Starting Roll Call")
            .reprompt("Waiting on input")
            .getResponse();
    }
}

We create a handler for the roll call state. For now, it simply exits the skill on any input.


export class RollCallHandler implements RequestHandler {
    canHandle(handlerInput: HandlerInput): boolean {
        const sessionAttr = handlerInput.attributesManager.getSessionAttributes();
        return sessionAttr.inRollcall;
    }

    handle(handlerInput: HandlerInput): Response {
        const sessionAttr = handlerInput.attributesManager.getSessionAttributes();
        sessionAttr.inRollcall = false;
        handlerInput.attributesManager.setSessionAttributes(sessionAttr);

        const resp = LocalizedStrings.goodbye();
        return handlerInput.responseBuilder
            .speak(resp.speech)
            .getResponse();
    }
}

The code layout now looks as follows. Note that we have both a RollCall helper and a RollCall handler.

Let’s see how this works. We do this by initializing a new skill using ask new -n My Games, copying over the lambda and models directory and then running ask deploy. We can now navigate to the skill’s Test tab. We can respond either yes or no to the launch response and it routes us accordingly.

Diving Into the Roll Call

We now create the code to build the roll call intent handler. In our RollCall module, we create a function called createRollCallDirective and a field const rollcallHandlerTemplate. The const is a template for the input handler and we generate new directives based on it.


export module RollCall {
    ...

    const rollcallHandlerTemplate: interfaces.gameEngine.StartInputHandlerDirective = {
        type: "GameEngine.StartInputHandler",
        proxies: [],
        recognizers: {
            "all pressed": {
                type: "match",
                fuzzy: true,
                anchor: "start",
                pattern: []
            }
        },
        events: {
            complete: {
                meets: ["all pressed"],
                reports: "matches",
                shouldEndInputHandler: true
            },
            failed: {
                meets: ["timed out"],
                reports: "history",
                shouldEndInputHandler: true
            }
        }
    };

    export function createRollCallDirective(numOfButtons: number, timeout?: number): interfaces.gameEngine.StartInputHandlerDirective {
        const handler = JSON.parse(JSON.stringify(rollcallHandlerTemplate));
        if (timeout) {
            handler.timeout = timeout;
        }

        if (numOfButtons > 4 || numOfButtons < 1) {
            throw new Error("Only 1-4 buttons are supported.");
        }

        for (let i = 0; i < numOfButtons; i++) {
            const proxy = "btn" + (i + 1);

            const patternStep: services.gameEngine.Pattern = {
                action: "down",
                gadgetIds: [proxy]
            };
            handler.proxies!.push(proxy);

            (handler.recognizers!["all pressed"] as services.gameEngine.PatternRecognizer)
                .pattern!.push(patternStep);

        }

        return handler;
    }
}

The code is basically adding a proxy and pattern step for every player. Here is what the code produces for three players.


{
  "type": "GameEngine.StartInputHandler",
  "proxies": [
    "btn1",
    "btn2",
    "btn3"
  ],
  "recognizers": {
    "all pressed": {
      "type": "match",
      "fuzzy": true,
      "anchor": "start",
      "pattern": [
        {
          "action": "down",
          "gadgetIds": [
            "btn1"
          ]
        },
        {
          "action": "down",
          "gadgetIds": [
            "btn2"
          ]
        },
        {
          "action": "down",
          "gadgetIds": [
            "btn3"
          ]
        }
      ]
    }
  },
  "events": {
    "complete": {
      "meets": [
        "all pressed"
      ],
      "reports": "matches",
      "shouldEndInputHandler": true
    },
    "failed": {
      "meets": [
        "timed out"
      ],
      "reports": "history",
      "shouldEndInputHandler": true
    }
  }
}

We now change the RollCall.initialize call to send the directive.


export module RollCall {
    export function initialize(handlerInput: HandlerInput): Response {
        const sessionAttr = handlerInput.attributesManager.getSessionAttributes();
        sessionAttr.inRollcall = true;
        handlerInput.attributesManager.setSessionAttributes(sessionAttr);

        return handlerInput.responseBuilder
            .speak("Starting Roll Call")
            .reprompt("Waiting on input")
            .addDirective(createRollCallDirective(2, 20000))
            .getResponse();
    }
}

If we were to try to run this code, our skill would fail because we have not yet enabled our skill to support the GameEngine and GadgetController interfaces. This page shows us how we can do it. Here are the necessary fields to add to our skill.json manifest.


{
  "publishingInformation": {
    "gadgetSupport": {
      "requirement": "REQUIRED", // or "OPTIONAL"
      "numPlayersMin": int,
      "numPlayersMax": int, // or null
      "minGadgetButtons": int,
      "maxGadgetButtons": int // or null     
    }
  },
  "apis": {
    "custom": {
      "interfaces": [
        {
          "type": "GAME_ENGINE"
        },
        {
          "type": "GADGET_CONTROLLER"
        }
      ]
    }
  }
}

We modify our skill manifest file to reflect this. Here are the values I utilized.


{
  "manifest": {
    "publishingInformation": {
       // other publishingInformation goes here
      "gadgetSupport": {
        "requirement": "REQUIRED",
        "numPlayersMin": 1,
        "numPlayersMax": 1,
        "minGadgetButtons": 1,
        "maxGadgetButtons": 4
      }
    },
    "apis": {
      "custom": {
        "endpoint": {
          "sourceDir": "lambda/custom"
        },
        "interfaces": [
          {
            "type": "GAME_ENGINE"
          },
          {
            "type": "GADGET_CONTROLLER"
          }
        ]
      }
    }
    // any additional manifest data here
  }
}

When we run ask deploy, the Test tab will now have button simulators!

We can run the skill in the simulator, after we answer “yes” when asked if we want to play a game, our skill sends the message “Starting Roll Call” and the generated directive JSON. At that point, you can use the simulator Echo Buttons. Press two different ones and you’ll notice that after pressing two, the skills responds with “Ok, good bye.”

This is great news; this good bye message is generated by our RollCallHandler. That means the input handler is working as expected. We should verify that our 20 second timeout works as well. Since our RollCallHandler always closes the skill, we should see the same behavior if we don’t press the buttons. Go ahead and verify it.

We now make the following changes:

  1. Add functionality to the RollCall module to support input events from the buttons.
  2. Add timeout retry functionality, so that if the roll call times out, we give the user a chance to try again.
  3. Break roll call state handling into two handlers: one focused on events from buttons and the other on user utterances as a response to timeouts.

To address item 1, we add a method called handleInput, which calls into handleTimeout or handleDone depending on which event was received. In handleDone, we retrieve the button gadgetIds from the message, assign them to our sessionAttributes, log them and exit the skill for the time being. handleTimeout implements the logic for item 2. The skill asks the user if they would like to retry. If there are two timeouts in a row, the skill exits. Here is the code for the updated RollCall module.


    export function initialize(handlerInput: HandlerInput): Response {
        const sessionAttr = handlerInput.attributesManager.getSessionAttributes();
        sessionAttr.inRollcall = true;
        handlerInput.attributesManager.setSessionAttributes(sessionAttr);

        const resp = LocalizedStrings.rollcall_start();
        return handlerInput.responseBuilder
            .speak(resp.speech)
            .reprompt(resp.reprompt)
            .addDirective(createRollCallDirective(2, 20000))
            .getResponse();
    }

    export function handleInput(handlerInput: HandlerInput,
        input: interfaces.gameEngine.InputHandlerEventRequest): Response {
        const inputEvents = input.events!;

        if (inputEvents.some(p => p.name === "failed")) {
            return handleTimeoutOut(handlerInput);
        } else {
            const complete = inputEvents.find(p => p.name === "complete");
            if (complete) {
                return handleDone(handlerInput, complete);
            } else {
               throw new Error("Unexpected event");
            }
        }
    }

    export function handleDone(handlerInput: HandlerInput,
        complete: services.gameEngine.InputHandlerEvent): Response {
        const sessionAttr = handlerInput.attributesManager.getSessionAttributes();
        clearSessionAttr(sessionAttr);
        const btns = complete!.inputEvents!.map(p => { return { name: "", id: p.gadgetId }; });
        for (let i = 0; i < btns.length; i++) {
            btns[i].name = "btn" + (i + 1);
        }

        sessionAttr.rollcallResult = btns;
        handlerInput.attributesManager.setSessionAttributes(sessionAttr);

        console.log(`Registered buttons: \n${JSON.stringify(btns, null, 2)}`);

        const resp = LocalizedStrings.rollcall_done();
        return handlerInput.responseBuilder
            .speak(resp.speech)
            .withShouldEndSession(true)
            .getResponse();
    }

    function clearSessionAttr(sessionAttr: { [key: string]: any }): void {
        delete sessionAttr.inRollcall;
        delete sessionAttr.rollcallTimeout;
    }

    ...

}

For item 3, we create a RollCallRetryTimeoutHandler and move some logic over from RollCallHandler. The two handlers are shown below.


export class RollCallHandler implements RequestHandler {
    canHandle(handlerInput: HandlerInput): boolean {
        const sessionAttr = handlerInput.attributesManager.getSessionAttributes();
        return sessionAttr.inRollcall &&
            (handlerInput.requestEnvelope.request.type === "GameEngine.InputHandlerEvent");
    }

    handle(handlerInput: HandlerInput): Response {
        const inputEventRequest = handlerInput.requestEnvelope.request as interfaces.gameEngine.InputHandlerEventRequest;
        if (inputEventRequest) {
            return RollCall.handleInput(handlerInput, inputEventRequest);
        } else {
            throw new Error("Unexpected event type. Not supported in roll call.");
        }
    }
}

export class RollCallTimeoutRetryHandler implements RequestHandler {
    canHandle(handlerInput: HandlerInput): boolean {
        const sessionAttr = handlerInput.attributesManager.getSessionAttributes();
        return sessionAttr.inRollcall &&
            sessionAttr.rollcallTimeout > 0 &&
            handlerInput.requestEnvelope.request.type === "IntentRequest" &&
            (handlerInput.requestEnvelope.request.intent.name === "AMAZON.YesIntent" ||
                handlerInput.requestEnvelope.request.intent.name === "AMAZON.NoIntent");
    }

    handle(handlerInput: HandlerInput): Response {
        const intentRequest = handlerInput.requestEnvelope.request as IntentRequest;
        if (intentRequest) {
            if (intentRequest.intent.name === "AMAZON.YesIntent") {
                return RollCall.initialize(handlerInput);
            } else if (intentRequest.intent.name === "AMAZON.NoIntent") {
                const resp = LocalizedStrings.goodbye();
                return handlerInput.responseBuilder
                    .speak(resp.speech)
                    .withShouldEndSession(true)
                    .getResponse();
            }
        }
        throw new Error("Unexpected input type. Not supported.");
    }
}

We can go ahead and build and deploy the code to our skill. The happy path pressing the two buttons works and we can see the gadgetIds in our CloudWatch logs on AWS. Even though we did not know the button identifiers before, we now have them and can send specific commands to each button. Try letting the input handler time out and observe the behavior as well as the retry option.

Providing Feedback for a Button Tap

If there is anything odd to state about the experience it is that when we press the first button, there is no feedback of any sort. The buttons stay dark, and there is no acknowledgement of a button press. In fact, our skill doesn’t even know a button has been pressed. We previously suggested that we can get around this and get those events. We will do so now.

First, we add code to generate the right input handler JSON. We modify the RollCall module’s createRollCallDirective function. The result is that we generate a recognizer and event for each individual button.


    export function createRollCallDirective(numOfButtons: number, timeout?: number): interfaces.gameEngine.StartInputHandlerDirective {
        const handler = JSON.parse(JSON.stringify(rollcallHandlerTemplate));
        if (timeout) {
            handler.timeout = timeout;
        }

        if (numOfButtons > 4 || numOfButtons < 1) {
            throw new Error("Only 1-4 buttons are supported.");
        }

        for (let i = 0; i < numOfButtons; i++) {
            const proxy = "btn" + (i + 1);
            const recognizer = "recognizer_" + proxy;
            const eventName = "event_" + proxy;

            const patternStep: services.gameEngine.Pattern = {
                action: "down",
                gadgetIds: [proxy]
            };
            handler.proxies!.push(proxy);

            (handler.recognizers!["all pressed"] as services.gameEngine.PatternRecognizer)
                .pattern!.push(patternStep);

            const newRecognizer: services.gameEngine.PatternRecognizer = {
                anchor: "end",
                fuzzy: true,
                type: "match",
                pattern: [patternStep]
            };

            handler.recognizers![recognizer] = newRecognizer;

            handler.events![eventName] = {
                shouldEndInputHandler: false,
                maximumInvocations: 1,
                meets: [recognizer],
                reports: "matches"
            };
        }

        return handler;
    }

The JSON produced by this code for three players is shown below.


{
  "type": "GameEngine.StartInputHandler",
  "proxies": [
    "btn1",
    "btn2",
    "btn3"
  ],
  "recognizers": {
    "all pressed": {
      "type": "match",
      "fuzzy": true,
      "anchor": "start",
      "pattern": [
        {
          "action": "down",
          "gadgetIds": [
            "btn1"
          ]
        },
        {
          "action": "down",
          "gadgetIds": [
            "btn2"
          ]
        },
        {
          "action": "down",
          "gadgetIds": [
            "btn3"
          ]
        }
      ]
    },
    "recognizer_btn1": {
      "anchor": "end",
      "fuzzy": true,
      "type": "match",
      "pattern": [
        {
          "action": "down",
          "gadgetIds": [
            "btn1"
          ]
        }
      ]
    },
    "recognizer_btn2": {
      "anchor": "end",
      "fuzzy": true,
      "type": "match",
      "pattern": [
        {
          "action": "down",
          "gadgetIds": [
            "btn2"
          ]
        }
      ]
    },
    "recognizer_btn3": {
      "anchor": "end",
      "fuzzy": true,
      "type": "match",
      "pattern": [
        {
          "action": "down",
          "gadgetIds": [
            "btn3"
          ]
        }
      ]
    }
  },
  "events": {
    "complete": {
      "meets": [
        "all pressed"
      ],
      "reports": "matches",
      "shouldEndInputHandler": true
    },
    "failed": {
      "meets": [
        "timed out"
      ],
      "reports": "history",
      "shouldEndInputHandler": true
    },
    "event_btn1": {
      "shouldEndInputHandler": false,
      "maximumInvocations": 1,
      "meets": [
        "recognizer_btn1"
      ],
      "reports": "matches"
    },
    "event_btn2": {
      "shouldEndInputHandler": false,
      "maximumInvocations": 1,
      "meets": [
        "recognizer_btn2"
      ],
      "reports": "matches"
    },
    "event_btn3": {
      "shouldEndInputHandler": false,
      "maximumInvocations": 1,
      "meets": [
        "recognizer_btn3"
      ],
      "reports": "matches"
    }
  }
}

It is worth taking a minor detour at this point. You may have noticed that the interfaces.gameEngine.InputHandlerEventRequest interface contains an array called events. The implication is that one button down may result in a request with more than one event. Using the directive above as an example, when we press a button, we receive a request with one event called event_btn1. When we press the second button, we receive a second request with two events: event_btn2 and complete. This means that any logic that we write to generate responses must take this behavior into account. For example, we are planning on adding a response message when the user presses a button. However, when the user pressed the second button, the skill must also inform the user that the roll call is done and the game is starting. All that to say, we’ll need to be cognizant of these factors when building our skills.

For now, we will create a function called handleCheckin that handles the individual events. We update handleInput as well.


    export function handleInput(handlerInput: HandlerInput,
        input: interfaces.gameEngine.InputHandlerEventRequest): Response {
        const inputEvents = input.events!;

        if (inputEvents.some(p => p.name === "failed")) {
            return handleTimeoutOut(handlerInput);
        } else {
            const complete = inputEvents.find(p => p.name === "complete");
            if (complete) {
                return handleDone(handlerInput, complete);
            } else {
                return handleButtonCheckin(handlerInput);
            }
        }
    }

    export function handleButtonCheckin(handlerInput: HandlerInput): Response {
        const sessionAttr = handlerInput.attributesManager.getSessionAttributes();
        sessionAttr.rollcallButtonsCheckedIn++;
        handlerInput.attributesManager.setSessionAttributes(sessionAttr);

        const resp = LocalizedStrings.rollcall_checkin(numOfButtons - sessionAttr.rollcallButtonsCheckedIn);
        return handlerInput.responseBuilder.speak(resp.speech).getResponse();
    }

When we deploy the skill we should be able to receive feedback when the first button is pressed.

The timeout behavior is a bit odd. If we press one button but never press the second one, our skill asks if we want to retry. At that point, the skill doesn’t remember the first button. We will leave the behavior as is for now.

We have made some really great progress towards setting up a game on Alexa’s Gadget Skills API. In further posts we will explore setting color animations on the buttons to provide user feedback and actually getting a game in place.

The code for this post can be found on GitHub.