Blackjack

Tags: large, game, cardgame

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()

https://inventwithpython.com/bigbookpython/project4.html