Advanced Angular Chatroom Tutorial: No Database or Sockets Required

Leave a comment Standard
angular chat with pubnub

This is an expansion on my basic angular chatroom to add new functionality. In this version of the chat room there is now a popup color picker to change font colors, a way to change from one channel to another, click-able hyperlinks, HTML messages and emoticons.

1. Create a PubNub Account

To get started you will need to create a PubNub account. This will handle all the chat messages for you so you don’t need a server or database in order to quickly and easily create your chat room. There is a free plan and several paid for plans available for you to choose from. Once your account is created you’ll need to find your subscribe key and publish key.

2. Create the chat file

Now you’re going to create the main chat file. This will display your chat room. At the top of the file replace the subscribe_key and publish_key with the one you see in your newly created account.

index.html

<!doctype html>
<html ng-app="angular_chat">
  <head>
    <script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.0.5/angular.js"></script>
    <script src="http://code.jquery.com/jquery-1.9.1.js"></script>
    <script src="http://angular-ui.github.io/bootstrap/ui-bootstrap-tpls-0.6.0.js"></script>
    <div pub-key="PUB_KEY_GOES_HERE" sub-key="SUB_KEY_GOES_HERE" ssl="off" origin="pubsub.pubnub.com" id="pubnub"></div>
    <script src="http://cdn.pubnub.com/pubnub-3.1.min.js"></script>
    <script src="angular-chat.js"></script>
    <script src="angular-colorpicker.js"></script>
    <script src="angular-colorpicker-modal.js"></script>
    <link rel="stylesheet" href="style.css" rel="stylesheet">
    <link href="//netdna.bootstrapcdn.com/twitter-bootstrap/2.3.1/css/bootstrap-combined.min.css" rel="stylesheet">
  </head>
  <body>
  <div class='container-fluid' ng-controller="chatCtrl">
    <div ng-include="'chatHeader.html'"></div>
    <div ng-Show="!loggedIn" id="login">
      <h2>Login</h2>
      <label for="username">
        <b>Username:</b>
      </label>
      <label>
        <input type="text" ng-model="message.username" />
      </label>
      <span ng-click="attemptLogin()">
        <i></i> Go Chat
      </span>
    </div>
    <div ng-Show="loggedIn" id="chat">
      <table>
        <tr ng-repeat="chat in chatMessages">
            <td style="width: 30%;">
              <b>{{chat.username}}</b><br/>
              <small>
               {{chat.date}} @ {{chat.time}}
              </small>
            </td>
            <td colspan="2">
              <font color="{{chat.color}}"><span ng-bind-html-unsafe="chat.text"></span></font>
            </td>
        </tr>
        <tr ng-show="chatMessages.length == 0">
          <td colspan="3">No messages yet!</td>
        </tr>
      </table>
    </div>
    <form ng-Show="loggedIn" ui-keypress="{13:'postMessage()'}">
      <div id="inputMessage">
        <div ng-controller="ColorPickerCtrl">
          <script type='text/ng-template' id='fontcolor.html'>
            <div>
              <h3>Change Font Color</h3>
            </div>
            <div ng-modal="colorPicker">
              <ng-color-circle height="250" width="250" model-object="colorpicker" model-property="color"></ng-color-circle>
              <div style="background-color:{{colorpicker.color}}">Selected Color</div>
            </div>
            <div>
              <button ng-click="ok()">Done</button>
            </div>
          </script>
          <span ng-click="open()" id="fontModalButton" style="background:{{color}} !important;">
            <i></i>
          </span>
        </div>
        <span ng-click="toggleEmoticons = !toggleEmoticons">
          <img ng-src="{{emoticon_url}}{{emoticons[':)']}}" />
        </span>
        <input type="text" placeholder="Say hello!" ng-model="message.text" />
        <span ng-click="postMessage()">
          <i></i> Post
        </span>
      </div>
      <span ng-show="toggleEmoticons == true" id="emoticonList" ng-repeat="(key, image) in emoticons">
        <img ng-src="{{emoticon_url}}{{image}}" ng-click="insertEmoticon(key)" />
      </span>
    </form>
    <div ng-include="'chatFooter.html'"></div>
  </div>
  </body>
</html>

3. Create the header and footer files

chatHeader.html

<div>
  <h1>Ng-Angular Chat</h1>
  <h5><i></i> using Bootstrap and PubNub</h5>
  <p>&nbsp;</p>
  <div ng-show="realtimeStatus == 0">
    <span><i></i></span>
  </div>
  <div ng-show="realtimeStatus == 1">
    <span><i></i></span>
  </div>
  <div ng-show="realtimeStatus == 2">
    <span><i></i></span>
  </div>
  <div id="channelDropdown"><select ng-model="selectedChannel" ng-change="initChat(selectedChannel)" ng-options="obj.value as obj.text for obj in chatChannels"></select></div>
  <div ng-show="loggedIn" id="logout">
    <span ng-click="attemptLogout()"><i></i> Logout</span>
  </div>
</div>
<p>&nbsp;</p>
<div ng-show="errorMsg">
  <i></i> <b>Error:</b> {{errorMsg}}
</div>

chatFooter.html

<div>
  2013 &copy; design1online.com
</div>

4. Create the stylesheet

This example is using bootstrap however we still need some additional tweaks to get it looks just right.

style.css

#chat {
 height: 31em; 
 overflow: auto;
 background-color: #EEE;
 border-radius: .25em;
 margin-bottom: 1em;
}

#logout {
  float: right;
  margin-top: -1.35em;
}

#channelDropdown {
  margin-right: .3em;
  margin-left: .3em;
  float: left;
  margin-top: -1.35em;
}

#fontModalButton {
  height: 21px;
}

#selectedColor {
  width: 100%;
  border: 1px solid #000000; 
}

.alert {
  padding: 8px !important;
}

.status {
  float: left;
  margin-top: -1em;
}

.left {
  float: left;
  margin-right: .25em;
}

.icon {
  cursor: pointer;
}

5. Create the angular files

angular-chat.js

/***
 * File: angular-chat.js
 * Author: Jade Krafsig
 * Source: design1online.com, LLC
 * License: GNU Public License
 ***/

/***
 * Purpose: load bootstrap ui angular modules
 * Precondition: none
 * Postcondition: modules loaded
 ***/
angular.module('angular_chat', ['ui.bootstrap', 'directive.colorPicker']);

/***
 * Purpose: load the existing chat logs
 * Precondition: none
 * Postcondition: chat logs have been loaded
 ***/
