Blackjack Game Logic Basics — Built With React Hooks
By: Tyler J Funk
Tags: Blackjack, Game Development, React, React Hook, Software Engineering

Before I decided to make the career change to tech and become a software engineer, I worked at a casino for a few years as a table games dealer. I learned all the major games, including craps, roulette, blackjack, baccarat, and also learned the “carnival” style games offered too.

Naturally, I felt it would be a good exercise to work through how to develop a blackjack game. This post is going to break down some of the game logic required, and won’t go into any detail about styling. I am most familiar with React, and have recently started using Hooks for my state management and lifecycle method type events, so that’s what I have used to build my app.

Before we get started, for those who don’t know much about Blackjack, here is an explanation of the game progression:

— Face cards are valued at 10, number cards are valued by their number, and an ace can be valued as 1 or 11.

The player and the dealer both receive two cards, the dealer keeps one card face down until his/her turn. Dealer checks for blackjacks, which is an ace paired with any card valued at 10 (makes 21). If the dealer has blackjack, the player instantly loses their wager unless they too have blackjack. If the player has blackjack and dealer does not, the wager gets paid 3 to 2, that is to say, 1.5 times the original bet. (There is also something called blackjack insurance in the casinos if the dealer is showing an ace, but I did not include it in my app) If there are no blackjacks, it is now the players turn, who has the choice to either hit, stay, or double. Hit, meaning add a card to their hand, stay, meaning keep their current score, and double, meaning take only one more card, after doubling the original bet. Actual blackjack games allow players to also split hands that have are pairs, such as two 8’s, but I have not yet added this capability. The player’s goal is to achieve a score of 21, although in some cases it is best to stay at a low number and not risk “busting” (going over 21) because the dealer also has a chance to bust which causes the player to win too. If the player does not bust, and stays, the dealer reveals their face down card, and draws cards as necessary until they achieve a score of 17 or higher (most casinos make their dealers hit when they have what’s called a “soft 17”, meaning they have an ace in their hand that can still be valued as a 1 or an 11, my app stays on all 17s). If the dealer count goes over 21, he/she instantly pays out every player who still has a hand, but if the dealer does not bust, he/she compares the player count to the dealer count and pays or takes the bet accordingly. The player picks up winnings/places their next bet after a loss, and the cycle starts again.

That’s pretty much it — as short as possible, get as close to 21 as possible without going over, and hope the dealer either scores lower than you or busts.

Jumping right into it, I thought a good way to start would be to create an array with every playing card as a string, and set it to a variable.

const deckOfCards = [

"A of Hearts", "2 of Hearts", "3 of Hearts",

"4 of Hearts", "5 of Hearts", "6 of Hearts",

"7 of Hearts", "8 of Hearts", "9 of Hearts",

"10 of Hearts", "J of Hearts", "Q of Hearts",

"K of Hearts",

"A of Clubs", "2 of Clubs", "3 of Clubs",

"4 of Clubs", "5 of Clubs", "6 of Clubs",

"7 of Clubs", "8 of Clubs", "9 of Clubs",

"10 of Clubs", "J of Clubs", "Q of Clubs",

"K of Clubs",

"A of Diamonds", "2 of Diamonds", "3 of Diamonds",

"4 of Diamonds", "5 of Diamonds", "6 of Diamonds",

"7 of Diamonds", "8 of Diamonds", "9 of Diamonds",

"10 of Diamonds", "J of Diamonds", "Q of Diamonds",

"K of Diamonds",

"A of Spades", "2 of Spades", "3 of Spades",

"4 of Spades","5 of Spades", "6 of Spades",

"7 of Spades", "8 of Spades", "9 of Spades",

"10 of Spades", "J of Spades", "Q of Spades",

"K of Spades"

]

I used regex to find the letter or number at the beginning of a string, and determine that cards value, and used a switch statement to determine what image should be used for which cards.

Following this, I made it into two decks, and shuffled them using a ‘lodash’ shuffle function I imported, found here.

const twoDecks = [...deckOfCards, ...deckOfCards] const shuffledDoubleDeck = shuffle(twoDecks)

Now that I have a shuffled array of two decks of cards, the next step is to set the shuffled decks to a variable in state when a hand begins. Below is a look at all the state I used in my app, keep in mind, my blackjack app allows a player to bet, then hit, double, or stay, but for simplicity sake, does not currently include the ability to split. A lot of the logic lies with changing these state variables when certain events happen and conditions are met, but there will be more about that and how to use the useEffect Hook later on. To learn more about React Hooks, you can visit the documentation here.

