Card Game: nextPlayer and invoking determineWinner

As the title implies, I’m having some difficulties with the logic of if and when there is a next player that is eligible to play, otherwise determine a winner.

I have tried using different boolean conditions depending on the player state, such as this.hasBlackJack = false | true where this.cards.length === 2 && this.total() === 2
this.hasBusted = false | true where this.total() > 21
this.hasStood = false | true where player has elected or AI probability to bust is < 50%

I am currently trying to simplify the check to isEligible as seen below…

I would greatly appreciate a nudge in the right direction!

(I work 2 jobs, so I’m always working on this when I"m already pretty tired. I am sure I’m missing the obvious at this point… )

https://codepen.io/ifaus/pen/qBvrBVb – If you’d like to test it live.

In class BlackJack

nextPlayer() {
  const activePlayer = this.getActivePlayer();
  console.log(activePlayer.name + ': ' + activePlayer.total())
           activePlayer.deactivate();


  // Find the next eligible player who has not busted
  const eligiblePlayers = this.players.filter(player => player.isEligible); // hasn't busted and hasn't stood
  let nextEligiblePlayer = eligiblePlayers[indexNextPlayer];

  // Continue finding the next eligible player
  while (!nextEligiblePlayer && eligiblePlayers.length > 0) {
    indexNextPlayer = (indexNextPlayer - 1 + this.players.length) % this.players.length;
    nextEligiblePlayer = eligiblePlayers[indexNextPlayer];
  }

  if (nextEligiblePlayer) {
    nextEligiblePlayer.activate();
  } else {
    // If there are no eligible players left, determine the winner
    const remainingPlayers = this.players.filter(player => player.isEligible);
    if (remainingPlayers.length === 0) {
      this.determineWinner();
    }
  }
}

In class Player

class Player {
  constructor(playerName, isAI, app) {
    this.isAI = isAI;
    this.AI_delayID = null;
    this.app = app;
    this.name = playerName;
    this.cards = [];
    this.template = new PlayerTemplate();
    this.isActive = false;
    this.isEligible = true;
  }

  activate() {
    this.isActive = true;
    this.turn();
  }

  turn() {
    const shouldHit = (this.total() < 21) && (this.calcBustProbability() < 0.5);

    if (this.isAI) {
      this.AI_delayID = setTimeout(() => {
        if (this.name === 'House' && this.total() === 17) {
          this.stand();
        }

        if (this.total() === 21) {
          this.has21();
        }

        if (this.total() < 21 && shouldHit) {
          this.app.renderDeal();
        } else if (this.total() > 21) {
          this.bust();
        } else {
          console.log(this.name + ' elected to STAND');
          this.stand();
        }
      }, 1000);
    } else {
      if (this.total() > 21) {
        this.bust();
      } else if (this.total() === 21) {
        this.has21();
      }
    }
  }

  calcBustProbability() {
    const numCardsRemain = this.app.blackjack.deck.getCardsRemaining();
    const currTotal = this.total();
    let bustCount = 0;

    for (let card of numCardsRemain) {
      const newTotal = currTotal + card.getValue();
      if (newTotal > 21) {
        bustCount++;
      }
    }
    const probability = bustCount / numCardsRemain.length;
    return probability;
  }

  bust() {
    console.log(this.name + ' BUSTED');
    this.isEligible = false;
    if (this.isAI) {
      clearTimeout(this.AI_delayID);
    }
    this.app.blackjack.nextPlayer();
  }

  stand() {
    console.log(this.name + ' STOOD');
    this.isEligible = false;
    if (this.isAI) {
      clearTimeout(this.AI_delayID);
    }
    this.app.blackjack.nextPlayer();
  }

  has21() {
    console.log(this.name + ' has 21');
    this.hasBlackJack = true;
    if (this.isAI) {
      this.app.blackjack.nextPlayer();
      clearTimeout(this.AI_delayID);
    }
  }

  deactivate() {
    this.isActive = false;
  }

  hit(card) {
    this.cards.push(card);
    this.renderCard();
    if (this.isAI) {
      this.app.blackjack.nextPlayer();
      clearTimeout(this.AI_delayID);
    }
  }

  total() {
    let sum = 0;
    this.cards.forEach(card => {
      sum += card.getValue();
    });
    const adjTotal = this.adjustForAces(sum);

    return adjTotal;
  }

  adjustForAces(sum) {
    let numAces = 0;
    for (const card of this.cards) {
      if (card.rank === 'A') {
        numAces++;
      }
    }

    while (numAces > 0 && sum > 21) {
      if (this.name === 'House' && numAces === 1) {
        break;
      }
      sum -= 10;
      numAces--;
    }
    return sum;
  }
}

Either I create an infinite while loop that freezes the browser, or a stalemate and determineWinner is never invoked. I’m not sure what I’m missing…

Well for starters, that’s not how Blackjack plays… might suggest going back and rereading the procedures and rules of blackjack.

Let’s see…

  $('btnStand').addEventListener('click', function () {
    const player = app.blackjack.getActivePlayer();
    console.log(`${player.name} has chosen to STAND`);
    app.blackjack.nextPlayer();
  });

Your code for btnStand doesnt… actually invoke the stand method on the player…

(in determineWinner)

    // Filter out players who have busted
    const eligiblePlayers = this.players.filter(player => player.isEligible);

But in order to get to this point, all players have been marked as not Eligible…
(in nextPlayer)

  } else {
      // If there are no eligible players left, determine the winner
      const remainingPlayers = this.players.filter(player => player.isEligible);
      if (remainingPlayers.length === 0) {
        this.determineWinner();
      }

So you will never enter determineWinner with any Eligible players. Your determineWinner shouldnt be looking at the flag; it should evaluate all players.

1 Like

I certainly appreciate the assistance. It is very much a work in progress. Much of the current state is for debugging so I can see what is actually going on.

I’ve modified the initialization so that each player is dealt 1 card at a time until all players have 2 cards, and that the Dealer (House) 2nd card remains face down.

I’ll have to revisit the player logic later. I have to leave for work in a few minutes.

Thank you for the insight regarding checking the flags for the players. I think I have an idea how to approach that…

    async initRound() {
        const players = this.blackjack.players.reverse();
    
        while (!this.allPlayersHaveTwoCards()) {
            for (const player of players) {
                if (player.cards.length < 2) {
                    const card = this.blackjack.deal();
                    player.hit(card);
    
                    await this.delay(500);
                    this.renderDeal(player, card);
                }
            }
        }
    }
    
    delay(ms) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }
    
    allPlayersHaveTwoCards() {
        return this.blackjack.players.every(player => player.cards.length === 2);
    }

    renderDeal(player, card) {
        QS({ p: $(player.name),
             s: '.placeholder' }).appendChild(card.component.template);
    
        this.updatePlayerTotal(player);
    
        if (player.name !== 'House') {
            const timeoutId = setTimeout(() => {
                card.component.flipCard();
            }, 500);
        }
    
        const animationEndHandler = function () {
            clearTimeout(timeoutId);
            card.component.template.removeEventListener('animationend', animationEndHandler);
        };
    
        card.component.template.addEventListener('animationend', animationEndHandler);
    }

    updatePlayerTotal(player) {
        QS({ p: $(`h3${player.name}`),
             s: 'span' }).textContent = ' ' + player.total();
    }
    
}

    hit(card) {
        this.cards.push(card);
        if (this.cards.length === 1 && this.name === 'House') {
            card.component.flipCard();
        }
        this.renderCard(card);
        // if (this.isAI) {
        //     this.app.blackjack.nextPlayer();
        //     clearTimeout(this.AI_delayID);
        // }
    }

Well, if it were me, it would be fairly simple (Pseudocode):
All players who busted have a score of 0.
Find (filter) all players that have a higher score than the Dealer. Those players won.
End of logic.

Agreed. When you pointed out I was checking the player status via the flags, I realized that I was overcomplicating the process, even more so that I was doing it after the fact.