iT邦幫忙

2025 iThome 鐵人賽

DAY 13
0
昨天 coding 得很開心,今天繼續完成洗牌功能!

https://ithelp.ithome.com.tw/upload/images/20250926/20168437xIRubv9a4n.png

實作步驟

  1. 準備塔羅牌資料,暫時存放在 cards.py
  2. 建立 deck.py:實作洗牌與抽牌功能。
  3. 建立 main.py:呼叫抽牌,印出結果。

前一篇做了洗牌的實作,接下來要將洗好的牌給收攏。

抽牌實作:Day 12. 只是隨機一點都不浪漫

塔羅牌的洗牌方法

  1. 預先洗牌(就是普通的洗牌方法)
  2. 將牌全部攤開在桌面,固定以順時鐘的方式或逆時鐘的方向洗牌。
  3. 將牌收攏成疊
  4. 分成三疊
            占卜師
      ┌──────────────┐          
      │    ███ ②     │   ← 第 2 疊(從第 1 疊隨意抓取)
      │    ███ ①     │   ← 第 1 疊(原先放在桌上的)
      │    ███ ③     │   ← 第 3 疊(從第 2 疊隨意抓取)
      └──────────────┘
             自己
    
  5. 依 1 → 2 → 3 的順序,重新將牌收成一疊。
    (第一疊在最上面,第三疊在最下面)
  6. 以逆時針方向來調整牌的方向,將牌轉直。

處理之前,這是第 2 步驟處理完的樣子

  • spread_shuffle ->
    [
      {"name": "Ⅴ 皇帝 — The Emperor",         "position": "→"},
      {"name": "ⅩⅧ 星星 — The Star",           "position": "→"},
      {"name": "ⅩⅣ 死神 — Death",              "position": "↓"},
      {"name": "Ⅰ 愚者 — The Fool",            "position": "←"},
      {"name": "Ⅳ 皇后 — The Empress",         "position": "←"},
      {"name": "ⅩⅢ 吊人 — The Hanged Man",     "position": "↑"},
      {"name": "Ⅺ 命運之輪 — Wheel of Fortune",  "position": "↓"},
      {"name": "Ⅱ 魔術師 — The Magician",       "position": "←"},
      {"name": "ⅩⅫ 世界 — The World",          "position": "↓"},
      {"name": "Ⅷ 戰車 — The Chariot",         "position": "↓"},
      {"name": "ⅩⅨ 月亮 — The Moon",           "position": "←"},
      {"name": "ⅩⅤ 節制 — Temperance",         "position": "→"},
      {"name": "ⅩⅦ 高塔 — The Tower",          "position": "←"},
      {"name": "ⅩⅥ 惡魔 — The Devil",          "position": "↑"},
      {"name": "Ⅲ 女祭司 — The High Priestess", "position": "↑"},
      {"name": "Ⅻ 正義 — Justice",             "position": "↓"},
      {"name": "Ⅹ 隱者 — The Hermit",          "position": "→"},
      {"name": "Ⅵ 教宗 — The Hierophant",      "position": "↓"},
      {"name": "Ⅸ 力量 — Strength",            "position": "←"},
      {"name": "ⅩⅩ 太陽 — The Sun",            "position": "→"},
      {"name": "ⅩⅪ 審判 — Judgement",          "position": "↓"},
      {"name": "Ⅶ 戀人 — The Lovers",          "position": "↑"}
    ]
    

3. 將牌收攏成疊 (deck.py)

資料已經是 List 了,還需要收攏成疊嗎?

當然要!!!

雖然已經是 List,但牌裡面有方向啊!
所以這一步,就是要將洗亂的牌收成橫放的樣子,牌的方向也只會剩下左跟右。

def gather_deck(self, deck): 
    """
    把整副牌 deck 收攏
    模擬雙手在牌面上整理的狀態,先決定理牌的是左還是右手。
    如果使用左手往內收,朝上會變成向右,朝下會變成向左;反之亦然
    """
    lefthand = { "→":"→", "↑":"→", "←":"←", "↓":"←"}
    righthand = { "→":"→", "↑":"←", "←":"←", "↓":"→"}
    for card in deck:
        hand_map = random.choice([lefthand, righthand])
        card['position'] = hand_map[card['position']]
    return deck
  • 結果
    gather_deck ->
    [
      {"name": "Ⅴ 皇帝 — The Emperor",          "position": "→"},
      {"name": "ⅩⅧ 星星 — The Star",            "position": "→"},
      {"name": "ⅩⅣ 死神 — Death",               "position": "→"},
      {"name": "Ⅰ 愚者 — The Fool",             "position": "←"},
      {"name": "Ⅳ 皇后 — The Empress",          "position": "←"},
      {"name": "ⅩⅢ 吊人 — The Hanged Man",      "position": "←"},
      {"name": "Ⅺ 命運之輪 — Wheel of Fortune",   "position": "→"},
      {"name": "Ⅱ 魔術師 — The Magician",        "position": "←"},
      {"name": "ⅩⅫ 世界 — The World",           "position": "←"},
      {"name": "Ⅷ 戰車 — The Chariot",          "position": "→"},
      {"name": "ⅩⅨ 月亮 — The Moon",            "position": "←"},
      {"name": "ⅩⅤ 節制 — Temperance",          "position": "→"},
      {"name": "ⅩⅦ 高塔 — The Tower",           "position": "←"},
      {"name": "ⅩⅥ 惡魔 — The Devil",           "position": "→"},
      {"name": "Ⅲ 女祭司 — The High Priestess", "position": "→"},
      {"name": "Ⅻ 正義 — Justice",              "position": "→"},
      {"name": "Ⅹ 隱者 — The Hermit",           "position": "→"},
      {"name": "Ⅵ 教宗 — The Hierophant",       "position": "←"},
      {"name": "Ⅸ 力量 — Strength",             "position": "←"},
      {"name": "ⅩⅩ 太陽 — The Sun",             "position": "→"},
      {"name": "ⅩⅪ 審判 — Judgement",           "position": "←"},
      {"name": "Ⅶ 戀人 — The Lovers",           "position": "←"}
    ]
    

