游戏中经常使用的另一种随机事件是抽牌。这是随机的,因为会洗牌,所以你不知道下一张是什么牌。每次洗牌时,顺序都不同。
至于掷骰子和扔硬币,我们说每次扔出都有相同的概率,因为硬币(或骰子)没有记忆。不过纸牌就不同了。从一副牌中抽牌时,剩下的牌越来越少(大多数游戏中都是如此)。这会改变抽出剩余各张牌的概率。
例如,开始时是一整副牌,抽出红桃 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_id
和 rank_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_id
和 rank_id
创建其他属性 (花色、点数和分值)。这样还可以很容易地比较两张牌的点数,看哪一张牌的点数更大。
还应当另外增加两个属性,来方便在程序中使用这个纸牌对象。程序需要打印纸牌时,它可能希望打印类似“4H”或“4 of Hearts”(红桃 4)。对于花牌,可能打印成“JD”或“Jack of Diamonds”(方块 J)。我们将增加属性 short_name
和 long_name
,这样程序很容易就可以打印纸牌的不同描述(包括短名和长名)。
下面为纸牌建立一个类。见代码清单 23-4。
代码清单 23-4
Card
类
代码中的错误检查确保 rank_id
和 suit_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 张不同的牌。不论你运行多少次,都不会有同一张牌在手中出现两次的情况。
现在我们可以建立一副牌,并且可以从中随机地抽牌,增加到自己手中。听起来已经万事俱备,可以建立一个纸牌游戏了!在下一节,我们会建立一个纸牌游戏,这样你就可以与计算机玩这个游戏了。