Build

Broadcasting from One-to-Many: AngularJS Gamecast App

Michael Carroll on Mar 10, 2015
Broadcasting from One-to-Many: AngularJS Gamecast App

DIY gamecast app with broadcast pub/sub designTo be apart of live action, whether it be sports, competitions, or gaming, audiences no longer need to be in the stadium. In fact, with the explosion of mobile, audiences don't even need to be at home.

And today, users expect live game casts, statistics, and updates as they happen. With real-time publish/subscribe and a broadcast design pattern, we can push updates in real time from one controller to any number of subscribers simultaneously.

As a result, we can build sports game cast applications, live blogging platforms, and or even trigger device action on connected IoT devices.

What is a Broadcast Design Pattern?

A broadcast pub/sub messaging design pattern is publishing data from one publisher (broadcaster) to multiple subscribers (listeners) in real time. Updates are sent from the broadcaster, and are received on the subscriber simultaneously.

The design pattern in its simplest form below:

broadcast publish/subscribe messaging real-time ember.js
A broadcast design pattern publishes from one broadcaster to multiple subscribers in real time.

Real-time Gamecast Tutorial Overview

In this two part blog post, we're going to build a sportscast gamecast web and mobile application with AngularJS.

In part one, we'll build the controller application, where a user can edit different fields then publish the updates that will me immediately displayed on the subscriber application (part two).

The whole application is actually two applications:

We have a live-working demo that you can play with here. Open up a single gamecast controller, then a couple of the end-user subscriber windows. Push updates and watch as they update in real time!

Let's get started!

It’s simple to build with the PubNub Pub/Sub Messaging API. This tutorial is a demonstration of how easy and fun it is to develop with the PubNub API. It is intended for anyone interested in getting started with PubNub and AngularJS. It requires no previous experience.

