Thunderjack! HTML5 Game Update: Stand and Double Down

Hello! I hope your holiday season has been a safe and fun one. I spent Christmas day out with extended family, and got to spend some time with some people I don’t see that often. πŸŽ„ And of course the food was outstanding – gotta get a little of everything! πŸ–πŸ πŸ’ͺ🏿

In this article, we’ll be handling two more moves you can perform in the game: standing, and doubling down.

We’ve already covered the first move, hitting, which you can check out here.

  • Hit
  • Stand
  • Double
  • Split
  • Surrender

Stand

cIn the game of Thunderjack!, if you feel you have a competitive score, or do not wish to risk busting with a hit, you can stand. This will end your turn, and pass the turn to the next player. If there are no more players playing against the dealer, then it will be the dealer’s turn. Standing is a perfectly safe move, and can be performed at any time during your turn.

Here, after a cuppa hits, you have a pretty strong hand with four cards and 19 points. 😎 Standing sounds like a good idea… unless you’re crazy of course. You could go for another hit – you never you – you just might draw an Ace or a two! But no. Keeping your sanity, you decide to stay, and as you can see it’s now the next player’s turn. You will play against the dealer with 19 points.

(Even though the dealer has 12 points with both cards exposed, haha! You may have noticed that throughout the development of this game. It was intentional during the development of the game. Later on, we’ll hide one of the dealer’s cards until later in the round.)

