Day 011 of the 100 Days of Python challenge brings together the previous lessons in a command-line Blackjack game. The player competes against a computer dealer using random cards and standard score comparisons.

This is the most ambitious project in the series so far โ€” it combines random card dealing, score calculation with a special rule for aces, a full player-versus-dealer turn structure, and running statistics across multiple rounds, all split across focused, well-named functions.

This guide walks through the complete Day 011 source code, explains how scoring and the dealer’s logic work, and traces through a real scoring bug involving hands with more than one ace.

Project Overview

The program represents cards as numbers. Number cards keep their values, face cards are represented by 10, and an ace starts as 11. The deal_card() function chooses one card at random from a 13-value list representing a standard deck.

The calculate_score() function handles two important rules: a two-card total of 21 is Blackjack, represented internally by 0, and an ace changes from 11 to 1 when a hand would otherwise exceed 21.

Source Code

The Day 011 project is contained in day011/main.py in the 100daysofpython repository:

from art import LOGO
import random

print(LOGO)
print("*******************************************")
print("Welcome to The Blackjack Game!")
print("*******************************************")

def deal_card():
    cards = [2, 3, 4, 5, 6, 7, 8, 9, 10, 10, 10, 10, 11]
    card = random.choice(cards)
    return card

def calculate_score(cards):
    if sum(cards) == 21 and len(cards) == 2:
        return 0
    if 11 in cards and sum(cards) > 21:
        cards.remove(11)
        cards.append(1)
    return sum(cards)

def compare(u_score, c_score):
    if u_score == c_score:
        return "Draw ๐Ÿ™ƒ", "draw"
    elif c_score == 0:
        return "Lose, opponent has Blackjack ๐Ÿ˜ฑ", "lose"
    elif u_score == 0:
        return "Win with a Blackjack ๐Ÿ˜Ž", "win"
    elif u_score > 21:
        return "You went over. You lose ๐Ÿ˜ญ", "lose"
    elif c_score > 21:
        return "Opponent went over. You win ๐Ÿ˜", "win"
    elif u_score > c_score:
        return "You win ๐Ÿ˜ƒ", "win"
    else:
        return "You lose ๐Ÿ˜ค", "lose"

wins = 0
losses = 0
draws = 0

def play_game():
    global wins, losses, draws

    print(LOGO)
    user_cards = []
    computer_cards = []
    computer_score = -1
    user_score = -1
    is_game_over = False

    for _ in range(2):
        user_cards.append(deal_card())
        computer_cards.append(deal_card())

    while not is_game_over:
        user_score = calculate_score(user_cards)
        computer_score = calculate_score(computer_cards)
        print(f"Your cards: {user_cards}, current score: {user_score}")
        print(f"Computer's first card: {computer_cards[0]}")

        if user_score == 0 or computer_score == 0 or user_score > 21:
            is_game_over = True
        else:
            user_should_deal = input("Type 'y' to get another card, type 'n' to pass: ")
            if user_should_deal == "y":
                user_cards.append(deal_card())
            else:
                is_game_over = True

    while computer_score != 0 and computer_score < 17:
        computer_cards.append(deal_card())
        computer_score = calculate_score(computer_cards)

    print(f"Your final hand: {user_cards}, final score: {user_score}")
    print(f"Computer's final hand: {computer_cards}, final score: {computer_score}")

    message, outcome = compare(user_score, computer_score)
    print(message)

    if outcome == "win":
        wins += 1
    elif outcome == "lose":
        losses += 1
    else:
        draws += 1

    print(f"\nScore โ€” Wins: {wins} | Losses: {losses} | Draws: {draws}")

while input("Do you want to play a game of Blackjack? Type 'y' or 'n': ") == "y":
    print("\n" * 20)
    play_game()

How the Program Works

Representing Cards and Detecting Blackjack

if sum(cards) == 21 and len(cards) == 2:
    return 0

Rather than adding a separate Boolean flag for “is this Blackjack,” the function returns 0 as a special sentinel value whenever the very first two cards add up to 21. Every function that checks a score โ€” compare(), the game-over check โ€” treats 0 as “this hand is a natural Blackjack,” which keeps the scoring logic in one place.

The Dealer’s Fixed Strategy

while computer_score != 0 and computer_score < 17:
    computer_cards.append(deal_card())
    computer_score = calculate_score(computer_cards)

The dealer follows a simple, fixed rule common to real Blackjack: keep drawing cards until reaching at least 17, unless it already has a natural Blackjack. Unlike the player, the dealer never “chooses” to stop early โ€” its strategy is entirely mechanical.

Splitting Behavior Into Small Functions

The game is broken into deal_card(), calculate_score(), compare(), and play_game(), each with one clear responsibility. This separation makes it possible to reason about (and test) the scoring logic independently from the turn-by-turn game flow โ€” a habit worth carrying into any larger program.

A Real Bug: Hands With Two or More Aces

Now look closely at the ace-adjustment logic:

if 11 in cards and sum(cards) > 21:
    cards.remove(11)
    cards.append(1)
return sum(cards)

This is an if, not a while โ€” it converts at most one ace from 11 to 1 per call, even if a hand contains multiple aces that would each need converting to avoid busting.

Trace through a hand of [11, 11, 10, 5] โ€” two aces, a 10, and a 5:

  1. sum(cards) is 37, 11 in cards is True, so the adjustment runs once.
  2. One 11 is removed and one 1 is appended: cards becomes [11, 10, 5, 1].
  3. The function returns sum(cards), which is 27 โ€” still over 21.

But the correct Blackjack value of that hand treats both aces as 1: 1 + 1 + 10 + 5 = 17, a perfectly good, non-busting hand. Because calculate_score() only adjusts one ace, the player (or dealer) would be incorrectly told they busted with a 27, when the hand is actually a valid 17.

This kind of bug is easy to miss during casual testing, since it only surfaces when a hand contains two or more aces โ€” a relatively rare event โ€” which is exactly why it’s worth deliberately testing edge cases like this rather than relying on regular play to catch them.

What This Project Teaches

Day 011 practices:

  • Lists and random selection for simulating a deck of cards
  • Encoding a special case (Blackjack) as a sentinel return value
  • Breaking a larger program into small, focused functions
  • Module-level state (wins, losses, draws) shared across function calls with global
  • Why single-pass adjustments can fail on inputs that need to be corrected more than once

Running the Project

git clone https://github.com/victorhugo81/100daysofpython.git
cd 100daysofpython/day011
python main.py

The project requires the supporting art.py file in the same folder.

Possible Next Steps

Once you understand the existing code, try extending it:

  • Fix the multi-ace bug by changing the if to a while loop, so every ace over the limit gets converted, not just the first one
  • Skip the dealer’s drawing loop entirely when the player has already busted, since the outcome is already decided
  • Add support for splitting a hand when the first two cards match
  • Persist wins, losses, and draws to a file so the running statistics survive between program runs

Conclusion

Day 011 of the 100 Days of Python challenge ties together everything from earlier days โ€” random selection, functions, loops, and module-level state โ€” into a genuinely playable Blackjack game. It’s also a strong reminder that “handles the common case” and “handles every case correctly” aren’t the same thing: the ace-scoring bug here only shows up with two or more aces in a hand, which is exactly the kind of edge case worth testing deliberately rather than hoping to stumble into during normal play.

View the Day 011 source code on GitHub, then continue through the repository’s project index as you build more Python programs, one day and one small project at a time.