4-5. 分牌/疊牌 (deck.py)

雖說是將牌分成三疊,但也要考慮到實際抓牌的時候不會真的抓到很邊緣,所以會先設定區間。(這個方式昨天實作洗牌的時候也有類似的作法)

def split_and_merge(self, deck):
	"""
	把整副牌 deck 分成三疊 (①, ②, ③)
	從第一疊抓取一部分 → 第二疊,再從第二疊抓取 → 形成第三疊。
	每疊至少保留 5 張。
	→ 設定牌的順序 0 是最下面的一張
	"""
	n = len(deck)
	min_size = (n // 5)   # 每疊最少要有的張數 (總牌數的 1/5)
	
    print(f"min_size={min_size}")
          
	# 從第一疊取出第二疊
    cut_1 = random.randint(min_size, n - 2 * min_size)  
    pile1 = deck[:cut_1]
    pile2 = deck[cut_1:]
                 
	print(f"cut_1={cut_1}")
	print(f"pile1:{pile1}")
          
	# 從第二疊取出第三疊
	cut_2 = random.randint(min_size, len(pile2) - min_size)
	pile3 = pile2[cut_2:]
	pile2 = pile2[:cut_2]
                  
	print(f"cut_2={cut_2}")
	print(f"pile2:{pile2}")
	print(f"pile3:{pile3}")
	return pile3 + pile2 + pile1
  • 結果
    先上圖:
    https://ithelp.ithome.com.tw/upload/images/20250927/20168437GPg49zC7fu.png

  • 第一次切牌:切在第 7 張

    # 第一疊,cut_1=7
    [
      {"name": "Ⅴ 皇帝 — The Emperor",        "position": "→"},
      {"name": "ⅩⅧ 星星 — The Star",          "position": "→"},
      {"name": "ⅩⅣ 死神 — Death",             "position": "→"},
      {"name": "Ⅰ 愚者 — The Fool",           "position": "←"},
      {"name": "Ⅳ 皇后 — The Empress",        "position": "←"},
      {"name": "ⅩⅢ 吊人 — The Hanged Man",    "position": "←"},
      {"name": "Ⅺ 命運之輪 — Wheel of Fortune", "position": "→"}
    ]
    
  • 第二次切牌,切在第 6 張

    # 第二疊,cut_2=6
    [
      {"name": "Ⅱ 魔術師 — The Magician", "position": "←"},
      {"name": "ⅩⅫ 世界 — The World",    "position": "←"},
      {"name": "Ⅷ 戰車 — The Chariot",   "position": "→"},
      {"name": "ⅩⅨ 月亮 — The Moon",     "position": "←"},
      {"name": "ⅩⅤ 節制 — Temperance",   "position": "→"},
      {"name": "ⅩⅦ 高塔 — The Tower",    "position": "←"}
    ]
    
    # 第三疊
    [
      {"name": "ⅩⅥ 惡魔 — The Devil",          "position": "→"},
      {"name": "Ⅲ 女祭司 — The High Priestess", "position": "→"},
      {"name": "Ⅻ 正義 — Justice",             "position": "→"},
      {"name": "Ⅹ 隱者 — The Hermit",          "position": "→"},
      {"name": "Ⅵ 教宗 — The Hierophant",      "position": "←"},
      {"name": "Ⅸ 力量 — Strength",            "position": "←"},
      {"name": "ⅩⅩ 太陽 — The Sun",            "position": "→"},
      {"name": "ⅩⅪ 審判 — Judgement",          "position": "←"},
      {"name": "Ⅶ 戀人 — The Lovers",          "position": "←"}
    ]
    
  • 最後把它疊回去
    split_and_merge ->

    [
      {"name": "ⅩⅥ 惡魔 — The Devil",          "position": "→"},
      {"name": "Ⅲ 女祭司 — The High Priestess", "position": "→"},
      {"name": "Ⅻ 正義 — Justice",             "position": "→"},
      {"name": "Ⅹ 隱者 — The Hermit",          "position": "→"},
      {"name": "Ⅵ 教宗 — The Hierophant",      "position": "←"},
      {"name": "Ⅸ 力量 — Strength",            "position": "←"},
      {"name": "ⅩⅩ 太陽 — The Sun",            "position": "→"},
      {"name": "ⅩⅪ 審判 — Judgement",          "position": "←"},
      {"name": "Ⅶ 戀人 — The Lovers",          "position": "←"},
      {"name": "Ⅱ 魔術師 — The Magician",      "position": "←"},
      {"name": "ⅩⅫ 世界 — The World",         "position": "←"},
      {"name": "Ⅷ 戰車 — The Chariot",        "position": "→"},
      {"name": "ⅩⅨ 月亮 — The Moon",          "position": "←"},
      {"name": "ⅩⅤ 節制 — Temperance",        "position": "→"},
      {"name": "ⅩⅦ 高塔 — The Tower",         "position": "←"},
      {"name": "Ⅴ 皇帝 — The Emperor",        "position": "→"},
      {"name": "ⅩⅧ 星星 — The Star",          "position": "→"},
      {"name": "ⅩⅣ 死神 — Death",             "position": "→"},
      {"name": "Ⅰ 愚者 — The Fool",           "position": "←"},
      {"name": "Ⅳ 皇后 — The Empress",        "position": "←"},
      {"name": "ⅩⅢ 吊人 — The Hanged Man",    "position": "←"},
      {"name": "Ⅺ 命運之輪 — Wheel of Fortune", "position": "→"}
    ]
    

6. 逆時針調整牌的方向

def turn_counterclockwise(self, deck):
	"""
	將整副牌逆時針轉向
	"""
	counterclockwise = { "→":"↑", "←":"↓"}
	for card in deck:
	    card['position'] = counterclockwise[card['position']]
	return deck
  • 結果
    counterclockwise ->
    [
      {"name": "ⅩⅥ 惡魔 — The Devil",          "position": "↑"},
      {"name": "Ⅲ 女祭司 — The High Priestess", "position": "↑"},
      {"name": "Ⅻ 正義 — Justice",             "position": "↑"},
      {"name": "Ⅹ 隱者 — The Hermit",          "position": "↑"},
      {"name": "Ⅵ 教宗 — The Hierophant",      "position": "↓"},
      {"name": "Ⅸ 力量 — Strength",            "position": "↓"},
      {"name": "ⅩⅩ 太陽 — The Sun",            "position": "↑"},
      {"name": "ⅩⅪ 審判 — Judgement",          "position": "↓"},
      {"name": "Ⅶ 戀人 — The Lovers",          "position": "↓"},
      {"name": "Ⅱ 魔術師 — The Magician",       "position": "↓"},
      {"name": "ⅩⅫ 世界 — The World",          "position": "↓"},
      {"name": "Ⅷ 戰車 — The Chariot",         "position": "↑"},
      {"name": "ⅩⅨ 月亮 — The Moon",           "position": "↓"},
      {"name": "ⅩⅤ 節制 — Temperance",         "position": "↑"},
      {"name": "ⅩⅦ 高塔 — The Tower",          "position": "↓"},
      {"name": "Ⅴ 皇帝 — The Emperor",         "position": "↑"},
      {"name": "ⅩⅧ 星星 — The Star",           "position": "↑"},
      {"name": "ⅩⅣ 死神 — Death",              "position": "↑"},
      {"name": "Ⅰ 愚者 — The Fool",            "position": "↓"},
      {"name": "Ⅳ 皇后 — The Empress",         "position": "↓"},
      {"name": "ⅩⅢ 吊人 — The Hanged Man",     "position": "↓"},
      {"name": "Ⅺ 命運之輪 — Wheel of Fortune",  "position": "↑"}
    ]
    

最後一步其實放在步驟 3. 將牌收攏成疊 也可以吧?

是沒錯啦...雖然結果是一樣的,但是就感覺很沒溫度...
反正是練習專案,會想選擇自己喜歡的方式也很合理。

在實做過程中一度想把方向從4個改成8個甚至12個,但時間上來不及了...也不是這次題目的重點。
還是先往下進行後續的功能比較重要!

對了,最後的 main.py 長這個樣子:

if __name__ == "__main__":

    # 準備一副牌
    deck = major_arcana.copy()

    print(deck)

    deckService = DeckService()

    # 預先洗牌
    deck = deckService.shuffle_and_cut(deck)
    print(f"shuffle_and_cut -> {deck}")
    # 攤牌洗牌
    deck = deckService.spread_shuffle(deck)
    print(f"spread_shuffle -> {deck}")
    # 將牌收攏
    deck = deckService.gather_deck(deck)
    print(f"gather_deck -> {deck}")
    # 切牌
    deck = deckService.split_and_merge(deck)
    print(f"split_and_merge -> {deck}")
    # 逆時針調整牌的方向
    deck = deckService.turn_counterclockwise(deck)
    print(f"counterclockwise -> {deck}")

    print(deck)

上一篇
Day 12. 只是隨機一點都不浪漫
系列文
科學的盡頭是玄學?AI占卜小助手與知識庫驗證13
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言