Angular: Card game

Amazing demo

by Freewind

HTML

<script src="http://code.angularjs.org/angular-1.0.0rc9.js"></script>
<script type="text/ng-template" id="tile.html">
  <div ng-click="onFlip(tile)" class="tile" ng-class="{flipped: tile.flipped, matched: tile.matched}">
    <div class="back"><img ng-src="https://github.com/IgorMinar/Memory-Game/raw/master/app/img/back.png"/></div>
    <div class="front"><img ng-src="https://github.com/IgorMinar/Memory-Game/raw/master/app/img/{{tile.display}}.png"/></div>
  </div>
</script>

<div ng-controller="GridCtrl" >
    <div>Pairs left to match: {{unmatchedPairs}}</div>
    <div>Matching: {{guesses[0].display}}</div>
    <table class="tiles" ng-click="newGameIfFinished()" ng-class="{won: unmatchedPairs == 0}">
        <tr ng-repeat="row in grid">
            <td ng-repeat="tile in row">
                <flip-tile tile="tile" on-flip="flipTile"></flip-tile>
            </td>
        </tr>
    </table>

    <div>{{message}}</div>
    
</div>

CSS

/* app css stylesheet */
@-webkit-keyframes spin {
    from { -webkit-transform: scale(0.8) rotateX(180deg) rotateZ(0); }
    to   { -webkit-transform: scale(0.8) rotateX(180deg) rotateZ(-360deg); }
}

body {
  font-family: Segoe UI;
  fonz-size: 16pt;
}

.menu {
  list-style: none;
  border-bottom: 0.1em solid black;
  margin-bottom: 2em;
  padding: 0 0 0.5em;
}

.menu:before {
  content: "[";
}

.menu:after {
  content: "]";
}

.menu > li {
  display: inline;
}

.menu > li:before {
  content: "|";
  padding-right: 0.3em;
}

.menu > li:nth-child(1):before {
  content: "";
  padding: 0;
}

.tiles {
  -webkit-perspective: 1000px;
  -webkit-transition: all 1s ease-in-out;
}

.tiles.won:hover {
    cursor: pointer;
    -webkit-transform: scale(0.9);
}

.tile {
  -webkit-transition: all 0.4s ease-in-out;
  -webkit-transform-style: preserve-3d;
  position: relative;
  width: 165px;
  height: 200px;
}

.tile:hover {
 -webkit-transform: rotateX(20deg);
}

.tile.flipped {
  -webkit-transform: rotateX(180deg);
}

.tile.flipped.matched {
  -webkit-transform: scale(0.8) rotateX(180deg); 
  opacity: 0.7;
}

.won .matched {
  -webkit-animation: spin 1s 2 linear;
}

.front {
  position: absolute;
  -webkit-backface-visibility: hidden;
  -webkit-transform: rotateX(180deg);
}

.back {
  position: absolute;
  -webkit-backface-visibility: hidden;
}

JavaScript

angular.module('myApp', [])
.directive('flipTile', function() {
    return {
        restrict: 'E',
        scope: {tile: 'evaluate', onFlip: 'evaluate'},
        templateUrl: 'tile.html'
    };
});

function Tile(title) {
    this.title = title;
    this.display = 'back';
    this.flipped = false;
    this.matched = false;

    this.show = function() {
        this.display = this.title;
        this.flipped = true;
    };

    this.hide = function() {
        this.display = 'back';
        this.flipped = false;
    };
}

function GridCtrl($scope) {
    $scope.grid = newGame();
    $scope.guesses = [];
    var canStartNew = false;
    $scope.newGameIfFinished = function() {
        if ($scope.unmatchedPairs != 0)  {
            return false;
        }
        
        if (!canStartNew) {
            canStartNew = true;
            return false;
        }
        
        canStartNew = false;
        
        $scope.grid = newGame();
        $scope.guesses = [];
    };

    function getTile(tileDeck) {
        var i = Math.floor(Math.random()*tileDeck.length);
        return tileDeck.splice(i, 1)[0];
    }

    /* Create an array with two of each tileName in it */
    function makeDeck(tileNames) {
        var tileDeck = [];
        for (var i = 0; i < tileNames.length; i++) {
            tileDeck.push(new Tile(tileNames[i]));
            tileDeck.push(new Tile(tileNames[i]));
        }

        return tileDeck;
    }

    /* Make a 2-D array with the specified number of rows and columns */
    function makeGrid(numRows, numCols) {
        var grid = new Array(numRows);

        for (var row = 0; row < numRows; row++) {
            for (var col = 0; col < numCols; col++) {
                grid[row] = new Array(numCols);
            }
        }

        return grid;
    }

    /* Fill the grid with tiles picked at random */
    function newGame() {
        //var tileNames = ['foo', 'bar', 'baz', 'bon', 'fir', 'fur', 'onz', 'huq'];
        var tileNames = ['8-ball',...