function chatCtrl($scope, $http) { 

  /***
   * Configurable global variables
   ***/
  $scope.messageLimit = 50;
  $scope.defaultUsername = "Guest";
  $scope.selectedColor = "#000000";

  $scope.chatChannels = [
    {
      text: "Basic Channel",
      value: "angular_chat", 
      default: true
    },
    {
      text: "Advanced Channel",
      value: "angular_chat_advanced"
    },
  ];

  $scope.emoticon_url = "http://www.freesmileys.org/smileys/smiley-basic/";
  $scope.emoticons = {
    ':-)' : 'biggrin.gif',
    ':)' : 'biggrin.gif',
    ':D' : 'laugh.gif',
    ':-D' : 'laugh.gif',
    ':-|' : 'mellow.gif',
    ':|' : 'mellow.gif',
    ':-p' : 'tongue.gif',
    ':p' : 'tongue.gif',
    ':-(' : 'sad.gif',
    ':(' : 'sad.gif'
  };

  /***
   * Static global variables, do not change
   ***/
  $scope.loggedIn = false;
  $scope.errorMsg;
  $scope.realtimeStatus = 0;
  $scope.toggleEmoticons = false;

  /***
   * Purpose: clear the message object
   * Precondition: none
   * Postcondition: message object has been reset
   ***/
  $scope.clearMsg = function() {
    $scope.message = {
      username: $scope.defaultUsername,
      email: 'n/a',
      text: ''
    };
  }

  /***
   * Purpose: listen for a font color change broadcast
   * Precondition: color value
   * Postcondition: selected color has been set
   ***/
  $scope.$on('colorChange', function(obj, color){
    $scope.selectedColor = color;
  });    

  /***
   * Purpose: load the existing chat logs
   * Precondition: none
   * Postcondition: chat logs have been loaded
   ***/
  $scope.chatLogs = function() {
    PUBNUB.history( {
      channel : $scope.selectedChannel,
      limit   : $scope.messageLimit
    }, function(messages) {
      // Shows All Messages
      $scope.$apply(function(){
        $scope.chatMessages = messages.reverse();          
      }); 
    });
   }

  /***
   * Purpose: load the existing chat logs
   * Precondition: none
   * Postcondition: chat logs have been loaded
   ***/
   $scope.attemptLogin = function() {
    $scope.errorMsg = "";

    if (!$scope.message.username) {
      $scope.errorMsg = "You must enter a username.";
      return;
    }

    if (!$scope.realtimeStatus) {
      $scope.errorMsg = "You're not connect to PubNub.";
      return;
    }

    $scope.loggedIn = true;
   }

  /***
   * Purpose: remove error message formatting when the message input changes
   * Precondition: none
   * Postcondition: error message class removed from message input
   ***/
  $scope.$watch('message.text', function(newValue, oldValue) {
    if (newValue) {
      $("#inputMessage").removeClass("error");
      $scope.errorMsg = "";
    }
  }, true);

  /***
   * Purpose: trying to post a message to the chat
   * Precondition: loggedIn
   * Postcondition: message added to chatMessages and sent to chatLog
   ***/
  $scope.postMessage = function() {

    //make sure they are logged in
    if (!$scope.loggedIn) {
      $scope.errorMsg = "You must login first.";
      return;
    }

    //make sure they enter a chat message
    if (!$scope.message.text) {
      $scope.errorMsg = "You must enter a message.";
      $("#inputMessage").addClass("error");
      return;
    }

    //set the message date
    d = new Date();
    $scope.message.date = d.getDay() + "/" + d.getMonth() + "/" + d.getFullYear();
    $scope.message.time = d.getHours() + ":" + d.getMinutes() + ":" + d.getSeconds();

    //change font color
    $scope.message.color = $scope.selectedColor;

    //replace hyperlinks with clickable urls
    $scope.message.text = $scope.replaceURLWithLink($scope.message.text);

    //replace smileys
    $scope.message.text = $scope.replaceEmoticons($scope.message.text);

    //send the message to the selected channel
    PUBNUB.publish({
      channel : $scope.selectedChannel,
      message : $scope.message
    });

    //reset the message input box
    $scope.message.text = "";
  };

  /***
   * Purpose: connect and access pubnub channel
   * Preconditions: pubnub js file init
   * Postconditions: pubnub is waiting and ready
   ***/
  $scope.initChat = function(newChannel) {
    if (newChannel)
      $scope.selectedChannel = newChannel;

    if (!$scope.chatChannels.length) {
      $scope.errorMsg = "Missing chat channels, check config.";
      return;
    }

    if (!$scope.selectedChannel) {
      $scope.errorMsg = "You must select a channel.";
      return;
    }

    //clear out any chat messages from a previous channel
    $scope.chatMessages = Array();

    //unsubscribe to the active channel
    angular.forEach($scope.chatChannels, function(channel, key){
      PUBNUB.unsubscribe({channel: channel.value});
    });

    //subscribe to the selected channel
    PUBNUB.subscribe({
      channel    : $scope.selectedChannel,
      restore    : false, 
      callback   : function(message) { 
          //update messages with the new message
          $scope.$apply(function(){
          $scope.chatMessages.unshift(message);          
        }); 
      },

      error      : function(data) {
        $scope.errorMsg = data;
      },

      disconnect : function() {   
        $scope.$apply(function(){
          $scope.realtimeStatus = 0;
        });
      },

      reconnect  : function() {   
        $scope.$apply(function(){
          $scope.realtimeStatus = 1;
        });
      },

      connect    : function() {
        $scope.$apply(function(){
          $scope.realtimeStatus = 2;
          //load the chat logs
          $scope.chatLogs();
        });
      }
    });
  }

  /***
   * Purpose: trying to post a message to the chat
   * Precondition: loggedIn
   * Postcondition: message added to chatMessages and sent to chatLog
   ***/
  $scope.attemptLogout = function() {
    $("#inputMessage").removeClass("error");
    $scope.clearMsg();
    $scope.loggedIn = false;
  }

  /***
   * Purpose: set the chat channel to the default channel value
   * Precondition: at least one channel defined
   * Postcondition: selectedChannel set to the default
   ***/
  $scope.defaultChannel = function() {
    var chatChannel;

    angular.forEach($scope.chatChannels, function(channel, key){

     if (!chatChannel)
      chatChannel = channel;

     if (channel.default) {
      chatChannel = channel;
      return;
     }
    });

    $scope.selectedChannel = chatChannel.value;
  }

  /***
   * Purpose: add an emoticon to the message input
   * Precondition: emoticon has been selected
   * Postcondition: emoticon value appended to message input
   ***/
  $scope.insertEmoticon = function(selected) {
    $scope.message.text = $scope.message.text + " " + selected + " ";
  }

  /***
   * Purpose: regex to replace emoticons with image html
   * Precondition: text to replace emoticons in
   * Postcondition: any emoticons found have been replaced with html images
   ***/
  $scope.replaceEmoticons = function(text) {
    patterns = [];
    metachars = /[[\]{}()*+?.\\|^$\-,&#\s]/g;

    // build a regex pattern for each defined property
    for (var i in $scope.emoticons) {
      if ($scope.emoticons.hasOwnProperty(i)){ // escape metacharacters
        patterns.push('('+i.replace(metachars, "\\$&")+')');
      }
    }

    // build the regular expression and replace
    return text.replace(new RegExp(patterns.join('|'),'g'), function (match) {
      return typeof $scope.emoticons[match] != 'undefined' ?
        '<img src="'+$scope.emoticon_url+$scope.emoticons[match]+'"/>' :
        match;
    });
  }

  /***
   * Purpose: replace url text with a clickable link (opens in a new window)
   * Precondition: text to replace urls in
   * Postcondition: urls have been replaced with a clickable hyperlink
   ***/
  $scope.replaceURLWithLink = function(text) {
    var exp = /(\b(https?):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig;
    return text.replace(exp,"<a href='$1' target='_blank'>$1</a>"); 
  }

  /***
   * Purpose: Initialize the chatroom
   * Preconditions: none
   * Postconditions: none
   ***/
  $scope.clearMsg();
  $scope.defaultChannel();
  $scope.initChat();

}

angular-colorpicker-modal.js

/***
 * File: angular-colorpicker-modal.js
 * Author: Jade Krafsig
 * Source: design1online.com, LLC
 * License: GNU Public License
 ***/

/***
 * Purpose: modal template controller
 * Preconditions: $rootScope, $modal and ui-bootstrap module
 * Postconditions: creates a modal window using the specified template
 *  and broadcasts the selected color back to the main controller when
 *  the window is closed
 ***/
var ColorPickerCtrl = function ($rootScope, $scope, $modal) {

  $scope.open = function () {
    $scope.color = "#000000";

    var modalInstance = $modal.open({
      templateUrl: 'fontcolor.html',
      controller: 'ColorpickerModalCtrl'
    });

    //when the color changes broadcast back to the main chat controller
    modalInstance.result.then(function (data) {
      $scope.color = data.color;
      $rootScope.$broadcast('colorChange',  $scope.color);
    });
  }
}

//create a modal popup window and pass it back to the modal parent controller
/***
 * Purpose: modal controller
 * Preconditions: instance of the modal being created
 * Postconditions: functionality defined for all modals created of $modalInstance
 ***/
var ColorpickerModalCtrl = function ($scope, $modalInstance) {
  $scope.colorpicker = {};
  $scope.colorpicker.color = "#000000";

  $scope.ok = function () {
    $modalInstance.close({color: $scope.colorpicker.color});
  };
};

angular-colorpicker.js

/***
 * Method taken from Brian Grinstead and modified the return 
 * of rounded r,g and b.
 * @https://github.com/bgrins/TinyColor/blob/master/tinycolor.js
 ***/
(function(angular) {
  var ngColorPicker = angular.module('directive.colorPicker', []);

  function hsvToRgb(h, s, v) {
    h*=6;
    var i = ~~h,
      f = h - i,
      p = v * (1 - s),
      q = v * (1 - f * s),
      t = v * (1 - (1 - f) * s),
      mod = i % 6,
      r = [v, q, p, p, t, v][mod] * 255,
      g = [t, v, v, q, p, p][mod] * 255,
      b = [p, p, t, v, v, q][mod] * 255;

      return [~~r, ~~g, ~~b, "rgb("+ ~~r + "," + ~~g + "," + ~~b + ")"];
  }

  function rgbToHex(r, g, b) {
    return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
  }

  ngColorPicker.directive('ngColorCircle', ['$timeout', function($timeout) {
    return {
      restrict  : 'E',
      replace   : true,
      scope     : '@=',
      template  : '<div>'
      +             '<canvas id="colorCircle">'
      +             '</canvas>'
      +           '</div>',
      compile: function compile(tElement, tAttrs, transclude) {
        return {
          pre: function preLink(scope, iElement, iAttrs, controller) {
            var canvas        = document.getElementById("colorCircle"),
                context       = canvas.getContext('2d'),
                width         = canvas.width = iAttrs.width ||  300,
                height        = canvas.height = iAttrs.height || 300,
                imageData     = context.createImageData(width, height),
                pixels        = imageData.data,
                oneHundred    = 100,
                two55         = 255,
                circleOffset  = 10,
                diameter      = width - circleOffset * 2,
                wheelPixel    = circleOffset * 4 * width + circleOffset * 4;

            iElement.css({
              width: width,
              height: height
            });

            scope.radius            = diameter / 2;
            scope.radiusPlusOffset  = scope.radius + circleOffset;
            scope.radiusSquared     = scope.radius * scope.radius;
            scope.currentY          = oneHundred;
            scope.currentX          = -scope.currentY;

            for (y = 0; y < height; y++) {
              for (x = 0; x < width; x++) {
                var rx  = x - scope.radius,
                    ry  = y - scope.radius,
                    d   = rx * rx + ry * ry,
                    rgb = hsvToRgb((Math.atan2(ry, rx) + Math.PI) / (Math.PI * 2),
                          Math.sqrt(d) / scope.radius, 1); 

                pixels[wheelPixel++] = rgb[0];
                pixels[wheelPixel++] = rgb[1];
                pixels[wheelPixel++] = rgb[2];
                pixels[wheelPixel++] = d > scope.radiusSquared ? 0 : two55;
              }
            }

            context.putImageData(imageData, 0, 0);
          },
          post: function postLink(scope, iElement, iAttrs, controller) { 
            $(iElement).click(function(event) {
              scope.currentX = event.pageX - this.offsetLeft - scope.radiusPlusOffset || scope.currentX;
              scope.currentY = event.pageY - this.offsetTop - scope.radiusPlusOffset || scope.currentY;

              var theta = Math.atan2(scope.currentY, scope.currentX),
                  d = scope.currentX * scope.currentX + scope.currentY * scope.currentY;

              if (d > scope.radiusSquared) {
                scope.currentX = scope.radius * Math.cos(theta);
                scope.currentY = scope.radius * Math.sin(theta);
                theta = Math.atan2(scope.currentY, scope.currentX);
                d = scope.currentX * scope.currentX + scope.currentY * scope.currentY;
              }

              var color = hsvToRgb((theta + Math.PI) / (Math.PI * 2),
                Math.sqrt(d) / scope.radius, 1);
              var hex = rgbToHex(color[0], color[1], color[2]);

              $timeout(function(){
                scope.$parent[iAttrs.modelObject][iAttrs.modelProperty] = hex;                
              });
            });
          }
        };
      }
    };
  }]);

  ngColorPicker.directive('ngColorPicker', [function() {
    return {
      restrict: 'E',
      replace: true,
      scope : '@=',
      template: '<table>'
      +           '<tr>'
      +           '<td ng-repeat="color in colorList">'
      +             '<div style="width: 8px; height: 8px; border: {{color.select}}px solid #000; padding: 5px; background-color: {{color.color}}" ng-click="selectColor(color)">'
      +             '</div>'
      +           '<td>'
      +           '</tr>'
      +         '</table>',
      compile: function compile(tElement, tAttrs, transclude) {
        return {
          post: function postLink(scope, iElement, iAttrs, controller) { 
            scope.modelObject   = iAttrs.modelObject;
            scope.modelProperty = iAttrs.modelProperty;
            scope.colorList = [];
            angular.forEach(colors, function(color) {
              scope.colorList.push({
                color : color,
                select : 1
              });
            });
          }
        };
      },
      controller: function($scope, $element, $timeout) {
        $scope.selectColor = function(color) {
          for (var i = 0; i < $scope.colorList.length; i++) {
            $scope.colorList[i].select = 1;
            if ($scope.colorList[i] === color) {
              $scope[$scope.modelObject][$scope.modelProperty] = color.color;
              $scope.colorList[i].select = 2;
            }
          }
        };
      }
    };
  }]);
})(window.angular);

6. Working Demo

That’s it. Now you can test your files or you can be lazy and fork my working version on plunker or fork it on GitHub.

AngularJS Chatroom Tutorial: No Database or Sockets Required

Comments 8 Standard
angular chat with pubnub

So I’ve been playing around with Angular lately and I decide to try my hand at making an Angular chat room since angular is great when it comes to keeping the page display current as the DOM changes. In order to deal with the message sending (and for the ease of getting a working version going) I decided to use PubNub which handles all the message transport quickly and easily.

1. Create A PubNub Account

In order to use this you’ll need to sign up for a PubNub account. There is a free plan and several paid for plans available for you to choose from. Once your account is created you’ll need to find your subscribe key and publish key.

2. Create the chat file

Now you’re going to create the main chat file. This will display your chat room. At the top of the file replace the subscribe_key and publish_key with the one you see in your newly created account.

index.html

<!doctype html>
<html ng-app="angular_chat">
  <head>
    <script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.0.5/angular.js"></script>
    <script src="http://code.jquery.com/jquery-1.9.1.js"></script>
    <script src="http://angular-ui.github.io/bootstrap/ui-bootstrap-tpls-0.4.0.js"></script>
    <div pub-key="PUB_KEY_GOES_IN_HERE" sub-key="SUBSCRIBE_KEY_GOES_IN_HERE" ssl="off" origin="pubsub.pubnub.com" id="pubnub"></div>
    <script src="http://cdn.pubnub.com/pubnub-3.1.min.js"></script>
    <script src="angular-chat.js"></script>
    <link rel="stylesheet" href="style.css" rel="stylesheet">
    <link href="//netdna.bootstrapcdn.com/twitter-bootstrap/2.3.1/css/bootstrap-combined.min.css" rel="stylesheet">
  </head>
  <body>
  <div class='container-fluid' ng-controller="chatCtrl">
    <div ng-include="'chatHeader.html'"></div>
    <div ng-Show="!loggedIn" id="login">
      <h2>Login</h2>
      <label for="username">
        <b>Username:</b>
      </label>
      <label>
        <input type="text" ng-model="message.username" />
      </label>
      <span ng-click="attemptLogin()">
        <i></i> Go Chat
      </span>
    </div>
    <div ng-Show="loggedIn" id="chat">
      <table>
        <tr ng-repeat="chat in chatMessages">
            <td style="width: 30%;">
              <b>{{chat.username}}</b><br/>
              <small>
               {{chat.date}} @ {{chat.time}}
              </small>
            </td>
            <td colspan="2">{{chat.text}}</td>
        </tr>
        <tr ng-show="chatMessages.length == 0">
          <td colspan="3">No messages yet!</td>
        </tr>
      </table>
    </div>
    <form ng-Show="loggedIn" ui-keypress="{13:'postMessage()'}">
      <div id="inputMessage">
        <input type="text" placeholder="Say hello!" ng-model="message.text" />
        <span ng-click="postMessage()">
          <i></i> Post
        </span>
      </div>
    </form>
    <div ng-include="'chatFooter.html'"></div>
  </div>
  </body>
</html>

3. Create the angular file

Now we’re going to add in a file for our angular scripts. At the top of the file you’ll find some config variables which you can change as you see fit to set the default username, how many messages to show at a time, and the name of the chat channel you’ll be connecting to.

angular-chat.js

/***
 * File: angular-chat.js
 * Author: Jade Krafsig
 * Source: design1online.com, LLC
 * License: GNU Public License
 ***/

/***
 * Purpose: load bootstrap ui angular modules
 * Precondition: none
 * Postcondition: modules loaded
 ***/
angular.module('angular_chat', ['ui.bootstrap']);

/***
 * Purpose: load the existing chat logs
 * Precondition: none
 * Postcondition: chat logs have been loaded
 ***/
function chatCtrl($scope, $http) { 

  /***
   * Configurable global variables
   ***/
  $scope.chatChannel = "angular_chat";
  $scope.messageLimit = 50;
  $scope.defaultUsername = "Guest";

  /***
   * Static global variables
   ***/
  $scope.loggedIn = false;
  $scope.errorMsg;
  $scope.realtimeStatus = 0;

  /***
   * Purpose: clear the message object
   * Precondition: none
   * Postcondition: message object has been reset
   ***/
  $scope.clearMsg = function() {
    $scope.message = {
      username: $scope.defaultUsername,
      email: 'n/a',
      text: ''
    };
  }

  $scope.clearMsg();

  /***
   * Purpose: load the existing chat logs
   * Precondition: none
   * Postcondition: chat logs have been loaded
   ***/
  $scope.chatLogs = function() {
    PUBNUB.history( {
      channel : $scope.chatChannel,
      limit   : $scope.messageLimit
    }, function(messages) {
      // Shows All Messages
      $scope.$apply(function(){
        $scope.chatMessages = messages.reverse();          
      }); 
    });
   }

  /***
   * Purpose: load the existing chat logs
   * Precondition: none
   * Postcondition: chat logs have been loaded
   ***/
   $scope.attemptLogin = function() {
    $scope.errorMsg = "";

    if (!$scope.message.username) {
      $scope.errorMsg = "You must enter a username.";
      return;
    }

    if (!$scope.realtimeStatus) {
      $scope.errorMsg = "You're not connect to PubNub.";
      return;
    }

    $scope.loggedIn = true;
   }

  /***
   * Purpose: remove error message formatting when the message input changes
   * Precondition: none
   * Postcondition: error message class removed from message input
   ***/
  $scope.$watch('message.text', function(newValue, oldValue) {
    if (newValue)
      $("#inputMessage").removeClass("error");
      $scope.errorMsg = "";
  }, true);

  /***
   * Purpose: trying to post a message to the chat
   * Precondition: loggedIn
   * Postcondition: message added to chatMessages and sent to chatLog
   ***/
  $scope.postMessage = function() {

    //make sure they are logged in
    if (!$scope.loggedIn) {
      $scope.errorMsg = "You must login first.";
      return;
    }

    //make sure they enter a chat message
    if (!$scope.message.text) {
      $scope.errorMsg = "You must enter a message.";
      $("#inputMessage").addClass("error");
      return;
    }

    //set the message date
    d = new Date();
    $scope.message.date = d.getDay() + "/" + d.getMonth() + "/" + d.getFullYear();
    $scope.message.time = d.getHours() + ":" + d.getMinutes() + ":" + d.getSeconds();

   PUBNUB.publish({
      channel : $scope.chatChannel,
      message : $scope.message
    });

    $scope.message.text = "";
  };

  /***
   * Purpose: connect and access pubnub channel
   * Preconditions: pubnub js file init
   * Postconditions: pubnub is waiting and ready
   ***/
  PUBNUB.subscribe({
    channel    : $scope.chatChannel,
    restore    : false, 
    callback   : function(message) { 
      //update messages with the new message
      $scope.$apply(function(){
        $scope.chatMessages.unshift(message);          
      }); 
    },

    error      : function(data) {
      $scope.errorMsg = data;
    },

    disconnect : function() {   
      $scope.$apply(function(){
        $scope.realtimeStatus = 0;
      });
    },

    reconnect  : function() {   
      $scope.$apply(function(){
        $scope.realtimeStatus = 1;
      });
    },

    connect    : function() {
      $scope.$apply(function(){
        $scope.realtimeStatus = 2;
        //load the chat logs
        $scope.chatLogs();
      });
    }
  });

  /***
   * Purpose: trying to post a message to the chat
   * Precondition: loggedIn
   * Postcondition: message added to chatMessages and sent to chatLog
   ***/
  $scope.attemptLogout = function() {
    $("#inputMessage").removeClass("error");
    $scope.clearMsg();
    $scope.loggedIn = false;
  }
}

4. Create the header and footer files

These files will put a static header and footer on your chat room.

chatHeader.html

<div>
  <h1>Angular Chat</h1>
  <h5><i></i> using Bootstrap and PubNub</h5>
  <p>&nbsp;</p>
  <div ng-show="realtimeStatus == 0">
    <span><i></i> Disconnected</span>
  </div>
  <div ng-show="realtimeStatus == 1">
    <span><i></i> Connecting...</span>
  </div>
  <div ng-show="realtimeStatus == 2">
    <span><i></i> Connected</span>
  </div>
  <div ng-show="loggedIn" id="logout">
    <span ng-click="attemptLogout()"><i></i> Logout</span>
  </div>
</div>
<p>&nbsp;</p>
<div ng-show="errorMsg">
  <i></i> <b>Error:</b> {{errorMsg}}
</div>

chatFooter.html

<div>
  2013 &copy; design1online.com
</div>

5. Create the stylesheet

This will adjust the height of the chat window and the chat header status and logout buttons.

style.css

#chat {
 height: 33em; 
 overflow: auto;
 background-color: #EEE;
 border-radius: .25em;
 margin-bottom: 1em;
}

.status {
  float: left;
  margin-top: -1em;
}

#logout {
  float: right;
  margin-top: -1.2em;
}

