← 返回大綱
實作練習

21 點
Blackjack

用 list、dict、def 打造完整的 21 點遊戲!

結合第四章函式 + 第五章資料結構

遊戲規則

21 點怎麼玩?

目標:手牌點數總和盡量接近 21 點,但不能超過。
點數計算:2~10 = 牌面數字、J/Q/K = 10 點、A = 11 或 1 點
A 的特殊規則:A 先算 11 點,如果總和超過 21,自動變成 1 點。
玩家回合:可以選擇「抽牌 (Hit)」或「停牌 (Stand)」。
莊家回合:莊家必須在 17 點以下繼續抽牌。
爆牌 (Bust):超過 21 點就輸了!
Step 1

建立牌組 & 洗牌

用 list comprehension 建出 52 張牌,再用 shuffle 洗牌:

import random

def create_deck():
    """建立一副 52 張撲克牌"""
    suits = ["♠", "♥", "♦", "♣"]
    ranks = ["A","2","3","4","5","6","7","8","9","10","J","Q","K"]
    deck = [r + s for s in suits for r in ranks]
    random.shuffle(deck)
    return deck

deck = create_deck()
print(f"牌堆:{len(deck)} 張")
print(f"前 5 張:{deck[:5]}")
為什麼用函式?

把「建牌」包成函式,以後想重新開局只要呼叫一次 create_deck() 就好。

Step 2

發牌函式

從牌堆頂抽牌,用 pop() 同時移除和回傳:

def deal(deck, hand, n=1):
    """從牌堆抽 n 張牌加到手牌中"""
    for i in range(n):
        card = deck.pop()     # 抽出最上面那張
        hand.append(card)     # 加入手牌

# 使用
deck = create_deck()
player_hand = []       # 玩家手牌(空 list)
dealer_hand = []       # 莊家手牌(空 list)

deal(deck, player_hand, 2)    # 玩家發 2 張
deal(deck, dealer_hand, 2)    # 莊家發 2 張

print(f"你的手牌:{player_hand}")
print(f"莊家的手牌:[{dealer_hand[0]}, ?]")   # 莊家第 2 張蓋住
Step 3

計算手牌點數

這是最關鍵的函式 — A 要能自動判斷算 11 還是 1:

def hand_value(hand):
    """計算手牌總點數,A 自動判斷 11 或 1"""
    values = {"A":11, "K":10, "Q":10, "J":10,
              "10":10,"9":9,"8":8,"7":7,
              "6":6,"5":5,"4":4,"3":3,"2":2}

    total = 0
    ace_count = 0    # 記錄有幾張 A

    for card in hand:
        rank = card[:-1]          # 去掉花色,例如 "A♠" → "A"
        total += values[rank]
        if rank == "A":
            ace_count += 1

    # 如果爆了,且手上有 A,就把 A 從 11 改成 1(減 10)
    while total > 21 and ace_count > 0:
        total -= 10
        ace_count -= 1

    return total
A 的處理

先全部算 11,如果超過 21 就逐張改成 1(減 10)。
例如 A + A + 9 = 11+11+9 = 31 → 改一張 A → 21!

Step 4

顯示手牌狀態

把手牌和點數漂亮地印出來:

def show_hand(name, hand, hide_second=False):
    """顯示某位玩家的手牌和點數"""
    if hide_second:
        # 莊家的第二張牌蓋住
        display = f"{hand[0]}, ?"
        print(f"{name}:{display}")
    else:
        display = ", ".join(hand)
        total = hand_value(hand)
        print(f"{name}:{display}({total} 點)")

# 使用
show_hand("你", ["A♠", "K♥"])            # 你:A♠, K♥(21 點)
show_hand("莊家", ["7♦", "J♣"], True)    # 莊家:7♦, ?
參數 hide_second

預設 False,只有莊家在玩家回合時才需要蓋牌,傳入 True
這就是預設參數的實用場景!

Step 5

玩家回合:Hit or Stand

def player_turn(deck, hand):
    """玩家回合:選擇抽牌或停牌"""
    while True:
        show_hand("你", hand)
        total = hand_value(hand)

        if total == 21:
            print("Blackjack!🎉")
            break

        if total > 21:
            print("爆牌了!💥")
            break

        choice = input("要抽牌(h)還是停牌(s)?")
        if choice == "h":
            deal(deck, hand)
            print(f"抽到:{hand[-1]}")     # 最後一張就是剛抽的
        else:
            print("你選擇停牌。")
            break
Step 6

莊家回合:17 點以下必須抽

def dealer_turn(deck, hand):
    """莊家回合:未滿 17 點必須繼續抽牌"""
    print("\n--- 莊家翻牌 ---")
    show_hand("莊家", hand)

    while hand_value(hand) < 17:
        deal(deck, hand)
        print(f"莊家抽了一張:{hand[-1]}")
        show_hand("莊家", hand)

    total = hand_value(hand)
    if total > 21:
        print("莊家爆牌了!💥")
