Angular Project Blackjack: 11 – User Experience

(This post is part of my “from scratch” AngularJS project. If you are feeling lost, the first post is here.)

Foreward

The coding portion of this post was BY FAR the most amount of time and effort I’ve put into any of them so far. My CSS skills were very rusty, so I had to do a lot of research and back and forth to get things right. More advanced front-end people may find some issues with my code, but it is, as always, a work in progress.

Experience

While our blackjack game is technically correct, it has all the glimmer of a game written for the TI-82. Don’t get me wrong, I love bootstrap, but for a game, it just isn’t enough.

New Visual Style

Here is what our game currently looks like:
Screenshot 2015-04-12 21.58.11

If you walked into a casino and had to play blackjack this way, you’d definitely find another casino quickly. Luckily for us, we’ve already setup most of our game objects as directives (game, hand, card) so we can go through and layout each item. Since most of this visual styling doesn’t have much to do with angular, I’ll just share some helpful links:

Putting all those together, we get a much nicer looking game area:
Screenshot 2015-04-12 21.59.31

Animations

The bad thing about angular is how fast it is. Well, let me clarify. It is tough to generate any suspense when the cards are dealt immediately to all players. The solution for suspense comes in the form of $timeout. Let’s use our ‘card back’ state in the card directive to hide the card’s value:

<div class="card back" ng-class="vm.cardIndexClass" ng-if="vm.card.hideValue"></div>

Now, when we deal the card to the player, we’ll pause for a second before revealing the card:

game.dealCardToPlayer = function(card, animate, callback){
    if(animate) {
        card.hideValue = true;
        game.playerCards.push(card);
        $timeout(function () {
            card.hideValue = false;
            callback();
        }, 250);
    }
    else{
        game.playerCards.push(card);
        callback();
    }
};

We also want to give the same suspense after we finish our hand and have the dealer complete, but since we are looping our commands through, it is a bit more complicated:

dealer.finish = function (callback) {

    var loop = {
        next: function(){
            dealer.getHandValue();
            if(dealer.handValue < dealer.minValue) {
                //Animate Card In
                dealer.hit(false, true, function () {
                    loop.next();
                });
            }
            else{
                loop.done();
            }
        },
        done: function(){
            if(dealer.handValue > dealer.maxValue){
                dealer.busted = true;
            }
            dealer.isDone = true;
            callback();
        }
    };

    //Reveal first card, then play:
    $timeout(function(){
        dealer.cards.forEach(function(card){
            card.hideValue = false;
        });
        loop.next();
    },500);

};

Now we’ve built in some timeouts to generate some player anxiety!

Our new user experience

Our new user experience

Testing $timeout

Now that we are using the $timeout service, when we run our tests, we don’t really want to have to wait like a regular user would. With the help of the angular team, we don’t have to! angular-mocks comes with a custom $timeout with a function called .flush(). This will clear anything in the $timeout queue and immediately kick it off. Here is how I’m testing our dealer function:

it('should set isDone value after finishing', inject(function ($timeout) {
    dealer.deal();
    expect(dealer.isDone).toBe(false);
    spyOn(dealer,'getHandValue').andCallFake(function(){
        dealer.handValue = 23;
    });
    dealer.finish(function(){

    });
    $timeout.flush();
    expect(dealer.isDone).toBe(true);
}));

By injecting $timeout, it makes it available to the test spec. You can see we’re doing dealer.finish() then immediately calling $timeout.flush() to not have to wait for the timeout to finish before checking our expectations.

Finally

Now we’ve got it looking pretty, the next step is to actually deploy!

You can checkout our code to this point here.

Angular Project Blackjack: 10 – The Dealer

(This post is part of my “from scratch” AngularJS project. If you are feeling lost, the first post is here.)

In order to make the game fun and realistic, we need an opponent to play against. In any given casino around the world, this opponent is the dealer. This dealer operates on a very limited set of specific rules, which makes writing a virtual version of him easy!

Dealer Service

The first thing we are going to do is create a blackjack.dealer module that gives us a DealerService. This service will give us a dealer object that we can use in the game. During each hand, the dealer gets two cards and then waits for everyone to finish. Once they are finish, the dealer plays his hand, stopping once he gets above 16 or busts. This matches up to our service, we have two main functions dealer.deal() and dealer.finish(). Sadly, we’re not doing anything too mind breaking with this service, so I’ll just post it here:

(function() {
    'use strict';

    angular
        .module('blackjack.dealer')
        .factory('DealerService', DealerService);

    DealerService.$inject = ['GameService'];

    function DealerService(GameService) {
        var service = {
            newDealer: newDealer,
            Dealer: Dealer
        };

        function newDealer(deck) {
            var dealer = new Dealer(deck);
            return dealer;
        }

        function Dealer(deck) {
            var dealer = this;
            dealer.deck = deck;

            /**
             * Creates initial values for dealer object
             */
            dealer.init = function() {
                dealer.cards = [];
                dealer.handValue = 0;
                dealer.isDone = false;
                dealer.busted = false;
                dealer.maxValue = GameService.maxValue();
                dealer.minValue = 17;
            };

            /**
             * When a new game is started, dealer gets two cards
             */
            dealer.deal = function() {
                dealer.init();
                dealer.hit();
                dealer.hit();
            };

            /**
             * After player has completed game, tell dealer to finish
             * by dealing out until hand is busted or between 17-21
             */
            dealer.finish = function() {
                while (dealer.handValue & lt; dealer.minValue) {
                    dealer.hit();
                }
                if (dealer.handValue & gt; dealer.maxValue) {
                    dealer.busted = true;
                }
                dealer.isDone = true;

            };

            /**
             * Deals a card to the dealer's hand
             */
            dealer.hit = function() {
                dealer.cards.push(dealer.deck.deal());
                dealer.getHandValue();
            };

            /**
             * Uses game service to calculate hand value
             */
            dealer.getHandValue = function() {
                dealer.handValue = GameService.handValue(dealer.cards);
            };

            dealer.init();

        }

        return service;

        ////////////////

    }

})();

