小王)并实现自动洗牌功能"/>
用Python创建扑克牌(无大小王)并实现自动洗牌功能
斗地主的游戏大家都玩过,那么怎么用Python如何用最少的代码写出一副牌,并实现自动洗牌的功能呢?接下来,我就带大家来实现这个功能
首先用collections.namedtuple构建一个简单的类,来表示一张纸牌
import collectionscard = collections.namedtuple('纸牌',['点数','花色'])class FrenchDeck:ranks = [str(n) for n in range(2,11)] + list('JQKA') #ranks为点数suits = '黑桃 方片 梅花 红桃'.split() #suits为花色#黑桃:spades 方片:diamonds 梅花:clubs 红桃:heartsdef __init__(self):self._cards = [card(rank,suit) for suit in self.suitsfor rank in self.ranks]def __len__(self):return len(self._cards)def __getitem__(self,position):return self._cards[position]
接下来我们输出一下
beer_card = card('9','黑桃')
print(beer_card)
运行结果为:
很轻松就得到了一个纸牌对象
我们再看看一叠牌一个有多少张牌,就要用到FrenchDeck函数了,它既短小又精悍,再结合len()函数就能实现啦
deck = FrenchDeck()
print(len(deck))
输出结果为:
这是没有包括大小王的,刚好52张。
接下来我们从中抽取特定的一张纸牌,比如第一张和最后一张:
print(deck[0])
print(deck[-1])
输出结果为:
结果没错,依照我们的排序顺序来的!
但每次都要人为抽取,太麻烦,于是干脆就让机器自己来吧!这时候就要用到从一个序列中随机选出一个元素的函数random.choice,代码如下:
from random import choice
print(choice(deck))
多执行几次,结果如下:
已经实现了随机抽取的目的。
因为__getitem__方法把[]操作交给了self._cards列表,所以我们的deck类自动支持切片(slicing)操作。再举个例子:下面列出了查看一摞牌最上面3张牌和只看牌面是A的牌的操作,其中第二种操作的具体方式是:先抽出索引是12的那张牌,然后每向后数13张牌拿一张。
print(deck[:3]) #查看一摞牌最上面的三张
print(deck[12::13]) #只看牌面是A的牌的操作(先抽出索引是12的那张牌,然后每向后数13张牌拿一张)
输出结果如下:
另外,仅仅实现了__getitem__方法,这一摞牌就变成可迭代的了:
for card in deck: #doctest:+ELLIPSIS(迭代)print(card)
输出结果如下:
反迭代也没关系:
for card in reversed(deck): # doctese : +ELLIPSIS(反迭代)print(card)
输出结果如下:
迭代通常是隐式的,譬如说一个集合类型没有实现__contains__方法,那么in运算符就会按顺序做一次迭代搜索。于是,in运算可以用在我们的FrenchDeck类上,因为它是可迭代的:
print(card('Q','红桃') in deck)
输出结果如下:
那么怎么排序呢?我们按照常规,用点数来判断扑克牌的大小,2最小,A最大;同时还要加上对花色的判定,黑桃最大、红桃次之、方块再次、梅花最小。通过以下代码来实现:
suit_values = dict(黑桃=3, 红桃=2, 方片=1, 梅花=0)
def spades_high(Card):rank_value = FrenchDeck.ranks.index(Card.点数)return rank_value * len(suit_values) + suit_values[Card.花色]
有了spades_high函数。就能对这摞牌进行升序排序了:
虽然FrenchDeck隐式地继承了object类,但功能却不是继承而来的。我们通过数据模型和一些合成来实现这些功能。通过实现__len__和__getitem__这两个特殊方法,FrenchDeck就跟一个Python自有的序列数据一样,可以体现出Python的核心语言特性(例如迭代跟切片)。同时这个类还可以用于标准库中诸如random.choice、reversed和sorted这些函数。另外,对合成的运用使得__len__和__getitem__的具体实现可以代理给self.__cards这个python列表(即list列表)
接下来就是重中之重的洗牌功能的实现了!
标准库中的random.shuffle函数用法如下:
from random import shuffle
l = list(range(10))
shuffle(l)
print(l)
输出结果如下:
然而,如果尝试打乱FrenchDeck实例,就会出现异常:
from random import shuffl
deck = FrenchDeck()
shuffle(deck)
print(deck)
其输出结果为:
错误消息相当明确, “'FrenchDeck' object does not support item assignment”(“FrenchDeck”对象不支持为元素赋值)。这个问题的原因是,shuffle函数要调换集合中元素的位置,而FrenchDeck只实现了不可变的序列协议。可变的序列还必须提供__setitem__方法。
Python是动态语言,因此我们可以在运行时修正这个问题,甚至还可以在交互式控制台中:
def set_card(deck, position, Card): #定义一个函数,它的参数为deck、position和Carddeck._cards[position] = Card
FrenchDeck.__setitem__= set_card #把那个函数赋值给FrenchDeck类的__setitem__属性
shuffle(deck) #现在可以打乱deck了,因为FrenchDeck实现了可变序列协议所需的方法。
print(deck[:5])
其多次输出结果为:
至此,我们就完成了洗牌的功能。
最后,附上完整代码:
import collections
from random import choice
from random import shufflecard = collections.namedtuple('卡片',['点数','花色'])class FrenchDeck:ranks = [str(n) for n in range(2,11)] + list('JQKA')suits = '黑桃 方片 梅花 红桃'.split() #黑桃:spades 方片:diamonds 梅花:clubs 红桃:heartsdef __init__(self):self._cards = [card(rank,suit) for suit in self.suitsfor rank in self.ranks]def __len__(self):return len(self._cards)def __getitem__(self,position):return self._cards[position]deck = FrenchDeck()def set_card(deck, position, Card): #定义一个函数,它的参数为deck、position和Carddeck._cards[position] = Card
FrenchDeck.__setitem__= set_card #把那个函数赋值给FrenchDeck类的__setitem__属性
shuffle(deck) #现在可以打乱deck了,因为FrenchDeck实现了可变序列协议所需的方法。
print(deck[:5])
码字不易,如果对大家有帮助,希望点个赞,如果有什么建议或者意见,可以在评论区提出来,我看见都会回复的。
——资料参考来自《流程的Python》
更多推荐
用Python创建扑克牌(无大小王)并实现自动洗牌功能
发布评论