6. Try the working version

See what the chat room looks like on my plunkr account or fork it on GitHub.

Female Gamers: Trends and Statistics

Leave a comment Standard

So every few years I put out a new survey to all the members on my text-based internet games. Most of the members of my games are teenage girls. All of my games are animal related and targeted at a tween/teen age range. Most of my members spend four hours a day playing my games in 20 minute intervals. Some of the members of my games have been playing for close to 10 years.

Video Game Demographics

Total Participants: 225

Age:

10: 2%
11: 2%
12: 8%
13: 13%
14: 7%
15: 5%
16: 4%
17: 3%
18: 4%
19: 2%
20: 4%
21: 5%
22: 2%
23: 3%

Average Age of Participant: 14

Gender:

No Answer: 16%
Male: 1%
Female: 83%

Current Video Game Habits


Which of these do you play video games on?

Cell Phone: 33%
Traditional Board Games: 37%
Internet: 76%
Console (Playstation, Xbox, Wii, PSP, DS): 46%
Computer: 80%


Do you play video games with?

Yes Sometimes No
Violence 16% 24% 52%
Blood, Guts and Gore 11% 23% 58%
Drugs 4% 10% 77%
Alcohol 6% 14% 72%
Foul Language 10% 24% 59%
Adult Themes/Situations 13% 30% 49%
Sexual Themes 5% 16% 71%
Nudity 4% 12% 75%
Death 16% 22% 55%
Guns 12% 24% 56%
Fighting 16% 24% 52%
Killing 14% 21% 57%


