Skip to content

Cards

7 of DiamondsCard Back

A fun demo that’s also good for learning JavaScript is to model the concepts around card games. Not only do we get to design JavaScript objects with interesting properties, we also get to create interesting logic for manipulating playing cards within a deck.

The Playing Card

How would you design the idea of a playing card in a standard deck of 52 cards? Begin with pondering the information that makes up the concept of a playing card.

A playing card can be one of four suits.

  • ♠️ Spades
  • ♥️ Hearts
  • ♦️ Diamonds
  • ♣️ Clubs

Next, a playing card has a value which can range from 2 to 10 and the named values of “Jack”, “Queen”, “King”, and “Ace”.

Together, the suit and value make up the essense of a standard playing card. In JavaScript, we could represent it as an object.

let card = { suit: 'Diamonds', value: '7' };

Design Implications

While the idea of a playing card is pretty simple, we can quickly run into a number of design implications when we want to use apply the concept to any given program.

First of all is the question of the range of possible values for any given playing card. Next might be how we want to conceptualize those values. After that, we will probably want to investigate the idea of collections of cards and the most common collection: the Deck. Once we start working with collections, we might want some way to identify specific cards in the deck.

All of these core questions and concerns will, themselves, have implications for how a particular card game might be designed, such as War, Go Fish or Solitaire.

In this demo, I want to walk you through some possible ways to address those core concerns. You will find that JavaScript can be an excellent language to model playing cards!

Possible Values

It can be useful to have a couple of sources that represent all the possible suits and values in a standard deck. Your first thought might be to make these as simple arrays.

const cardSuits = [ 'Spades', 'Hearts', 'Diamonds', 'Clubs' ];
const cardValues = [ 'Ace', '2', '3', '4', '5', '6', '7',
'8', '9', '10', 'Jack', 'Queen', 'King' ];

Notice how we treat all the values as strings. We’re doing that to have a sense of type consistency between the idea of a “Two” and an “Ace”.

A problem with using arrays, however, is that arrays are mutable by default. That means we could potentially add or remove items from the array.

// Bad idea
suits.push('Feet'); // ??
// [ 'Spades', 'Hearts', 'Diamonds', 'Clubs', 'Feet' ]

Is there a way to prevent that? Yes, there is! We can use Object.freeze(). With it, we can make our suits and values immutable (or unchangeable).

“Freezing an object is the highest integrity level that JavaScript provides.”MDN

Here’s how we can do this. (For the sake of completeness, I’ve also included the ‘Joker’ as a possible value, even though it’s “special” in the sense that a standard deck comes with only two Jokers and they are never associated with a card suit.)

const CARD_SUITS = [ 'Spades', 'Hearts', 'Diamonds', 'Clubs' ];
Object.freeze(CARD_SUITS); // Make suits immutable
const CARD_VALUES = [ 'Ace', '2', '3', '4', '5', '6', '7', '8',
'9', '10', 'Jack', 'Queen', 'King', 'Joker' ];
Object.freeze(CARD_VALUES); // Make values immutable

Notice the little adjustment to the names we’re using for these fixed arrays. We’re putting the variable names in all upper-case as a visual ‘cue’ that these cannot physically be modified. That cue can remind us that these are immutable/unchanging.

Value Concepts

Queen of HeartsCard Back

With the frozen CARD_SUITS and CARD_VALUES, we already have a pretty healthy start to our game pieces. If we wanted to, we could start writing the code for a simple game using just these two variables.

But what if we wanted some variety in how we communicate the ideas of suits and values? For example, we might want to talk about the “2 of Hearts” or the “Two of Hearts”. One way uses the digit 2 while the other uses the word "Two". Furthermore, we might be interested in using emoji symbols for the suits.

Let’s expand on our two arrays with two more arrays: CARD_VALUE_NAMES and CARD_SUIT_SYMBOLS.

