Skip to content

Text-based Blackjack Game

Abstract

Blackjack is the perfect “first serious OOP project.” You model real objects — cards, decks, hands, chips — and watch class composition do its job. You also implement rules with edge cases (the ace counts as 1 or 11), simulate a deterministic dealer AI, and run a betting loop. In this tutorial you build the complete game, refactor where the original could be cleaner, and add features (double-down, split, statistics) that show how a good class design absorbs new requirements without panic.

You will leave understanding:

  • How to model cards, decks, and hands as Python classes.
  • The “aces count as 1 or 11” rule, encoded cleanly.
  • The standard dealer rule (“hit on 16, stand on 17”).
  • How to keep player chips and bets in one place.
  • How to extend the game: double-down, split, insurance, basic strategy hints.

The Rules (Quick)

  • Goal: beat the dealer without exceeding 21 (“bust”).
  • Cards 2–10 are worth their face value, J/Q/K count as 10, A counts as 1 or 11.
  • Player and dealer each get two cards. One of the dealer’s is face down.
  • Player choices: hit (take another card), stand (stop).
  • Dealer must hit until reaching 17 or higher.
  • 21 on the first two cards is a blackjack (typically pays 3:2).
  • Ties = push (bet returned).

Prerequisites

  • Python 3.6 or above.
  • A text editor or IDE.
  • Comfort with classes and methods. (See Personal Diary for an OOP refresher.)

Getting Started

Create the project

  1. Create folder blackjack-gameblackjack-game.
  2. Inside, create blackjack.pyblackjack.py.

Write the code

BlackjackSource
Blackjack
# Text-based Blackjack Game
 
import random
 
class Card:
    def __init__(self, suit, rank):
        self.suit = suit
        self.rank = rank
    
    def __str__(self):
        return f"{self.rank} of {self.suit}"
    
    def value(self):
        if self.rank in ['Jack', 'Queen', 'King']:
            return 10
        elif self.rank == 'Ace':
            return 11  # Will be handled in Hand class
        else:
            return int(self.rank)
 
class Deck:
    def __init__(self):
        self.cards = []
        suits = ['Hearts', 'Diamonds', 'Clubs', 'Spades']
        ranks = ['2', '3', '4', '5', '6', '7', '8', '9', '10', 'Jack', 'Queen', 'King', 'Ace']
        
        for suit in suits:
            for rank in ranks:
                self.cards.append(Card(suit, rank))
        
        self.shuffle()
    
    def shuffle(self):
        random.shuffle(self.cards)
    
    def deal_card(self):
        return self.cards.pop()
 
class Hand:
    def __init__(self):
        self.cards = []
        self.value = 0
        self.aces = 0
    
    def add_card(self, card):
        self.cards.append(card)
        self.value += card.value()
        
        # Track aces
        if card.rank == 'Ace':
            self.aces += 1
        
        # Adjust for aces
        self.adjust_for_ace()
    
    def adjust_for_ace(self):
        # If total value > 21 and we have aces, convert ace from 11 to 1
        while self.value > 21 and self.aces:
            self.value -= 10
            self.aces -= 1
    
    def __str__(self):
        hand_str = "Hand contains:\n"
        for card in self.cards:
            hand_str += f"  {card}\n"
        hand_str += f"Total value: {self.value}"
        return hand_str
 
class Chips:
    def __init__(self, total=100):
        self.total = total
        self.bet = 0
    
    def win_bet(self):
        self.total += self.bet
    
    def lose_bet(self):
        self.total -= self.bet
 
def take_bet(chips):
    while True:
        try:
            chips.bet = int(input(f"You have {chips.total} chips. How many would you like to bet? "))
        except ValueError:
            print("Sorry, please enter a valid number!")
        else:
            if chips.bet > chips.total:
                print(f"Sorry, you only have {chips.total} chips!")
            else:
                break
 
def hit(deck, hand):
    hand.add_card(deck.deal_card())
 
def hit_or_stand(deck, hand):
    global playing
    
    while True:
        choice = input("\nWould you like to Hit or Stand? Enter 'h' or 's': ").lower()
        
        if choice == 'h':
            hit(deck, hand)
        elif choice == 's':
            print("Player stands. Dealer is playing.")
            playing = False
        else:
            print("Sorry, please try again.")
            continue
        break
 
def show_some(player, dealer):
    print("\nDealer's Hand:")
    print(" <card hidden>")
    print(f" {dealer.cards[1]}")
    print(f"\nPlayer's Hand: {player.value}")
    for card in player.cards:
        print(f" {card}")
 