What type of internet games do you play?

Facebook (FarmVille, Mafia Wars, SongPop, etc): 39%
Flash (MiniClip, Pogo, Habbo Hotel, etc): 26%
MMO (World of Warcraft, Runescape, etc): 22%
Console (Halo, America’s Army, etc): 27%
Computer (SecondLife, EverQuest, Free Realms, etc): 61%
Mobile (Draw Something, Angry Birds, etc): 56%

Video Game Ownership


Which consoles do you own or have owned in the past?

Wii: 61%
Playstation: 22%
Nintendo 3DS: 12%
Nintendo DSi: 22%
Playstation Vita: 0%
Nintendo 3DS XL: 1%
Nintendo DSi XL: 6%
Gameboy: 31%
Playstation 2: 34%
Playstation 3: 14%
Xbox: 31%
Gameboy Advanced: 25%
GameCube: 16%
Nintendo DS: 53%
PSP: 9%


How often do you (or a parent) buy video games?

Once A Week 0%
Once Every Two Weeks 1%
Once Every Three Weeks 0%
Once A Month 13%
Every Six Months 28%
Every Year 33%


What type of video games do you currently own?

Music: 25%
Puzzles: 40%
Trivia: 20%
Arcade: 31%
Kinect Motion: 10%
Adventure: 71%
Exploration: 43%
Role Playing: 38%
Strategy: 45%
Racing: 45%
Building: 35%
Sports: 33%
Fighting: 29%
Simulation: 49%
Brain Training: 24%
Educational: 24%
Dancing: 31%
Party (4+ player games): 28%
MMORPG: 22%


