Kevin Hakanson
Created October 15, 2018

Rochambeau Buttons

Rochambeau Buttons is a two player, Echo Buttons enabled version of rochambeau/roshambo (a.k.a Rock Paper Scissors).

IntermediateShowcase (no instructions)20

Things used in this project

Story

Read more

Schematics

Logical Architecture

Echo Buttons connect to the Echo Dot. Alexa Skills Kit requests are handled by AWS Lambda (JavaScript), which writes logs to CloudWatch and metrics to X-Ray.
Roshambo buttons 146m0uhfca

Gadgets Skills Interaction

This image from Understand the Gadgets Skill API [1] illustrates how the Game Engine and Gadget Controller interfaces interact with the skill code.

[1] https://developer.amazon.com/docs/gadget-skills/understand-gadgets-skill-api.html
Gadget skills api overview  tth  cy3chlbnvl

Code

PlayGameIntent vs LaunchRequest

JavaScript
A "launch Rochambeau Buttons" will generate a LaunchRequest and setup the buttons via "roll call" but when the user says "launch Rochambeau Buttons and play a game" that needs to be handled as well. The snippet below checks for a new session, sets up the roll call, but also allows the skill to proceed directly into the game after setup by valuing a session attribute.
    let rounds = getSlotValue(request.intent, 'rounds') || 1;
    // validation logic...
    sessionAttributes.rounds = rounds;
   
    let response;
    if (requestEnvelope.session.new) {
      console.log('launched into PlayGameIntentHandler, setup roll call first');
      response = handlerInput.responseBuilder
        .speak(LAUNCH_PLAY + TICKING_SOUND)
        .getResponse();

      buttonUtil.setupRollCall(request, response, sessionAttributes);
    } else {
      response = handlerInput.responseBuilder
        .speak(PLAY_NOW + WAITING_SOUND)
        .getResponse();

      buttonUtil.setupGame(request, response, sessionAttributes);
    }

    return response;

Varying the "tie" response

JavaScript
Tiebreakers are frustrating, and hearing the same long winded response that "It's a tie, go again" would make it even more so. The code snippet below highlights how the first, second, and remaining tie responses get more concise to pick up the pace.
            let speak = `Player 1 chose ${sessionAttributes.one.color} and Player 2 chose ${sessionAttributes.two.color}. `;
            winner = engine.compare(sessionAttributes.one.color, sessionAttributes.two.color);
            console.log(`winner => ${winner}`);
            if (winner == 'tie') {
              sessionAttributes.tieCount += 1;
              switch (sessionAttributes.tieCount) {
                case 1:
                  speak = speak + `It's a tie, go again. ` + WAITING_SOUND;
                  break;
                case 2:
                  speak = `Both players chose ${sessionAttributes.one.color}. Another tie, go again. ` + WAITING_SOUND;
                  break;
                default:
                  speak = `Tie on ${sessionAttributes.one.color}, go again. ` + WAITING_SOUND;
              }

'tearDownGame' response directives

JavaScript
After one of the players has won and the results are announced by Alexa, there is a winning animation that runs for a few seconds (and a lose animation sets the light to 000000). The input handler directive includes a time out value that will used to trigger the 'timeout' event in the next code snippet.
exports.teardownGame = function(request, response, sessionAttributes) {
  sessionAttributes.CurrentInputHandlerID = request.requestId;

  response.directives = response.directives || [];
  let short = (sessionAttributes.rounds == 1);
  response.directives.push(buttonOutroInputHandlerDirective(short));

  let winner = sessionAttributes.winner;
  let loser = (winner == 'one' ? 'two' : 'one');
  response.directives.push(buildButtonWinAnimationDirective([sessionAttributes[winner].buttonId]));
  response.directives.push(buildButtonLoseAnimationDirective([sessionAttributes[loser].buttonId]));

  delete response.shouldEndSession;

  return response;
};

GameEngine.InputHandlerEvent "timeout" event

JavaScript
The 'timeout' handler might be invoked for different reasons. If neither play "buzzed in" there is one message, but if either player did, they get a "you win" message.

The 'timeout' also handles a case where the game is complete with a winner, but I wanted to let a button animation for a bit before the "Goodbye" message was spoken and the session ended.
        case 'timeout': {
          let response;
          let winner = sessionAttributes.winner;
          console.log(`timeout winner => ${winner}`);
          if (winner == undefined) {
            let speak = '';
            console.log(`one => ${sessionAttributes.one.color} ; two => ${sessionAttributes.two.color}`);
            if (sessionAttributes.one.color == null && sessionAttributes.two.color == null) {
              speak = NEGATIVE_SOUND + 'Neither player selected a color. ';
            } else if (sessionAttributes.one.color == null) {
              speak = PLAYER_2_SOUND + `Player 1 didn't select a color, player 2 wins by forfeit. `;
            } else if (sessionAttributes.two.color == null) {
              speak = PLAYER_1_SOUND + `Player 2 didn't select a color, player 1 wins by forfeit. `;
            }
            response = handlerInput.responseBuilder
              .speak(speak + THANK_YOU)
              .getResponse();
          } else {
            let loser = (winner == 'one' ? 'two' : 'one');
            let speak = `Player ${winner} wins the best of ${sessionAttributes.rounds}: `
              + `${sessionAttributes[winner].winCount} to ${sessionAttributes[loser].winCount}. `;
            console.log(speak);
            response = handlerInput.responseBuilder
              .speak(THANK_YOU)
              .withSimpleCard(`Results`, speak) // doesn't seem to render
              .getResponse();
          }

          buttonUtil.stopInputHandler(response, sessionAttributes);
          response.shouldEndSession = true;
          return response;
        }

AWS X-Ray Integration

Adds an AWS X-Ray subsegment and Metadata for an Alexa Skills Kit triggered Lambda.

Credits

Kevin Hakanson

Kevin Hakanson

2 projects • 0 followers

Comments