def show_all(player, dealer):
    print("\nDealer's Hand:", dealer.value)
    for card in dealer.cards:
        print(f" {card}")
    print(f"\nPlayer's Hand: {player.value}")
    for card in player.cards:
        print(f" {card}")
 
def player_busts(player, dealer, chips):
    print("Player busts! Dealer wins!")
    chips.lose_bet()
 
def player_wins(player, dealer, chips):
    print("Player wins!")
    chips.win_bet()
 
def dealer_busts(player, dealer, chips):
    print("Dealer busts! Player wins!")
    chips.win_bet()
 
def dealer_wins(player, dealer, chips):
    print("Dealer wins!")
    chips.lose_bet()
 
def push(player, dealer):
    print("Dealer and Player tie! It's a push.")
 
# Game Logic
def play_blackjack():
    global playing
    playing = True
    
    print("Welcome to BlackJack! Get as close to 21 as you can without going over!")
    print("Dealer hits until she reaches 17. Aces count as 1 or 11.")
    
    # Create & shuffle the deck, deal two cards to each player
    deck = Deck()
    player_hand = Hand()
    dealer_hand = Hand()
    
    player_hand.add_card(deck.deal_card())
    player_hand.add_card(deck.deal_card())
    dealer_hand.add_card(deck.deal_card())
    dealer_hand.add_card(deck.deal_card())
    
    # Set up the Player's chips
    player_chips = Chips()
    
    # Prompt the Player for their bet
    take_bet(player_chips)
    
    # Show cards (but keep one dealer card hidden)
    show_some(player_hand, dealer_hand)
    
    while playing:  # recall this variable from our hit_or_stand function
        
        # Prompt for Player to Hit or Stand
        hit_or_stand(deck, player_hand)
        
        # Show cards (but keep one dealer card hidden)
        show_some(player_hand, dealer_hand)
        
        # If player's hand exceeds 21, run player_busts() and break out of loop
        if player_hand.value > 21:
            player_busts(player_hand, dealer_hand, player_chips)
            break
    
    # If Player hasn't busted, play Dealer's hand until Dealer reaches 17
    if player_hand.value <= 21:
        
        while dealer_hand.value < 17:
            hit(deck, dealer_hand)
        
        # Show all cards
        show_all(player_hand, dealer_hand)
        
        # Run different winning scenarios
        if dealer_hand.value > 21:
            dealer_busts(player_hand, dealer_hand, player_chips)
        
        elif dealer_hand.value > player_hand.value:
            dealer_wins(player_hand, dealer_hand, player_chips)
        
        elif dealer_hand.value < player_hand.value:
            player_wins(player_hand, dealer_hand, player_chips)
        
        else:
            push(player_hand, dealer_hand)
    
    # Inform Player of their chips total
    print(f"\nPlayer's winnings stand at {player_chips.total}")
    
    # Ask to play again
    new_game = input("Would you like to play another hand? Enter 'y' or 'n': ").lower()
    if new_game == 'y':
        playing = True
        play_blackjack()
    else:
        print("Thanks for playing!")
 
if __name__ == "__main__":
    play_blackjack()
 
Blackjack
# Text-based Blackjack Game
 
import random
 
class Card:
    def __init__(self, suit, rank):
        self.suit = suit
        self.rank = rank
    
    def __str__(self):
        return f"{self.rank} of {self.suit}"
    
    def value(self):
        if self.rank in ['Jack', 'Queen', 'King']:
            return 10
        elif self.rank == 'Ace':
            return 11  # Will be handled in Hand class
        else:
            return int(self.rank)
 
class Deck:
    def __init__(self):
        self.cards = []
        suits = ['Hearts', 'Diamonds', 'Clubs', 'Spades']
        ranks = ['2', '3', '4', '5', '6', '7', '8', '9', '10', 'Jack', 'Queen', 'King', 'Ace']
        
        for suit in suits:
            for rank in ranks:
                self.cards.append(Card(suit, rank))
        
        self.shuffle()
    
    def shuffle(self):
        random.shuffle(self.cards)
    
    def deal_card(self):
        return self.cards.pop()
 