How many video games do you currently have?

1 3%
2 1%
3 4%
4 3%
5 7%
6 3%
7 2%
8 1%
9 3%
10+ 57%


How often do you play video games?

Several Times A Day 12%
Once A Day 19%
Several Times A Week 12%
Once A Week 15%
Several Times A Month 6%
Once A Month 11%
Several Times A Year 7%
Once A Year 4%


How many hours straight (without leaving the game) do you spend playing video games?

5-10 Minutes 0%
10-20 Minutes 10%
20-45 Minutes 15%
1 Hour 22%
2 Hours 19%
3 Hours 10%
4 Hours 4%
5 Hours 0%
6+ Hours 5%

Video Game Preferences


I would make sure my video game had:

Yes No
Violence 21% 73%
Blood, Guts and Gore 15% 79%
Drugs 5% 88%
Alcohol 8% 85%
Foul Language 12% 81%
Adult Themes/Situations 25% 68%
Sexual Themes 11% 81%
Nudity 6% 87%
Death 22% 71%
Guns 18% 76%
Fighting 26% 66%
Killing 20% 73%


Some things are more important than others in my video game are:

Important Not Important
Storyline 80% 14%
Character Development 77% 17%
Sound Effects 44% 49%
Animation 74% 20%
Graphics 79% 14%
Music 45% 48%
Easy To Learn 77% 17%
Easy To Play 72% 22%
Easy To Navigate 82% 11%
Easy To Start/Install 83% 11%


My perfect video game would have:

Contests: 38%
Flash: 12%
Social Interaction: 37%
Turns: 8%
Fighting: 19%
Shooting: 15%
Killing: 16%
Businesses: 21%
Sports: 18%
Racing: 30%
Strategy: 40%
Adventure: 61%
Polls: 26%
Role Playing: 47%
Puzzles: 23%
Exploration: 45%
Weapons: 18%
Action: 55%
Building: 29%
Simulation: 51%
Design: 38%
Free Downloads: 35%
Music: 40%
Storyline: 52%
Magic: 40%
Vampires: 21%
Experimentation: 24%
Building & Construction: 25%
Management of Other Players: 15%
Music: 40%
Animation: 52%
Lifecycles (birth, growth, death): 56%
Pay To Play: 2%
Free To Play: 62%
Upgrade For Extra Features: 21%
Upgrade For Game Perks: 12%
Lots of Graphics: 43%
Minimal Graphics: 4%
Drag/Drop Interface: 14%
Expand/Collapse Interface: 14%
Customizable Site Layout: 30%
Zombies: 12%
Aliens: 6%
Werewolves: 21%
Phone Friendly: 32%
iPhone/iPad App: 27%
Role Playing: 49%
Insects: 9%
Fish: 20%
Birds: 19%
Conquest: 16%
World Domination: 14%
Resource Management: 16%
Economics: 18%
Witches: 20%
Animals: 86%
Fantastical Creatures: 37%
People: 44%
Clothes & Fashion: 32%
Creativity: 56%
Discovery: 42%
Surprises: 47%
Player vs Player Competition: 28%
Player vs Computer Competition: 18%
Free For All Competition: 30%
Team Competitions: 19%
Advertisements: 2%
Prizes: 38%


Video game m
usic would be:

Upbeat 51%
Dramatic 16%
Slow 7%
Spooky 5%


The video game would include these themes:

Romance: 47%
Comedy: 54%
Suspense: 33%
Drama: 44%
Horror: 16%
Violence: 19%
Family Friendly: 46%
Realistic: 60%
Educational: 25%
Science Fiction: 28%
Fantasy: 52%
Occult: 5%
Mystery: 52%


The video game would have these features:

Message Boards: 51%
Items: 64%
Stores: 67%
Maps: 51%
Level Ups: 52%
Bank System: 62%
Avatars: 61%
Weather: 56%
Customizable Characters: 69%
Customizable Clothes: 55%
Customizable Weapons: 30%
Live News Updates: 27%
Newspaper: 40%
RSS Feeds: 7%
Photo Gallery: 36%
Journals: 36%
Pet/Character Trading: 64%
Chat Rooms: 53%
Dynamically Colored Pets/Characters: 57%
Item Trading: 46%
Messaging System: 51%
Giving Gifts To Other Members: 52%
Random Occuring Events: 51%
Randomly Found Items: 60%
Quizzes: 37%
Polls: 40%
Limited Edition Items: 43%
Collectibles: 40%
Player Avatars/Tags: 47%
Pets/Characters: 84%
Limited Edition Pets/Characters: 52%
Currency Trading: 44%
Mini Games: 48%
Clubs: 41%

These results are provided for free  by
Design1online.com, LLC | Games For Girls.

AJAX Tutorial: 2 Player Tic-Tac-Toe Game (no database required)

Comments 7 Standard

If you haven’t read the first tutorial, the PHP version of this game without AJAX, then you should start there. This tutorial assumes you’ve already read the first one so I’m not going to explain how I actually go about programming the game. Instead this will focus on how you modify the original game so it works with AJAX. That means you won’t need to refresh the page between each player’s turn.

Download the Working Source Code
.zip archive

Getting jQuery

First thing is first we’ll need to get the jQuery library before we start updating any code. For those of you that don’t know, jQuery is code library that makes doing JavaScript quick and pretty painless. You can download or link to a copy offered on a Content Delivery Network (CDN) like Google Code. Personally I recommend downloading a copy if you’re going to post this game on a high traffic/visibility website. Sometimes if you link to a copy of jQuery on a CDN your site can experience lag and/or delays if the CDN is slow and your game won’t work at all if the CDN is down. On the other hand if you have a small website or limited space to store your files then there’s no need to download a copy of jQuery, linking to it is a better option. In this tutorial I’ve downloaded and included the jQuery file in the js folder of the .zip archive.

Setting Up For AJAX

Because we want our game to display using AJAX we need to create a new JavaScript file called hangman.js. This is what we’ll use to make all of our AJAX calls and respond to the player’s input.

