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);
usedeck.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 valuethis.values[cardId % 13]
- the function
drawCard
lets you draw more than one. It should be calleddrawCards
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 cardconst topCard = deck.card
You can also add a card to the top of the deck
deck.card = cardId
or add it randomlydeck.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)));