JavaScript Deck Builder: Build Deck, Draw Random Card from Deck

Posted on

Problem

this is similar to another project I did here: JavaScript Deck Builder: Shuffle, Draw Cards
Except this script does not shuffle or draw cards from the top of the deck. Instead the script builds a deck, then will randomly choose a card from the deck. The card that is chosen is then removed from the deck and placed into the discard pile. Like my previous post I am looking for critiques or different ways I could approach writing this script.

const cardGame = {
values: [ 'Ace', 2, 3, 4, 5, 6, 7, 8, 9, 10, 'Joker', 'Queen', 'King' ],
suits: [ 'Clubs', 'Spades', 'Hearts', 'Diamonds' ],
deck: [],
discard: [],
pick(arr) {
    const idx = Math.floor(Math.random() * arr.length);
    return arr[idx];
},
buildDeck() {
    const { deck, discard, values, suits } = this;

    //clear deck and discard pile
    deck.splice(0, 52);
    discard.splice(0, 52);

    //regenerate/build deck
    for (let suit of suits) {
        for (let value of values) {
            deck.push({ value, suit });
        }
    }
},
getCard() {
    const { pick, deck } = this;
    return pick(deck);
},
drawCard(numCards = 1) {
    const { deck, discard } = this;
    const drawnCards = [];

    //draw cards
    while (drawnCards.length < numCards) {
        let card = this.getCard();
        //check if card has been drawn
        for (let drawnCard of drawnCards) {
            let matchSuit = card.suit === drawnCard.suit;
            let matchValue = card.value === drawnCard.value;
            if (matchSuit && matchValue) {
                card = this.getCard();
            }
        }
        console.log(card);
        drawnCards.push(card);
    }

    //discard drawn cards from deck
    for (let drawnCard of drawnCards) {
        for (let i = 0; i < deck.length; i++) {
            let card = deck[i];
            let matchSuit = card.suit === drawnCard.suit;
            let matchValue = card.value === drawnCard.value;
            if (matchSuit && matchValue) {
                deck.splice(i, 1);
                discard.push(card);
            }
        }
    }
    console.log(deck.length);
}
};
cardGame.buildDeck();

Solution

General points

  • To empty an array use the length property eg. Rather than use deck.splice(0, 52); use deck.length = 0;
  • You can simplify the card array and use a number 0 to 51 to represent a card. The card suit is the this.suits[(value / 13 | 0) % 4] and the card value this.values[cardId % 13]
  • the function drawCard lets you draw more than one. It should be called drawCards

Rewrite

The rewrite uses a completely different approach for handling a set (deck of card) of unique items

  • IIFE (Immediately Invoke Function Expression) to encapsulate the deck so that code using the deck can not break the state of the deck.

  • Cards are stored by id (Number 0 – 51) rather than using object.

  • Adding and removing single cards is done using getters and setters. Eg to get a random card const randomCard = deck.random or to get top card const topCard = deck.card

    You can also add a card to the top of the deck deck.card = cardId or add it randomly deck.random = cardId

    Note that cards will not be added if the deck already contains that card.

    Note that if the deck is out of cards getting cards will return undefined

  • deck.drawCards can draw random or from the top of the deck.

  • Cards are defined by unique ID. The IIFE has a deckCount argument to define the number of decks used. This means that when adding cards back to the deck only cards handed out will be accepted back.

    Note max deckCount is set to 12 however this value is arbitrary and can be any value < infinity

const deck = ((deckCount) => {
    deckCount = Math.min(12, Math.max(1, isNaN(deckCount) ? 2 : deckCount | 0));
    const VALUE = "A,2,3,4,5,6,7,8,9,10,J,Q,K".split(",");
    const SUITS = "♣,♠,♥,♦".split(",");
    const deck = [];
    const randIdx = () => Math.random() * deck.length | 0;
    return (Object.freeze({
        nameCard(id) { return VALUE[id % 13] + "" + SUITS[(id / 13 | 0) % 4] },
        handAsStr(hand) { return hand.map(cardId => this.nameCard(cardId)).join(", ") },
        set card(id) { !deck.includes(id) && deck.push(id) },
        set random(id) { !deck.includes(id) && deck.splice(randIdx(), 0, id) },
        get card() { return deck.pop() },
        get random() { return deck.length ? deck.splice(randIdx(), 1)[0] : undefined },
        newDeck() {
            deck.length = 0;
            while(deck.length < 52 * deckCount) { this.card = deck.length }
            return this;
        },
        drawCards(cards = 1, rand = true) {
            var c;
            const drawn = [];
            while (cards-- > 0 && deck.length) { 
                (c = (rand ? this.random : this.card)) !== undefined && drawn.push(c);
            }
            return drawn;               
        },
        get length() { return deck.length },
    })).newDeck();
})("pho");
console.log(deck.handAsStr(deck.drawCards(5)));
console.log(deck.handAsStr(deck.drawCards(5)));
console.log(deck.handAsStr(deck.drawCards(5)));

Leave a Reply

Your email address will not be published. Required fields are marked *