Object-Oriented JavaScript: Deck of Cards
Let’s implement a deck of playing cards in an object-oriented way with JavaScript. Then we’ll add methods for additional functionality such as a shuffle operation, a reset operation, and a deal operation that will return a random card and remove it from the deck.
This type of task is a very common interview question and example given by teachers when discussing object-oriented programming patterns. JavaScript is such a flexible language that even though it is prototype-based it can be used in a roughly class-based manner, especially with the introduction of class
syntax in ES6 which is a syntactic-sugar over the base prototype
.
Create a Deck
To start, we need to think about our requirements. There are 52 cards in a deck, which can have one of 4 suits and one of 13 values. We can store our deck
as an array. By iterating over each suit and then each value we can populate it as follows:
class Deck {
constructor() {
this.deck = [];
const suits = ['Hearts', 'Spades', 'Clubs', 'Diamonds'];
const values = ['Ace', 2, 3, 4, 5, 6, 7, 8, 9, 10, 'Jack', 'Queen', 'King'];
for (let suit in suits) {
for (let value in values) {
this.deck.push(`${values[value]} of ${suits[suit]}`);
}
}
}
}
const deck1 = new Deck();
console.log(deck1.deck);
// ["Ace of Hearts", "2 of Hearts", "3 of Hearts", "4 of Hearts", "5 of Hearts", "6 of Hearts", "7 of Hearts", "8 of Hearts", "9 of Hearts", "10 of Hearts", "Jack of Hearts", "Queen of Hearts", "King of Hearts", "Ace of Spades", "2 of Spades", "3 of Spades", "4 of Spades", "5 of Spades", "6 of Spades", "7 of Spades", "8 of Spades", "9 of Spades", "10 of Spades", "Jack of Spades", "Queen of Spades", "King of Spades", "Ace of Clubs", "2 of Clubs", "3 of Clubs", "4 of Clubs", "5 of Clubs", "6 of Clubs", "7 of Clubs", "8 of Clubs", "9 of Clubs", "10 of Clubs", "Jack of Clubs", "Queen of Clubs", "King of Clubs", "Ace of Diamonds", "2 of Diamonds", "3 of Diamonds", "4 of Diamonds", "5 of Diamonds", "6 of Diamonds", "7 of Diamonds", "8 of Diamonds", "9 of Diamonds", "10 of Diamonds", "Jack of Diamonds", "Queen of Diamonds", "King of Diamonds"]
Shuffle a deck
For our first method, we want to randomly shuffle the deck. Mike Bostock, the creator of the D3 visualization library, has a fantastic post discussing why you need to use the Fisher-Yates Shuffle for a truly non-biased shuffle.
Using Bostock’s code for a generic shuffle
function, we can add a shuffle
method to our class as follows:
shuffle() {
const { deck } = this;
let m = deck.length, i;
while (m) {
i = Math.floor(Math.random() * m--);
[deck[m], deck[i]] = [deck[i], deck[m]];
}
return this;
}
Note that we use Object Destructuring in the first part of our shuffle
method. In other words, the following two lines are equivalent:
const { deck } = this;
const deck = this.deck;
We also use another shortcut when declaring the m
and i
variables to keep them on one line. The following three approaches are equivalent:
// Option 1
let m = deck.length;
let i;
// Option 2
let me = deck.length,
i;
// Option 3
let m = deck.length, i;
Then later on we use Array Destructuring to swap i
and m
before finally returning this
. We need to remember to call our shuffle
method on our newly created deck instance deck1
, too, when we want to use it.
Deal a card
Now we need to add a deal
method that will return one card and remove it from the deck. Fortunately we can use the pop
method to do exactly this so our code is quite concise for this one!
deal(){
return this.deck.pop();
}
Our complete code now looks like this:
class Deck {
constructor() {
this.deck = [];
const suits = ['Hearts', 'Spades', 'Clubs', 'Diamonds'];
const values = ['Ace', 2, 3, 4, 5, 6, 7, 8, 9, 10, 'Jack', 'Queen', 'King'];
for (let suit in suits) {
for (let value in values) {
this.deck.push(`${values[value]} of ${suits[suit]}`);
}
}
}
shuffle(){
const { deck } = this;
let m = deck.length, i;
while(m){
i = Math.floor(Math.random() * m--);
[deck[m], deck[i]] = [deck[i], deck[m]];
}
return this;
}
deal(){
return this.deck.pop();
}
}
const deck1 = new Deck();
deck1.shuffle()
console.log(deck1.deck);
deck1.deal()
console.log(deck1.deck);
At the bottom we create a new instance deck1
of our Deck
class, call shuffle()
on it, and output the result. Then we deal()
one card and if we look at our deck1
again we can see that the last card has been removed!
Reset
The last step is to add a reset()
method that will give us a fresh, randomly shuffled deck of cards. If you think about it, we don’t really need new code for this, we just need to rearrange what we already have.
Our reset
method is really just our original code for creating a basic Deck
. We just need to separate it out from our constructor
into its own method. As an additional bonus, we can make it so that whenever Deck
creates a new object instance, that new object will automatically be shuffled.
Here’s what the code looks like:
class Deck{
constructor(){
this.deck = [];
this.reset();
this.shuffle();
}
reset(){
this.deck = [];
const suits = ['Hearts', 'Spades', 'Clubs', 'Diamonds'];
const values = ['Ace', 2, 3, 4, 5, 6, 7, 8, 9, 10, 'Jack', 'Queen', 'King'];
for (let suit in suits) {
for (let value in values) {
this.deck.push(`${values[value]} of ${suits[suit]}`);
}
}
}
shuffle(){
const { deck } = this;
let m = deck.length, i;
while(m){
i = Math.floor(Math.random() * m--);
[deck[m], deck[i]] = [deck[i], deck[m]];
}
return this;
}
deal(){
return this.deck.pop();
}
}
const deck1 = new Deck();
console.log(deck1.deck);
deck1.reset();
console.log(deck1.deck);
Now each and every time we create a new Desk
instance it will be randomly shuffled but we can always reset
it to an ordered configuration. Plus we can deal
a card or add additional shuffles
as desired.
Want to improve your JavaScript? I have a list of recommended JavaScript books.