Thunderjack! HTML5 Game Update: Splitting and Surrendering

Happy New Year, guys! ⭐ I wish you a very fun, and happy 2019, complete with experiences that’ll challenge you and allow you to grow and become a stronger version of yourself.

In this article, we’ll be handling the final two moves you can perform in the game: splitting and surrendering.

We’ve already covered the first three moves, hitting, standing and doubling down.

  • Hit
  • Stand
  • Double
  • Split
  • Surrender

Splitting

If you have a pair in your opening hand, you have the option to split the cards into two hands. Each new will automatically be dealt another card. This gives you an additional hand to try and beat the dealer.

Like doubling down, you must place an addition bet equal to the original wager of the hand in order to split.

There are a few things to be aware of with splitting:

  • Split hands cannot win via a Thunderjack or Blackjack
  • Split hands may double down
  • Split hands may not surrender (will be covered in surrender functionality)
  • If splitting a pair of aces, both hands must stand after being dealt another card (can’t have more than two cards)

We’ll need a button that will allow the player to splir when pressed. Using the same steps as in previous articles, create the button (using Phaser Editor):

  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_split.
  6. Set the callback to this._onSplitButtonPressed, 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. We also want to ensure there is the callback function, onSplitButtonPressed. We’ll write it like:

onSplitButtonPressed
GamePlay.prototype._onSplitButtonPressed = function() {
  this._beginTurnPlayerSplit();
};
beginTurnPlayerSplit
GamePlay.prototype._beginTurnPlayerSplit = function() {
  var splitPlayerId = this._insertUpperPlayerIdIntoTurnPlayerIds();
  this._setSplitStatusForPlayers(splitPlayerId);
  this._paySplitCostForSplitPlayer(splitPlayerId);
  
  var turnPlayerId = this._getTurnPlayerId();
  this._moveCardFromPlayerToPlayer(turnPlayerId, splitPlayerId);
  
  this._dealCardTo(turnPlayerId);
  this._dealCardTo(splitPlayerId);
};

The beginTurnPlayerSplit function is where the actual splitting functionality takes place.

First, we’re adding a new player into the game: the player that is created from the split. This is what insertUpperPlayerIdIntoTurnPlayerIds does, as well as return the player ID of the new player.

Player IDs

Each player is assigned a unique “player ID”. We use these to reference the PlayerHandData for the player we want. From there, we can access the cards in the player’s hand, as well as other information about that player.

So far, we’ve only used the “lower” IDs, not the “upper” ones. Here are all six player hand IDs:

Thunderjack.PlayerHandIds.LOWER_RIGHT = "lowerRight";
Thunderjack.PlayerHandIds.LOWER_MIDDLE = "lowerMiddle";
Thunderjack.PlayerHandIds.LOWER_LEFT = "lowerLeft";

Thunderjack.PlayerHandIds.UPPER_RIGHT = "upperRight";
Thunderjack.PlayerHandIds.UPPER_MIDDLE = "upperMiddle";
Thunderjack.PlayerHandIds.UPPER_LEFT = "upperLeft";

A split is always initiated from one of the three “lower” hands. The new had that is created is its “upper” counterpart.

We split the original hand into two by moving its top card to the new hand. In the beginTurnPlayerSplit function, the turnPlayerId represents the original player’s (the player’s whose turn it is), and splitPlayerId presents the new hand.

We want to insert the new player ID into the array of turn player IDs, (this._turnPlayerIds) after the current turn player’s ID. The insertUpperPlayerIdIntoTurnPlayerIds function does this, and the pseudo code for it looks like:

insertUpperPlayerIdIntoTurnPlayerIds

insertUpperPlayerIdIntoTurnPlayerIds() {
  get the splitPlayerId, which is the corresponding "upper" ID of the turn player
  get the array index of the turn player ID within the turnPlayerIDs array
  insert the splitPlayerId into the turnPlayerIDs array after the turn player ID
};

We can use the JavaScript function, Array.prototype.splice to insert the split player ID directly after the current turn player ID, so that when the turn player is finished, the next turn will be the split player. After the split player’s turn, game play normally resumes.

When getting the splitPlayerId, which is the corresponding “upper” ID of the turn player, the mapping is:

Thunderjack.PlayerHandIds.LOWER_RIGHT ==> Thunderjack.PlayerHandIds.UPPER_RIGHT
Thunderjack.PlayerHandIds.LOWER_MIDDLE ==> Thunderjack.PlayerHandIds.UPPER_MIDDLE
Thunderjack.PlayerHandIds.LOWER_LEFT ==> Thunderjack.PlayerHandIds.UPPER_LEFT

An example of how the turnPlayerIds is modified is below.

Say, you have two players (left and right) and the dealer in the game. The turnPlayerIds would look like:

[
  Thunderjack.PlayerHandIds.LOWER_LEFT,
  Thunderjack.PlayerHandIds.LOWER_RIGHT,
  Thunderjack.PlayerHandIds.DEALER
]

It’s currently the “lower left” player’s turn. They split their hand, creating the “upper left” hand. The turnPlayerIds would then look like:

[
  Thunderjack.PlayerHandIds.LOWER_LEFT,
  Thunderjack.PlayerHandIds.UPPER_LEFT,    //<-- new player ID inserted via splice
  Thunderjack.PlayerHandIds.LOWER_RIGHT,
  Thunderjack.PlayerHandIds.DEALER
]

You can see that Thunderjack.PlayerHandIds.UPPER_LEFT was inserted directly after the current turn player’s ID, Thunderjack.PlayerHandIds.LOWER_LEFT. And when the “lower left” player’s turn is done, the next turn is the “upper left”. After that, play resumes with “lower right” (same is it would, had the “lower left” player never split).