規則

莊家沒有選擇權 — 未滿 17 點一定要抽,17 點以上就停。
這個規則用 while hand_value(hand) < 17 一行就搞定!

Step 7

判斷勝負

def judge(player_hand, dealer_hand):
    """判斷勝負"""
    p = hand_value(player_hand)
    d = hand_value(dealer_hand)

    print(f"\n--- 結算 ---")
    print(f"你:{p} 點  vs  莊家:{d} 點\n")

    if p > 21:
        print("你爆牌了,莊家贏!😢")
    elif d > 21:
        print("莊家爆牌,你贏了!🎉")
    elif p > d:
        print("你贏了!🎉")
    elif d > p:
        print("莊家贏!😢")
    else:
        print("平手!🤝")
Step 8

主程式:把所有函式串起來

# --- 遊戲主程式 ---
print("🃏 歡迎來到 21 點!\n")

# 1. 建立牌組
deck = create_deck()

# 2. 發牌
player_hand = []
dealer_hand = []
deal(deck, player_hand, 2)
deal(deck, dealer_hand, 2)

# 3. 顯示初始手牌(莊家蓋一張)
show_hand("莊家", dealer_hand, hide_second=True)
print()

# 4. 玩家回合
player_turn(deck, player_hand)

# 5. 如果玩家沒爆,換莊家
if hand_value(player_hand) <= 21:
    dealer_turn(deck, dealer_hand)

# 6. 判斷勝負
judge(player_hand, dealer_hand)
函式的威力

主程式只有十幾行!所有複雜的邏輯都藏在函式裡面。
讀主程式就像在讀遊戲規則,一目瞭然。

完整程式碼

21 點 — 完整版

import random def create_deck(): suits = ["♠", "♥", "♦", "♣"] ranks = ["A","2","3","4","5","6","7","8","9","10","J","Q","K"] deck = [r + s for s in suits for r in ranks] random.shuffle(deck) return deck def deal(deck, hand, n=1): for i in range(n): hand.append(deck.pop()) def hand_value(hand): values = {"A":11,"K":10,"Q":10,"J":10, "10":10,"9":9,"8":8,"7":7,"6":6,"5":5,"4":4,"3":3,"2":2} total = 0 aces = 0 for card in hand: rank = card[:-1] total += values[rank] if rank == "A": aces += 1 while total > 21 and aces > 0: total -= 10 aces -= 1 return total def show_hand(name, hand, hide_second=False): if hide_second: print(f"{name}:{hand[0]}, ?") else: print(f"{name}:{', '.join(hand)}({hand_value(hand)} 點)") def player_turn(deck, hand): while True: show_hand("你", hand) total = hand_value(hand) if total == 21: print("Blackjack!🎉") break if total > 21: print("爆牌了!💥") break choice = input("要抽牌(h)還是停牌(s)?") if choice == "h": deal(deck, hand) print(f"抽到:{hand[-1]}") else: print("你選擇停牌。") break def dealer_turn(deck, hand): print("\n--- 莊家翻牌 ---") show_hand("莊家", hand) while hand_value(hand) < 17: deal(deck, hand) print(f"莊家抽了:{hand[-1]}") show_hand("莊家", hand) if hand_value(hand) > 21: print("莊家爆牌了!💥") def judge(p_hand, d_hand): p, d = hand_value(p_hand), hand_value(d_hand) print(f"\n--- 結算 ---") print(f"你:{p} 點 vs 莊家:{d} 點\n") if p > 21: print("你爆牌了,莊家贏!😢") elif d > 21: print("莊家爆牌,你贏了!🎉") elif p > d: print("你贏了!🎉") elif d > p: print("莊家贏!😢") else: print("平手!🤝") # === 主程式 === print("🃏 歡迎來到 21 點!\n") deck = create_deck() player_hand, dealer_hand = [], [] deal(deck, player_hand, 2) deal(deck, dealer_hand, 2) show_hand("莊家", dealer_hand, hide_second=True) print() player_turn(deck, player_hand) if hand_value(player_hand) <= 21: dealer_turn(deck, dealer_hand) judge(player_hand, dealer_hand)
回顧

這個遊戲用了哪些技巧?

技巧用在哪裡
list牌堆 deck、手牌 hand
list comprehension一行建出 52 張牌
.pop() / .append()抽牌 / 加入手牌
dict牌面 → 點數對照 values
def + return6 個函式各司其職
預設參數deal(n=1)show_hand(hide_second=False)
while 迴圈玩家回合、莊家回合、A 的處理
random.shuffle()洗牌

21 點完成!

你已經用 Python 寫出一個完整的 21 點遊戲!
試試看加入更多功能吧。

挑戰:加入「下注系統」、「多回合記分」或「分牌 (Split)」功能!