Implementation

In our GameController, we will tell the dealer when to do his thing. During game.deal() we’ll deal cards to our player as well as telling the dealer to game.dealer.deal(). Once a player ends their game (either by pressing ‘stay’ or busting), we’ll tell the dealer to finish via game.dealer.finish(). Then, we’ll do some hand comparisons to see how we shape up against the dealer and set our game results properly. Here is our game.controller.js file:

(function() {
    'use strict';

    angular
        .module('blackjack.game')
        .controller('GameController', GameController);

    GameController.$inject = ['PlayerService', 'CardService', 'GameService', 'DealerService'];

    function GameController(PlayerService, CardService, GameService, DealerService) {
        var game = this;

        /**
         * Our game can have multiple states:
         * 1) Game with no player (started = false, canDeal = false, showResults = false)
         * 2) Game with a player, nothing dealt (started = true, canDeal = true, showResults = false)
         * 3) Game with a player, game in progress (started = true, canDeal = false, showResults = false)
         * 4) Game with a player, game over (started = true, canDeal = true, showResults = true)
         */

        /**
         * Initialize our controller data
         */
        game.init = function() {
            game.maxValue = GameService.maxValue();
            game.canDeal = false;
            game.started = false;
            game.showResults = false;
            game.deck = CardService.newDeck();
            game.dealer = DealerService.newDealer(game.deck);
            game.betValue = 100;
            game.playerCards = [];
            game.handValue = 0;
        };

        /**
         * Starts a game by creating a new player
         */
        game.start = function() {
            game.player = PlayerService.newPlayer('Ringo', 100);
            game.started = true;
            game.canDeal = true;
            game.showResults = false;
        };

        /**
         * Deals a new hand by 'paying' from the score,
         * shuffles the deck, and deals two cards to the
         * player.
         */
        game.deal = function() {
            //Initialize values each game
            game.busted = false;
            game.started = true;
            game.canDeal = false;
            game.showResults = false;

            //Our bet defaults to 100
            game.player.changeScore(game.betValue * -1);

            //Shuffle before dealing
            game.deck.shuffle();

            //Empty our dealt card array
            game.playerCards = [];

            //Deal the cards
            game.hit();
            game.hit();

            //Deal to the dealer
            game.dealer.deal();
        };

        /**
         * Adds a card to our hand and calculates value.
         */
        game.hit = function() {
            game.playerCards.push(game.deck.deal());
            game.getHandValue();
        };

        /**
         * Ends the game for the current hand. Checks for wins
         * and 'pays' to player score
         */
        game.end = function() {
            //Tell the dealer to finish his hand
            game.dealer.finish();

            if (game.busted) {
                game.results = "BUSTED";
            } else {
                var wonGame = false;
                var tiedGame = false;
                //Check against dealer's hand
                if (game.dealer.busted) {
                    //Auto Win if dealer busts
                    wonGame = true;
                } else {
                    if (game.dealer.handValue === game.handValue) {
                        tiedGame = true;
                    } else {
                        wonGame = (game.handValue & gt; game.dealer.handValue);
                    }
                }

                if (wonGame) {
                    //Winning pays double the bet
                    game.player.changeScore(game.betValue * 2);
                    game.results = "YOU WON!";
                } else if (tiedGame) {
                    //A 'PUSH' gives the player back their bet
                    game.player.changeScore(game.betValue);
                } else {
                    game.results = "DEALER WON";
                }
            }

            game.canHit = false;
            game.canDeal = true;
            game.showResults = true;
        };

        /**
         * Resets our player's score and re-inits
         */
        game.reset = function() {
            game.player = null;
            game.init();
        };

        /**
         * Calculates value of player's hand via GameService
         * Determines if player can still hit or if busted.
         */
        game.getHandValue = function() {
            game.handValue = GameService.handValue(game.playerCards);
            game.canHit = game.handValue & lt;
            game.maxValue;
            game.busted = game.handValue & gt;
            game.maxValue;
            if (game.handValue & gt; = game.maxValue) {
                game.end();
            }
        };

        game.init();
    }
})();

Display

We’ve also changed our display a bit to show our dealer’s hand (only one card before the player is done). We are also cleaning things up a bit with our hand value display and using ng-if to only display when the game has been started. Here is what we are doing with game.directive.html:

