2013-12-29
Experiments in Trivia

Bored, want to see how fast I can develop a simple multiplayer trivia app in my blog environment using Firebase.

Let’s have a QuizBot manage a timer and a state machine that allows others to answer questions in the Chat window that are posed.

Simple Quiz Experiment

For kicks, I’ll use the code from [Christmas Presence]() earlier in this blog to keep track of the number of participants and their locations.

{[totalViewers]} viewers are viewing
  • Geo: {[viewer.city]}, {[viewer.region]}, {[viewer.country]} -- {[viewer.loc]}
    Net: {[viewer.ip]} {[viewer.hostname]} {[viewer.org]}

The code for the above is below:

<div>
  <script>
    angular.module('BlogApp')

    .controller('PresenceController', ['$scope', 'PresenceService',
      function($scope, PresenceService) {
        $scope.totalViewers = 0;
        $scope.allViewers = [];

        $scope.$on('onOnlineUser', function() {
          $scope.$apply(function() {
            $scope.totalViewers = PresenceService.getOnlineUserCount();
            $scope.allViewers = PresenceService.getOnlineUserList();
          });
        });
      }
    ])

    .factory('PresenceService', ['$rootScope', '$http',
      function($rootScope, $http) {
        var onlineUsers = [];
        var numOnlineUsers = 0;
        var myUserInfo = {};

        // Create our references
        var listRef = new Firebase('https://doctorbud.firebaseIO.com/simplequizpresence1');
        var userRef = listRef.push(); // This creates a unique reference for each user
        var presenceRef = new Firebase('https://doctorbud.firebaseIO.com/.info/connected');

        var jsonp = $http.jsonp('https://ipinfo.io/');

        jsonp.then(
          function(response) {
            myUserInfo.city = response.city;
            myUserInfo.country = response.country;
            myUserInfo.hostname = response.hostname;
            myUserInfo.ip = response.ip;
            myUserInfo.loc = response.loc;
            myUserInfo.org = response.org;
            myUserInfo.region = response.region;
          });

        // Add ourselves to presence list when online.
        presenceRef.on('value', function(snap) {
          if (snap.val()) {
            userRef.set(myUserInfo);
            // Remove ourselves when we disconnect.
            userRef.onDisconnect().remove();
          }
        });

        // Get the user count and notify the application
        listRef.on('value', function(snap) {
          numOnlineUsers = snap.numChildren();
          onlineUsers = [];
          snap.forEach(function(childSnapshot) {
            // This code will be called twice.
            var name = childSnapshot.name();
            var childData = childSnapshot.val();
            onlineUsers.push(childData);
          });

          $rootScope.$broadcast('onOnlineUser');
        });

        var getOnlineUserCount = function() {
          return numOnlineUsers;
        }

        var getOnlineUserList = function() {
          return onlineUsers;
        }

        return {
          getOnlineUserCount: getOnlineUserCount,
          getOnlineUserList: getOnlineUserList
        }
      }
    ]);
  </script>

  <div ng-controller="PresenceController" class="card bg-well p-1" style="padding:20px;max-width:600px;">
    <h6>{{totalViewers}} viewers are viewing</h6>

    <div scroll-glue class="overflowable" style="font-size:small; color:#1111AA;background-color:#DDDDDD;">
      <ul>
        <li ng-repeat='viewer in allViewers'>
          Geo: {{viewer.city}}, {{viewer.region}}, {{viewer.country}} -- {{viewer.loc}}
          <br/>
          Net: {{viewer.ip}} {{viewer.hostname}} {{viewer.org}}
        </li>
      </ul>
    </div>
  </div>
</div>

The Trivia Game

Here is the main event, a trivia game.

{[msg.from]}: {[msg.body]}

Machine Time: {[ MA.getMachineTime() ]}
Machine State: {[ MA.getMachineState() ]}
Machine Running: {[ MA.isRunning() ]}
Question Number: {[ questionNumber ]}
Countdown: {[ countdown ]}
Answers: {[ answers ]}
State: {[ state ]}

The code for the above is below:

