Blackjack
Blackjack, also known as 21, is a card game where players try to get as close to 21 points as possible without going over. This program uses images drawn with text characters, called ASCII art. American Standard Code for Information Interchange (ASCII) is a mapping of text characters to numeric codes that computers used before Unicode replaced it. The playing cards in this program are an example of ASCII art:
___ ___
|A | |10 |
| ♣ | | ♦ |
|__A| |_10|
How It Works
The card suit symbols don’t exist on your keyboard, which is why we call the chr() function to create them. The integer passed to chr() is called a Unicode code point, a unique number that identifies a character according to the Unicode standard. Unicode is often misunderstood. However, Ned Batchelder’s 2012 PyCon US talk “Pragmatic Unicode, or How Do I Stop the Pain?” is an excellent introduction to Unicode, and you can find it at https://youtu.be/sgHbC6udIqc/. Appendix B gives a full list of Unicode characters you can use in your Python programs.
You can find other rules and the history of this card game at https://en.wikipedia.org/wiki/Blackjack.
blackjack.py
1"""Blackjack, by Al Sweigart al@inventwithpython.com
2The classic card game also known as 21. (This version doesn't have
3splitting or insurance.)
4More info at: https://en.wikipedia.org/wiki/Blackjack
5This code is available at https://nostarch.com/big-book-small-python-programming
6Tags: large, game, card game"""
7
8import random, sys
9
10# Set up the constants:
11HEARTS = chr(9829) # Character 9829 is '♥'.
12DIAMONDS = chr(9830) # Character 9830 is '♦'.
13SPADES = chr(9824) # Character 9824 is '♠'.
14CLUBS = chr(9827) # Character 9827 is '♣'.
15# (A list of chr codes is at https://inventwithpython.com/charactermap)
16BACKSIDE = 'backside'
17
18
19def main():
20 print('''Blackjack, by Al Sweigart al@inventwithpython.com
21
22 Rules:
23 Try to get as close to 21 without going over.
24 Kings, Queens, and Jacks are worth 10 points.
25 Aces are worth 1 or 11 points.
26 Cards 2 through 10 are worth their face value.
27 (H)it to take another card.
28 (S)tand to stop taking cards.
29 On your first play, you can (D)ouble down to increase your bet
30 but must hit exactly one more time before standing.
31 In case of a tie, the bet is returned to the player.
32 The dealer stops hitting at 17.''')
33
34 money = 5000
35 while True: # Main game loop.
36 # Check if the player has run out of money:
37 if money <= 0:
38 print("You're broke!")
39 print("Good thing you weren't playing with real money.")
40 print('Thanks for playing!')
41 sys.exit()
42
43 # Let the player enter their bet for this round:
44 print('Money:', money)
45 bet = getBet(money)
46
47 # Give the dealer and player two cards from the deck each:
48 deck = getDeck()
49 dealerHand = [deck.pop(), deck.pop()]
50 playerHand = [deck.pop(), deck.pop()]
51
52 # Handle player actions:
53 print('Bet:', bet)
54 while True: # Keep looping until player stands or busts.
55 displayHands(playerHand, dealerHand, False)
56 print()
57
58 # Check if the player has bust:
59 if getHandValue(playerHand) > 21:
60 break
61
62 # Get the player's move, either H, S, or D:
63 move = getMove(playerHand, money - bet)
64
65 # Handle the player actions:
66 if move == 'D':
67 # Player is doubling down, they can increase their bet:
68 additionalBet = getBet(min(bet, (money - bet)))
69 bet += additionalBet
70 print('Bet increased to {}.'.format(bet))
71 print('Bet:', bet)
72
73 if move in ('H', 'D'):
74 # Hit/doubling down takes another card.
75 newCard = deck.pop()
76 rank, suit = newCard
77 print('You drew a {} of {}.'.format(rank, suit))
78 playerHand.append(newCard)
79
80 if getHandValue(playerHand) > 21:
81 # The player has busted:
82 continue
83
84 if move in ('S', 'D'):
85 # Stand/doubling down stops the player's turn.
86 break
87
88 # Handle the dealer's actions:
89 if getHandValue(playerHand) <= 21:
90 while getHandValue(dealerHand) < 17:
91 # The dealer hits:
92 print('Dealer hits...')
93 dealerHand.append(deck.pop())
94 displayHands(playerHand, dealerHand, False)
95
96 if getHandValue(dealerHand) > 21:
97 break # The dealer has busted.
98 input('Press Enter to continue...')
99 print('\n\n')
100
101 # Show the final hands:
102 displayHands(playerHand, dealerHand, True)
103
104 playerValue = getHandValue(playerHand)
105 dealerValue = getHandValue(dealerHand)
106 # Handle whether the player won, lost, or tied:
107 if dealerValue > 21:
108 print('Dealer busts! You win ${}!'.format(bet))
109 money += bet
110 elif (playerValue > 21) or (playerValue < dealerValue):
111 print('You lost!')
112 money -= bet
113 elif playerValue > dealerValue:
114 print('You won ${}!'.format(bet))
115 money += bet
116 elif playerValue == dealerValue:
117 print('It\'s a tie, the bet is returned to you.')
118
119 input('Press Enter to continue...')
120 print('\n\n')
121
122
123def getBet(maxBet):
124 """Ask the player how much they want to bet for this round."""
125 while True: # Keep asking until they enter a valid amount.
126 print('How much do you bet? (1-{}, or QUIT)'.format(maxBet))
127 bet = input('> ').upper().strip()
128 if bet == 'QUIT':
129 print('Thanks for playing!')
130 sys.exit()
131
132 if not bet.isdecimal():
133 continue # If the player didn't enter a number, ask again.
134
135 bet = int(bet)
136 if 1 <= bet <= maxBet:
137 return bet # Player entered a valid bet.
138
139
140def getDeck():
141 """Return a list of (rank, suit) tuples for all 52 cards."""
142 deck = []
143 for suit in (HEARTS, DIAMONDS, SPADES, CLUBS):
144 for rank in range(2, 11):
145 deck.append((str(rank), suit)) # Add the numbered cards.
146 for rank in ('J', 'Q', 'K', 'A'):
147 deck.append((rank, suit)) # Add the face and ace cards.
148 random.shuffle(deck)
149 return deck
150
151
152def displayHands(playerHand, dealerHand, showDealerHand):
153 """Show the player's and dealer's cards. Hide the dealer's first
154 card if showDealerHand is False."""
155 print()
156 if showDealerHand:
157 print('DEALER:', getHandValue(dealerHand))
158 displayCards(dealerHand)
159 else:
160 print('DEALER: ???')
161 # Hide the dealer's first card:
162 displayCards([BACKSIDE] + dealerHand[1:])
163
164 # Show the player's cards:
165 print('PLAYER:', getHandValue(playerHand))
166 displayCards(playerHand)
167
168
169def getHandValue(cards):
170 """Returns the value of the cards. Face cards are worth 10, aces are
171 worth 11 or 1 (this function picks the most suitable ace value)."""
172 value = 0
173 numberOfAces = 0
174
175 # Add the value for the non-ace cards:
176 for card in cards:
177 rank = card[0] # card is a tuple like (rank, suit)
178 if rank == 'A':
179 numberOfAces += 1
180 elif rank in ('K', 'Q', 'J'): # Face cards are worth 10 points.
181 value += 10
182 else:
183 value += int(rank) # Numbered cards are worth their number.
184
185 # Add the value for the aces:
186 value += numberOfAces # Add 1 per ace.
187 for i in range(numberOfAces):
188 # If another 10 can be added without busting, do so:
189 if value + 10 <= 21:
190 value += 10
191
192 return value
193
194
195def displayCards(cards):
196 """Display all the cards in the cards list."""
197 rows = ['', '', '', '', ''] # The text to display on each row.
198
199 for i, card in enumerate(cards):
200 rows[0] += ' ___ ' # Print the top line of the card.
201 if card == BACKSIDE:
202 # Print a card's back:
203 rows[1] += '|## | '
204 rows[2] += '|###| '
205 rows[3] += '|_##| '
206 else:
207 # Print the card's front:
208 rank, suit = card # The card is a tuple data structure.
209 rows[1] += '|{} | '.format(rank.ljust(2))
210 rows[2] += '| {} | '.format(suit)
211 rows[3] += '|_{}| '.format(rank.rjust(2, '_'))
212
213 # Print each row on the screen:
214 for row in rows:
215 print(row)
216
217
218def getMove(playerHand, money):
219 """Asks the player for their move, and returns 'H' for hit, 'S' for
220 stand, and 'D' for double down."""
221 while True: # Keep looping until the player enters a correct move.
222 # Determine what moves the player can make:
223 moves = ['(H)it', '(S)tand']
224
225 # The player can double down on their first move, which we can
226 # tell because they'll have exactly two cards:
227 if len(playerHand) == 2 and money > 0:
228 moves.append('(D)ouble down')
229
230 # Get the player's move:
231 movePrompt = ', '.join(moves) + '> '
232 move = input(movePrompt).upper()
233 if move in ('H', 'S'):
234 return move # Player has entered a valid move.
235 if move == 'D' and '(D)ouble down' in moves:
236 return move # Player has entered a valid move.
237
238
239# If the program is run (instead of imported), run the game:
240if __name__ == '__main__':
241 main()