Simple AngularJS app TicTacToe Game



A note from the author
: “After checking AngularJS I said to my colleague let’s blog about it… so here we are”.

This article is based on a simple example that it was quickly done with this MVC library. We started reading the book AngularJS from O’Reilly, by Brad Green & Shyam Seshadri and we wanted to use it right away. We are used to the MVC pattern because of MVC.net, when we saw AngularJS we immediately wanted to try it; also we had some influence from our front-end developers (Just to let you know part of our background is back-end development).

Basically we wanted to focus to the initial part of our study around this book providing examples around simple items. In this case we selected an example that can illustrate data binding, the watch function and CSS classes. We did not wanted to do the “Hello world” example from the book; as always we tried to do something fun, so we thought, let’s do a simple TIC-TAC-TOE game experience, THAT YOU PLAY ON THE SAME DEVICE (the 2 users should share it... yes I little to simple, we know!).

It had to be quick as well, so just a simple example would do the trick. Keep in mind this code can be refactor and make it way much better. There is a potential to use ng-repeat, which we did not include it here. I believe refactoring can reduce the amount of lines of code in our example. This was thought –created-done in less than 2 hours. Writing this article took us longer.

Try the TicTacToe here: http://www.sunitedsolutions.com/tictactoe/default.html

Download it from here: https://github.com/consultingti/simpletictactoe


What you won't find in this article
  • as I mentioned before this is a basic example, no complex work, but rather demonstrating the $watch function. No security discussion, we might work on that later on. But if you are interest and want to read or learn more about sanitizing or how to make your app more secure when using angularjs then this is a good place to start: http://www.youtube.com/watch?v=18ifoT-Id54 . In that youtube video the host is using laravel, which is a php framework, but you can do the same thing by using .Net (which is my preference because of my .Net background) or any other language and framework you feel comfortable.
  • Another good read with good comments and suggestions here: http://blog.brunoscopelliti.com/deal-with-users-authentication-in-an-angularjs-web-app
  • There is no API calls or back-end functionality (data, authorization functionality, etc.). Perhaps in the future if we move to a second version of this example.

For our little experiment we used AngularJS (of course, the star of the article) and also BootStrap as we wanted to play a little more and combined tools. Below you can find a brief definition of both items. After we’ll go into the example:

  1.  AngularJS: open-source javascript framework, maintained by Google that assists with running single-page applications. Its goal is to augment browser-based applications with MVC capability, in an effort to make both development and testing easier. – definition from Wikipedia.
  2.  $watch: this is from AngularJS. It is a $scope function that is called with an expression to observe and a callback that gets invoked whenever that expression changes.
  3. Bootstrap: sleek, intuitive and powerful mobile front-end framework for faster and easier web development. – definition from Bootstrap.

The Example:

It is about a web-base, simple Tic-Tac-Toe game.

Some requirements:

  •       Responsive app, so you can load it in your phone (this is where BootStrap comes into play)
  •       MVC (angularJS role)
  •       2 users: 1 named “X” the other named “O”. The site will make it clear the turn for each user.
  •       Honor the rule of the game:  two players, X and O, who take turns marking the spaces in a 3×3 grid. The player who succeeds in placing three respective marks in a horizontal, vertical, or diagonal row wins the game.

What do you need?

  1. Download Bootstrap from http://getbootstrap.com
  2. Download AngularJS from http://angularjs.org
  3. Some knowledge about HTML, CSS, Javascript. But no expert level is required for this simple example.

Some key part from the code:

  1. The ng-app attribute tells Angularjs which parts of the page it should manage. Since we’ve placed it on the <html> element, we’re telling Angularjs that we want it to manage the whole page.<html lang="en" ng-app>
  2. The bootstrap css was added, along with a custom table.css that I created for the tic-tac-toe html section.
  3. The angular.js.  <script src="./assets/js/angular.js"></script>
  4. The code that makes the little game possible. It may need refactoring and you may even want to put it in a different file and not in the same HTML as I did. As I said this is just a simple example, introduction level.  I’ll describe the code on the next section.
  5. The ng-controller. With it you manage areas of the page with JavaScript classes called controllers. By including a controller in the body tag, you will manage everything inside the <body> area.<body ng-controller="simpleGameController" onload="inigame();" >
  6.  <div id="d11" class="cell"><a  style="color:red" href="/" ng-click="clickrow('d11')" ng-class="{playing: row1[0].played == 'true'}">{{row1[0].title}}</a></div> The ng-click. A call to action to a method of the controller when you click this element.    The ng-class. In this case we have a condition that if an object has a particular value then we’ll add the “playing” class to the element.    {{object}}. To display the value.

Explanation of the Controller code:

  • simpleGameController: is our AngularJS controller receiving the scope object, which refers to the application model.
  • Player variable: to flag if is “X” or “O” that is playing. In our example “X” is the starting user always. As I said it is a simple demo so in this example you don’t get to choose who plays first, in this case the “X” will always play first.
  • There are 3 arrays (row1, row2, row3) for the rows. In each array you have:
    • Title. Initialize with string ”xod”. As the player clicks this value will change either to “X” or “O”, depending on the user.
    • Played. Initialize to “false”. As the player clicks it will change to “true” indicating you cannot click again on that cell.
  • Winner: function that indicates if a user won.
  • Clickrow: it is triggered when you click a cell where the user is playing. It received the parameter rowCoord, which indicates what cell was clicked. It also loads the modal window indicating the turn of the user.
  • Inigame: just called when the page loads to start the game, indicating the first player.
  • $watch: angularjs goodie that we use to observe the player variable. When this variable changes the value the winner function gets trigger to check if there was a winner or not.

