This is one of my earliest JavaScript projects that I've chosen to bring back to life. It's a simple deck of cards which is configurable and returns cards in the simple object format { rank: "", suit: "" }
. I'll walk you through construction of the module.
interface Card
{
suit: string;
rank: string;
limit?: number;
}
interface DeckOptions
{
extend: Card[];
suits: string[];
ranks: string[];
multiply: number;
}
I'm diving right in and introducing the entire module to you all at once, including configuration options. That is why you see an optional limit
attribute on cards.
The purpose of this will become clear as we advance.
class Deck
{
private _opt: DeckOptions;
cards: Card[];
constructor (opt: Object = {})
{
this._opt = {
extend: opt['extend'] || [],
suits: opt['suits'] || ['spades', 'hearts', 'diamonds', 'clubs'],
ranks: opt['ranks'] || ['ace', 'king', 'queen', 'jack', '10', '9', '8', '7', '6', '5', '4', '3', '2'],
multiply: opt['multiply'] || 1
};
if (this._opt.multiply < 1)
this._opt.multiply = 1;
this.shuffle();
}
}
We instantiate a cards array, which acts as storage of undrawn cards from the deck. An options variable, so that configuration options for the deck are available throughout the class.
Then we populate that options variable with defaults.
The simple way to do this is using the variable = variable || xzy;
method. If the variable is defined it will be used, otherwise, the default.
You see we have an option to multiply
the deck which means during construction of the cards, do it multiple times. Setting this option to 2
for example would be the equivalent of two decks together. You may choose to set an upper limit here, as a deck of too large a size might become resource intensive. In this case we trust that we aren't using any multiplier too large.
shuffle ()
{
this.cards = [];
for (let i = 0; i < this._opt.multiply; i++) {
for (let suit of this._opt.suits) {
for (let rank of this._opt.ranks) {
this.inlay({ suit: suit, rank: rank });
}
}
for (let card of this._opt.extend) {
if (!card.limit || i < card.limit)
this.inlay({ suit: card.suit, rank: card.rank });
}
}
}
The shuffle
method is essentially for construction of the deck. We don't store the cards in any particular order. Instead we pick a random card whenever one is drawn. So all we have to worry about here is populating the cards
array.
As you can see we now check the cards for their limit
attribute. If one exists and we already have that many of that particular card in the deck, it doesn't generate any more. In this way if you wanted a deck with say 3 decks in it but only two jokers, it is possible.
inlay (card: Card): boolean
{
if (card && card.suit && card.rank) {
this.cards.push(card);
return true;
}
else
return false;
}
Performs a rudimentary verification that the card has both a suit and rank before inserting it into the deck.
count (): number
{
return this.cards.length;
}
Simple helper method tells us how many cards are yet to be drawn.
draw (): Card
{
let count = this.count();
if (count > 0)
return this.cards.splice(Math.floor(Math.random() * count), 1)[0] || null;
else
return null;
}
And finally our draw method. Actually removes a card at random from our cards
array and returns it. As per my README.md building a deck might look like the following.
var deck = new Deck({
extend: [
{ rank: "joker", suit: "black" },
{ rank: "joker", suit: "red" },
{ rank: "blank", suit: "none", limit: 1 }
],
multiply: 2
});
// This deck has:
//
// * 52 cards from 2 decks.
// * 4 jokers.
// * 1 blank card.
// * A total of 109 cards.
And that's about all there is to it. A follow-up will be to explain how to build a poker hand scorer using this deck module.
In this instance we have something I've written tests for. In order to be able to use the script server-side, or to import it as a module we need to export it. This is because we haven't used CommonJS or any kind of export. We add this to the bottom of our script to handle both cases.
var module = <any>module;
if (typeof module == "object" && typeof module.exports == "object")
module.exports = Deck;
This way in our tests we can use require
.