const CARD_SUITS = [ 'Spades', 'Hearts', 'Diamonds', 'Clubs' ];
const CARD_SUIT_SYMBOLS = [ '♠️', '♥️', '♦️', '♣️' ];
const CARD_VALUES = [ 'Ace', '2', '3', '4', '5', '6', '7', '8',
'9', '10', 'Jack', 'Queen', 'King', 'Joker' ];
const CARD_VALUE_NAMES = [ 'Ace', 'Two', 'Three', 'Four', 'Five',
'Six', 'Seven', 'Eight', 'Nine', 'Ten',
'Jack', 'Queen', 'King', 'Joker' ];
// Make these immutable
Object.freeze(CARD_SUITS);
Object.freeze(CARD_SUIT_SYMBOLS);
Object.freeze(CARD_VALUES);
Object.freeze(CARD_VALUE_NAMES);

Notice the great care I took in building these arrays. Each pair of arrays are set up as parallel arrays. A Parallel Array (or corresponding array) is one where the items in the arrays correspond to each other based on their position in the array.

Thus, CARD_VALUES[1] maps to CARD_VALUE_NAMES[1], allowing me to treat '2' and 'Two' as being conceptually equivalent. Likewise, CARD_SUITS[2] and CARD_SUIT_SYMBOLS[2] are also talking about the same suit: ♦️ is the symbol for ‘Diamonds’.

With these, we can create nice utility functions to generate content for the end-user.

function nameCard(valueIndex, suitIndex) {
return `${CARD_VALUE_NAMES[valueIndex]} of ${CARD_SUITS[suitIndex]}`;
}
let myCard = nameCard(1, 2); // 'Two of Diamonds'

What about when we combine a suit and a value to be a single card? What should such an object “look like”? Honestly, it doesn’t matter. You can design it in whatever way suits you. (I’ve been waiting to deal out that pun!)

Here is one such design (with hard-coded values).

let tuckInSleeve = {
suit: '♦️',
value: 'Ace',
name: 'Ace of Diamonds'
}

We’ll revise our object designs once we get into creating certain algorithms. As long as we’ve got some common core concepts coded, we can transform and re-map these to whatever shape we want later on.

Card Codes

Redesigned Playing Card

Here’s my re-design of the Playing Card. Notice how I’m using JSDoc comments to define the structure of my playing card.

/** @typedef {{ code: string, value: string, suit: string}} Card */

This will become very useful down the road in terms of generating intellisense within VS Code. For example, consider that utility function we created earlier for getting the name of any given card. We can re-write that as follows:

/**
* Describe a playing card.
*
* @param {Card} card A playing card object
* @param {boolean} valueName Whether or not to use the name or the digit for the value (defaults to `true`)
* @param {boolean} emoji Whether or not to use an emoji for the suit (defaults to `false`)
*/
function nameCard(card, valueName = true, emoji = false) {
const valueIndex = CARD_VALUES.indexOf(card.value);
const suitIndex = CARD_SUITS.indexOf(card.suit);
let theSuit = CARD_SUITS[suitIndex];
if(emoji) {
theSuit = CARD_SUIT_SYMBOLS[suitIndex];
}
let theName = CARD_VALUES[valueIndex];
if(valueName) {
theName = CARD_VALUE_NAMES[valueIndex];
}
return `${theName} of ${theSuit}`;
}

Decks

Creating A Fresh Deck

Piles

Card Algorithms

Shuffling Cards

Dealing Cards


TEMP CODE

Here’s some raw code to integrate into this page…