class Hand:
    def __init__(self):
        self.cards = []
        self.value = 0
        self.aces = 0
    
    def add_card(self, card):
        self.cards.append(card)
        self.value += card.value()
        
        # Track aces
        if card.rank == 'Ace':
            self.aces += 1
        
        # Adjust for aces
        self.adjust_for_ace()
    
    def adjust_for_ace(self):
        # If total value > 21 and we have aces, convert ace from 11 to 1
        while self.value > 21 and self.aces:
            self.value -= 10
            self.aces -= 1
    
    def __str__(self):
        hand_str = "Hand contains:\n"
        for card in self.cards:
            hand_str += f"  {card}\n"
        hand_str += f"Total value: {self.value}"
        return hand_str
 
class Chips:
    def __init__(self, total=100):
        self.total = total
        self.bet = 0
    
    def win_bet(self):
        self.total += self.bet
    
    def lose_bet(self):
        self.total -= self.bet
 
def take_bet(chips):
    while True:
        try:
            chips.bet = int(input(f"You have {chips.total} chips. How many would you like to bet? "))
        except ValueError:
            print("Sorry, please enter a valid number!")
        else:
            if chips.bet > chips.total:
                print(f"Sorry, you only have {chips.total} chips!")
            else:
                break
 
def hit(deck, hand):
    hand.add_card(deck.deal_card())
 
def hit_or_stand(deck, hand):
    global playing
    
    while True:
        choice = input("\nWould you like to Hit or Stand? Enter 'h' or 's': ").lower()
        
        if choice == 'h':
            hit(deck, hand)
        elif choice == 's':
            print("Player stands. Dealer is playing.")
            playing = False
        else:
            print("Sorry, please try again.")
            continue
        break
 
def show_some(player, dealer):
    print("\nDealer's Hand:")
    print(" <card hidden>")
    print(f" {dealer.cards[1]}")
    print(f"\nPlayer's Hand: {player.value}")
    for card in player.cards:
        print(f" {card}")
 
def show_all(player, dealer):
    print("\nDealer's Hand:", dealer.value)
    for card in dealer.cards:
        print(f" {card}")
    print(f"\nPlayer's Hand: {player.value}")
    for card in player.cards:
        print(f" {card}")
 
def player_busts(player, dealer, chips):
    print("Player busts! Dealer wins!")
    chips.lose_bet()
 
def player_wins(player, dealer, chips):
    print("Player wins!")
    chips.win_bet()
 
def dealer_busts(player, dealer, chips):
    print("Dealer busts! Player wins!")
    chips.win_bet()
 
def dealer_wins(player, dealer, chips):
    print("Dealer wins!")
    chips.lose_bet()
 
def push(player, dealer):
    print("Dealer and Player tie! It's a push.")
 
# Game Logic
def play_blackjack():
    global playing
    playing = True
    
    print("Welcome to BlackJack! Get as close to 21 as you can without going over!")
    print("Dealer hits until she reaches 17. Aces count as 1 or 11.")
    
    # Create & shuffle the deck, deal two cards to each player
    deck = Deck()
    player_hand = Hand()
    dealer_hand = Hand()
    
    player_hand.add_card(deck.deal_card())
    player_hand.add_card(deck.deal_card())
    dealer_hand.add_card(deck.deal_card())
    dealer_hand.add_card(deck.deal_card())
    
    # Set up the Player's chips
    player_chips = Chips()
    
    # Prompt the Player for their bet
    take_bet(player_chips)
    
    # Show cards (but keep one dealer card hidden)
    show_some(player_hand, dealer_hand)
    
    while playing:  # recall this variable from our hit_or_stand function
        
        # Prompt for Player to Hit or Stand
        hit_or_stand(deck, player_hand)
        
        # Show cards (but keep one dealer card hidden)
        show_some(player_hand, dealer_hand)
        
        # If player's hand exceeds 21, run player_busts() and break out of loop
        if player_hand.value > 21:
            player_busts(player_hand, dealer_hand, player_chips)
            break
    
    # If Player hasn't busted, play Dealer's hand until Dealer reaches 17
    if player_hand.value <= 21:
        
        while dealer_hand.value < 17:
            hit(deck, dealer_hand)
        
        # Show all cards
        show_all(player_hand, dealer_hand)
        
        # Run different winning scenarios
        if dealer_hand.value > 21:
            dealer_busts(player_hand, dealer_hand, player_chips)
        
        elif dealer_hand.value > player_hand.value:
            dealer_wins(player_hand, dealer_hand, player_chips)
        
        elif dealer_hand.value < player_hand.value:
            player_wins(player_hand, dealer_hand, player_chips)
        
        else:
            push(player_hand, dealer_hand)
    
    # Inform Player of their chips total
    print(f"\nPlayer's winnings stand at {player_chips.total}")
    
    # Ask to play again
    new_game = input("Would you like to play another hand? Enter 'y' or 'n': ").lower()
    if new_game == 'y':
        playing = True
        play_blackjack()
    else:
        print("Thanks for playing!")
 
