2

I'm trying to build a basic tic tac toe game in angular 1. When I click on a square I want to either add X or O depending on the turn. I cannot figure out how to do that. I can do it in jquery but I want to do it in angular.

<div class="squares-container" ng-repeat="square in squares">
  <div class="s" id="{{ $index + 1 }}" ng-click="move(square)", ng-bind="{{ $index + 1 }}"></div>
</div>

angular
.module('app')
.directive('ticTacToe', function(){
    return {
      restrict: 'E',
      templateUrl: 'tic-tac-toe.html',
      controller: controller

    };

    function controller($scope){
        function init() {
            let turn = 0;
            let currentPiece = 'X';

            $scope.squares = [1, 2, 3, 4, 5, 6, 7, 8, 9];
            $scope.move = function(id){
                currentPiece = turn % 2 === 0 ? 'X' : 'O';
                $scope.id = currentPiece;
                turn++;  
            };
        }
        init();
    }
})

Here's the code. https://plnkr.co/edit/UwHsltXVLFAVG6pHKKwO?p=preview

Stack Juice
  • 137
  • 1
  • 2
  • 11

1 Answers1

4

You can set your squares to objects and simply alter the properties of them.

When you click on a square, pass it to your move function, find the index of it and change the piece property of that square.

(function() {

  'use strict';

  angular.module('app', []);

})();

(function() {

  'use strict';

  angular.module('app').directive('ticTacToe', TicTacToeDirective);

  function TicTacToeDirective() {

    return {
      restrict: 'E',
      templateUrl: 'tic-tac-toe.html',
      controller: TicTacToeController,
      controllerAs: 'TicTacToeCtrl'
    };

  }

  TicTacToeController.$inject = [];

  function TicTacToeController() {

    // reference this to make it available in the view using the controller as syntax
    var vm = this;

    // expose our move function to the view
    vm.move = move;

    // we can even expose our newGame function to start a new game
    vm.newGame = newGame;

    // set our defaults - we will add turn to this controller so we can display it in the view 
    vm.turn = 0;
    var currentPiece = "X";

    // start a new game
    newGame();

    function move(square) {

      // get the index of the square passed in
      var index = vm.squares.indexOf(square);

      // set the squares piece property to the correct piece if it doesn't already have one set

      if (!vm.squares[index].piece) {

        vm.squares[index].piece = vm.turn % 2 === 0 ? 'X' : 'O';

        // increment the number of turns
        vm.turn++;

      }

    }

    function newGame() {

      // reset turn
      vm.turn = 0;

      // reset current piece
      currentPiece = "X";

      // setup our squares
      vm.squares = [{
          id: 1,
          piece: null
        }, {
          id: 2,
          piece: null
        },
        {
          id: 3,
          piece: null
        },
        {
          id: 4,
          piece: null
        },
        {
          id: 5,
          piece: null
        },
        {
          id: 6,
          piece: null
        },
        {
          id: 7,
          piece: null
        }, {
          id: 8,
          piece: null
        },
        {
          id: 9,
          piece: null
        }
      ];
    }

  }

})();
/* Styles go here */

.container {
  background-color: #14bdac;
}

.s {
  border: 1px solid #000;
  width: 50px;
  height: 50px;
  float: left;
}

.divider {
  display: block;
  clear: both;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.1/angular.min.js"></script>

<div ng-app="app">

  <tic-tac-toe></tic-tac-toe>

  <script type="text/ng-template" id="tic-tac-toe.html">

    <button ng-click="TicTacToeCtrl.newGame()">New game</button> Turns taken: {{TicTacToeCtrl.turn}}

    <hr>

    <div class="squares-container" ng-repeat-start="square in TicTacToeCtrl.squares track by $index">
      <div class="s" id="{{::square.id}}" ng-click="TicTacToeCtrl.move(square)">
        {{::square.id}}
        <span class="checked">{{square.piece}}</span>
      </div>
    </div>
    <div ng-repeat-end class="divider" ng-if="!(square.id % 3)"></div>

  </script>

</div>
cnorthfield
  • 3,384
  • 15
  • 22
  • awesome. any other ideas on how I can write this code cleaner? – Stack Juice Mar 18 '17 at 21:22
  • 2
    Hey I follow all your updates and they make sense, but what is the point of the javascript automatic function execution? – Stack Juice Mar 18 '17 at 21:39
  • 1
    See the [Javascript Scopes](https://github.com/johnpapa/angular-styleguide/tree/master/a1#style-y010) y010 section of the angular style guide endorsed by the AngularJS team :) – cnorthfield Mar 18 '17 at 21:41
  • 2
    got it. also, why use the .$inject if you don't pass it anything? – Stack Juice Mar 18 '17 at 21:44
  • Good practice, if you inject dependencies into the controller without passing them into `$inject` and minify your code, you will run into issues. Of course, you can omit it in this instance if you prefer. Take a look at [dependency annotation](https://docs.angularjs.org/guide/di#dependency-annotation). – cnorthfield Mar 18 '17 at 21:45
  • 1
    ok got it. last question... how can I style this so that it looks like a tic tac toe game? It's tricky since it's in a ng-repeat – Stack Juice Mar 18 '17 at 21:48
  • 1
    We shouldn't discuss more in comments, but as a tip, make use of CSS classes with the [`ngClass`](https://docs.angularjs.org/api/ng/directive/ngClass) directive using the modulus operator on the `$index`/`square.id` to get your 3's. Or use the [`ngIf`](https://docs.angularjs.org/api/ng/directive/ngIf) or [`ngShow`](https://docs.angularjs.org/api/ng/directive/ngShow) directives with the modulus operator to add a div to contain a row. – cnorthfield Mar 18 '17 at 21:50
  • @StackJuice I couldn't resist so added it to the code snippet. I made use of [`ngRepeat` start and end points](https://docs.angularjs.org/api/ng/directive/ngRepeat#special-repeat-start-and-end-points). – cnorthfield Mar 18 '17 at 22:01