Now that we have two JavaScript files we have to update our index.php file so it knows both of those JavaScript files are necessary to run the game. In the <head> tag, under the link to our stylesheet, we add two lines of code that tell our browser to load our JavaScript files:

<head>
        <title>Tic Tac Toe</title>
        <link rel="stylesheet" type="text/css" href="inc/style.css" />
        <script language="Javascript" src="js/jquery-1.7.1.min.js"></script>
        <script language="Javascript" src="js/tic-tac-toe.js"></script>
</head>

We also need to remove our $_SESSION[‘game’][‘tictactoe’] check from the top of our index file. Our AJAX calls will be running the game so it’s no longer necessary here. However we leave the session_start() at the top of the page so that if the player accidentally refreshes the page they don’t loose the game they had in progress.

Next we remove the code inside of our <div id=”content”> and set that aside for later because we’ll need to use parts of it in our AJAX. In it’s place we’re going to enter a loading message. This is what the page will display before the AJAX calls have finished processing.

<body>
        <div id="content">
        <center>Loading Tic-Tac-Toe.... please wait.</center>
        </div>
</body>

Writing the Tic-tac-toe.js File

Now it’s time to jump into jQuery and AJAX.

$(document).ready(function() {

    //start the game when the page loads
    playGame();

});

jQuery uses a special function to check when the page has finished loading. Anything you put inside of this function will run as soon as the page is loaded, or in response to something someone has done on the page, like mouse clicks or using the keyboard. For this tutorial all we need it to do is call our AJAX function that will load and display the game to the content div on our index.php file.

/***
* Purpose: play tic-tac-toe
* Preconditions: none
* Postconditions: new game has started or existing game is in play
***/
function playGame()
{
    $.ajax({
        url: 'ajax/index.php',
        data: {type: 'playGame'},
        success: function( data ) {
            $("#content").html(data);
        }
    });
}

Inside of our playGame() function we use AJAX to tell the browser to load our ajax file and send it some data (the type of action we’re doing, whether or not to start a new game, the player taking their turn). Then we use a success callback function to load the results of our ajax call onto the page. A callback function is a function that runs as a result of being called by another process. In this case our callback function, success, is loading the data we’ve retrieved through AJAX and putting it into the content div’s HTML property.

/***
* Purpose: place an X or O on the board
* Preconditions: none
* Postconditions: game status updated
***/
function makeMove()
{    
    var selected_spot = null;

    //find the first spot on the board they have an X or O inside of
    $(".board_spot").each(function() {
        if ($(this).val()) {
            //they have a value in this spot, break out of the loop
            selected_spot = $(this);
            return false;
        }
    });

    //make sure they've selected a spot
    if (selected_spot == null) {
        alert("You must select a spot on the board first.");
        return false;
    }

    //make sure we only get an X or O value
    if ($(selected_spot).val() != "X" && $(selected_spot).val() != "O") {
        alert("Invalid selection made. Please try again.");
        return false;
    }

    //pass the spot and the value they selected to our ajax file
    $.ajax({
        url: 'ajax/index.php',
        data: {type: 'playGame', spot:$(selected_spot).attr("name"), value:$(selected_spot).val()},
        success: function( data ) {
            $("#content").html(data); 
        }
    });
}

If we want to respond to someone trying to pick a spot we use a fairly similar process. First we loop through all of the input boxes on the page (we’ve added a call called board_spot to them in our displayBoard class function) and save the first one that has a value into our selected_spot variable. Then we check to make sure they’ve selected a spot and that the value in the spot is either an X or an O. Finally we send the spot they selected and the value to our ajax file.

/***
* Purpose: start a brand new game
* Preconditions: none
* Postconditions: new game has started
***/
function newGame()
{
    $.ajax({
        url: 'ajax/index.php',
        data: {type: 'playGame', newgame: true},
        success: function( data ) {
            $("#content").html(data);
        }
    });
}

The last piece of our ajax file is the newGame function. When the game is over we want to make sure we use AJAX to update the game state and then refresh the page to a new game.

Writing the AJAX File

Now that our JavaScript file will send AJAX requests we need to write the file on the other end of the request — the file that responds to the data we’re sending it. Create a new folder and name is ajax. Inside of it make an index.php file. Inside of the file put the following:

<?php
/***
* File: ajax/index.php
* Author: design1online.com, LLC
* Created: 4.26.2012
* License: Public GNU
* Description: respond to ajax calls
***/

//include the required files
require_once('../oop/class.game.php');
require_once('../oop/class.tictactoe.php');

//this will keep the game data as they make a new ajax request
session_start();

//respond to AJAX requests
echo doAction($_GET);

function doAction($getdata)
{
    switch ($getdata['type'])
    {
        case "playGame":
            return playGame($getdata['spot'], $getdata['value']);
        default:
            return "Invalid option selected.";
    }
}

/***
* Purpose: Display and play the game
* Preconditions: a game exists
* Postconditions: the current game is displayed to the screen
***/
function playGame($spot, $value)
{    
    //if they haven't started a game yet let's load one
    if (!$_SESSION['game']['tictactoe'])
        $_SESSION['game']['tictactoe'] = new tictactoe();

    echo "<h2>Let's Play Tic Tac Toe!</h2>";
    $_SESSION['game']['tictactoe']->playGame($spot, $value);
}

In this file we have two functions, doAction and playGame. Both functions take the $_GET data sent by our AJAX calls and do various things depending on the data sent to them. doAction is run every time this ajax file loads. It decides which function it needs to call, depending on the type we’ve sent it, and then returns the result to the screen.

Now pull up that code I had you set aside earlier. We’re going to move that into our playGame function with a few adjustments. We no longer need the <form> tags around our game data. In addition instead of sending $_POST data to our hangman object we’re now sending $_GET data. The $_GET data contains the information we were previously posting from the <form> tags. Since AJAX is refreshing what appears inside of our content div there’s no reason to use our <form> and $_POST variables anymore.

Last But Not Least

We’re almost done! Before we can view our handy work we need to open our tic tac toe OOP file. Anywhere we had a $_POST statement we need to change that to a $_GET since we’re no longer using the $_POST variable. We also need to adjust the move() function so it accepts both the spot and the value of that spot. Our new move function looks like this:

/**
    * Purpose: trying to place an X or O on the board
    * Preconditions: the position they want to make their move, the X or O value
    * Postconditions: the game data is updated
    **/
    function move($spot, $value)
    {            

        if ($this->isOver())
            return;

        if ($value == $this->player)
        {    
            //update the board in that position with the player's X or O 
            $coords = explode("_", $spot);
            $this->board[$coords[0]][$coords[1]] = $this->player;

            //change the turn to the next player
            if ($this->player == "X")
                $this->player = "O";
            else
                $this->player = "X";

            $this->totalMoves++;
        }

        if ($this->isOver())
            return;
    }

Finally, we need to update our new game and take turn buttons so they run the newGame and playGame javascript functions we wrote earlier:

<input type=\"submit\" name=\"move\" value=\"Take Turn\" onClick=\"makeMove();\" /><br/>

<div id=\"start_game\"><input type=\"submit\" name=\"newgame\" value=\"New Game\" onClick=\"newGame()\" /></div>"

Conclusion

Well, that wasn’t so hard now was it? Try the working version to see what it’s like! In this tutorial we took our existing PHP Tic Tac Toe game and modified it to use AJAX. If you play the game you’ll see the page no longer has to refresh each time you click the take turn button or when you hit the new game button. To accomplish this we first downloaded jQuery, created a JavaScript file to make AJAX requests, wrote a file to return a response to our AJAX requests, and then updated our tic tac toe class to use $_GET and our new JavaScript functions.

Game Journal 1.5: Ten Games You Played As A Child

Leave a comment Standard

In my last post I created a game with a passive player. I also included a short blurb to describe what I consider a passive player. This exercise challenges you to list ten games you played as a child and describe what you liked most about them.

Exercise 1.5