Raw Code
const cardCodes = ['AS', '2S', '3S', '4S', '5S', '6S', '7S', '8S', '9S', '0S', 'JS', 'QS', 'KS', 'AD', '2D', '3D', '4D', '5D', '6D', '7D', '8D', '9D', '0D', 'JD', 'QD', 'KD', 'AC', '2C', '3C', '4C', '5C', '6C', '7C', '8C', '9C', '0C', 'JC', 'QC', 'KC', 'AH', '2H', '3H', '4H', '5H', '6H', '7H', '8H', '9H', '0H', 'JH', 'QH', 'KH'];
const deck = presetDeck();
const back = '/back.png';
const cards = cardCodes.map(code => `/${code}.svg`);
class Player {
constructor(name) {
}
}
const player_1 = new Player('Player Won');
const log = console.log;
log(draw(deck, 50).map(x => x.code));
log(deck.cards.map(x => x.code));
log(presetDeck().cards.map(x => x.code));
/**
* Generates the short code of a playing card
* @param {Card} card A Playing Card
* @returns {string} The code for the playing card
*/
function asCode(card) {
return card.code;
}
/**
* Generates the descriptive name of a playing card
* @param {Card} card A Playing Card
* @returns {string} The name of the playing card
*/
function asName(card) {
return `${card.value} of ${card.suit}`;
}
/** @typedef {{ code: string, value: string, suit: string}} Card */
/** @typedef {{success: boolean, deck_id: string, cards: Card[], remaining: number}} Deck */
/**
* Draws cards from a deck (if any are available).
*
* This is a mutating function, meaning that it will modify the list of cards in the deck
* @param {Deck} deck
* @param {number} count
* @returns {Card[]} An array of cards from the deck
*/
function draw(deck, count) {
const result = deck.cards.splice(0,count);
deck.remaining = deck.cards.length;
return result;
}
/**
* Make a deck of 52 cards in a preset (shuffled) order.
* @returns {Deck}
*/
function presetDeck() {
return {
"success": true,
"deck_id": "3bwa184i6asi",
"cards": [
{
"code": "4D",
"image": "https://deckofcardsapi.com/static/img/4D.png",
"images": {
"svg": "https://deckofcardsapi.com/static/img/4D.svg",
"png": "https://deckofcardsapi.com/static/img/4D.png"
},
"value": "4",
"suit": "DIAMONDS"
},
{
"code": "3D",
"image": "https://deckofcardsapi.com/static/img/3D.png",
"images": {
"svg": "https://deckofcardsapi.com/static/img/3D.svg",
"png": "https://deckofcardsapi.com/static/img/3D.png"
},
"value": "3",
"suit": "DIAMONDS"
},
{
"code": "4H",
"image": "https://deckofcardsapi.com/static/img/4H.png",
"images": {
"svg": "https://deckofcardsapi.com/static/img/4H.svg",
"png": "https://deckofcardsapi.com/static/img/4H.png"
},
"value": "4",
"suit": "HEARTS"
},
{
"code": "6S",
"image": "https://deckofcardsapi.com/static/img/6S.png",
"images": {
"svg": "https://deckofcardsapi.com/static/img/6S.svg",
"png": "https://deckofcardsapi.com/static/img/6S.png"
},
"value": "6",
"suit": "SPADES"
},
{
"code": "3H",
"image": "https://deckofcardsapi.com/static/img/3H.png",
"images": {
"svg": "https://deckofcardsapi.com/static/img/3H.svg",
"png": "https://deckofcardsapi.com/static/img/3H.png"
},
"value": "3",
"suit": "HEARTS"
},
{
"code": "4S",
"image": "https://deckofcardsapi.com/static/img/4S.png",
"images": {
"svg": "https://deckofcardsapi.com/static/img/4S.svg",
"png": "https://deckofcardsapi.com/static/img/4S.png"
},
"value": "4",
"suit": "SPADES"
},
{
"code": "JC",
"image": "https://deckofcardsapi.com/static/img/JC.png",
"images": {
"svg": "https://deckofcardsapi.com/static/img/JC.svg",
"png": "https://deckofcardsapi.com/static/img/JC.png"
},
"value": "JACK",
"suit": "CLUBS"
},
{
"code": "3C",
"image": "https://deckofcardsapi.com/static/img/3C.png",
"images": {
"svg": "https://deckofcardsapi.com/static/img/3C.svg",
"png": "https://deckofcardsapi.com/static/img/3C.png"
},
"value": "3",
"suit": "CLUBS"
},
{
"code": "7C",
"image": "https://deckofcardsapi.com/static/img/7C.png",
"images": {
"svg": "https://deckofcardsapi.com/static/img/7C.svg",
"png": "https://deckofcardsapi.com/static/img/7C.png"
},
"value": "7",
"suit": "CLUBS"
},
{
"code": "6C",
"image": "https://deckofcardsapi.com/static/img/6C.png",
"images": {
"svg": "https://deckofcardsapi.com/static/img/6C.svg",
"png": "https://deckofcardsapi.com/static/img/6C.png"
},
"value": "6",
"suit": "CLUBS"
},
{
"code": "8H",
"image": "https://deckofcardsapi.com/static/img/8H.png",
"images": {
"svg": "https://deckofcardsapi.com/static/img/8H.svg",
"png": "https://deckofcardsapi.com/static/img/8H.png"
},
"value": "8",
"suit": "HEARTS"
},
{
"code": "KD",
"image": "https://deckofcardsapi.com/static/img/KD.png",
"images": {
"svg": "https://deckofcardsapi.com/static/img/KD.svg",
"png": "https://deckofcardsapi.com/static/img/KD.png"
},
"value": "KING",
"suit": "DIAMONDS"
},
{
"code": "KH",
"image": "https://deckofcardsapi.com/static/img/KH.png",
"images": {
"svg": "https://deckofcardsapi.com/static/img/KH.svg",
"png": "https://deckofcardsapi.com/static/img/KH.png"
},
"value": "KING",
"suit": "HEARTS"
},
{
"code": "JH",
"image": "https://deckofcardsapi.com/static/img/JH.png",
"images": {
"svg": "https://deckofcardsapi.com/static/img/JH.svg",
"png": "https://deckofcardsapi.com/static/img/JH.png"
},
"value": "JACK",
"suit": "HEARTS"
},
{
"code": "QH",
"image": "https://deckofcardsapi.com/static/img/QH.png",
"images": {
"svg": "https://deckofcardsapi.com/static/img/QH.svg",
"png": "https://deckofcardsapi.com/static/img/QH.png"
},
"value": "QUEEN",
"suit": "HEARTS"
},
{
"code": "7H",
"image": "https://deckofcardsapi.com/static/img/7H.png",
"images": {
"svg": "https://deckofcardsapi.com/static/img/7H.svg",
"png": "https://deckofcardsapi.com/static/img/7H.png"
},
"value": "7",
"suit": "HEARTS"
},
{
"code": "AS",
"image": "https://deckofcardsapi.com/static/img/AS.png",
"images": {
"svg": "https://deckofcardsapi.com/static/img/AS.svg",
"png": "https://deckofcardsapi.com/static/img/AS.png"
},
"value": "ACE",
"suit": "SPADES"
},
{
"code": "8S",
"image": "https://deckofcardsapi.com/static/img/8S.png",
"images": {
"svg": "https://deckofcardsapi.com/static/img/8S.svg",
"png": "https://deckofcardsapi.com/static/img/8S.png"
},
"value": "8",
"suit": "SPADES"
},
{
"code": "3S",
"image": "https://deckofcardsapi.com/static/img/3S.png",
"images": {
"svg": "https://deckofcardsapi.com/static/img/3S.svg",
"png": "https://deckofcardsapi.com/static/img/3S.png"
},
"value": "3",
"suit": "SPADES"
},
{
"code": "8C",
"image": "https://deckofcardsapi.com/static/img/8C.png",
"images": {
"svg": "https://deckofcardsapi.com/static/img/8C.svg",
"png": "https://deckofcardsapi.com/static/img/8C.png"
},
"value": "8",
"suit": "CLUBS"
},
{
"code": "0S",
"image": "https://deckofcardsapi.com/static/img/0S.png",
"images": {
"svg": "https://deckofcardsapi.com/static/img/0S.svg",
"png": "https://deckofcardsapi.com/static/img/0S.png"
},
"value": "10",
"suit": "SPADES"
},
{
"code": "7S",
"image": "https://deckofcardsapi.com/static/img/7S.png",
"images": {
"svg": "https://deckofcardsapi.com/static/img/7S.svg",
"png": "https://deckofcardsapi.com/static/img/7S.png"
},
"value": "7",
"suit": "SPADES"
},
{
"code": "6H",
"image": "https://deckofcardsapi.com/static/img/6H.png",
"images": {
"svg": "https://deckofcardsapi.com/static/img/6H.svg",
"png": "https://deckofcardsapi.com/static/img/6H.png"
},
"value": "6",
"suit": "HEARTS"
},
{
"code": "4C",
"image": "https://deckofcardsapi.com/static/img/4C.png",
"images": {
"svg": "https://deckofcardsapi.com/static/img/4C.svg",
"png": "https://deckofcardsapi.com/static/img/4C.png"
},
"value": "4",
"suit": "CLUBS"
},
{
"code": "JD",
"image": "https://deckofcardsapi.com/static/img/JD.png",
"images": {
"svg": "https://deckofcardsapi.com/static/img/JD.svg",
"png": "https://deckofcardsapi.com/static/img/JD.png"
},
"value": "JACK",
"suit": "DIAMONDS"
},
{
"code": "9S",
"image": "https://deckofcardsapi.com/static/img/9S.png",
"images": {
"svg": "https://deckofcardsapi.com/static/img/9S.svg",
"png": "https://deckofcardsapi.com/static/img/9S.png"
},
"value": "9",
"suit": "SPADES"
},
{
"code": "0D",
"image": "https://deckofcardsapi.com/static/img/0D.png",
"images": {
"svg": "https://deckofcardsapi.com/static/img/0D.svg",
"png": "https://deckofcardsapi.com/static/img/0D.png"
},
"value": "10",
"suit": "DIAMONDS"
},
{
"code": "5S",
"image": "https://deckofcardsapi.com/static/img/5S.png",
"images": {
"svg": "https://deckofcardsapi.com/static/img/5S.svg",
"png": "https://deckofcardsapi.com/static/img/5S.png"
},
"value": "5",
"suit": "SPADES"
},
{
"code": "AC",
"image": "https://deckofcardsapi.com/static/img/AC.png",
"images": {
"svg": "https://deckofcardsapi.com/static/img/AC.svg",
"png": "https://deckofcardsapi.com/static/img/AC.png"
},
"value": "ACE",
"suit": "CLUBS"
},
{
"code": "9H",
"image": "https://deckofcardsapi.com/static/img/9H.png",
"images": {
"svg": "https://deckofcardsapi.com/static/img/9H.svg",
"png": "https://deckofcardsapi.com/static/img/9H.png"
},
"value": "9",
"suit": "HEARTS"
},
{
"code": "2C",
"image": "https://deckofcardsapi.com/static/img/2C.png",
"images": {
"svg": "https://deckofcardsapi.com/static/img/2C.svg",
"png": "https://deckofcardsapi.com/static/img/2C.png"
},
"value": "2",
"suit": "CLUBS"
},
{
"code": "0H",
"image": "https://deckofcardsapi.com/static/img/0H.png",
"images": {
"svg": "https://deckofcardsapi.com/static/img/0H.svg",
"png": "https://deckofcardsapi.com/static/img/0H.png"
},
"value": "10",
"suit": "HEARTS"
},
{
"code": "9C",
"image": "https://deckofcardsapi.com/static/img/9C.png",
"images": {
"svg": "https://deckofcardsapi.com/static/img/9C.svg",
"png": "https://deckofcardsapi.com/static/img/9C.png"
},
"value": "9",
"suit": "CLUBS"
},
{
"code": "8D",
"image": "https://deckofcardsapi.com/static/img/8D.png",
"images": {
"svg": "https://deckofcardsapi.com/static/img/8D.svg",
"png": "https://deckofcardsapi.com/static/img/8D.png"
},
"value": "8",
"suit": "DIAMONDS"
},
{
"code": "6D",
"image": "https://deckofcardsapi.com/static/img/6D.png",
"images": {
"svg": "https://deckofcardsapi.com/static/img/6D.svg",
"png": "https://deckofcardsapi.com/static/img/6D.png"
},
"value": "6",
"suit": "DIAMONDS"
},
{
"code": "KS",
"image": "https://deckofcardsapi.com/static/img/KS.png",
"images": {
"svg": "https://deckofcardsapi.com/static/img/KS.svg",
"png": "https://deckofcardsapi.com/static/img/KS.png"
},
"value": "KING",
"suit": "SPADES"
},
{
"code": "QC",
"image": "https://deckofcardsapi.com/static/img/QC.png",
"images": {
"svg": "https://deckofcardsapi.com/static/img/QC.svg",
"png": "https://deckofcardsapi.com/static/img/QC.png"
},
"value": "QUEEN",
"suit": "CLUBS"
},
{
"code": "KC",
"image": "https://deckofcardsapi.com/static/img/KC.png",
"images": {
"svg": "https://deckofcardsapi.com/static/img/KC.svg",
"png": "https://deckofcardsapi.com/static/img/KC.png"
},
"value": "KING",
"suit": "CLUBS"
},
{
"code": "5H",
"image": "https://deckofcardsapi.com/static/img/5H.png",
"images": {
"svg": "https://deckofcardsapi.com/static/img/5H.svg",
"png": "https://deckofcardsapi.com/static/img/5H.png"
},
"value": "5",
"suit": "HEARTS"
},
{
"code": "2S",
"image": "https://deckofcardsapi.com/static/img/2S.png",
"images": {
"svg": "https://deckofcardsapi.com/static/img/2S.svg",
"png": "https://deckofcardsapi.com/static/img/2S.png"
},
"value": "2",
"suit": "SPADES"
},
{
"code": "JS",
"image": "https://deckofcardsapi.com/static/img/JS.png",
"images": {
"svg": "https://deckofcardsapi.com/static/img/JS.svg",
"png": "https://deckofcardsapi.com/static/img/JS.png"
},
"value": "JACK",
"suit": "SPADES"
},
{
"code": "QD",
"image": "https://deckofcardsapi.com/static/img/QD.png",
"images": {
"svg": "https://deckofcardsapi.com/static/img/QD.svg",
"png": "https://deckofcardsapi.com/static/img/QD.png"
},
"value": "QUEEN",
"suit": "DIAMONDS"
},
{
"code": "2H",
"image": "https://deckofcardsapi.com/static/img/2H.png",
"images": {
"svg": "https://deckofcardsapi.com/static/img/2H.svg",
"png": "https://deckofcardsapi.com/static/img/2H.png"
},
"value": "2",
"suit": "HEARTS"
},
{
"code": "7D",
"image": "https://deckofcardsapi.com/static/img/7D.png",
"images": {
"svg": "https://deckofcardsapi.com/static/img/7D.svg",
"png": "https://deckofcardsapi.com/static/img/7D.png"
},
"value": "7",
"suit": "DIAMONDS"
},
{
"code": "AD",
"image": "https://deckofcardsapi.com/static/img/aceDiamonds.png",
"images": {
"svg": "https://deckofcardsapi.com/static/img/aceDiamonds.svg",
"png": "https://deckofcardsapi.com/static/img/aceDiamonds.png"
},
"value": "ACE",
"suit": "DIAMONDS"
},
{
"code": "2D",
"image": "https://deckofcardsapi.com/static/img/2D.png",
"images": {
"svg": "https://deckofcardsapi.com/static/img/2D.svg",
"png": "https://deckofcardsapi.com/static/img/2D.png"
},
"value": "2",
"suit": "DIAMONDS"
},
{
"code": "0C",
"image": "https://deckofcardsapi.com/static/img/0C.png",
"images": {
"svg": "https://deckofcardsapi.com/static/img/0C.svg",
"png": "https://deckofcardsapi.com/static/img/0C.png"
},
"value": "10",
"suit": "CLUBS"
},
{
"code": "AH",
"image": "https://deckofcardsapi.com/static/img/AH.png",
"images": {
"svg": "https://deckofcardsapi.com/static/img/AH.svg",
"png": "https://deckofcardsapi.com/static/img/AH.png"
},
"value": "ACE",
"suit": "HEARTS"
},
{
"code": "9D",
"image": "https://deckofcardsapi.com/static/img/9D.png",
"images": {
"svg": "https://deckofcardsapi.com/static/img/9D.svg",
"png": "https://deckofcardsapi.com/static/img/9D.png"
},
"value": "9",
"suit": "DIAMONDS"
},
{
"code": "5C",
"image": "https://deckofcardsapi.com/static/img/5C.png",
"images": {
"svg": "https://deckofcardsapi.com/static/img/5C.svg",
"png": "https://deckofcardsapi.com/static/img/5C.png"
},
"value": "5",
"suit": "CLUBS"
},
{
"code": "QS",
"image": "https://deckofcardsapi.com/static/img/QS.png",
"images": {
"svg": "https://deckofcardsapi.com/static/img/QS.svg",
"png": "https://deckofcardsapi.com/static/img/QS.png"
},
"value": "QUEEN",
"suit": "SPADES"
},
{
"code": "5D",
"image": "https://deckofcardsapi.com/static/img/5D.png",
"images": {
"svg": "https://deckofcardsapi.com/static/img/5D.svg",
"png": "https://deckofcardsapi.com/static/img/5D.png"
},
"value": "5",
"suit": "DIAMONDS"
}
],
"remaining": 0
}
}