<html>
  <head>
    <script src="https://cdn.pubnub.com/pubnub.min.js"></script>
    <script src="https://cdn.pubnub.com/pubnub-crypto.min.js"></script>
    <script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.2.0/angular.min.js"></script>
    <script src="http://code.jquery.com/jquery-1.10.1.min.js"></script>
    <script src="http://pubnub.github.io/angular-js/scripts/pubnub-angular.js"></script>
    <link rel="stylesheet" href="http://netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css">
    <link rel="stylesheet" href="styles.css">
    <link href='http://fonts.googleapis.com/css?family=Lato' rel='stylesheet' type='text/css'>
    </head>
    <body ng-app="PubNubAngularApp" ng-controller="MainCtrl">
       <div class="stats col-md-4 col-md-offset-1">
          <h1>Game Controller</h1>
          <hr>
          <input class="col-md-4" type="text" ng-model="state.home_team" placeholder="Home Team" />
          <input class="col-md-2" type="text" ng-model="state.home_score" placeholder="Home Score" />
          <br /><br />
          <input class="col-md-4" type="text" ng-model="state.away_team" placeholder="Away Team"/>
          <input class="col-md-2" type="text" ng-model="state.away_score" placeholder="Away Score"/>
          <br /><br />
          <input class="col-md-4" type="text" ng-model="state.inning" placeholder="Inning"/>
          <br /><br />
          <button ng-click="click('balls')">Ball</button>
          <button ng-click="click('strikes')">Strike</button>
          <button ng-click="click('outs')">Out</button>
          <br /><br />
          <button ng-click="click('first_bases')">On First</button>
          <button ng-click="click('second_bases')">On Second</button>
          <button ng-click="click('third_bases')">On Third</button>
          <br /><br />
          <input class="col-md-3" type="text" ng-model="state.last_play" placeholder="Last Play" />
          <br />
          <hr>
          <button ng-click="reset()" class="reset">Reset Game</button>
          <button ng-click="publish()" class="send">Send Game Stats</button>
        </div>
      <div class="broadcast col-md-4 col-md-offset-1 vcenter">
        <h1>Live Game</h1>
        <hr>
        <h2>{{state.home_team}}: {{state.home_score}}</h2>
        <h2>{{state.away_team}}: {{state.away_score}}</h2>
        <h2>Inning: {{state.inning}}</h2>
        <hr>
        <br />
        <svg
           xmlns:svg="http://www.w3.org/2000/svg"
           xmlns="http://www.w3.org/2000/svg"
           version="1.1"
           width="500"
           height="400"
           id="gamecast_svg">
          <g>
            <rect id="base1" ng-class="{first_base:true,rect:true,first_base_on:state.first_bases[0]}" width="100" height="100" x="-60" y="350" transform="matrix(0.70710678,-0.70710678,0.70710678,0.70710678,50,0)" />
            <rect id="base2" ng-class="{second_base:true,rect:true,second_base_on:state.second_bases[0]}" width="100" height="100" x="-60" y="240" transform="matrix(0.70710678,-0.70710678,0.70710678,0.70710678,50,0)" />
            <rect id="base3" ng-class="{third_base:true,rect:true,third_base_on:state.third_bases[0]}" width="100" height="100" x="-170" y="240" transform="matrix(0.70710678,-0.70710678,0.70710678,0.70710678,50,0)" />
            <path id="b1" ng-class="{ball:true,circle:true,ball_on:state.balls[0]}" d="m 185,20 a 16.5,15 0 1 1 -33,0 16.5,15 0 1 1 33,0 z"/>
            <path id="b2" ng-class="{ball:true,circle:true,ball_on:state.balls[1]}" d="m 235,20 a 16.5,15 0 1 1 -33,0 16.5,15 0 1 1 33,0 z"/>
            <path id="b3" ng-class="{ball:true,circle:true,ball_on:state.balls[2]}" d="m 285,20 a 16.5,15 0 1 1 -33,0 16.5,15 0 1 1 33,0 z"/>
            <path id="b4" ng-class="{ball:true,circle:true,ball_on:state.balls[3]}" d="m 335,20 a 16.5,15 0 1 1 -33,0 16.5,15 0 1 1 33,0 z"/>
            <path id="k1" ng-class="{strike:true,circle:true,strike_on:state.strikes[0]}" d="m 185,60 a 16.5,15 0 1 1 -33,0 16.5,15 0 1 1 33,0 z"/>
            <path id="k2" ng-class="{strike:true,circle:true,strike_on:state.strikes[1]}" d="m 235,60 a 16.5,15 0 1 1 -33,0 16.5,15 0 1 1 33,0 z"/>
            <path id="k3" ng-class="{strike:true,circle:true,strike_on:state.strikes[2]}" d="m 285,60 a 16.5,15 0 1 1 -33,0 16.5,15 0 1 1 33,0 z"/>
            <path id="o1" ng-class="{out:true,circle:true,out_on:state.outs[0]}" d="m 185,100 a 16.5,15 0 1 1 -33,0 16.5,15 0 1 1 33,0 z"/>
            <path id="o2" ng-class="{out:true,circle:true,out_on:state.outs[1]}" d="m 235,100 a 16.5,15 0 1 1 -33,0 16.5,15 0 1 1 33,0 z"/>
            <path id="o3" ng-class="{out:true,circle:true,out_on:state.outs[2]}" d="m 285,100 a 16.5,15 0 1 1 -33,0 16.5,15 0 1 1 33,0 z"/>
          </g>
        </svg>
        <h3>Last Play: {{state.last_play}}</h3>
        <br />
        <hr>
      </div>
      <script>
        var app = angular.module('PubNubAngularApp',["pubnub.angular.service"]);
        app.controller("MainCtrl", function($rootScope, $scope, PubNub){
          $scope.userId = "User " + Math.round(Math.random()*1000);
          $scope.channel = 'Gamecast Game';
          if (!$rootScope.initialized) {
            PubNub.init({
              subscribe_key: 'demo',
              publish_key: 'demo',
              uuid:$scope.userId
            });
            $rootScope.initialized = true;
          }
          $scope.reset = function() {
            $scope.state = {
              home_team: "Home team",
              away_team: "Away team",
              home_score: 0,
              away_score: 0,
              inning: "",
              first_bases: [false, false],
              second_bases: [false, false],
              third_bases: [false, false],
              balls: [false, false, false, false],
              strikes: [false, false, false],
              outs: [false, false, false],
              last_play: ""
            };
            $scope.count = {
              first_bases: 0,
              second_bases: 0,
              third_bases: 0,
              balls: 0,
              strikes: 0,
              outs: 0
            };
          };
          $scope.reset();
          $scope.click = function(type) {
            $scope.count[type] = ($scope.count[type] + 1) % $scope.state[type].length;
            for (var i = 0; i < $scope.state[type].length; i++) {
              $scope.state[type][i] = (i < $scope.count[type]);
            }
          }
          $scope.publish = function() {
            PubNub.ngPublish({
              channel: $scope.channel,
              message: $scope.state
            });
          };
        });
      </script>
  </body>