const [randomizedDecks, setRandomizedDecks] = useState([])

const [chipCount, setChipCount] = useState(1000)

const [betAmount, setBetAmount] = useState(0)

const [lockedBet, setLockedBet] = useState(0)

const [previousBet, setPreviousBet] = useState(0)

const [dealersCards, setDealersCards] = useState([])

const [dealerCount, setDealerCount] = useState(0)

const [playersCards, setPlayersCards] = useState([])

const [playerCount, setPlayerCount] = useState(0)

const [isBlackjack, setIsBlackJack] = useState(false)

const [isPlayerBusted, setIsPlayerBusted] = useState(false)

const [didDouble, setDidDouble] = useState(false)

const [isDealersTurn, setIsDealersTurn] = useState(false)

const [isDealerBusted, setIsDealerBusted] = useState(false)

const [isHandComplete, setIsHandComplete] = useState(true)

const [winner, setWinner] = useState("")

For those who just want a quick refresher about the useState Hook, the left side of the destructured variable has 2 parts; The first part is the variable name we’ll call to retrieve our current state, and the second part is a function which allows us to set that state to a new value (convention is to use the variable name and add a prefix of “set-”). On the right, we call the useState Hook, and pass it an argument of our choosing, which will become the default value.

Now I’ll briefly go over each line of state and give an overview of how it is used in the app (I attempted to order them as sequentially as possible):

const [randomizedDecks, setRandomizedDecks] = useState([])

This is where the shuffled decks of “cards” will get stored at the beginning of every new hand. const [chipCount, setChipCount] = useState(1000)

Player’s running chip total, default set at 1000 which I felt was an appropriate starting amount. const [betAmount, setBetAmount] = useState(0)

The pending bet amount to be placed on the table, increases as the player clicks on poker chips with corresponding values on them. const [lockedBet, setLockedBet] = useState(0)

When the player hits the “Place Bet” button, it places the lockedBet amount on the table, and the hand is now ready to be started. const [previousBet, setPreviousBet] = useState(0)

Once the hand is started, the lockedBet becomes the previousBet which allows me to create a “Same Bet” button, allowing the player to effortlessly start the next hand with the exact same size of bet. const [dealersCards, setDealersCards] = useState([])

An array containing all of the dealers cards, starting initially with two, and as mentioned earlier, one of them starts face down. const [dealerCount, setDealerCount] = useState(0)

The score associated with the dealersCards , used in a lot of places in the logic to decide who the winner is, if any. const [playersCards, setPlayersCards] = useState([])

An array containing all of the players cards, starting initially with two. const [playerCount, setPlayerCount] = useState(0)

The score associated with the playersCards , used in a lot of places in the logic to decide who the winner is, if any. const [isBlackjack, setIsBlackJack] = useState(false)

Stores a boolean that keeps track of whether or not a blackjack has occurred. Used in conjunction with winner state to determine which hand is a blackjack. const [isPlayerBusted, setIsPlayerBusted] = useState(false)

If there is no blackjack, we’ll store this boolean that keeps track of whether the player has busted or not upon hitting. const [didDouble, setDidDouble] = useState(false)

If the player still only has two cards, and has enough points in their chipCount to cover the value in their current bet, upon clicking the “Double” button, set didDouble to true which calls for a different pay/take amount (twice as much, hence “Double”) const [isDealersTurn, setIsDealersTurn] = useState(false)

This boolean informs our app whose turn it is. Upon the player staying or doubling, we call this function to set isDealersTurn to true , indicating it is no longer the players turn, the dealer then draws cards as necessary. const [isDealerBusted, setIsDealerBusted] = useState(false)

If the dealer scores over 21, then we set isDealerBusted to true to indicate that the dealer has lost, ultimately triggering the payout sequence. const [isHandComplete, setIsHandComplete] = useState(true)

A general catch-all for if a winning event happens, for example, blackjack, or busting, we’ll flip this boolean to true so we can use it as a means to know which elements we should display, and when.

I won’t go through all of the logic, because I am linking the Github repository here for anyone who wants to check it out (a little messy, but it works!), but I want to show at least one example of a useEffect Hook I used because they were essential in making certain events happen based on specified state changing — I’ll explain in more detail below.

First, let’s take a look at a working version I have right now to get a visual of how some of the different states affects the app. I made this diagram to help give a better idea of how each piece of state is being used, but doesn’t highlight all states used to make it. This is a screen you might see after a hand has been completed: