A Pen by Svetlana Linuxenko on CodePen.
Created
March 25, 2016 18:24
-
-
Save linuxenko/90e2962c7ffa8a57638f to your computer and use it in GitHub Desktop.
Build a Tic Tac Toe Game [freeCodeCamp [Advanced Projects]] (Challenge)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <div class="tic-container" ng-app="ticApp"> | |
| <div class="tic-badge" ng-controller="boardCtrl"> | |
| <div class="board"> | |
| <div data-pos="0" class="pol" data-selected="o"></div> | |
| <div data-pos="1" class="pol" data-selected="x"></div> | |
| <div data-pos="2" class="pol" data-selected="o"></div> | |
| <div data-pos="3" class="pol" data-selected="x"></div> | |
| <div data-pos="4" class="pol" data-selected="o"></div> | |
| <div data-pos="5" class="pol" data-selected="x"></div> | |
| <div data-pos="6" class="pol" data-selected="o"></div> | |
| <div data-pos="7" class="pol" data-selected="x"></div> | |
| <div data-pos="8" class="pol" data-selected="o"></div> | |
| </div> | |
| <br /> | |
| <div class="row playground"> | |
| <div class="col-xs-12"> | |
| <div class="text-center" ng-if="state=='showstart'"> | |
| <span class="btn btn-primary" ng-click="startGame()">Lets GO !</span> | |
| </div> | |
| <div class="text-center" ng-if="state=='complete'"> | |
| <span class="btn btn-danger" ng-click="resetGame()">Restart</span> | |
| </div> | |
| <div class="text-center" ng-if="state=='win'"> | |
| You Win !!! | |
| <span class="btn btn-info" ng-click="resetGame()">Play Again</span> | |
| </div> | |
| <div class="text-center" ng-if="state=='wait'"> | |
| Choose you will play for: <br /> | |
| <label class="radio-inline"> | |
| <input type="radio" name="player" value="x" ng-click="set('player', 'x');set('state','showstart')" /> | |
| <strong>X</strong> | |
| </label> | |
| <label class="radio-inline"> | |
| <input type="radio" name="player" value="o" ng-click="set('player', 'o');set('state','showstart')" /> | |
| <strong>O</strong> | |
| </label> | |
| </div> | |
| <br /> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="copy"><a href="http://www.linuxenko.pro">© Svetlana Linuxenko</a></div> | |
| </div> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| var app = angular.module('ticApp', []); | |
| app.directive('pol', function() { | |
| return { | |
| restrict : 'C', | |
| link : function(scope, element, attrs) { | |
| scope.elements.push({ | |
| pos : Number.parseInt(attrs.pos, 10), | |
| update : function(p) { | |
| attrs.$set('selected', p); | |
| } | |
| }) | |
| element.on('click', function() { | |
| var pos = Number.parseInt(attrs.pos, 10); | |
| if (scope.state === 'start' && scope.checkBoardState(pos)) { | |
| attrs.$set('selected', scope.player); | |
| scope.updateBoard(pos, scope.player); | |
| } | |
| }); | |
| scope.$watch('state', function() { | |
| if (scope.state === 'start') { | |
| attrs.$set('selected', ''); | |
| } | |
| }); | |
| } | |
| } | |
| }); | |
| app.controller('boardCtrl', function($scope) { | |
| $scope.state = 'wait'; | |
| $scope.player = 'x'; | |
| $scope.elements = []; | |
| var board; | |
| var combos = [ | |
| [0,1,2], | |
| [3,4,5], | |
| [6,7,8], | |
| [0,4,8], | |
| [2,4,6], | |
| [0,3,6], | |
| [2,5,8], | |
| [1,4,7] | |
| ]; | |
| function initBoard() { | |
| board = new Array(8); | |
| for (var i = 0; i <= 8; i++) { | |
| board[i] = 'E'; | |
| } | |
| } | |
| initBoard(); | |
| function aiTurn() { | |
| var player = $scope.player === 'x' ? 'o' : 'x'; | |
| var possibleCombos = combos.filter(checkComboPossible); | |
| var mostCombo = possibleCombos[Math.floor(Math.random() * possibleCombos.length)]; | |
| var points = comboTurns(mostCombo); | |
| for (var i = 0 ; i < possibleCombos.length; i++) { | |
| var nextPoints = comboTurns(possibleCombos[i]); | |
| if (nextPoints > 0 && nextPoints < points) { | |
| points = nextPoints; | |
| mostCombo = possibleCombos[i]; | |
| } | |
| } | |
| points = 0; | |
| for (var i = 0 ; i < possibleCombos.length; i++) { | |
| var nextPoints = comboTurnsMine(possibleCombos[i], player); | |
| if (nextPoints > 0 && nextPoints > points) { | |
| points = nextPoints; | |
| mostCombo = possibleCombos[i]; | |
| } | |
| } | |
| var turn = Math.round(Math.random() * 2); | |
| while (board[mostCombo[turn]] !== 'E') { | |
| turn = Math.round(Math.random() * 2); | |
| } | |
| $scope.updateBoard(mostCombo[turn], player); | |
| } | |
| function comboTurnsMine(combo, player) { | |
| return combo.reduce(function(p,c) { | |
| if (board[c] !== 'E' && board[c] === player) { | |
| return p + 1; | |
| } else { | |
| return 0; | |
| } | |
| }, 0); | |
| } | |
| function comboTurns(combo) { | |
| return combo.reduce(function(p,c) { return board[c] !== 'E' ? p - 1 : p; }, 3); | |
| } | |
| function checkComboPossible(combo) { | |
| return combo.some(function(c) { return board[c] === 'E'; }); | |
| } | |
| function checkPlayerCombo(combo, player) { | |
| return combo.every(function(c) { return board[c] === player; }); | |
| } | |
| function isPlayerWin(player) { | |
| return combos.some(function(c) { return checkPlayerCombo(c, player); }); | |
| } | |
| function isBoardComplete() { | |
| return combos.every(function(c) { return !checkComboPossible(c); }); | |
| } | |
| $scope.checkBoardState = function(pos) { | |
| return board[pos] === 'E'; | |
| } | |
| $scope.updateBoard = function(pos, player) { | |
| board[pos] = player; | |
| if (player !== $scope.player) { | |
| $scope.elements.forEach(function(c) { | |
| if (c.pos === pos) { | |
| c.update(player); | |
| } | |
| }); | |
| } | |
| if (isPlayerWin($scope.player) === true) { | |
| $scope.state = 'win'; | |
| $scope.$apply(); | |
| } else { | |
| if (isPlayerWin(player) === true || isBoardComplete() === true) { | |
| $scope.state = 'complete'; | |
| $scope.$apply(); | |
| return; | |
| } | |
| if (player === $scope.player) { | |
| aiTurn(); | |
| } | |
| } | |
| } | |
| $scope.startGame = function() { | |
| initBoard(); | |
| $scope.state = 'start'; | |
| setTimeout(function() { | |
| if ($scope.player === 'o') { | |
| aiTurn(); | |
| } | |
| }, 50); | |
| } | |
| $scope.resetGame = function() { | |
| initBoard(); | |
| $scope.state = 'wait'; | |
| } | |
| $scope.set = function(key, val) { | |
| $scope[key] = val; | |
| } | |
| }); | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.14/angular.min.js"></script> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| @import url(https://fonts.googleapis.com/css?family=Roboto); | |
| body,html,.tic-container { | |
| width: 100%; | |
| height: 100%; | |
| font-family: 'Roboto', sans-serif; | |
| } | |
| .tic-badge { | |
| margin: auto; | |
| } | |
| .tic-container { | |
| display: flex; | |
| flex-direction: column; | |
| .copy { | |
| display: flex; | |
| align-items: flex-end; | |
| justify-content: center; | |
| padding: 10px 0px; | |
| } | |
| } | |
| @borderColor : #924da3; | |
| @import url(https://fonts.googleapis.com/css?family=Coming+Soon); | |
| .playground { | |
| font-family: 'Coming Soon', cursive; | |
| font-size: 18px; | |
| } | |
| .board { | |
| width: 340px; | |
| height: 340px; | |
| .pol { | |
| display: flex; | |
| width: 110px; | |
| height: 110px; | |
| float: left; | |
| margin: 1px; | |
| font-size: 64px; | |
| cursor: default; | |
| &:after { | |
| content: attr(data-selected); | |
| display: flex; | |
| margin: auto; | |
| text-transform: uppercase; | |
| font-family: 'Coming Soon', cursive; | |
| } | |
| &:hover { | |
| background: #fcffff; | |
| } | |
| &[data-selected="x"] { | |
| color: #7a4da3; | |
| } | |
| &[data-selected="o"] { | |
| color: #5f4da3; | |
| } | |
| &:nth-child(2), | |
| &:nth-child(5), | |
| &:nth-child(8) { | |
| border-left: 1px solid @borderColor; | |
| border-right: 1px solid @borderColor; | |
| } | |
| &:nth-child(4), | |
| &:nth-child(5), | |
| &:nth-child(6) { | |
| border-top: 1px solid @borderColor; | |
| border-bottom: 1px solid @borderColor; | |
| } | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet" /> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment