iT邦幫忙

2025 iThome 鐵人賽

DAY 12
0
終於可以做點有趣的事情了!

開心搓手

既然訂好了項目,當然就要開始逐步實踐。
先從抽牌開始!

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

實作步驟

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

先準備好要抽的牌 (cards.py)

22 張大阿爾克納,整整齊齊。

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

建立洗牌及抽牌邏輯 (deck.py)

def shuffle_and_cut(self, deck):
    random.shuffle(deck)
    return deck

寫完了!

...才怪。

雖然直接用 random 就能達到類似的效果,但既然是要占卜,當然就要照塔羅的規矩來才有儀式感!

塔羅牌的洗牌方法是這樣的:

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

依序實作

  1. 預先洗牌
    進行 3-5 次的洗牌+切牌

    def shuffle_and_cut(self, deck):
        """
        Shuffle the deck of cards in place.
        """
        actions = ['shuffle', 'cut'] # 定義動作:洗牌或切牌
        times = random.randint(3, 5) # 隨機決定要做幾次動作
    
        random.shuffle(deck) # 先洗一次牌
    
        for _ in range(times):
            action = random.choice(actions) # 隨機決定要洗牌還是切牌
            if action == 'shuffle':
                random.shuffle(deck)
            elif action == 'cut':
                # 切牌位置 (避免太靠近兩端,這裡限制在 1/4 ~ 3/4 之間)
                cut_point = random.randint(len(deck)//4, len(deck)*3//4)
                # 切牌: 把上半段移到下半段之後
                deck = deck[cut_point:] + deck[:cut_point]
    
        return deck
    
  2. 將牌全部攤開在桌面,固定以順時鐘的方式或逆時鐘的方向洗牌,依直覺喊停

    def spread_shuffle(self, deck):
        """
        模擬攤牌洗牌
        Args:
            deck (list): 牌堆
        Returns:
            list: 洗好的牌堆
        """
        direction = random.choice(["clockwise", "counterclockwise"]) # 順時鐘或逆時鐘
        rounds = random.randint(5, 10) # 要轉幾圈
    
        dq = deque(deck)
    
        for _ in range(rounds):
    
            # 每一圈隨機旋轉幾張方向
            steps = random.randint(len(deck)//3, len(deck)//2)
            for _ in range(steps):
                if direction == "clockwise":
                    dq.rotate(steps)   # 順時鐘
                else:
                    dq.rotate(-steps)  # 逆時鐘
    
                # 模擬攪動時會有些隨機亂數
                temp = list(dq) # 為處理先轉為 list
                random.shuffle(temp)
    
    
                # 模擬攪動時牌的方向會改變
                n = random.randint(3, len(temp)//2)  # 最少3張,最多整副牌的一半
                result = random.sample(range(0, len(temp)), n)
                for index in result:
                    temp[index]['position'] = self.__rotate_card(temp[index]['position'], direction)
    
                dq = deque(temp) # 處理完還原回 dq
    
        return list(dq)
    
    def __rotate_card(self, position, direction):
        """
        模擬塔羅牌方向變動
        """
        # clockwise	        順時針方向	(→ ↓ ← ↑)
        # counterclockwise	逆時針方向	 (← ↓ → ↑)
        clockwise = { "→":"↓", "↓":"←", "←":"↑", "↑":"→"}
        counterclockwise = { "←":"↓", "↓":"→", "→":"↑", "↑":"←"}
    
        choice =  clockwise if direction == 'clockwise' else counterclockwise
    
        return choice[position]
    

最後來看看運行的結果

from deck import DeckService
from cards import major_arcana

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}")
  • 還沒洗牌的樣子:print(deck)

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

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

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

直接 random 冷冰冰的,一點溫度都沒有。
占卜就是要有點儀式感才浪漫啊!

其實也有理性的因素在。
如果真的 50:50 處理正位逆位,牌面會出現過多的逆位,導致解讀趨向過度負面,那就失去占卜的意義了。


上一篇
Day 11. 有勇無謀是匹夫
系列文
科學的盡頭是玄學?AI占卜小助手與知識庫驗證12
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言