First, let’s create a button that will allow the player to stand when pressed. The steps we use to create the button (using Phaser Editor are similar to what we did in the previous article with the hit button:

  1. Right-click on the main game canvas, then select Add, then Button.
  2. From the Add Sprite dialog, select the image you want to use as the main button sprite, then press OK.
  3. This will add a button to your canvas. You can drag it to position it where ever you want.
  4. In the button’s properties, set the field property to true. This will create a reference to the button in the code that the canvas compiler generates, allowing you to reference it as well.
  5. Set a varName for the button. I decided to use the game_button_stand.
  6. Set the callback to this._onStandButtonPressed, and set the callbackContext to this.
  7. Set the four “Frame” properties to the appropriate sprite images, so that the button appears responsive when the it is moused over, pressed, and released.

When the canvas is saved, it’ll add the code that creates and positions the button, as well as specify the callback function onStandButtonPressed. That function:

onStandButtonPressed
GamePlay.prototype._onStandButtonPressed = function() {
  this._beginTurnPlayerStand();
};
beginTurnPlayerStand
GamePlay.prototype._beginTurnPlayerStand = function() {
  this._setNextTurnPlayer();
};

When the stand button is pressed, it calls another function, beginTurnPlayerStand. Hmm, why not just put that here? Because, we’ll be using this functionality for both, the players, and the dealer. Standing is also used by the dealer, after doubling down, and if you want the player to automatically stand after splitting a pair of aces. So, we want the function to be easily accessible to those moves as well. Also, should we need to add other functionality that is exclusive to standing, we can add it to this function.

As you can see, beginTurnPlayerStand simply calls setNextTurnPlayer, which we’ve already seen when setting up our hit function earlier.

That’s it for implementing the standing move. We’ll move onto the next move: doubling down.

Double Down

This move can only be performed on your opening hand. At the cost of a new wager equal to the bet of the hand, you can take one more card (a hit). Then, you must stand; the game will automatically end your turn. If you beat the dealer, you will win twice your original wager. Beware: like a hit, there is a risk that you will bust, causing you to lose your new wager. There is also that, even after the hit, your score may still not be high enough to beat the dealer, resulting in lost wages.

With a score of 9, the bottom center player decided to take a chance and double their original bet of 10, making it 20.

One more card was dealt, then they must automatically stand.

The dealer busted, and the player won double their winnings, bringing their total winnings to 40.

So, we’ll break the double down function into four sections in this article:

  1. Adding the double down button to the canvas
  2. Showing the double down button when appropriate during a player’s turn
  3. Implementing the actual double down function
  4. Awarding double the winnings if the player beats the dealer
1. Adding the double down button to the canvas

First, we’ll create a button that will allow the player to double down when pressed. The steps we use to create the button very similar to what we did in the the stand button. The varName will be game_button_double, and the callback is onDoubleDownButtonPressed.

onDoubleDownButtonPressed
GamePlay.prototype.onDoubleDownButtonPressed = function() {
  this._beginTurnPlayerDoubleDown();
};
2. Showing the double down button when appropriate during a player’s turn

Next, we’ll implement thebeginTurnPlayerDoubleDown later in this article. For now, you can leave it as an empty function.

Doubling down is only available on the opening hand of a player’s turn, so the button should only be available during their first move. We have a setTurnPlayer function that we defined earlier:

setTurnPlayer
GamePlay.prototype._setTurnPlayer = function(i_turnPlayerIndex) {
  this._hideAllGameButtons();
  
  this._turnPlayerIndex = i_turnPlayerIndex;
  var s_playerId = this._getTurnPlayerId();
  if (!s_playerId) {
    //no turn player to set 
    return(false);
  }
   
  if (this._isDealerId(s_playerId)) {
    this._beginDealerTurn();
  } else {
    this._beginPlayerTurn(s_playerId);
  }
  
  return(true);
};

First, we want to hide all the game buttons, because the state of each player will be different after every move. When we are done setting the player’s turn, the updated setTurnPlayer will have only the appropriate game buttons visible for the player. We’ll add this new function:

hideAllGameButtons
GamePlay.prototype._hideAllGameButtons = function() {
  this.fGame_button_hit.visible = this.fGame_button_stand.visible = this.fGame_button_double.visible = false;
};

Also in setTurnPlayer, there is the _beginPlayerTurn function. We’ll use this to set up which move buttons. We haven’t really needed it until now, because the hit and stand buttons are always visible during a player’s turn.

beginPlayerTurn
GamePlay.prototype._beginPlayerTurn = function(s_playerId) {
  var playerHandData = this._playerData.getHandData(s_playerHandId);
  this._showGameButtonsForPlayer(playerHandData);
};

In this function, we first get the PlayerHandData of the current turn player. On the PlayerData class, we’ll add a new function, getHandData.

getHandData
PlayerData.prototype.getHandData = function (s_playerHandId) {
  return(this._hands[s_playerHandId]);
};

Next, there is a showGameButtonsForPlayer function. This will display the buttons appropriate for the current player, and hide the rest.

showGameButtonsForPlayer
GamePlay.prototype._showGameButtonsForPlayer = function(playerHandData) {
  this.fGame_button_hit.visible = true;
	this.fGame_button_stand.visible = true;
  this.fGame_button_double.visible = this._isDoubleEligible(playerHandData);
};

The hit and stand buttons are always visible for every move. However, setting visibility of the double down button is not a simple true or false. In order to keep showGameButtonsForPlayer simple, an isDoubleEligible function is created, which will determine whether or not the double down button should be shown.

isDoubleEligible
GamePlay.prototype._isDoubleEligible = function(playerHandData) {
return(
  this._isOpeningHand(playerHandData) &&
  this._canAffordDouble(playerHandData));
};

The double down button will be visible only on the player’s opening hand (when the player has two cards), and the player must have enough credits to afford placing the double wager.

isOpeningHand
GamePlay.prototype._isOpeningHand = function(playerHandData) {
  var NUM_CARDS_IN_OPENING_HAND = 2;
  return(playerHandData.getNumCards() === NUM_CARDS_IN_OPENING_HAND);
};
canAffordDouble
GamePlay.prototype._canAffordDouble = function(playerHandData) {
  return(this._playerData.credits >= playerHandData.betValue);
};

With these changes, adding and showing the double down button is complete. It will only appear on the opening hand of each player’s turn, and if the player has enough credits to afford doubling down. Next, we’ll focus on the double down functionality.

3. Implementing the actual double down function

This step is much easier than the the last, because we’ve already seen most of the code. The implementation consists of:

  • deducting the cost of the double down wager from the player’s credits
  • updating the player hand’s status to indicate double down
  • drawing one card (taking a hit)
  • automatic standing (if the player did not bust)

We’ll revisit our empty beginTurnPlayerDoubleDown method and place the code in it.

beginTurnPlayerDoubleDown
GamePlay.prototype._beginTurnPlayerDoubleDown = function() {
  this._setTurnPlayerDoubleDownStatus();
  this._payDoubleDownCostForTurnPlayer();
  this._dealDoubleDownCardToTurnPlayer();
};
setTurnPlayerDoubleDownStatus
GamePlay.prototype._setTurnPlayerDoubleDownStatus = function() {
  this._getTurnPlayer().hasDoubled = true;
};
payDoubleDownCostForTurnPlayer
GamePlay.prototype._payDoubleDownCostForTurnPlayer = function() {
  var playerHandData = this._getTurnPlayer();
  this._playerData.credits -= playerHandData.betValue;
};
dealDoubleDownCardToTurnPlayer
GamePlay.prototype._dealDoubleDownCardToTurnPlayer = function() {
  var turnPlayer = this._getTurnPlayer();
  
  this._dealCardToPlayer(turnPlayer);
  this.updateScores();
  if (this.didPlayerBust(turnPlayer)) {
    this._beginPlayerBust(turnPlayer);
    this._setNextTurnPlayer();
  } else {
    this._beginTurnPlayerStand();
  }
};

The dealDoubleDownCardToTurnPlayer is very similar to the beginTurnPlayerHit, except that if the player did not bust, they will automatically stand. Either way, the player’s turn ends after doubling down.

4. Awarding double the winnings if the player beats the dealer.

If the player beats the dealer, they will be awarded double their winnings. This final piece of code handles that. This is where setting the hasDoubled property of the player’s hand to true.

You may have seen an endRound function used in the pseudo code from this article, and we’ll begin coverage for that here.

endRound
GamePlay.prototype._endRound = function() {
  if (this._dealerData.isBust) {
    this._presentPlayersWinViaDealerBust();
  } else {
    this._compareCards();
  }
  
  this._showBetUi();
};

If the dealer busts, all players that are playing against the dealer automatically win. And players who doubled down will win double their winnings. Otherwise, those same players will have their scores compared against the dealer’s.

presentPlayersWinViaDealerBust
GamePlay.prototype._presentPlayersWinViaDealerBust = function() {
  var a_playerHands = this._playerData.getAllHands();
  
  for (var i_index = 0; i_index < a_playerHands.length; i_index++) {
    var playerHandData = a_playerHands[i_index];
    if (this._isPlayingVsDealer(playerHandData)) {
      this._presentPlayerWon(playerHandData);
    }
  }
};

The isPlayingVsDealer determines if a player is playing against the dealer. Our last implementation of the function looked like this:

function isPlayingVsDealer(handData) {
  return(
    !(playerHandData.hasBlackjack ||
    playerHandData.isPush ||
    playerHandData.hasThunderjack));
}

We need to update this to include players that have not busted are also eligible to play against the dealer:

isPlayingVsDealer
function isPlayingVsDealer(handData) {
  return(
    !(playerHandData.hasBlackjack ||
    playerHandData.isPush ||
    playerHandData.isBust ||
    playerHandData.hasThunderjack));
}
presentPlayerWon
GamePlay.prototype._presentPlayerWon = function(playerHandData) {
  var n_betValue = playerHandData.betValue;
  var WIN_RATIO_WIN = 1 / 1;
  var n_amountWon = this._calcWinAmount(n_betValue, WIN_RATIO_WIN); 
  
  if (playerHandData.hasDoubled) {
    n_amountWon *= 2;
  }
  
  this._playerData.credits += n_amountWon;
};

The presentPlayerWon function is where the winnings are added to the player’s credits. Inside the function, if the player has doubled down, the amount won is doubled.

But, what’s this calcWinAmount function, you ask? (:

That’s what determines how much the player won, based on the hand’s bet value.

calcWinAmount
GamePlay.prototype._calcWinAmount = function(n_betValue, n_winRatio) {
  return(n_betValue + n_betValue * n_winRatio);
};

The WIN_RATIO_WIN value is treated as a constant and is defined as 1 / 1. This reads as “one-to-one”, or “1:1”, meaning equal to the wager. For example, if the player placed a bet of 10 credits and won, they’d win (10 + 10 * 1) = 20 credits. There are also other ratios for other types of wins, such as blackjack (3:2), or dealer busts (1:1).

If the player is playing against the dealer, we want to compare the player’s score with the dealer’s. This is what the compareCards function does.

compareCards
GamePlay.prototype._compareCards = function() {
  var i_dealerScore = this._dealerData.score;
  
  var a_playerHands = this._playerData.getAllHands();
  for (var i_index = 0; i_index < a_playerHands.length; i_index++) {
    var playerHandData = a_playerHands[i_index];
    if (!this._isPlayingVsDealer(playerHandData)) {
      continue;
    }
    
    if (i_dealerScore < playerHandData.score) {
      this._presentPlayerWon(playerHandData);
    } else if (i_dealerScore === playerHandData.score) {
      this._presentPlayerPush(playerHandData);
    }
  }
};
presentPlayerPush
GamePlay.prototype._presentPlayerPush = function(playerHandData) {
  playerHandData.isPush = true;
  this._playerData.credits += playerHandData.betValue;
};

Finally in the endRound function, the showBetUi function will show all the controls that help us place bets, these are the:

  • hand buttons
  • bet chip buttons
  • bet button
  • clear button

That does it for this standing and doubling down. The next article will cover splitting, which allows us to create two hands from a hand of two cards with the same face value.

Whew! We covered a lot in this article, including payouts, comparisons against the dealer, and helping set the stage for future development of the remaining two moves, splitting and surrendering. If you read all of this, and you’re still with me, kudos! ⭐

And here is a video that shows standing and doubling down. On the third round, I did win a double-down bet, turning my original wager of 10 into winnings of 40.

Thanks, and take care,

– C. out.