if __name__ == "__main__":
    play_blackjack()
 

Run it

command
C:\Users\username\Documents\blackjack-game> python blackjack.py
Welcome to BlackJack! Get as close to 21 as you can without going over!
    Dealer hits until she reaches 17. Aces count as 1 or 11.
 
How many chips would you like to play with? 100
Current chips: 100
How much would you like to bet? 20
 
Dealer Hand:
<card hidden>
Four of Hearts
 
Player Hand:
Jack of Spades
Seven of Hearts
Total: 17
 
Would you like to Hit or Stand? Enter 'h' or 's': s
Dealer Wins!
Player chips total only 80
Would you like to play again? y/n
command
C:\Users\username\Documents\blackjack-game> python blackjack.py
Welcome to BlackJack! Get as close to 21 as you can without going over!
    Dealer hits until she reaches 17. Aces count as 1 or 11.
 
How many chips would you like to play with? 100
Current chips: 100
How much would you like to bet? 20
 
Dealer Hand:
<card hidden>
Four of Hearts
 
Player Hand:
Jack of Spades
Seven of Hearts
Total: 17
 
Would you like to Hit or Stand? Enter 'h' or 's': s
Dealer Wins!
Player chips total only 80
Would you like to play again? y/n

Step-by-Step Explanation

1. The CardCard class

blackjack.py
SUITS = ("Hearts", "Diamonds", "Spades", "Clubs")
RANKS = ("Two","Three","Four","Five","Six","Seven","Eight","Nine","Ten",
         "Jack","Queen","King","Ace")
VALUES = {**{r: i for i, r in enumerate(RANKS[:9], start=2)},
          "Jack": 10, "Queen": 10, "King": 10, "Ace": 11}
 
class Card:
    def __init__(self, suit: str, rank: str):
        self.suit = suit
        self.rank = rank
    def __str__(self):
        return f"{self.rank} of {self.suit}"
blackjack.py
SUITS = ("Hearts", "Diamonds", "Spades", "Clubs")
RANKS = ("Two","Three","Four","Five","Six","Seven","Eight","Nine","Ten",
         "Jack","Queen","King","Ace")
VALUES = {**{r: i for i, r in enumerate(RANKS[:9], start=2)},
          "Jack": 10, "Queen": 10, "King": 10, "Ace": 11}
 
class Card:
    def __init__(self, suit: str, rank: str):
        self.suit = suit
        self.rank = rank
    def __str__(self):
        return f"{self.rank} of {self.suit}"

Putting VALUESVALUES outside the class avoids recomputing it for every card.

2. The DeckDeck class

blackjack.py
import random
class Deck:
    def __init__(self):
        self.cards = [Card(s, r) for s in SUITS for r in RANKS]
        random.shuffle(self.cards)
    def deal(self) -> Card:
        return self.cards.pop()
blackjack.py
import random
class Deck:
    def __init__(self):
        self.cards = [Card(s, r) for s in SUITS for r in RANKS]
        random.shuffle(self.cards)
    def deal(self) -> Card:
        return self.cards.pop()

A list comprehension builds all 52 cards in one line. pop()pop() removes from the end — O(1).

3. The HandHand class with ace adjustment

blackjack.py
class Hand:
    def __init__(self):
        self.cards: list[Card] = []
        self.value = 0
        self.aces = 0
    def add(self, card: Card):
        self.cards.append(card)
        self.value += VALUES[card.rank]
        if card.rank == "Ace":
            self.aces += 1
        self.adjust_for_ace()
    def adjust_for_ace(self):
        while self.value > 21 and self.aces:
            self.value -= 10            # demote one Ace from 11 → 1
            self.aces -= 1
blackjack.py
class Hand:
    def __init__(self):
        self.cards: list[Card] = []
        self.value = 0
        self.aces = 0
    def add(self, card: Card):
        self.cards.append(card)
        self.value += VALUES[card.rank]
        if card.rank == "Ace":
            self.aces += 1
        self.adjust_for_ace()
    def adjust_for_ace(self):
        while self.value > 21 and self.aces:
            self.value -= 10            # demote one Ace from 11 → 1
            self.aces -= 1

The ace rule is only awkward because it depends on the rest of the hand. The trick is to count aces and demote them one at a time when needed.

4. The ChipsChips class

