Programming Blackjack
One direction where we can take our programming skills is game development. Here, we'll build a text based Blackjack engine that allows us to play against a dealer, who follows conventional house rules. The logic of blackjack is simple, but is sufficiently complex that we can gain valuable experience for making more complicated games later on. As we said, this engine has several simplifications as compared to a commercial Blackjack platform:
- It will be entirely text based.
- It will have just a single player, and the dealer.
- It will have no gambling system.
Each of these are things we can add in a future post, by using a GUI system such as Tk, by folding the game logic into an object oriented game design, and building out an accounting system to keep track of chip counts, tabs, and bets.
For those who'd like to follow along, here is the complete code.
To remind ourselves, the rules of blackjack are as follows:
The player and the dealer receive two cards from a shuffled deck. In our case, we'll use a single deck, though casinos usually use a 'shoe' consisting of six decks.
After the first two cards are dealt to dealer and player, the player is asked if they'd like another card (called 'hitting'), or if they are happy with the cards they have already (called 'staying'). The object is to make the sum of your card values as close to 21, without going over. If we make 21 exactly, we have blackjack, which can't be beat. If we go over 21, we 'bust' and we lose the round. The player is allowed to stop hitting at any point.
The number cards (2 through 10) are worth the number displayed, face cards are worth 10, and an Ace can be worth either 1 or 11. For example, if our first two cards are a Jack and an Ace, we'd want to count the Ace as 11 since 10 + 11 = 21 and we'd have blackjack, but, if we had already had a hand worth 18, decided to hit, and got an Ace, we'd want to count it as 1, since counting it as 11 would put us at 29 and we'd bust.
Once our hand is finished, the dealer tries to do the same. The dealer must keep hitting until they get to 17. If they get above 17 without busting, they can stay.
Contents
Scoring the game of Blackjack
Finally, the game is settled by simple rules
If the player has blackjack, they win, unless the dealer also has blackjack, in which case the game is a tie.
If the dealer busts and the player doesn't, the player wins.
If the player busts, the dealer wins.
If the player and the dealer both don't bust, whoever is closest to 21 wins.
Setting the stage
The first thing we need to play Blackjack is a shuffled deck of cards. First we will write code that accomplishes this.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
We laid out the ranks and suits of the cards with lists, and then wrote a simple function get_deck()
that constructs the deck using a list comprehension.
Finally, we used Python's random
library, which has various functions used in generating randomness. In particular, we employ shuffle
, which takes any list and returns it in random order, to shuffle our deck of cards.
With this functionality in place, we can deal the player and dealer's first two cards. We use the destructive pop
operation which returns the last element from a list and removes it from the list as a side effect. We also define a boolean variable to keep track of whether the player is still actively betting (that they haven't busted, and haven't asked to 'stay' with their current hand)
1 2 3 4 5 6 |
|
To start the game of Blackjack, players are dealt two cards at random from a shuffled deck.
You write the following code to simulate the act of dealing an initial hand. To test the code, you deal a hand \(10^6\) times and record the number of times the player makes Blackjack on their first two cards. If the code is written correctly, what do you expect to find for \(\hat{f}_\text{blackjack}\), the fraction of initial hands that are Blackjack?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
Assumptions and Details
- A two card hand is said to be "blackjack" if it consists of an Ace and any card worth 10 (i.e. a ten, or a face card).
Scoring function
Next, we write a function that accepts a list containing the cards of the hand as tuples in the form (rank, suit)
. The logic is quite simple. First we write a helper function that takes a single card, and returns its value according to the scheme we outlined above. By default, we count each ACE
as 11. We map this function over the hand and store the sum of values as tmp_value
.
The second major piece of logic is to count up the total number of aces in the hand. We then check to see that the hand value is less than or equal to 21. If it isn't, and there was an ace in the hand, we subtract 11. If the hand is then less than 21, we return the hand. Otherwise, if there is a second ace in the hand, we again subtract 11 and check again. We continue to do this until we've exhausted all the hand's aces.
The final step is to return the value of the hand. If we have a score below 21, we return a two entry list containing a string representation of the score, and the integer value of the score. As a convention, we count busts as 100.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
|
Game logic
The next thing we code is the logic of gameplay. As we outlined above, we have to ask the player whether they'd like to hit or stay, and continue to ask them until they bust, or they decide to stay. One piece of information that's crucial to the player's decision is their current tally, therefore, we print the player's hand and current tally each time before asking for their response.
As long as the player's hand isn't a bust, we ask the question using Python's raw_input
function, which can take keyboard input. For convenience, we take 1
to symbolize "hit me", and 0
to symbolize "stay." If they ask to hit, we again pop
the deck, append the new card to our current hand, and immediately print the newly drawn card to the screen. If they ask to stay, we change the value of player_in
to False
and move on to the dealer.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
At this point, we calculate the player's score, and the dealer's current score (on his first two cards). If the player's hand isn't a bust, we print the dealer's tally and current hand. Then, while the dealer's hand is worth less than 17, the dealer is made to hit. After each dealer hit, we print their new card. By design, this loop halts when the dealer exceeds 17.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
Settling the winner
At this point, the game is nearly finished. All that remains is to check the player's score and the dealer's score against the list of scoring rules we outlined above. To start, we obtain the label, and number for the score of the dealer's hand. Next, we simply enumerate the possible end states, and ask which of them our game satisfies. Based on the outcome, we print to the screen declaring the victor.
1 2 3 4 5 6 7 8 9 10 |
|
Sample game play
Consider the two sample games below.
In the first, we're dealt an A\(\heartsuit\) and a 4\(\heartsuit\), which gives us 15. We then proceed to draw three cards (Q\(\spadesuit\), 2\(\heartsuit\), 3\(\heartsuit\)) that keep us under 20. Drawing another ace is unlikely, so we stay.
In the process, our A\(\heartsuit\) is downvalued in value from 11 to 1 to keep from going bust.
The dealer is forced to hit on his starting 13, draws a 9\(\spadesuit\), and goes bust.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31python blackjack_no_class.py You are at currently at 15 with the hand [['ACE', 'HEART '], [4, 'HEART ']] Hit or stay? (Hit = 1, Stay = 0)1 You draw ['QUEEN', 'SPADE'] You are at currently at 15 with the hand [['ACE', 'HEART '], [4, 'HEART '], ['QUEEN', 'SPADE']] Hit or stay? (Hit = 1, Stay = 0)1 You draw [2, 'HEART '] You are at currently at 17 with the hand [['ACE', 'HEART '], [4, 'HEART '], ['QUEEN', 'SPADE'], [2, 'HEART ']] Hit or stay? (Hit = 1, Stay = 0)1 You draw [3, 'HEART '] You are at currently at 20 with the hand [['ACE', 'HEART '], [4, 'HEART '], ['QUEEN', 'SPADE'], [2, 'HEART '], [3, 'HEART ']] Hit or stay? (Hit = 1, Stay = 0)0 Dealer is at 13 with the hand [[10, 'CLUB'], [3, 'SPADE']] Dealer draws [9, 'SPADE'] You beat the dealer!
In a second case, we're the ones to go bust and the program tells us we've done so. In this case, the dealer wins.
1 2 3 4 5 6 7 8 9 10You are at currently at 12 with the hand [[8, 'SPADE'], [4, 'DIAMOND']] Hit or stay? (Hit = 1, Stay = 0)1 You draw ['QUEEN', 'DIAMOND'] You are at currently at Bust! with the hand [[8, 'SPADE'], [4, 'DIAMOND'], ['QUEEN', 'DIAMOND']] Dealer wins!