Reputation: 29
I have a problem with my function that should add card values together. When i call the function with these arguments it logs 21 as it should.
score([{ suit: 'HEARTS', value: 1 }, { suit: 'SPADES', value: 11 }]) //Logs 21 as it should
But when i call the function with the same values but reversed it logs 11
score([{ suit: 'HEARTS', value: 11 }, { suit: 'SPADES', value: 1 }]) // Logs 11 but should log 21
I dont get how i should make it work, maybe someone can guide me in the right direction. Code below:
let score = function (cardObject) {
let getScore = 0;
for (let i = 0; i < cardObject.length; i++) {
let cardValue = cardObject[i].value;
if (cardValue >= 10 && cardValue <= 13) {
getScore += 10;
} else if (cardValue >= 2 && cardValue <= 9) {
getScore += cardValue;
} else if (cardValue === 1) { //Ace
getScore += 11;
if (getScore + cardValue > 21) {
getScore -= 10;
}
}
}
return getScore;
}
score([{ suit: 'HEARTS', value: 11 }, { suit: 'SPADES', value: 1 }]) // Logs 11 but should log 21
score([{ suit: 'HEARTS', value: 1 }, { suit: 'SPADES', value: 11 }]) //Logs 21 as it should
score([{ suit: 'HEARTS', value: 1 }, { suit: 'SPADES', value: 1 }, { suit: 'SPADES', value: 1 }, { suit: 'SPADES', value: 10 }]) //Logs 23 but should log 13
score([{ suit: 'HEARTS', value: 10 }, { suit: 'SPADES', value: 1 }, { suit: 'SPADES', value: 1 }, { suit: 'SPADES', value: 1 }]) //Logs 13 as it should
Upvotes: 1
Views: 109
Reputation: 44043
It would probably be easier to have a points
property to each object and keep it in a range of 2 to 11 like this:
{suit: '♠', face: 'ACE', points: 11}
Then a hand would be an array of objects to be passed through a function like this:
function score(hand) {
let ace = 0; // Define a variable to count aces
// total is an accumulating sum of points
// current is the current object (card)
return hand.reduce((total, current) => {
// If object.points is 11 then increment ace
if (current.points === 11) ace++;
// As long as the total and the object's points is more than 21 AND ace is more than 0
while (total + current.points > 21 && ace > 0) {
// total is 10 points less
total = total - 10;
// ace is decremented
ace--;
}
// result is total and object's points
return total + current.points;
}, 0);
}
Details are commented in example
/* For demo purposes */
/* Returns a shuffled array of objects -- each object represents a card
Example: {suit: '♠', face: 'ACE', points: 11}
*/
function deck() {
const suits = ['♠', '♣', '♥', '♦'];
const faces = ['TWO', 'THREE', 'FOUR', 'FIVE', 'SIX', 'SEVEN', 'EIGHT', 'NINE', 'TEN', 'JACK', 'QUEEN', 'KING', 'ACE'];
const points = [2, 3, 4, 5, 6, 7, 8, 9, 10, 10, 10, 10, 11];
let deck = [];
for (let s=0; s < suits.length; s++) {
for (let f=0; f < faces.length; f++) {
deck.push({suit: suits[s], face: faces[f], points: points[f]});
}
}
return shuffle(deck);
}
/* For demo purposes */
/* Returns an array in a random order */
function shuffle(array) {
let qty = array.length, temp, i;
while (qty) {
i = Math.floor(Math.random() * qty--);
temp = array[qty];
array[qty] = array[i];
array[i] = temp;
}
return array;
}
/* For demo purposes */
/* Returns a given number of cards */
function draw(cards, qty) {
return [...Array(qty)].map(_ => cards.pop());
}
/**
* Returns the total points of a given array of objects
* @param {array<object>} hand - An array of objects
* @return {number} - The total amount of points
*/
function score(hand) {
let ace = 0;
return hand.reduce((total, current) => {
if (current.points === 11) ace++;
while (total + current.points > 21 && ace > 0) {
total = total - 10;
ace--;
}
return total + current.points;
}, 0);
}
let D = deck();
console.log("========ROUND 1========");
let handA = draw(D, 2);
let scoreA = score(handA);
console.log("Player A draws: ");
console.log(handA);
console.log("Player A score: "+scoreA);
console.log("=======================");
let handB = draw(D, 2);
let scoreB = score(handB);
console.log("Player B draws: ");
console.log(handB);
console.log("Player B score: "+scoreB);
console.log("=======================");
console.log("========ROUND 2========");
let hitA = draw(D, 1);
/* After Round 1, merge new draws with the previous draw */
handA = [...handA, ...hitA];
scoreA = score(handA);
console.log("Player A draws: ");
console.log(hitA);
console.log("Player A score: "+scoreA);
console.log("=======================");
let hitB = draw(D, 1);
handB = [...handB, ...hitB];
scoreB = score(handB);
console.log("Player B draws: ")
console.log(hitB);
console.log("Plater B score: "+scoreB);
console.log("=======================");
console.log("========TEST ACE========");
let handC = [{points: 11}, {points: 4}];
let scoreC = score(handC);
console.log(JSON.stringify(handC));
console.log(scoreC);
let hitC = [{points: 8}];
handC = [...handC, ...hitC];
scoreC = score(handC);
console.log(JSON.stringify(handC));
console.log(scoreC);
Upvotes: 1
Reputation: 1427
A bit late for the party as trincot already explained why your code doesn't work like you expected and provided you a solution. My solution is a bit more lengthy but I'm a sucker for array methods, so in case anyone is interested here it is:
const score = (cards) => {
// add up all the cards
const total = cards.reduce((acc, { value }) => {
if (value > 9) {
return acc + 10;
}
return acc + value;
}, 0);
// check if there is ace in the cards
const hasAce = cards.some(({ value }) => value === 1);
// add 10 if cards contain and ace AND your result won't exceed 21
return hasAce && total <= 11 ? total + 10 : total;
};
Upvotes: 1
Reputation: 350766
The problem is that you still add the Ace's value
to the total to compare it with 21, at a moment you had already increased the score to account for that Ace. A second problem will occur when the Ace comes early, is counted as 11, and then subsequent cards still bust the 21 ceiling.
Here is a possible solution:
let score = function (cardObject) {
let getScore = 0;
let hasAce = false;
for (const {value} of cardObject) {
getScore += Math.min(10, value); // Count Ace as 1
hasAce ||= value == 1; // Remember if there was an Ace
}
// Only at the end consider whether an Ace could count as 11
return hasAce && getScore <= 11 ? getScore + 10 : getScore;
}
console.log(score([{ suit: 'HEARTS', value: 11 }, { suit: 'SPADES', value: 1 }])) // should log 21
console.log(score([{ suit: 'HEARTS', value: 1 }, { suit: 'SPADES', value: 11 }])) // should logs 21
console.log(score([{ suit: 'HEARTS', value: 1 }, { suit: 'SPADES', value: 1 }, { suit: 'SPADES', value: 1 }, { suit: 'SPADES', value: 10 }])) // should log 13
console.log(score([{ suit: 'HEARTS', value: 10 }, { suit: 'SPADES', value: 1 }, { suit: 'SPADES', value: 1 }, { suit: 'SPADES', value: 1 }])) // should logs 13
Some explanation about the code:
The for..of
loop syntax is more suitable here, as you don't need the index in the array, only the object.
{value}
immediately takes the value
property of the object that is visited in the array. This is called destructuring. It is suitable here because we don't need anything else from the object -- only the value.
Math.min(10, value)
is a handy shortcut to convert the values 11,12 and 13 to 10. It doesn't change the value of 1, but that is intended.
The ||=
assignment is short for hasAce = hasAce || value == 1
and so it makes sure that if ever an ace has been found this boolean can never return to false
, but remains true
.
At the end of the loop it is checked whether there was an ace, and whether the total score would allow for one ace to count as 11, and if so 10 more than the total is returned, otherwise that total is returned as-is. The conditional operator is used for this (? :
).
Upvotes: 1