<div>

    <!-- Scoreboard -->
    <div class="panel panel-default">
        <div class="panel-heading">Player Info</div>
        <div class="panel-body">
            <div class="row">
                <div class="col-md-6">Player: {{game.player.name}}</div>
                <div class="col-md-6">Score: {{game.player.score}}</div>
            </div>
        </div>
    </div>
    <!-- Card Table -->
    <div class="panel panel-default">
        <div class="panel-heading">
            <div>Game In Progress</div>
            <div>Press "Start" to play</div>
        </div>
        <div class="panel-body">
            <div class="row">
                <div>

                    Dealer Hand: {{game.dealer.handValue}}

                </div>
                <div>

                    Dealer

                </div>
                <div class="well well-lg">
                    <div>
                        <div class="card">{{game.dealer.cards[0].name()}}</div>
                        <div class="card">******</div>
                    </div>
                    <div class="card">{{card.name()}}</div>
                </div>
            </div>
            <div class="row">
                <div>

                    Player Hand: {{game.handValue}}

                </div>
                <div>

                    Player

                </div>
                <div class="well well-lg">
                    <div class="card row">{{card.name()}}</div>
                </div>
            </div>
        </div>
    </div>
    <!-- Game Actions -->
    <div class="panel panel-default">
        <div class="row">
            <div class="col-md-6 col-md-offset-3">
                <div class="btn-group btn-group-justified">
                    <div class="btn-group">
                        <button class="btn btn-primary">Deal</button>
                    </div>
                    <div class="btn-group">
                        <button class="btn btn-warning">HIT</button>
                    </div>
                    <div class="btn-group">
                        <button class="btn btn-danger">STAY</button>
                    </div>
                </div>
            </div>
        </div>
        <div class="row">
            <button class="btn btn-primary">Start</button>
        </div>
    </div>
    <!-- Game Results -->
    <div class="panel panel-default">
        <div class="panel-heading">Results</div>
        <div class="panel-body">{{game.results}}</div>
    </div>
    <button class="btn btn-danger btn-sm">Reset Game</button>

</div>

Let’s Play

We’ve now got a playable blackjack game that would match any casino experience! Well, without all of the actually winning and losing of the money and the free booze and all. But, close enough, right?

For our next post, let’s ramp up the user experience some by making the cards look like actual cards. Let’s also put some drama in with some css animations and time delays using $timeout.

Angular Project Blackjack: 9 – Game Service

(This post is part of my “from scratch” AngularJS project. If you are feeling lost, the first post is here.)

Back to it

We’ve got a good setup now, with our gulp file building and running as necessary. Let’s move back to working on our code!

A Game Service

In our GameController we have the ability to deal a hand of cards but we can’t really do anything with it yet. To do so, we need to start putting the blackjack game rules into our application. Let’s create a GameService to start handling these rules for us. The main thing our GameService will do for now will be to take a hand of cards and return the numeric value of that hand. We can then use that value in our controller to enable/disable actions a user can take on the hand.

Here is our GameService:

(function(){
    'use strict';

    angular
        .module('blackjack.game')
        .factory('GameService', GameService);

    function GameService(){
        var service = {
            handValue: handValue
        };

        return service;

        ////////////////////


        function handValue(hand){
            var aces = 0;
            var totalValue = 0;
            var faceRanks = ['J','Q','K'];

            //Get the values of each card (counting 1 for each ace)
            hand.forEach(function(card){
                //Face Cards
                if(faceRanks.indexOf(card.rank) !== -1){
                    totalValue += 10;
                }
                //Aces
                else if(card.rank === 'A'){
                    totalValue += 1;
                    aces++;
                }
                //Number Cards
                else {
                    totalValue += Number(card.rank);
                }
            });

            //Loop through aces and try to add 10 to get highest value of hand
            // We are adding 10 here because we already added 1 for the ace above
            for(var i=0; i<aces; i++){
                if(totalValue <= 11){
                    totalValue += 10;
                }
            }

            return totalValue;
        }

    }
})();

Playing the Game

As I started writing the game playing portion of the GameController, a conversation I had about state based programming with @JoelMartinez kept popping into my head. When I broke it down into basics, our controller could be in one of four basic states:

  • Game with no player (started = false, canDeal = false, showResults = false)
  • Game with a player, nothing dealt (started = true, canDeal = true, showResults = false)
  • Game with a player, game in progress (started = true, canDeal = false, showResults = false)
  • Game with a player, game over (started = true, canDeal = true, showResults = true)

Breaking it down this way, you can see what your controller variables are and what they should be during each state. You can also easily see what you need to do to change states. Let’s update our GameController to handle these states:

