用Python创建扑克牌(无大小王)并实现自动洗牌功能

斗地主的游戏大家都玩过,那么怎么用Python如何用最少的代码写出一副牌,并实现自动洗牌的功能呢?接下来,我就带大家来实现这个功能

首先用collections.namedtuple构建一个简单的类,来表示一张纸牌

 

import collections

card = collections.namedtuple('纸牌',['点数','花色'])

class FrenchDeck:
    ranks = [str(n) for n in range(2,11)] + list('JQKA')  #ranks为点数
    suits = '黑桃 方片 梅花 红桃'.split()   #suits为花色
    #黑桃:spades  方片:diamonds  梅花:clubs  红桃:hearts

    def __init__(self):
        self._cards = [card(rank,suit) for suit in self.suits
                                       for 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和Card
    deck._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 shuffle

card = collections.namedtuple('卡片',['点数','花色'])

class FrenchDeck:
    ranks = [str(n) for n in range(2,11)] + list('JQKA')
    suits = '黑桃 方片 梅花 红桃'.split() #黑桃:spades  方片:diamonds  梅花:clubs  红桃:hearts

    def __init__(self):
        self._cards = [card(rank,suit) for suit in self.suits
                                       for 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和Card
    deck._cards[position] = Card
FrenchDeck.__setitem__= set_card  #把那个函数赋值给FrenchDeck类的__setitem__属性
shuffle(deck)  #现在可以打乱deck了,因为FrenchDeck实现了可变序列协议所需的方法。
print(deck[:5])

 码字不易,如果对大家有帮助,希望点个赞,如果有什么建议或者意见,可以在评论区提出来,我看见都会回复的。

——资料参考来自《流程的Python》

  • 4
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值