blackjack.py
class Chips:
    def __init__(self, total: int = 100):
        self.total = total
        self.bet = 0
    def win(self):  self.total += self.bet
    def lose(self): self.total -= self.bet
blackjack.py
class Chips:
    def __init__(self, total: int = 100):
        self.total = total
        self.bet = 0
    def win(self):  self.total += self.bet
    def lose(self): self.total -= self.bet

A tiny class with one job. The single-line methods are intentional — “what happens on a win” is one source of truth.

5. Player turn — hit or stand

blackjack.py
def hit(deck, hand):
    hand.add(deck.deal())
 
def hit_or_stand(deck, hand, playing_ref):
    while True:
        choice = input("Hit (h) or Stand (s)? ").lower()
        if choice == "h":
            hit(deck, hand)
            return
        if choice == "s":
            playing_ref[0] = False
            return
        print("Enter h or s.")
blackjack.py
def hit(deck, hand):
    hand.add(deck.deal())
 
def hit_or_stand(deck, hand, playing_ref):
    while True:
        choice = input("Hit (h) or Stand (s)? ").lower()
        if choice == "h":
            hit(deck, hand)
            return
        if choice == "s":
            playing_ref[0] = False
            return
        print("Enter h or s.")

playing_refplaying_ref is a one-element list used as a “ref cell” — Python passes integers by value, so we wrap a mutable container to let hit_or_standhit_or_stand flip the flag for the outer loop.

6. Dealer logic

blackjack.py
while dealer_hand.value < 17:
    dealer_hand.add(deck.deal())
blackjack.py
while dealer_hand.value < 17:
    dealer_hand.add(deck.deal())

Standard rule, two lines.

7. Decide outcomes

blackjack.py
if player_hand.value > 21:
    print("Player busts.")
    chips.lose()
elif dealer_hand.value > 21 or player_hand.value > dealer_hand.value:
    print("Player wins.")
    chips.win()
elif dealer_hand.value > player_hand.value:
    print("Dealer wins.")
    chips.lose()
else:
    print("Push.")
blackjack.py
if player_hand.value > 21:
    print("Player busts.")
    chips.lose()
elif dealer_hand.value > 21 or player_hand.value > dealer_hand.value:
    print("Player wins.")
    chips.win()
elif dealer_hand.value > player_hand.value:
    print("Dealer wins.")
    chips.lose()
else:
    print("Push.")

The order matters — check player bust first so the dealer does not “win” automatically by also busting.

Polished Output

A small helper that hides the dealer’s hole card during play:

show.py
def show_some(player, dealer):
    print("\nDealer's Hand:")
    print("<hidden>")
    print(dealer.cards[1])
    print("\nPlayer's Hand:")
    for c in player.cards: print(c)
    print(f"Total: {player.value}")
 
def show_all(player, dealer):
    print("\nDealer's Hand:")
    for c in dealer.cards: print(c)
    print(f"Total: {dealer.value}")
    print("\nPlayer's Hand:")
    for c in player.cards: print(c)
    print(f"Total: {player.value}")
show.py
def show_some(player, dealer):
    print("\nDealer's Hand:")
    print("<hidden>")
    print(dealer.cards[1])
    print("\nPlayer's Hand:")
    for c in player.cards: print(c)
    print(f"Total: {player.value}")
 
def show_all(player, dealer):
    print("\nDealer's Hand:")
    for c in dealer.cards: print(c)
    print(f"Total: {dealer.value}")
    print("\nPlayer's Hand:")
    for c in player.cards: print(c)
    print(f"Total: {player.value}")

Betting Validation

bet.py
def take_bet(chips: Chips):
    while True:
        try:
            chips.bet = int(input(f"Bet (1-{chips.total}): "))
        except ValueError:
            print("Enter a whole number.")
            continue
        if 1 <= chips.bet <= chips.total:
            return
        print("Out of range.")
bet.py
def take_bet(chips: Chips):
    while True:
        try:
            chips.bet = int(input(f"Bet (1-{chips.total}): "))
        except ValueError:
            print("Enter a whole number.")
            continue
        if 1 <= chips.bet <= chips.total:
            return
        print("Out of range.")

Common Mistakes

ProblemCauseFix
Ace always counts as 11No demotion logicadjust_for_aceadjust_for_ace after every addadd
Dealer bust and player bust → player winsWrong condition orderCheck player bust first
Same card dealt twiceForgot to shuffle, or rebuilt deck wrongUse one DeckDeck instance per round, or reshuffle when low
Bet bigger than chip total acceptedNo upper-bound checkValidate against chips.totalchips.total
IndexError: pop from empty listIndexError: pop from empty listDid not reshuffleAdd if len(self.cards) < 10: reshuffle()if len(self.cards) < 10: reshuffle()
Score breaks at 22 with two acesAdjusted only oncewhilewhile, not ifif, in adjust_for_aceadjust_for_ace