The controller code:
<script>
     
     function simpleGameController($scope)
     {
     $scope.test = "1";
     $scope.player="X";
     $scope.win = "";
     $scope.lost="";
   
   
      $scope.row1 =  [{title: 'xod', played: 'false'},
                       {title: 'xod',played: 'false'},
                       {title: 'xod',played: 'false'}];
      $scope.row2 =  [{title: 'xod',played: 'false'},{title: 'xod',played: 'false'},{title: 'xod',played: 'false'}];
      $scope.row3 = [{title: 'xod',played: 'false'},{title: 'xod',played: 'false'},{title: 'xod',played: 'false' }];
                      
          
       $scope.winner = function()
       {
          var a = [
                    [$scope.row1[0].title,$scope.row1[1].title, $scope.row1[2].title],
                    [$scope.row2[0].title,$scope.row2[1].title, $scope.row2[2].title],
                    [$scope.row3[0].title,$scope.row3[1].title, $scope.row3[2].title]
                   
                  ]
          var matches = 1;
          //check the winner by evaluating who hit 3 in a line (vertical, horizontal, diagonal)
          //check horizontal
             //a. checking 1st array
             //b. checking 2nd array
             //c. checking 3rd array
           for (var i=0; i< 3;i++)
           {
             for (var j=0; j<3;j++)
             {
               if ((matches<=2)&& (j>0))
                {
                  if ((a[i][j-1]==a[i][j])&&(a[i][j-1]!="xod"))
                  {
                    matches=matches+1;
                  }
                }
             }
             if (matches<3)
                  matches=1;
             else
                {
                   $scope.test="winner";
                   $scope.lost = $scope.player;
                if ($scope.player=="X")
                     $scope.win = "O";
                     else
                     $scope.win = "X";
                return "winner";                    
                }
           }
            
          //check vertical
          for (var i=0; i< 3;i++)
           {
             for (var j=0; j<3;j++)
             {
               if ((matches<=2)&& (j>0))
                {
                  if ((a[j-1][i]==a[j][i])&&(a[j-1][i]!="xod"))
                  {
                    matches=matches+1;
                  }
                }
             }
             if (matches<3)
                  matches=1;
             else
              {
                $scope.test="winner";
                $scope.lost = $scope.player;
                if ($scope.player=="X")
                     $scope.win = "O";
                     else
                     $scope.win = "X";
               
                return "winner";           
              }
           }
          //check diagonal
          for (var i=0; i< 3;i++)
           {
            // for (var j=0; j<3;j++)
             //{
               if ((matches<=2)&& (i>0))
                {
                  if ((a[i-1][i-1]==a[i][i])&&(a[i-1][i-1]!="xod"))
                  {
                    matches=matches+1;
                  }
                }
             //}
             if (i==2)
             {
             if (matches<3)
                  matches=1;
             else
              {
                $scope.test="winner";
                $scope.lost = $scope.player;
                if ($scope.player=="X")
                     $scope.win = "O";
                     else
                     $scope.win = "X";
                return "winner";           
              }
              }
           }
        if (!($scope.test=="winner"))
        {
         $('#myModal').modal('show');
         }
       }
      
   
       $scope.clickrow = function(rowCoord)
       {
         //$scope.row1[0].title="wao";
         if (($scope.player.length > 0)&& !($scope.test=="winner"))
         {
          
            if (rowCoord.length >0)
            {
                var s = rowCoord.split("");
             
                if (s[1]=="1")
                {
                //row1
                     if ($scope.row1[parseInt(s[2])-1].title=="xod")
                     {
                         $scope.row1[parseInt(s[2])-1].title = $scope.player; 
                         //turn the value visible by chaging the background to white
                         $scope.row1[parseInt(s[2])-1].played="true";
                     }   
                }
                if (s[1]=="2")
                {
                //row2
                 if ($scope.row2[parseInt(s[2])-1].title=="xod")
                     {
                         $scope.row2[parseInt(s[2])-1].title = $scope.player; 
                         //turn the value visible by chaging the background to white
                         $scope.row2[parseInt(s[2])-1].played="true";
                     }   
                }
                if (s[1]=="3")
                {
                //row3
                 if ($scope.row3[parseInt(s[2])-1].title=="xod")
                     {
                         $scope.row3[parseInt(s[2])-1].title = $scope.player; 
                         //turn the value visible by chaging the background to white
                         $scope.row3[parseInt(s[2])-1].played="true";
                     }   
                }
               
                 
                  if ($scope.player=="X")
                     $scope.player="O";
                     else
                     $scope.player="X";
            }
         }
       
       }
      
       $scope.userturn = function(turn)
       {
          if (turn==null)
          {
            
             $('#myModal').modal('show');
             }
       }
      
       $scope.$watch('player',$scope.winner);
     }
   
     function inigame()
     {
     //$scope.player="X";
         $('#myModal').modal('show');
     }
   
      function restart()
       {
         location.reload();
       }
   
    </script>
Previous
Next Post »