When I was 6 or 7 I had a best friend right down the street. Tyler was totally into video games, and better yet his dad got him nintendo, SNES and SEGA as soon as they came out — in addition to a number of game cartridges for it. Tyler was one of the reasons I started playing and love video games. I can remember spending hours on the floor beside him trying to beat the next boss or get to the next level and our pieces of paper with all the skip level passwords on them. Yeah, that’s right. When I started playing video games there were no saved games, only special passwords or codes that would advance you back to the level where you last left off. So thank you Tyler for all those hours we spent trying to beat Shredder on Teenage Mutant Ninja Turtles.

  1. Mario for original Nintendo. This was the first game I felt like I could play over and over again without ever getting tired of it. Even though the controls were so simple (jump, duck, fire a fireball) this game was, and still is, one of the most challenging platform games I’ve ever played. One of my favorite things about this game was all the surprises that kept cropping up as you played it – hidden 1ups, beanstalks, and tubes with bonus coins or fire flowers.
  2. Donkey Kong for original Nintendo. I loved this game because it was simple and challenging. I don’t think I ever got past level 3 but that never stopped me from playing it again and again. I loved not knowing where the barrels were going to fall and the crazy mad dance of the monkey when you finally get the top of the level and save the princess.
  3. Teenage Mutant Ninja Turtles for SNES. I probably played this game with my friend Tyler more than any other game. I remember we used to fight over who was going to be Donatello and who was going to be Leo. This is the first two player game I remember playing with someone. I know the graphics were bad and the sound was pretty pitiful but I think this game holds a place in my heart because it was the first one I experienced in 2-player mode — and that made everything else seem unimportant.
  4. Pac Man the Arcade Game. I used to take Gynmastics when I was little and one of the things I loved about the place was the old Pac Man machine they had in the lobby. I use to waste quarters on it several times a week waiting for my mom to come pick me up from the gym. What I liked about this game was the foresight and planning it required. You had to think long term — keep in mind where all the ghosts were going, and save your power dots until the last minute.
  5. Mario Kart for SNES. This was the first racing game I ever played with my friend Tyler. I used to kick his butt in this game and he hated every minute of it. What I liked most about this game was the clever use of items to give you advantages over other racers, and the obstacles on the course that could give you the speed boost you needed or condemn you at the same time as you weren’t paying attention and fell off the side of a bridge.
  6. Super Mario World for SNES. This game was a cool mix of puzzles and reflexes. I loved the new items and the take off spin on the original Mario game. However what I loved best was the introduction of Yoshi. This was the first game I played where you could use another character beside your own character’s skill to complete a level.
  7. Sonic the Hedgehog for SEGA. This game was all about speed and reflexes. I loved the fast pace, quick to finish levels and the fact that I could play as a secondary character (Tails) without worry about dying and as a helping addition to the first player — without being the main player.
  8. Kirby for the original game boy. This was my first game on my game boy that I couldn’t put down. I’d spend hours trying to eat things and “puff” fly away from enemies. I loved the music, cute graphics, and the simple controls.
  9. Tetris for the original game boy. Okay, so I rock at Tetris. Although it was also available on SNES I’ve always felt this game was best played on a small screen where you could curl up on a couch and work your way through the simplest and most challenging five blocks invented.
  10. Paperboy for SNES. I loved this game for the random events and occurrences that would pop up but it was way too hard to play for more than a few levels. However this makes my list because this was the first game I played that took it’s inspiration from real life and made it into a game.

Game Journal 1.3: Your Life As A Game

Leave a comment Standard

In my previous game journal post I explored a game I hate to play, listed all the reasons I hated it, and then offered ways to fix the things I didn’t like.

Exercise 1.3

Today’s exercise challenges you to list 5 areas of your life that could be made into a game. Then give a small explanation of the game structure and rules that you’ve designed. So here it goes.

Area 1: Dorkie Yorkie

Every morning when I wake up I find myself in a mad rush to get all my things together and get out the door in time for work. The biggest obstacle I face isn’t always the traffic (although Northern VA has bumped up to the number 2 spot in the country) but my 8lb Yorkie who I affectionately call Steven Tyler. No matter what tricks I seem to pull, he never wants to come in from our fenced in back yard. He’s constantly chasing the fence line we share with a neighbors dog — barking madly of course — and runs away defiantly any time I try to catch him.

So this first game is called Dorkie Yorkie. You have a timer that’s running out in the corner of the screen and your job is to try and lure your dog back into the house before the timer runs out. You’ll have a side menu of items you can use to try and get the dog inside but depending on the weather, if the neighbors dog is outside, and how hungry/tired he is, he’ll respond to the different items to try to entice him with. The faster you can get him inside the more points you get which you can trade in for bigger and better items in your toolbox. Each time you successfully get him inside you advance to the next day aka the next level.

Area 2: Green, Green, Green Light

As I said earlier traffic in our area is a huge buzz kill. Northern Virginia and the Washington, DC area has been bumped up to the number 2 worst traffic in the US. I find the amount of time that I sit at red lights frustrating along with how long it takes the driver at the front of the light to actually GO once it turns green.

This game is called Green, Green, Green Light. It’s a timed racing type game mixed in with a bit of Simon Says. Your job is to get through as many consecutive green lights as you possibly can without running a red light. If you hit a red light you have to stop and wait for it to turn green again. If you run the red light you get a 30 second time penalty. Your goal is to get to your destination as quickly as you can and before the timer runs out. For each consecutive green light you make it through you get time bonuses. Hitting pedestrians, other cars, trash cans, and anything else will give you extra time penalties.

Area 3: Little Space

One of the things I like doing in my spare time is designing floor plans for my dream barn and office space. What I struggle with most is getting everything to fit into the size space that I can actually afford.

Little Space challenges your ability to efficiently and logically fit items into a small space. Think Tetris meets a birds eye view approach. Instead of blocks you have cubicles, conference rooms, barn stalls, bedrooms, dining rooms, living rooms, fireplaces, stairs, etc. The interior spaces, dimensions and sizes would change depending on the floor plan you’re attempting. All interior spaces could be rotated 90 degrees. Some buildings would have multiple floors which you could switch between to place interior items. The goal of this game being to get all of the interior spaces into the floor plan in a workable layout (ie you can access each interior space and no doors/windows are blocked by another interior space). As the levels progress the number of interior items you have to fit into the space grows the space gets smaller.

Area 4: Color Palette

Recently my mother has taken to learning watercolors. In her attempt to learn she’s asked me to try my hand at them as well. The task is a bit time consuming but relaxing as well. One of the things that always bugs me is how dirty the water gets when I switch paints.

Color Palette is a game that challenges you to match colors on the canvas while keeping your water that you use to clean your brush from turning a certain color. So it’s a bit of a two for one challenge. As you match colors on the canvas you get close to completing a picture on the canvas. When the picture is completely colored in you win the level. But you have to rinse your brush between each color you put on the canvas. Every time you clean your brush and match the color you’re not supposed to you get a strike. Three strikes and it’s a game over. With every three successful colors you add to the canvas you can choose to clean your water (or leave it the way it is). The challenge is to see how many pictures you can “paint” before you strike out.

Area 5: Conversation

I’m running out of ideas here so this one is my really reaching idea. So I spend a fair amount of time talking/emailing the members of my various games in a day which is the only way I can think to tie this one into my life as a game.

So Conversation is a multiplayer game that gives one person in the group a conversation topic and a direction in which to sway the other players about the conversation. They have 5 minutes to present their argument for or against the conversation topic. At the end of the 5 minutes everyone takes a vote for or against the argument. If they have the majority of votes they win a point, otherwise they loose a point. The game goes three rounds, with each player getting a chance to argue a conversation topic in each round. The person with the most points wins. If there’s a tie those members go head to head in a speed round until one person looses their argument.

Ehh, I know this last one is probably a bit of a stretch but it’s all I could come up with. It would probably need a lot of play testing to get conversation topics that could be swayed one way or another based solely on the presentation of the argument however if you could find enough of them it might be worth expanding upon this idea.