</html>

AngularJS Gamecast Application Concept

Below is a .gif of it in action. A live-working controller demo and end-user application demo are available here as well.

DIY gamecast app with broadcast pub/sub design

The concept is simple: assign values to a state variable and publish them in a channel as a message. Anyone who connects to the channel through PubNub can view the message! Can you think of other data you could send this way, besides baseball game stats? Feeling creative? Head on over to the PubNub community and share your ideas.

In this particular tutorial, the game cast will involve a broadcaster updating and sending game data to spectators. We’ll need three files: broadcaster.htmlspectator.html, and styles.css.

In PubNub API terminology, the broadcaster publishes game data to a channel, and spectators subscribe to that channel. The spectators will be able to view the scores of the home and away team, the inning, the current number of balls, strikes, and outs, and the most recent play.

Part 1: Building the Broadcaster

The broadcaster edits the state of the game data and publishes the entire state of the game to channel. The spectators can view the game data by subscribing to that channel.

Include Libraries

DIY gamecast app with broadcast pub/sub design

Start by including PubNub, AngularJS, jQuery, and Bootstrap libraries in broadcaster.html.

We’ll also build a stylesheet for the game board and add a nice font.

Design and Style

This is how we specify what the game board will look like when in play. The shapes will be generated as a scaled vector graphic (SVG) (more on that later). In the CSS code we determine the look of the board.

The Structure

Declare the PubNubAngularApp in ng-app and name the ng-controller in the body tag so the HTML and JavaScript can interact.

Display an application title, team names, and respective scores. Also use ng-click to allow your Angular code to call methods defined in the $scope.

Game Board SVG

An SVG for the game board was generated with InkScape. Each ball, strike, and out is a circle. Bases are rotated rectangles. It is NOT necessary to generate this code by hand. The code below is for reference.

Each of the SVG elements is given an id and ng-class. The ng-class is a map of the type, shape, and state. For example, for <path> with id="b1", the ng-class is {ball:true,circle:true,ball_on:state.balls[0]}. This means its classes are ball and circle. If a ball is called, ball_on style is applied to the first ball in the balls array. To reiterate, in the code below a “rect” tag represents the base. The “path” tag is a circle

Game Board Broadcaster

Below is a preview of what our controller (broadcaster) will look like:

DIY gamecast app with broadcast pub/sub design

The broadcaster needs a way to communicate that the game state has changed. This can be accomplished with text input, reset, and send buttons. When the buttons are clicked, the state is updated with ng-model and the reset andpublish functions are called via ng-click.

We’ll give the Broadcaster a preview of the state they’ll be sending to the spectators.

Making it all work: The Angular Controller

Create PubNub Angular module, define the controller, and specify the user and channel so the PubNub and JavaScript can interact with the HTML.

Initialize PubNub so we can use its methods, like publish and message event, for the broadcaster and spectator.

Reset and Publish

The default states can be established in the reset function.

We can keep track of the game data in $scope.state and $scope.count. The counts will increment as the buttons for the balls, strikes, and outs are clicked. The value for the bases will either be zero or one, depending on if that particular base is occupied.

Finally, the broadcaster publishes the state of the entire board to the spectators in the channel.

Wrapping up

And that's the end of part one: building the controller/broadcaster. Now it's time to build the subscriber (end-user application), and that full tutorial can be seen here!

This is just one of the many things you can do with a broadcast pub/sub design. Pretty much anytime you want to stream or send data from one publisher to several subscribers, broadcast is what you're looking for. Use cases include live-blogging, statistics, streaming stock quotes, and triggering device actions.