<div>
  <script>
    function GameController($scope, $firebase) {
      $scope.messages = $firebase(new Firebase('https://doctorbud.firebaseIO.com/simplequizchatmessages'));
      $scope.answers = $firebase(new Firebase('https://doctorbud.firebaseIO.com/simplequizchatanswers'));
      $scope.state = $firebase(new Firebase('https://doctorbud.firebaseIO.com/simplequizchatstate'));
      $scope.countdown = $firebase(new Firebase('https://doctorbud.firebaseIO.com/simplequizchatcountdown'));
      $scope.questionNumber = $firebase(new Firebase('https://doctorbud.firebaseIO.com/simplequizchatquestionNumber'));

      $scope.addMessage = function(e) {
        var curState = $scope.state[$scope.state.$getIndex()[0]];
        console.log('stateMachine: ', curState);

        if (e.keyCode != 13) return;
        if ($scope.msg === "#clear")
        {
          $scope.messages.$set([]);
          $scope.answers.$set([]);
        }
        else
        {
          if (curState == 'asked')
          {
            $scope.answers.$add({from: $scope.name, body: $scope.msg});
            $scope.messages.$add({from: $scope.name, body: $scope.name + ' has answered.'});
          }
          else
          {
            $scope.messages.$add({from: $scope.name, body: $scope.msg});
          }
          $scope.msg = "";
        }
      };

      $scope.addBotMessage = function(msg) {
        $scope.messages.$add({from: "QuizBot", body: msg});
      };

      $scope.resetGame = function() {
        $scope.messages.$set([]);
        $scope.answers.$set([]);
        $scope.state.$remove();
        $scope.state.$add("asking");
        $scope.countdown = 10;
        $scope.questionNumber = 4;
      };


      $scope.stateMachine = function() {
        var curState = $scope.state[$scope.state.$getIndex()[0]];
        console.log('stateMachine: ', curState);

        if (curState == 'asking')
        {
          $scope.addBotMessage('Question... What is ' + $scope.questionNumber + ' + ' + $scope.questionNumber + '?');
          $scope.state.$remove();
          $scope.state.$add("asked");
        }
        else if (curState == 'asked')
        {
          if ($scope.countdown > 0)
          {
            $scope.addBotMessage('T:' + $scope.countdown);
            $scope.countdown = $scope.countdown - 1;
          }
          else
          {
            $scope.state.$remove();
            $scope.state.$add("scoring");
          }
        }
        else if (curState == 'scoring')
        {
          $scope.addBotMessage('Scores for Q:' + $scope.questionNumber);
          console.log('$scope.answers=', $scope.answers);

          var keys = $scope.answers.$getIndex();
          keys.forEach(
              function(key, i) {
                var   answer = $scope.answers[key];
                console.log($scope.answers[key]);
                $scope.addBotMessage('   ' + answer.from + ' answered "' + answer.body + '"');
              });

          $scope.questionNumber = $scope.questionNumber - 1;
          $scope.countdown = 10;
          $scope.answers.$remove();
          $scope.state.$remove();
          $scope.state.$add("asking");
        }
      };
    }
  </script>

  <div style="padding:20px;max-width:500px; background-color: aliceblue;" ng-controller="GameController">

    <div scroll-glue class="overflowable" id="messagesDiv" style="color:#00AA00;background-color:#000000;">
      <div ng-repeat="msg in messages"><em>{{msg.from}}</em>: {{msg.body}}</div>
    </div>
    <br/>
    <input type="text" ng-model="name" placeholder="Name" size="10">
    <input type="text" ng-model="msg" ng-keydown="addMessage($event)" placeholder="Message..." size="50">

    <h6>Machine Time: {{ MA.getMachineTime() }}</h6>
    <h6>Machine State: {{ MA.getMachineState() }}</h6>
    <h6>Machine Running: {{ MA.isRunning() }}</h6>

    <h6>Question Number: {{ questionNumber }}</h6>
    <h6>Countdown: {{ countdown }}</h6>
    <h6>Answers: {{ answers }}</h6>
    <h6>State: {{ state }}</h6>

    <mlmmachine
      ptr="MA"
      ng-init="resetGame()"
      reset-fn="resetGame()"
      step-fn="stateMachine()"
      class="mlm background">

    <mlmpanel></mlmpanel>

    </mlmmachine>
  </div>
</div>