PHP Tutorial: 2 Player Tic-Tac-Toe Game (no database required)

Comments 9 Standard

In the first part of my games tutorial series we created a PHP Hangman game and then in the second installment we converted Hangman to use AJAX. Now it’s time to follow the same approach, only with Tic Tac Toe.

The Beauty of Classes

One of the things I like about classes is how easy it is to use and reuse them without writing a lot of code to go with it. So to start our Tic Tac Toe game we’re going to copy all the files from our original PHP Hangman game. We won’t need to change anything in the game class but we will need to make some modifications to the other class. First we’ll remove all references to letters, then we’ll remove any functions we don’t need anymore. Finally we change the main functions to display a Tic Tac Toe board instead of our hangman pictures, and finish it off by changing the logic behind how you win a game and how you make a move in the game.

Our new Tic Tac Toe game will need a player (or turn) variable so we know whose turn it is to place an X or an O. Then we’re going to add another variable to keep track of how many guesses have been made — that makes it easier to figure out if there’s a tie game instead of looping through the whole array each time someone guesses. We’ll also need a new array called Board to store all of the X and O’s placed on our game board.

The Index File

We’re going to keep things as short and easy as possible. The only changes we have to make to our index file is to load a tic tac toe game instead of a hangman game. Then we change the page title and we’re nearly done!

<?php
/***
* File: index.php
* Author: design1online.com, LLC
* Created: 1.31.2012
* License: Public GNU
* Description: PHP/MySQL Version of 2 Player Tic Tac Toe
***/
require_once('oop/class.game.php');
require_once('oop/class.tictactoe.php');

//this will store their information as they refresh the page
session_start();

//if they haven't started a game yet let's load one
if (!$_SESSION['game']['tictactoe'])
    $_SESSION['game']['tictactoe'] = new tictactoe();

?>
<html>
    <head>
        <title>Tic Tac Toe</title>
        <link rel="stylesheet" type="text/css" href="inc/style.css" />
    </head>
    <body>
        <div id="content">
        <form action="<?php echo $_SERVER['PHP_SELF']; ?>" method="POST">
        <h2>Let's Play Tic Tac Toe!</h2>
        <?php
            $_SESSION['game']['tictactoe']->playGame($_POST);
        ?>
        </form>
        </div>
    </body>
</html>

The Updated Class File

Here’s the class file with our updated game logic. Can you see how similar it is to our hangman class file? What’s changed and what looks the same?

<?php
/***
* File: oop/class.tictactoe.php
* Author: design1online.com, LLC
* Created: 1.31.2012
* License: Public GNU
* Description: tic tac toe game
***/

class tictactoe extends game
{
    var $player = "X";            //whose turn is
    var $board = array();        //the tic tac toe board
    var $totalMoves = 0;        //how many moves have been made so far        

    /**
    * Purpose: default constructor
    * Preconditions: none
    * Postconditions: parent object started
    **/
    function tictactoe()
    {
        /**
        * instantiate the parent game class so this class
        * inherits all of the game class's attributes 
        * and methods
        **/
        game::start();
    }

    /**
    * Purpose: start a new tic tac toe game
    * Preconditions: maximum number of guesses
    * Postconditions: game is ready to be displayed
    **/
    function newGame()
    {
        //setup the game
        $this->start();

        //reset the player
        $this->player = "X";
        $this->totalMoves = 0;

        //clear out the board
        $this->board = array();
    }

    /**
    * Purpose: run the game until it's tied or someone has won
    * Preconditions: all $_POST content
    * Postconditions: game is in play
    **/
    function playGame($postdata)
    {
        if (!$this->isOver() && $postdata['move'])
            $this->move($postdata);

        //player pressed the button to start a new game
        if ($_POST['newgame'])
            $this->newGame();

        //display the game
        $this->displayGame();
    }

    /**
    * Purpose: display the game interface
    * Preconditions: none
    * Postconditions: start a game or keep playing the current game
    **/
    function displayGame()
    {

        //while the game isn't over
        if (!$this->isOver())
        {
            echo "<div id=\"board\">";

            for ($x = 0; $x < 3; $x++)
            {
                for ($y = 0; $y < 3; $y++)
                {
                    echo "<div class=\"board_cell\">";

                    //check to see if that position is already filled
                    if ($this->board[$x][$y])
                        echo "<img src=\"images/{$this->board[$x][$y]}.jpg\" alt=\"{$this->board[$x][$y]}\" title=\"{$this->board[$x][$y]}\" />";
                    else
                    {
                        //let them choose to put an x or o there
                        echo "<select name=\"{$x}_{$y}\">
                                <option value=\"\"></option>
                                <option value=\"{$this->player}\">{$this->player}</option>
                            </select>";
                    }

                    echo "</div>";
                }

                echo "<div class=\"break\"></div>";
            }

            echo "
                <p align=\"center\">
                    <input type=\"submit\" name=\"move\" value=\"Take Turn\" /><br/>
                    <b>It's player {$this->player}'s turn.</b></p>
            </div>";
        }
        else
        {

            //someone won the game or there was a tie
            if ($this->isOver() != "Tie")
                echo successMsg("Congratulations player " . $this->isOver() . ", you've won the game!");
            else if ($this->isOver() == "Tie")
                echo errorMsg("Whoops! Looks like you've had a tie game. Want to try again?");

            session_destroy(); 

            echo "<p align=\"center\"><input type=\"submit\" name=\"newgame\" value=\"New Game\" /></p>";
        }
    }

    /**
    * Purpose: trying to place an X or O on the board
    * Preconditions: the position they want to make their move
    * Postconditions: the game data is updated
    **/
    function move($postdata)
    {            

        if ($this->isOver())
            return;

        //remove duplicate entries on the board    
        $postdata = array_unique($postdata);

        foreach ($postdata as $key => $value)
        {
            if ($value == $this->player)
            {    
                //update the board in that position with the player's X or O 
                $coords = explode("_", $key);
                $this->board[$coords[0]][$coords[1]] = $this->player;

                //change the turn to the next player
                if ($this->player == "X")
                    $this->player = "O";
                else
                    $this->player = "X";

                $this->totalMoves++;
            }
        }

        if ($this->isOver())
            return;
    }

    /**
    * Purpose: check for a winner
    * Preconditions: none
    * Postconditions: return the winner if found
    **/
    function isOver()
    {

        //top row
        if ($this->board[0][0] && $this->board[0][0] == $this->board[0][1] && $this->board[0][1] == $this->board[0][2])
            return $this->board[0][0];

        //middle row
        if ($this->board[1][0] && $this->board[1][0] == $this->board[1][1] && $this->board[1][1] == $this->board[1][2])
            return $this->board[1][0];

        //bottom row
        if ($this->board[2][0] && $this->board[2][0] == $this->board[2][1] && $this->board[2][1] == $this->board[2][2])
            return $this->board[2][0];

        //first column
        if ($this->board[0][0] && $this->board[0][0] == $this->board[1][0] && $this->board[1][0] == $this->board[2][0])
            return $this->board[0][0];

        //second column
        if ($this->board[0][1] && $this->board[0][1] == $this->board[1][1] && $this->board[1][1] == $this->board[2][1])
            return $this->board[0][1];

        //third column
        if ($this->board[0][2] && $this->board[0][2] == $this->board[1][2] && $this->board[1][2] == $this->board[2][2])
            return $this->board[0][2];

        //diagonal 1
        if ($this->board[0][0] && $this->board[0][0] == $this->board[1][1] && $this->board[1][1] == $this->board[2][2])
            return $this->board[0][0];

        //diagonal 2
        if ($this->board[0][2] && $this->board[0][2] == $this->board[1][1] && $this->board[1][1] == $this->board[2][0])
            return $this->board[0][2];

        if ($this->totalMoves >= 9)
            return "Tie";
    }
}

Try the working example or Download the source code.

Learn how to create an AJAX version of this game.