A View Inside My Head

Jason's Random Thoughts of Interest

NAVIGATION - SEARCH

Namespacing in JavaScript

Consider this code:

var Configuration = function (model) { 
   this.name = ko.observable(model.Name); 
   this.description = ko.observable(model.Description); 
   this.enabled = ko.observable(model.Enabled || false); 
}; 

Because this was defined outside of the scope of a function, that “Configuration” variable gets promoted to the global object.  In web browsers, that means that it gets appended to the window object as a property (window.Configuration = function(model)…). 

That’s just the nature of global objects in JavaScript.  But, instead of creating a bunch of new custom properties on window, it’s common to wrap all of your app’s global objects in a namespace so that window only gets polluted by one new property (especially when creating a reusable library of objects).

var MyNS = MyNS || {};         

///        
/// Configuration        
///        
MyNS.Configuration = function (model) {            
   this.name = ko.observable(model.Name);            
   this.description = ko.observable(model.Description);            
   this.enabled = ko.observable(model.Enabled || false);        
};

Now, in reality, there’s no such thing as a namespace in JavaScript.  What this is actually doing is creating a new global object called “MyNS” if it doesn’t already exist, and then adding the new objects as properties of MyNS.

If you need a sub-namespace, just create another empty object in MyNS:

var MyNS = MyNS || {};        
MyNS.Entities = MyNS.Entities || {};

Now the namespace and its members can be extracted into their own .js file(s) and included when needed.

But, thinking about a shared usage, someone may want to instantiate a Configuration object without having a model to pass in.  In that case, the code above will blow up because there are no properties on undefined (i.e., undefined.Name would result in an error).  This can be fixed by initializing model itself to an empty object if it comes in undefined:

MyNS.Configuration = function (model) {            
   model = model || {};            
   this.name = ko.observable(model.Name);            
   this.description = ko.observable(model.Description);            
   this.enabled = ko.observable(model.Enabled || false);        
};

But, wait – there’s no .Name property of an empty object.  How does this work if model = {} ?

All object in JavaScript are actually associative arrays (think dictionary, property bag, etc).  So, model.Name is exactly the same as model["Name"].  If that named element doesn’t exist in the collection, then the value will be returned as undefined, which is fine for initializing a ko.observable.

Additional Suggestion

Jason Karns (@jasonkarns) writes:

I like to use a micro-library, extend.js, to make the namespace initialization a bit cleaner. https://github.com/searls/extend.js It's a tiny library that does, admittedly, very little.

When breaking components across multiple files, ensuring that the namespace (and sub-namespace) is already created becomes annoying. You either end up declaring a very specific file load order, or copying boilerplate namespace code at the top of every file.

extend.js let's you define the namespace you want as a string (to any depth) and it will create it if it doesn't exist, or merge if it does exist. Small utility, but much cleaner than boilerplate code.

Knave 21 (Blackjack): The Fairness of Random Cards

Blackjack is one of my favorite games in the casino.  I'm a casual player, and certainly not an advantage player by any means.  To help me drill basic strategy, especially for things like soft hands and splits, I decided to write a version of Blackjack for Windows 8 and make it a game that *I* would like to play.  Since I'm not a high-roller, I also wanted to simulate the bankroll that casual players might use in an actual casino: $500 buy in at a $10 minimum table.

Thus, Knave 21 (soon to be renamed Knave Blackjack) was born after a couple of hundred hours of work in the evenings and weekends (the version in the Windows Store represents the third rewrite, and a version currently in development has undergone major refactoring recently as I prepare to add more features in the near future).  It's been a labor of love, and I hope that the attention to detail reveals that.  I've received a lot of positive feedback, and, recently, some negative feedback as well (which prompted this blog post).

One thing that every card game author likely struggles with is how to randomize the card selection so that it's fair to the player.  Knave 21 has the concept of a shoe, which is 2-6 decks of cards that are shuffled together.  It penetrates much deeper than a real casino (the reshuffle automatically occurs when 12 cards remain in the shoe), but the randomness of the deck is very dependent on whatever pseudo-random number generator can be coded in JavaScript.

Instead of relying on JavaScript's Math.random() function, Knave 21 utilizes the Alea library by Johannes Baagøe to provide a better distribution of random numbers.  And, since the shoe is serialized and saved to roaming storage (so you can resume a game on another computer), I didn't want to pre-shuffle the shoe.  Instead, a random card is selected and removed from the shoe upon demand.  If someone were to view the roaming state data, they could see what cards were left in the shoe, but not what the next card would be.  

More importantly (and in response to some recent negative feedback), the game doesn't care what cards have been played or how much you are betting when determining the next card - it simply picks a random number and that's the card that is drawn.  

I'm very concerned about Knave 21's reputation, especially since I have paying customers now that the app is listed in the Windows Store.  In the interest of openness, the code for the shoe implementation follows.  Note the "nextCard" function for the RNG implementation.

Knave.Shoe = (function (Alea, undefined) {

    var shoe = function (decks) {
        this.cards = [];

        for (var d = 0; d < decks; d++) {
            for (var s = 0; s < 4; s++) {
                for (var r = 0; r < 13; r++) {
                    this.cards.push(new Knave.Card("A23456789TJQK".charAt(r), "HDCS".charAt(s), true));
                }
            }
        }
    };

    shoe.prototype = {
        nextCard: function (facedown) {
            var random = new Alea();
            var index = random.uint32() % this.cards.length;
            
            var c = this.cards.splice(index, 1);

            if (c.length === 0)
                return undefined;

            var ret = c[0];

            if (facedown !== undefined)
                ret.facedown(facedown);

            return ret;
        },
        remaining: function () {
            return this.cards.length;
        }
    }

    return shoe;
})(Alea);