(function(){
    'use strict';

    angular
        .module('blackjack.game')
        .controller('GameController',GameController);

    GameController.$inject = ['PlayerService', 'CardService', 'GameService'];

    function GameController(PlayerService, CardService, GameService){
        var game = this;

        /**
         * Our game can have multiple states:
         * 1) Game with no player (started = false, canDeal = false, showResults = false)
         * 2) Game with a player, nothing dealt (started = true, canDeal = true, showResults = false)
         * 3) Game with a player, game in progress (started = true, canDeal = false, showResults = false)
         * 4) Game with a player, game over (started = true, canDeal = true, showResults = true)
         */

        /**
         * Initialize our controller data
         */
        game.init = function () {
            game.maxValue = 21;
            game.canDeal = false;
            game.started = false;
            game.showResults = false;
            game.deck = CardService.newDeck();
        };

        /**
         * Starts a game by creating a new player
         */
        game.start = function () {
            game.player = PlayerService.newPlayer('Ringo', 100);
            game.started = true;
            game.canDeal = true;
            game.showResults = false;
        };

        /**
         * Deals a new hand by 'paying' from the score,
         * shuffles the deck, and deals two cards to the
         * player.
         */
        game.deal = function () {
            //Initialize values each game
            game.busted = false;
            game.started = true;
            game.canDeal = false;
            game.showResults = false;

            //Our bet defaults to 100
            game.player.changeScore(-100);

            //Shuffle before dealing
            game.deck.shuffle();

            //Empty our dealt card array
            game.playerCards = [];

            //Deal the cards
            game.hit();
            game.hit();

            //Calculate value of hand
            game.getHandValue();
        };

        /**
         * Adds a card to our hand and calculates value.
         */
        game.hit = function () {
            game.playerCards.push(game.deck.deal());
            game.getHandValue();
        };

        /**
         * Ends the game for the current hand. Checks for wins
         * and 'pays' to player score
         */
        game.end = function () {
            //Since we have no dealer, we win if we don't bust
            if(!game.busted) {
                game.player.changeScore(200);
                game.results = "YOU WON!";
            }
            else{
                game.results = "BUSTED";
            }
            game.canHit = false;
            game.canDeal = true;
            game.showResults = true;
        };

        /**
         * Resets our player's score and re-inits
         */
        game.reset = function () {
            game.player = null;
            game.init();
        };

        /**
         * Calculates value of player's hand via GameService
         * Determines if player can still hit or if busted.
         */
        game.getHandValue = function () {
            game.handValue = GameService.handValue(game.playerCards);
            game.canHit = game.handValue < game.maxValue;
            game.busted = game.handValue > game.maxValue;
            if(game.handValue >= game.maxValue){
                game.end();
            }
        };

        game.init();
    }
})();

As you can see, our first state is when the controller is called via init(). We move to the next state with the start() function. Once we deal() a hand, we are in the third state and we continue that way until end() is called. Our controller is now using our CardService to deal cards and our GameService to play the game. This really keeps our controller neat and makes our services reusable and testable. (Side note: I had a lot of internal debate on keeping the canHit and busted calculations insdie the GameController. In the end, I’ve left it there, but don’t be surprised if in another post or commit you see them moved into the GameService.)

Testing Injections

If you run our tests now, you will see a failure for a provider not found. This is due to the fact that our controller needs the GameService and CardService to function. Let’s go into our game.controller.spec.js file and inject those services (as well as write a few more tests).
Here is the top of our test:

