dj92
dj92

Reputation: 29

Black Jack, adding card values together problem

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

Answers (3)

zer00ne
zer00ne

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

Avi
Avi

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

trincot
trincot

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

Related Questions