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:
sum(cards)is37,11 in cardsisTrue, so the adjustment runs once.- One
11is removed and one1is appended:cardsbecomes[11, 10, 5, 1]. - The function returns
sum(cards), which is27โ 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 withglobal - 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
ifto awhileloop, 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, anddrawsto 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.