Now that we have created the new split player, we’re going to set the split status of both players. We’ll set up the rule that player hands that have been involved in a split during the round cannot split again. The setSplitStatusForPlayers will accomplish this.

In order to set the split status, the function could look like:

setSplitStatusForPlayers
GamePlay.prototype._setSplitStatusForPlayers = function(splitPlayerId) {
  var turnPlayerHandData = this._getTurnPlayerHandData();
  turnPlayerHandData.hasSplit = true;
  
  var splitPlayerHandData = getPlayerHand(splitPlayerId);
  splitPlayerHandData.hasSplit = true;
  splitPlayerHandData.betValue = turnPlayerHandData.betValue;
};

There is a new property on the PlayerHandData object that we’ve not seen yet, hasSplit. We’ll add that to the constructor function so it now looks like:

PlayerHandData
PlayerHandData = function(s_handId) {
  HandData.call(this, s_handId);
  this.betValue = 0;
  this.hasThunderjack = false;
  this.isPush = false;
  this.hasBlitz = false;
  this.hasDoubled = false;
  this.hasSurrendered = false;
  this.hasSplit = false;
};

One of the rules of splitting is that we don’t want to split a hand that was already involved in a split. That means, for the current turn player, and the new hand that was created, you want to set their hasSplit property to true.

After splitting, when resuming the current player’s turn, or on the split player’s turn, that player can’t split, even if they have a pair. And of course, splitting isn’t free; it costs an additional wager equal to that placed on the hand that is splitting.

Note: Optionally, if you want to make it so that both players must automatically stand if aces are split, you can check if the turn player’s hand is a pair of aces. Then, when the split functionality is down, you can stand twice (once for the turn player, and again for the split hand).

Now that we have all the functionality for splitting done, we can update our showGameButtonsForPlayer function to include eligibility for splitting for the current turn player. If the player is allowed to split, we’ll show the split button. Otherwise, we’ll hide it.

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);
  this.fGame_button_split.visible = this._isSplitEligible(playerHandData);
};

The isOpeningHand function has already been covered here.

canAffordSplit
GamePlay.prototype._canAffordSplit = function(playerHandData) {
  return(this._playerData.credits >= playerHandData.betValue);
};

We also want to make sure that the opening two cards are a pair, meaning, they have the same value. For face cards, they both must be the same face. For example, you can split two jacks or two queens, but not a jack and a queen.

areCardsPair
GamePlay.prototype._areCardsPair = function(playerHandData) {
  if (!playerHandData || playerHandData.getNumCards() != 2) {
    return(false);
  }
  
  var cardData0 = playerHandData.getCardAt(0);
  var cardData1 = playerHandData.getCardAt(1);
  return(cardData0 && cardData1 && cardData0.value == cardData1.value);
};

That’s it for splitting! That was quite a bit to go through. If you have any questions on anything, please ask in the comments section below. Now, let’s move onto the final player move: surrendering.

Surrender

You can do this if your opening hand does not look good against the dealer’s revealed card, and you’re not feeling confident. If a player surrenders, they:

  • forfeit half their bet (they’re immediately paid)
  • automatically end their turn
  • will not play against the dealer

Also, a split hand may not surrender.

First, we create the surrender button. Follow the same steps as creating the split button, except:

  1. Set the varName for the button to game_button_surrender.
  2. Set the callback to this._onSurrenderButtonPressed, and set the callbackContext to this.
  3. 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.

Write the code for the onSurrenderButtonPressed, so it looks like:

onSurrenderButtonPressed
GamePlay.prototype._onSurrenderButtonPressed = function() {
  this._beginTurnPlayerSurrender();
};
beginTurnPlayerSurrender
GamePlay.prototype._beginTurnPlayerSurrender = function() {
  var playerHandData = this._getTurnPlayerHandData();
  this._presentPlayerSurrender(playerHandData);
  this._setNextTurnPlayer();
};

Here, we present the player as surrendering. This will include forfeiting half the bet, updating the player hand status, and moving onto the next turn player.

presentPlayerSurrender
GamePlay.prototype._presentPlayerSurrender = function(playerHandData) {
  var n_betValue = playerHandData.betValue;
  var WIN_RATIO_SURRENDER = -1 / 2;
  var n_amountWon = this._calcWinAmount(n_betValue, WIN_RATIO_SURRENDER); 
  
  playerHandData.hasSurrendered = true;
  
  this._playerData.credits += n_amountWon;
};

The presentPlayerSurrender function is very similar to the presentPlayerWon function discussed here. There is also another use of the calcWinAmount function. Except this time, we’re passing a different win ratio, WIN_RATIO_SURRENDER, defined as -1 / 2. When this ratio is passed into calcWinAmount, it will return only half of the original bet value.

We also set the hasSurrendered property of the PlayerHandData object to true.

Remember that a player has surrendered won’t play against the dealer. We previously defined a function isPlayingVsDealer, that determines if a player plays against a dealer. We’ll update that function to exclude players who’ve surrendered from playing against the dealer:

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

That’s it for the surrender functionality! It’s one of the shorter moves to implement, and most of the code that builds it has already been discussed in earlier articles.

We’re now finished with all five moves hit, stand, double down, split, and surrender… woot! 👍🏿

Now that most of the core gameplay is complete, we can finish off the game. Here’s what’s to come in the near future:

  • Recycling the deck (so the deck never runs out of cards)
  • Adding sound effects (yeah!)
  • Adding the Thunderjack effect (HELL YEAH!! 😠)

That’s it for this post, guys. Thanks, and talk to you later.

– C. out.