Variations to Try

1. Multiple decks

Casinos use 4–8 decks shuffled together:

multi_deck.py
class Deck:
    def __init__(self, num_decks=4):
        self.cards = [Card(s, r) for _ in range(num_decks)
                                 for s in SUITS for r in RANKS]
        random.shuffle(self.cards)
multi_deck.py
class Deck:
    def __init__(self, num_decks=4):
        self.cards = [Card(s, r) for _ in range(num_decks)
                                 for s in SUITS for r in RANKS]
        random.shuffle(self.cards)

2. Blackjack pays 3:2

A natural 21 on the first two cards pays 1.5× the bet:

natural.py
if len(player_hand.cards) == 2 and player_hand.value == 21:
    chips.total += int(chips.bet * 1.5)
    return "natural"
natural.py
if len(player_hand.cards) == 2 and player_hand.value == 21:
    chips.total += int(chips.bet * 1.5)
    return "natural"

3. Double Down

After the deal, the player may double the bet, take exactly one more card, and end the turn.

4. Split

With a pair, split into two hands, each played independently with a separate bet.

5. Insurance

When the dealer’s up-card is an Ace, the player can buy insurance (half the bet) that pays 2:1 if the dealer has blackjack.

6. Basic-strategy hints

Hard-code the standard chart. After each hand, show what basic strategy would have done.

7. Statistics

Track hands played, wins, losses, ties, biggest hand, longest streak. Save to a JSON file between sessions.

8. Card counting trainer

Show the running count after each hand. Quiz the player at the end of each shoe.

9. ASCII card art

Render each card as 5-line ASCII art:

text
┌─────────┐
7
│         │
│    ♥    │
│         │
7
└─────────┘
text
┌─────────┐
7
│         │
│    ♥    │
│         │
7
└─────────┘

10. GUI version

Tkinter or PyGame with real card images. See Basic Music Player for the Tkinter pattern.

11. Multiplayer (one dealer, multiple players)

Each player has their own HandHand and ChipsChips. Iterate through them before the dealer’s turn.

12. Web version

Flask backend + a React frontend. The server stores the deck and hands; the client renders.

Architecture Notes

The original code worked but mixed game logic with I/O. A cleaner separation:

  • CardCard, DeckDeck, HandHand, ChipsChips — pure game state, no inputinput/printprint.
  • GameGame — orchestrates a round: deals, asks for choices, decides winner.
  • UIUI — talks to the user. Could be CLI, GUI, or web.

That separation is what lets you swap CLI for GUI without rewriting rules.

Real-World Connections

  • Probability and combinatorics teaching.
  • Reinforcement learning — Blackjack is a classic toy problem in Sutton & Barto’s Reinforcement Learning.
  • Card-counting math (Kelly criterion, edge calculation).
  • Game-design fundamentals — state, choices, randomness, reward.

Educational Value

  • OOP composition — Card → Deck → Hand.
  • Edge-case modeling — Aces, blackjacks, busts, pushes.
  • State machines — round phases (deal, player turn, dealer turn, settle).
  • Game-theory thinking — when basic strategy beats intuition.

Next Steps

  • Implement double-down and split — the natural feature jumps from beginner Blackjack to “real” Blackjack.
  • Add a basic-strategy hint that prints what the optimal move would be.
  • Track session statistics and persist them.
  • Try a tiny reinforcement-learning agent that learns to play.
  • Compare with Hangman and Rock Paper Scissors for different game-state patterns.

Try it here

Deal yourself a hand — Hit to draw, Stand to let the dealer play. Get closer to 21 than the dealer without going over (aces count as 11 or 1):

sketch Play a hand of blackjack p5.js
Hit to draw a card, Stand to let the dealer play. Beat the dealer without going over 21.

Conclusion

You built a complete card game, modeled the rules of Blackjack including the Ace dance, simulated a deterministic dealer, and ran a betting loop. The same class composition (Card → Deck → Hand → GameCard → Deck → Hand → Game) underlies poker, solitaire, Hearts, and dozens of other card games. Full source on GitHub. Find more games on Python Central Hub.

If this helped you, consider buying me a coffee ☕

Buy me a coffee

Was this page helpful?

Let us know how we did