describe('GameController Unit Tests', function () {
    var gameController, CardService, PlayerService;
    beforeEach(function () {
        module('blackjack.game');
        module('blackjack.player');
        module('blackjack.card');
        inject(function ($controller, _CardService_, _PlayerService_) {
            CardService = _CardService_;
            PlayerService = _PlayerService_;
            gameController = $controller('GameController', {
                CardService: CardService,
                PlayerService: PlayerService
            });
        });
    });

The biggest change you’ll see here is the inject(function ($controller, _CardService_, _PlayerService_) { line. We didn’t make a typo by putting underscores before and after our service names, this is a testing feature in AngularJS that allows us to get a localized copy of each service it is injecting into the test.

The line up top: var gameController, CardService, PlayerService;is what sets up our local variables for the instances. Now we can use those variables to allow us to do fun things like watch and mock functions of those services. For now, we’ll wait on writing tests with those advanced features and save it for another blog post.

Updating the Display

The last thing we need to do is to update our game directive view with all of our new GameController logic. We’re still using Bootstrap for our layout and we’ll divide our game display into four separate Bootstrap Panels:

  • Scoreboard
  • Card Table
  • Game Actions
  • Results

With the use of our state programming, we can hide or show each panel as needed with an ng-if statement on each panel. We are also using ng-disabled to control our action buttons based on state. Here is our game.directive.html:

<div>
    <!-- Scoreboard -->
    <div class="panel panel-default" ng-if="game.started">
        <div class="panel-heading">Player Info</div>
        <div class="panel-body">
            <div class="row">
                <div class="col-md-6">
                    Player: {{game.player.name}}
                </div>
                <div class="col-md-6">
                    Score: {{game.player.score}}
                </div>
            </div>
        </div>
    </div>

    <!-- Card Table -->
    <div class="panel panel-default">
        <div class="panel-heading">
            <div ng-if="game.started">
                Game In Progress
            </div>
            <div ng-if="!game.started">
                Press "Start" to play
            </div>
        </div>
        <div class="panel-body">
            <div class="row">
                <div class="col-md-12">
                    <div class="card" ng-repeat="card in game.playerCards">{{card.name()}}</div>
                </div>
            </div>
        </div>
        <div class="panel-footer" ng-if="game.started">
            <p>Hand Value: {{game.handValue}}</p>
        </div>
    </div>

    <!-- Game Actions -->
    <div class="panel panel-default">
        <div class="row" ng-if="game.started">
            <div class="col-md-6 col-md-offset-3">
                <div class="btn-group btn-group-justified">
                    <div class="btn-group" role="group">
                        <button class="btn btn-primary" ng-click="game.deal()" ng-disabled="!game.canDeal">Deal</button>
                    </div>
                    <div class="btn-group" role="group">
                        <button class="btn btn-warning" ng-click="game.hit()" ng-disabled="!game.canHit">HIT</button>
                    </div>
                    <div class="btn-group" role="group">
                        <button class="btn btn-danger" ng-click="game.end()" ng-disabled="!game.canHit">STAY</button>
                    </div>
                </div>
            </div>
        </div>
        <div class="row" ng-if="!game.started">
            <button class="btn btn-primary" ng-click="game.start()">Start</button>
        </div>
    </div>

    <!-- Game Results -->
    <div class="panel panel-default" ng-if="game.showResults">
        <div class="panel-heading">Results</div>
        <div class="panel-body">
            {{game.results}}
        </div>
    </div>

    <button class="btn btn-danger btn-sm" ng-click="game.reset()">Reset Game</button>

</div>

Wrapping Up

We’ve now got our GameService and GameController working properly and displaying correctly. You should now be able to run gulp buildDev and then http-server and play a game or two of blackjack! We’ve also added the concept of ‘losing’ a game (by busting), so bust a few hands and watch your points dwindle. You can ‘game’ the system though, but simply ‘STAY’-ing after dealing your initial hand. We need an opponent to keep us honest, and in casinos around the world, that opponent is the Dealer!

Up Next: The Dealer

Angular Project Blackjack: 8 – Gulp Tasks

(This post is part of my “from scratch” AngularJS project. If you are feeling lost, the first post is here.)

Now that we have gulp setup and running and merging our files via gulp-concat, we don’t need to stop there. Let’s go ahead and make some more tasks to run and make our lives easier.

Testing

Since our unit tests are ready to be run from the command line via karma, we can add them as a gulp task. Karma is actually built into gulp, so there are no extra packages to install! In our gulpfile.js, let’s create a karma variable and a task to run our tests:

var karma = require('karma').server;

gulp.task('test', function (done) {
    karma.start({
        configFile: __dirname + '/karma.config.js',
        singleRun: true
    }, done);
});

We’re telling gulp to run our tests once and where to locate our karma configuration. Since gulp is powered by node, we can use the global variable __dirname to get the path that gulp is running from. You should now be able to run gulp test and watch our tests run!

Building

If you’ve completed the previous post and done any sort of debugging on it,
one thing you may have noticed is that debugging a concatenated app.js is very
confusing. We’ve optimized our project for our end users but we’ve made life difficult
for ourselves. This is where the whole “source” vs “release” builds comes in. What
we can do is have a separate folder that our production version will go into.
There are a lot of names used for these production folders: prod, release, deploy,
but we will use build in our project.

The first thing we’ll do to prepare our build is to change the js files that our index.html
loads depending on environment. We will do this by using gulp injection (not to be confused
with Angular’s dependency injection). Install the package with npm install gulp-inject --save-dev.
Next, we’ll need to prepare our index.html file for injection. This is done by inserting html comment markers that gulp will use:

<!DOCTYPE html>
<html data-ng-app="blackjack">
<head lang="en">
    <meta charset="UTF-8">
    <title>Blackjack</title>
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no" />

    <style>
        /* This helps the ng-show/ng-hide animations start at the right place. */
        /* Since Angular has this but needs to load, this gives us the class early. */
        .ng-hide {
            display: none!important;
        }
    </style>

    <!-- bower:css -->
    <!-- endinject -->

    <!-- inject:css -->
    <!-- endinject -->


</head>
<body>
    <div data-ng-include="'app/layout/shell.html'"></div>

    <!-- bower:js -->
    <!-- endinject -->

    <!-- Begin: Source JS -->
    <!-- inject:js -->
    <!-- endinject -->
    <!-- End: Source JS -->

</body>
</html>

You’ll notice that all of our files are gone! Don’t fret, they will be back when our task is completed. We’ve got a few different injection types in our index: bower:css, inject:css, bower:js, and inject:js.

Our injections will be separate based on if they are bower components or our code. The bower components that are injected will be the same on both environments (although, we could use the minified versions of each in the production environment, but it is a bit more configuration than I’d care to throw at this post!). What will change is our code that is getting injected. We want the concatenated version of our js files to go in the production release, while we want our files to be individually included for the development environment.

One more thing we want to do is to make sure our build folder could easily be deployed to a server. This means we want all the files necessary to serve our application within the build folder. There will be a few tasks that copy the necessary files to the build folder. Let’s look at our gulpfile.js:

var gulp = require('gulp');
var concat = require('gulp-concat');
var ngAnnotate = require('gulp-ng-annotate');
var karma = require('karma').server;
var del = require('del');
var inject = require('gulp-inject');
var bowerFiles = require('main-bower-files');

//Configuration
var buildFolder = __dirname + '/build/';

//Tasks
gulp.task('test', function (done) {
    karma.start({
        configFile: __dirname + '/karma.config.js',
        singleRun: true
    }, done);
});

gulp.task('index',['clean'],function(){
    return gulp.src('./src/index.html')
        .pipe(gulp.dest('./build'));
});

// Production Build Tasks
/**
 * Injects release files into index.html
 */
gulp.task('inject', ['concat', 'css', 'html', 'vendor'], function(){
    return gulp.src('./src/index.html')
        .pipe(gulp.dest('./build'))
        .pipe(inject(gulp.src('./build/vendor/*.js', {read: false}), {name: 'bower', relative: true}))
        .pipe(inject(gulp.src('./build/vendor/*.css', {read: false}), {name: 'bower', relative: true}))
        .pipe(inject(gulp.src('./build/app.js', {read: false}), {relative: true}))
        .pipe(inject(gulp.src('./build/content/*.css', {read: false}), {relative: true}))
        .pipe(gulp.dest('./build'));
});

/**
 * Copies vendor files from bower to the build/vendor folder
 */
gulp.task('vendor', function () {
    return gulp.src(bowerFiles())
        .pipe(gulp.dest('./build/vendor'));
});

/**
 * Cleans the build folder
 */
gulp.task('clean', function() {
    del(buildFolder + '**');
});

/**
 * Concats application js files into app.js
 */
gulp.task('concat', function () {
    return gulp.src(['src/app/**/*.module.js', 'src/app/**/*.js', '!src/app/**/*.spec.js'])
        .pipe(concat('app.js'))
        .pipe(ngAnnotate())
        .pipe(gulp.dest(buildFolder));
});

/**
 * Copies application css to build folder
 */
gulp.task('css', function () {
    return gulp.src('./src/content/*.css')
        .pipe(gulp.dest('./build/content'));
});

/**
 * Copies html files to build folder
 */
gulp.task('html', function () {
    return gulp.src('./src/app/**/*.html')
        .pipe(gulp.dest('./build/app'));
});

// Development Build Tasks
/**
 * Updates the index.html file
 * with all the injections
 */
gulp.task('injectDev', function(){
    return gulp.src('./src/index.html')
        .pipe(inject(gulp.src(['./src/app/**/*.module.js','./src/app/**/*.js','!./src/app/**/*.spec.js'], {read: false}), {relative: true, name: ''}))
        .pipe(inject(gulp.src(bowerFiles(), {read: false}), {name: 'bower', relative: true}))
        .pipe(inject(gulp.src('./src/content/*.css',{read:false}), {relative: true}))
        .pipe(gulp.dest('./src'));
});




gulp.task('build', ['inject']);

gulp.task('buildDev', ['injectDev']);

We now have two main tasks: build and buildDev. As you can see, these execute a series of sub tasks in our gulp file. One nice thing I found was the npm package main-bower-files. This uses your bower.json dependencies and builds a list of files for use in gulp. It makes it much easier to include bower files into your injections.

With these build tasks complete, we should be able to run http-server from either the src folder or the build folder and have our application work as expected.

Here is the result of our work.

Up Next: Playing the game

Angular Project Blackjack: 7 – File Concatenation with Gulp

(This post is part of my “from scratch” AngularJS project. If you are feeling lost, the first post is here.)

We’ve created quite a lot of files in just our application so far. Unfortunately, for every one of those files, it means we need to add an include to our index.html file and increase the request count for visitors just to load the app. Wouldn’t it be nice if we could just package all of our code into one file and just load that file in the application? That’s exactly what we’ll set out to do here.

There are a TON of applications that will get us the end result we are looking for: browserify, require, webpack, etc. Although, in my opinion, the simplest and easiest to understand is gulp-concat. At its very basic form, gulp-concat literally merges the files you give it into one output file. To get it going, we’ll need to have gulp installed, and get to gulp installed, we really should know what it is doing first!

Gulp (or gulp) is a javascript powered task runner. You define your tasks in a gulpfile.js file and run them from the command line. It is easiest to run gulp when installed globally, so we’ll install gulp via npm globally and also add it to our developer dependencies. We’ll also install two different gulp plugins: “gulp-concat” and “gulp-ng-annotate”. “ng-annotate” provides us with a safeguard for dependency injection. If it comes across code with a missing injection, while it is processing it will put those in.

npm install --global gulp
npm install --save-dev gulp gulp-concat gulp-ng-annotate

Let’s configure our file concatenation task in our gulpfile.js:

var gulp = require('gulp');
var concat = require('gulp-concat');
var ngAnnotate = require('gulp-ng-annotate');

gulp.task('concat', function () {
    return gulp.src(['src/app/**/*.module.js', 'src/app/**/*.js', '!src/app/**/*.spec.js'])
        .pipe(concat('app.js'))
        .pipe(ngAnnotate())
        .pipe(gulp.dest('src/app/'));
});

We’ve created a task called ‘concat’ that can be run from the command line with ‘gulp concat’. Inside this task, we are loading source files with the ‘src’ command and then piping those files into a flow of commands. Since all of our angular configuration modules need to be loaded before the ‘run’ code is loaded, we will put them first. We also don’t want our tests in there, so we’ll tell it to ignore all the ‘spec’ files in those paths. The first task is to merge them together with the ‘concat’ command. We are merging these into a file called app.js. Next, we’ll fix any issues with our injections via ‘ngAnnotate()’. Finally, we’ll tell the task to output the file into the ‘src/app’ directory.

If you run this task now, you’ll see our new ‘app.js’ file get created. We can now load our single app.js file in the index.html file instead of the multiple files we were loading before. This will help reduce the amount of requests each page load will make and keep our html source cleaner. You should now be able to run http-server and have it load only the app.js file.

<!DOCTYPE html>
<html data-ng-app="blackjack">
<head lang="en">
    <meta charset="UTF-8">
    <title>Blackjack</title>
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no" />

    <style>
        /* This helps the ng-show/ng-hide animations start at the right place. */
        /* Since Angular has this but needs to load, this gives us the class early. */
        .ng-hide {
            display: none!important;
        }
    </style>

    <!-- Begin: Vendor CSS -->
    <link href="bower_components/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet">
    <!-- End: Vendor CSS -->

    <!-- Begin: Source CSS -->
    <link href="content/blackjack.css" rel="stylesheet">
    <!-- End: Source CSS -->


</head>
<body>
    <div data-ng-include="'app/layout/shell.html'"></div>

    <!-- Begin: Vendor JS -->
    <script src="bower_components/angular/angular.min.js"></script>
    <script src="bower_components/bootstrap/dist/js/bootstrap.min.js"></script>
    <script src="bower_components/jquery/dist/jquery.min.js"></script>
    <!-- End: Vendor JS -->

    <!-- Begin: Source JS -->
    <script src="app/app.js"></script>
    <!-- End: Source JS -->
</body>
</html>

Now that we have gulp ready for tasks, our next step (and blog post) will be to automate some of our development activities.

Here is our project up to this point:

https://github.com/adamweeks/angular-blackjack/tree/blog-post-7

Up Next: More Gulp Tasks!

Angular Project Blackjack: 6 – Do You Even TDD Bro?

(This post is part of my “from scratch” AngularJS project. If you are feeling lost, the first post is here.)

Previously, I had mentioned that test driven development hasn’t really “stuck” for me and I find myself switching back to BDD for the most part. I find that this is usually because I don’t plan features out far enough in advance and I’m more of the “experiment and refactor” kind of developer. I have found instances where TDD actually works better for me, and today’s topic is one of those instances.

We are going to be working on the card service. This service will allow us to get a deck of cards, shuffle the deck, deal from the deck. Since this blackjack project is a game based off of set rules, we know how the cards should behave. We can write tests against these rules and then we’ll write our service to meet the rules. The rules are:

  • We should be able to get a new deck of cards with the object type “Deck”
  • The deck should contain 52 cards
  • There should be no duplicate cards in a deck
  • Each card should have a rank and a suit.
  • We should be able to ‘deal’ a card from a deck that has undealt cards in it.
  • Attempting to ‘deal’ from a deck with no undealt cards returns false
  • When a card is dealt, it is no longer in the cards array
  • We should be able to shuffle the deck and randomize all undealt cards.
  • We should be able to ‘reset’ a deck that will move all dealt cards into the undealt status and shuffle.

Now let’s write those tests:

describe('CardService Unit Tests', function () {
    var CardService;
    beforeEach(function () {
        module('blackjack.card');
        inject(function (_CardService_) {
            CardService = _CardService_;
        });
    });

    it('should be true', function () {
        expect(true).toBe(true);
    });

    describe('Deck related tests', function () {
        var deck;
        beforeEach(function () {
            deck = CardService.newDeck();
        });

        it('should be able to get a new deck of cards with the object type "Deck"', function () {
            expect(deck instanceof CardService.Deck).toBe(true);
        });

        it('The deck should contain 52 cards', function () {
            expect(deck.cards.length).toBe(52);
        });

        it('Each card should have a value and a rank.', function () {
            deck.cards.forEach(function (card) {
                expect(card.rank).toBeDefined();
                expect(card.suit).toBeDefined();
            });
        });

        it('There should be no duplicate cards in a deck', function () {
            var cardNames = [];
            deck.cards.forEach(function (card) {
                expect(cardNames.indexOf(card.name())).toBe(-1);
                cardNames.push(card.name());
            });
        });

        it('We should be able to "deal" a card from a deck that has undealt cards in it.', function () {
            expect(deck.deal).toBeDefined();
            var card = deck.deal();
            expect(card instanceof CardService.Card).toBe(true);
        });

        it('Attempting to "deal" from a deck with no undealt cards returns false', function () {
            deck.cards = [];
            var card = deck.deal();
            expect(card).toBe(false);
        });

        it('When a card is dealt, it is no longer in the cards array', function () {
            var card = deck.deal();
            expect(deck.cards.indexOf(card)).toBe(-1);
        });

        it('We should be able to shuffle the deck and randomize all undealt cards.', function () {
            expect(deck.shuffle).toBeDefined();
            var originalDeck = angular.copy(deck.cards);
            deck.shuffle();
            expect(originalDeck).not.toEqual(deck.cards);
        });

        it('We should be able to "reset" a deck that will move all dealt cards into the undealt status and shuffle.', function () {
            expect(deck.reset).toBeDefined();
            deck.deal();
            expect(deck.cards.length).not.toBe(52);
            deck.reset();
            expect(deck.cards.length).toBe(52);
        });

    });
});

So we’ve got the “T” part of our TDD done. Let’s start working on getting these tests passing. The Karma runner will watch our files for changes and rerun the tests. In my opinion, the fun part about doing TDD is watching the tests start passing. This is the implementation to pass those tests:

https://github.com/adamweeks/angular-blackjack/blob/blog-post-6/src/app/card/card.service.js

Once all of the tests are passing, we should be fairly confident that our service is ready to be used. Let’s put it in place and start dealing cards! We’re only making a few minor changes to game.controller.js:

game.init = function () {
    game.started = false;
    game.player = PlayerService.newPlayer('Ringo', 100);
    game.deck = CardService.newDeck();
};

game.start = function () {
    game.started = true;
    game.player.changeScore(-100);
    game.deck.shuffle();
    game.playerCards = [];
    game.hit();
    game.hit();
};

game.hit = function () {
    game.playerCards.push(game.deck.deal());
};

Our controller now has a deck object, as well as a playerCards array. Calling the hit function tells the deck to deal a card into the playerCards array. On the view side, we’ll wire up a button that will fire the hit function, as well as displaying the cards via an ng-repeat.

You’ll notice that we don’t have any actual blackjack game logic in place yet, and a player can keep dealing themselves cards until the deck runs out and they still win! All of that is for yet another blog post! We want to keep things as single-responsibility as possible. Consider an actual deck of cards and a dealer. If you were to place them down on any table, they could deal and shuffle cards regardless of what game they were playing. Yes, they would be a pretty terrible dealer if they didn’t have anyone enforcing the rules on them, but the cards wouldn’t change and the act of dealing wouldn’t change. That is what we are trying to recreate with this service. Now, if we wanted to build a poker app, we could use this exact service to deal cards without having to change a line of code!

View the results of our code here.

Up Next: Gulp!

Angular Project Blackjack: 5 – Player Service

(This post is part of my “from scratch” AngularJS project. If you are feeling lost, the first post is here.)

Now that we have used views, directives and controllers, let’s move on to another big part of the AngularJS library, “Services”. If you’ve used angular for any amount of time, I’m pretty sure you’ve already googled “What is the difference between service, factory and provider?”. I know I have and I still haven’t fully grasped it yet. The best answer I can give is that if you need it to be runtime configurable, use a provider. Otherwise, use a factory. Why? Because John Papa said to! Services and factories are nearly identical, but using the revealing module pattern, you can make them really look like header and implementation files, which makes the Objective-C developer in me very happy!

(One thing to note, I’ll use the word “Service” throughout this post and even name my factory “PlayerService”. This is because it makes more sense in the usage of these factories elsewhere in the code to call it a service. When you read “Inject the NetworkService”, you understand what that code object does better than calling it “Inject the NetworkFactory”.)

In our blackjack game, we will have a player who is playing the game. This player will have points/money and a name. Assigning the player a name and adding/subtracting points will all need logic somewhere. We could simply define all of this code in the Game Controller, but, that goes against our single responsibility rule. Instead, let’s make this into a PlayerService!

Our player service is going to live in a new folder, /src/app/player. We’ll create two files, the module for the folder (player.module.jsand the service (player.service.js). We’re going to go a little bit deeper into the code for the service because it requires us to make a javascript object class as well.

(function(){
    'use strict';

    angular
        .module('blackjack.player')
        .factory('PlayerService', PlayerService);

    function PlayerService(){
        var service = {
            newPlayer: newPlayer
        };

        /////////////

        function Player(playerName, initialScore) {
            this.score = initialScore;
            this.name = playerName;
        }

        Player.prototype.changeScore = function(amountToChange){
            if(!angular.isDefined(this.score)){
                this.score = 0;
            }

            this.score += amountToChange;
        };

        Player.prototype.resetScore = function () {
            this.score = 0;
        };

        function newPlayer(playerName, initialScore) {
            var player = new Player(playerName, initialScore);
            return player;
        }

        return service;
    }
})();

Right now, the only function available to PlayerService is ‘newPlayer’. This will create a new javascript ‘Player’ object and return it. We also have some (what we would call in OOP, ‘class methods’) prototype functions on the objects. These are the functions that we will call from our controller to modify our player object. Our ‘newPlayer’ function on the PlayerService creates the Player object and sets the parameters that are passed in to it.

Now that we have our service laid out, how do we go about using it? Well, once we update our index.html and our main blackjack.module.js to include our new service, we can simply inject our player service into the game controller. Our game.controller.js:

(function(){
    'use strict';

    angular
        .module('blackjack.game')
        .controller('GameController',GameController);

    GameController.$inject = ['PlayerService'];

    function GameController(PlayerService){
        var game = this;

        game.init = function () {
            game.started = false;
            game.player = PlayerService.newPlayer('Ringo', 100);
        };

        game.start = function () {
            game.started = true;
            game.player.changeScore(-100);
        };

        game.end = function () {
            game.started = false;
            game.player.changeScore(200);
        };

        game.reset = function () {
            game.player.resetScore();
            game.started = false;
        };

        game.init();
    }
})();

The two big lines to pay attention to here are:

GameController.$inject = ['PlayerService'];
function GameController(PlayerService){

This is AngularJS’s injection. The PlayerService is now available throughout our GameController and can be called from within it. Angular provides us with a service locator that will find these services simply by updating $inject.

We now have our player get created with a new game initialization and he’s winning his blackjack hand every time we “end” the game. Everything seems to be working great. That is, until we go back and run our unit tests and see we have a “Error: [$injector:unpr]” error being reported. That is because we haven’t given our game controller tests access to the PlayerService. Give it access to it by adding it to the listed modules it loads:

module('blackjack.player');

While we’re in there, let’s add a quick test as well:

it('should have a player defined', function (){
    expect(gameController.player).toBeDefined();
});

Since we’re writing tests, might as well write some for our PlayerService as well:

'use strict';

describe('PlayerService Unit Tests', function () {
    var PlayerService;
    beforeEach(function () {
        module('blackjack.player');
        inject(function (_PlayerService_) {
            PlayerService = _PlayerService_;
        });
    });

    it('should be true', function () {
        expect(true).toBe(true);
    });

    it('should create a new player with name and score', function () {
        var playerName = 'testPlayer';
        var playerScore = 10101;
        var player = PlayerService.newPlayer(playerName, playerScore);
        expect(player.name).toBe(playerName);
        expect(player.score).toBe(playerScore);
    });

});

One interesting thing to notice in our testing code is the weird “_PlayerService_” way of doing the injection. By surrounding the service with underscores, Angular knows to call the actual service here. When we assign the underscored version of PlayerService to our local var PlayerService, we are giving our test a copy of that service from within the test. This is important because it allows us to do things like mock results, change functions, etc., all from within our test. The reason it makes a copy is so that if the service is injected in other tests, our modifications aren’t passed over to them and they get a “clean” version of the service.

Our tests should be passing now with no errors. You can visit the code base here.

Up Next: TDD Card Service