讀古今文學網 > 父與子的編程之旅:與小卡特一起學Python > 23.3 創建一副牌 >

23.3 創建一副牌

遊戲中經常使用的另一種隨機事件是抽牌。這是隨機的,因為會洗牌,所以你不知道下一張是什麼牌。每次洗牌時,順序都不同。

至於擲骰子和扔硬幣,我們說每次扔出都有相同的概率,因為硬幣(或骰子)沒有記憶。不過紙牌就不同了。從一副牌中抽牌時,剩下的牌越來越少(大多數遊戲中都是如此)。這會改變抽出剩餘各張牌的概率。

例如,開始時是一整副牌,抽出紅桃 4 的機會是 1/52,或者大約 2%。這是因為一副牌裡有 52 張牌,而只有一張紅桃 4。如果繼續抽牌(還沒有抽到紅桃 4),整副牌只剩下一半時,得到紅桃 4 的機會就是 1/26,或者大約 4%。剩下最後一張牌時,如果還沒有抽到紅桃 4,說明抽出紅桃 4 的機會就是 1/1,或者 100%。可以肯定下一個肯定會抽到紅桃 4,因為只剩下這一張牌了。

為什麼要告訴你所有這些呢?我只是想說明:如果要建立一個利用一副紙牌實現的計算機遊戲,就需要在整個過程中跟蹤已經從這副牌中取走了哪些牌。要做到這一點有一個很好的方法,就是利用列表。開始時列表中包含一副牌中的所有 52 張牌,我們使用 random.choice 函數隨機地從這個列表中選牌。每選出一張牌,可以使用 remove 把它從列表(這副牌)中刪除。

洗牌

在一個真正的紙牌遊戲中,我們要洗牌,也就是說要把紙牌雜亂地混在一起,讓它們有一種隨機的順序。這樣一來,我們可以只取最上面的一張牌,這張牌是隨機的。不過利用 random.choice 函數,總能從列表中隨機選擇。我們不必取「最上面」的牌,所以「洗牌」沒有意義。這就像把牌攤開,說「選一張牌,隨便哪張都行」。在一個紙牌遊戲中,如果每個人都這麼做,這會很耗費時間,不過在計算機程序中這非常容易。

紙牌對像

我們要使用一個列表作為「一副牌」。不過這些牌本身怎麼表示?如何存儲每張牌呢?是存儲為字符串還是整數?我們需要知道每張牌的哪些方面?

在紙牌遊戲中,我們通常需要知道一張牌的 3 個方面。

  • 花色——方塊、紅桃、梅花或黑桃。

  • 點數——A、2、3,…10、J、Q、K。

  • 分值——用數字編號的牌(2 到 10),通常分值就等於牌的點數。對於 J、Q 和 K,分值通常是 10,A 的分值可能是 1、11 或者另外某個值,這要依具體遊戲而定。

點數

分值

A

1 或11

2

2

3

3

4

4

5

5

6

6

7

7

8

8

9

9

10

10

J

10

Q

10

K

10

所以我們要跟蹤這 3 個方面,而且需要用某種容器把它們彙集在一起。利用列表可以做到,不過我們還必須記住每一項分別是什麼。另一種辦法是建立一個包含右面屬性的「牌」:

card.suitcard.rankcard.value  

下面就採用這種做法。我們還會增加另外兩個屬性 suit_idrank_id

  • suit_id 表示花色,是一個從 1 到 4 的數,其中 1 = 方塊,2 = 紅桃,3 = 梅花,4 =黑桃。

  • rank_id 是從 1 到 13 的數,其中

    1 = A

    2 = 2

    3 = 3

    10 = 10

    11 = J

    12 = Q

    13 = K

增加這兩個屬性的原因是,這樣我們可以很容易地使用一個嵌套 for 循環建立一副 52 張牌。可以用一個內循環對應點數(1 到 13),另外用一個外循環對應花色(1 到 4)。紙牌對象的 __init__ 方法根據 suit_idrank_id 創建其他屬性 (花色、點數和分值)。這樣還可以很容易地比較兩張牌的點數,看哪一張牌的點數更大。

還應當另外增加兩個屬性,來方便在程序中使用這個紙牌對象。程序需要打印紙牌時,它可能希望打印類似「4H」或「4 of Hearts」(紅桃 4)。對於花牌,可能打印成「JD」或「Jack of Diamonds」(方塊 J)。我們將增加屬性 short_namelong_name,這樣程序很容易就可以打印紙牌的不同描述(包括短名和長名)。

下面為紙牌建立一個類。見代碼清單 23-4。

代碼清單 23-4 Card

代碼中的錯誤檢查確保 rank_idsuit_id 在正常範圍內,而且是整數。否則,在程序中顯示紙牌時你就會看到諸如「7 of SuitError」或「RankError of Clubs」之類的錯誤結果。

設置 short_name 的代碼只是取了紙牌點數(6 或者 Jack)的數字或者第一個字母以及花色(Diamonds)的第一個字母,然後將它們放在一起。比如紅桃 K(King of Hearts),其 short_name 就是 KH。再比如黑桃 6(6 of Spades),其 short_name 就是 6S。

代碼清單 23-4 不是一個完整的程序。這只是 Card 類的類定義。因為這個類可以在不同程序中反覆使用,可能應該把它建立為一個模塊。把代碼清單 23-4 的代碼保存為 cards.py。

現在需要建立紙牌的一些實例——實際上,我們完全可以建立一整副牌!要測試我們的 Card 類,下面建立一個程序,創建一副 52 張的牌,然後隨機選 5 張並顯示它們的屬性。代碼清單 23-5 提供了相應代碼。

代碼清單 23-5 建立一副牌

內循環處理一種花色中的每張牌,外循環處理每種花色(13 張牌 × 4 種花色 = 52 張牌)。然後代碼從這副牌中選出 5 張,放在手中(形成一手牌)。另外還要從這副牌中刪除選出的這些牌。

如果運行代碼清單 23-5 中的代碼,應該能得到類似下面的結果:

7D = 7 of Diamonds   Value: 79H = 9 of Hearts   Value: 9KH = King of Hearts   Value: 106S = 6 of Spades   Value: 6KC = King of Clubs   Value: 10  

再運行這個代碼,會得到 5 張不同的牌。不論你運行多少次,都不會有同一張牌在手中出現兩次的情況。

現在我們可以建立一副牌,並且可以從中隨機地抽牌,增加到自己手中。聽起來已經萬事俱備,可以建立一個紙牌遊戲了!在下一節,我們會建立一個紙牌遊戲,這樣你就可以與計算機玩這個遊戲了。