德州扑克
德州扑克不使用鬼牌,只使用52张扑克牌,是一种不换牌扑克游戏。
牌局进行流程
第一轮前先决定座位及起始发牌位置。
1、洗牌并切牌。
2、本次轮到担任盲注的牌手下盲注。
3、为每位牌手发两张底牌。
4、翻牌前(preflop)喊注。
5、消一张牌后发出三张公共牌。
6、翻牌圈(flop)喊注。
7、消一张牌后发出一张公共牌(转牌)。
8、转牌圈(turn)喊注。
9、消一张牌后发出一张公共牌(河牌)。
10、河牌圈(river)喊注。
11、若有两家或以上未盖牌则斗牌比大小,依胜负分配总彩金。
12、结束本回合,庄家位置依顺时钟次序移动至下一家进行下一轮。
牌型大小规则
- 牌型大小依序为:同花大顺 > 同花顺 > 四条 > 葫芦 > 同花 > 顺子 > 三条 > 两对 > 一对 > 散牌
- 同花大顺/皇家同花顺:同花色的A,K,Q,J和10
- 同花顺:五张同花色的连续数字牌
- 四条:其中四张是相同数字的扑克牌,第五张是剩下牌组中最大的一张牌
- 满堂红:由三张相同数字及任何两张其他相同数字的扑克牌组成
- 同花:同一花色的任意五张牌
- 顺子:由五张连续数字扑克牌组成
- 三条:由三张相同数字和两张不同数字的扑克牌组成
- 两对:两对数字相同但两两不同的扑克和一张杂牌组成
- 一对:由两张相同数字的扑克牌和另三张无法组成牌型的杂牌组成
- 散牌:无法组成以上任一牌型的杂牌
概要需求分析
- 首先,要模拟出一副不含鬼牌扑克牌并进行洗牌
- 接着我们要为玩家分发扑克牌
- 最后我们要判断玩家手中的牌的类型
编码实现
模拟扑克牌(文件:card.py)
需求分析:
我们需要模拟出52张扑克牌,涵盖了四种花色、13个数字,其中A、J、Q、K四个数字比较特殊,我们需要对其进行特殊处理。
因此,该类需要有两个成员属性——花色和数字(即color和value)并对其进行封装(@property、@color.setter、@value.setter)
在实例化Card对象时,数字为1-13,其中1为A,11、12、13分别为J、Q、K,为了将扑克牌更直观的展现给用户,我们可以通过复写__str__方法来实现这种效果。在__str__方法内,我们将1、11、12、13分别转换为A、J、Q、K进行输出。
class Card:
"""
Card类:用于模拟与描述单张扑克牌的类
"""
def __init__(self):
"""
构造方法
两个成员变量分别表示该张扑克牌的花色和数字
"""
self.__color = ''
self.__value = 1
@property
def color(self):
"""
相当于java中的color的getter方法
:return:
"""
return self.__color
@color.setter
def color(self, value):
"""
相当于java中的color的setter方法
:param value:
:return:
"""
self.__color = value
@property
def value(self):
"""
相当于java中的value的getter方法
:return:
"""
return self. __value
@value.setter
def value(self, v):
"""
相当于java中的value的setter方法
:param v:
:return:
"""
# 防止非法扑克牌的出现
if v < 1 or v >= 14:
self.__value = 1
else:
self.__value = v
def __str__(self):
"""
相当于java中的toString()方法
:return:
"""
# 定义特殊点数字典
special_value = {1: 'A', 11: 'J', 12: 'Q', 13: 'K'}
# 如果赋值的点数在特殊的点数中,那么我们需要处理一下
if self.value in special_value:
# 将特殊点数key对应的value取出来
real_value = special_value[self.value]
else:
# 如果不是特殊的点数,那么转成字符串即可
real_value = str(self.__value)
#拼接输出对象信息 花色+点数
return self.__color + real_value+" "
测试:
from card import Card
card1 = Card()
card1.color = '♥'
card1.value = 1
print('card1', card1)
print('card1\'s color', card1.color)
print('card1\'s value', card1.value)
------------------------------------------------------------------------
结果:
card1 ♥A
card1's color ♥
card1's value 1
模拟发牌(文件:poke.py)
需求分析:
在发牌前我们需要有一副扑克牌,所以我们需要提供生成一副扑克牌的方法,而最好的实现方式就是在实例化时就已经生成了一副,所以我们可以将生成一副扑克牌的方法写在构造函数里。
显然,在发牌前我们必须保证这副扑克牌不是按照顺序摆放的,我们需要保证他是乱序的,我们可以通过random模块中的shuffle方法来进行乱序排序。
我们知道,德州扑克每个玩家的手牌是五张,所以我们只要抽取五张牌给玩家即可,可以通过切片的方式将扑克牌列表分割。通过字典的方式绑定玩家与其手牌,手牌使用列表,在发完牌后将此字典返回。
from card import Card
from random import shuffle
class Poke:
def __init__(self):
"""
构造方法
在Poke类被实例化时就生成一副有序扑克牌(没有鬼牌)
"""
colors = ["♥", "♠", "♣", "♦"]
values = [i for i in range(1, 14)]
# 装牌列表
self.poke = []
# 生产牌
for color in colors:
for value in values:
# 生产card
c = Card()
# 赋值花色
c.color = color
# 赋值点数
c.value = value
# 拼接
self.poke.append(c)
def __show(self):
"""显示所有的扑克牌"""
index = 0
for card in self.poke:
if index % 13 == 0:
print("")
print(card, end='')
index += 1
def shuffle(self):
"""洗牌"""
shuffle(self.poke)
def deal_poke(self, playerNum):
"""发牌"""
players = dict()
for i in range(0, playerNum):
cards = self.poke[i * 5:i * 5 + 5]
players['player' + str(i + 1)] = cards
return players
判断类型(文件:play.py)
需求分析:
由于玩家人数不确定,所以需要进行排错处理,德州扑克的玩家数是2-9,所以当用户输入错误玩家数时,会给予提示,无法开始游戏。
判断玩家手牌类型是一个比较难的地方。根据惯性思维,只要这个玩家满足了高等级的牌型,就不许用再去判断他是否满足低等级的牌型了,因此我们直接从最大的牌型来判断,并依次降低,即判断的顺序为同花大顺 > 同花顺 > 四条 > 葫芦 > 同花 > 顺子 > 三条 > 两对 > 一对 > 散牌。
皇家同花顺: 首先必须是同色花,即花色的种类只有一种;其数值类型只有一种选择(1、10、11、12、13),因此我们可以直接使用暴力比较,将列表[1, 10, 11, 12, 13]与排序后的values进行比较。
同花顺: 首先判断同色花;然后判断是否是连续的五张牌,五张连续的牌必然是五种类型,并且极差是4
四条: 首先,具有两种类型的数值;其次数值数量多的牌的个数与数值数量少的牌的个数相差3;并且要求单独的那一张牌是最大的。分析后两个需求,即小的数的个数要比大的数的个数多3个,因此我们可以获取小的数的个数和大的数的个数来进行做差。【那么事实上,在第二种思路下我们就没有必要去考虑数值种类了,因为相差是,相加是5,只能有一种结果,就是4-1,即只会有两种类型的数值。也就是我们将valuesLen == 2条件删掉也不会影响结果】
满堂红: 首先,有两种类型的数值;其次,一种数值有三个,另一种数值有两个,即较多数值类型的个数与较少数值类型的个数之差是1。【这个时候必须要判断数值类型种类数,因为3-2等于1,2-1也等于1,例2、2、5、5、6】
同花: 很简单,只有一种花色即可
顺子: 不需要考虑花色。五种数值类型、极差为4
三条: 首先,三种数值类型;其次较多数值类型的个数与较少数值类型的个数之差是2。
两对: 首先,三种数值类型;其次较多数值类型的个数与较少数值类型的个数之差是1。
一对: 首先,四种数值类型;其次较多数值类型的个数与较少数值类型的个数之差是1。
散牌: 除以上内容即是散牌。
from poke import Poke
class Play:
def __init__(self, number=5):
"""
初始化扑克堆并进行发牌
:param number: 参与人数
"""
self.poke = Poke()
self.poke.shuffle()
if number < 2 or number > 10:
print('玩家数错误,无法开始游戏')
self.players = self.poke.deal_poke(number)
def showPlayers(self):
"""展示所有玩家"""
print(self.players.keys())
def showPlayerCard(self, player):
"""展示指定玩家的牌"""
cards = self.players[player]
print(player + '\'s cards: ', end='')
for card in cards:
print(card, end='')
print()
def showAllPlayersCard(self):
"""展示所有玩家的牌"""
for player in self.players.keys():
self.showPlayerCard(player)
def showPlayerCardsType(self, player):
"""
展示指定玩家的牌型
:param player:
:return:
"""
# 获取该玩家的手牌
cards = self.players[player]
# 将该玩家的手牌的花色和数值分开
colors = []
values = []
for card in cards:
colors.append(card.color)
values.append(card.value)
# 获取该玩家手牌的花色和数值的种类数
# set()集合不允许重复,通过set()强制转型并取其长度,即可获得种类数
colorsLen = len(set(colors))
valuesLen = len(set(values))
# 获取该玩家手牌中 数值数量最多的牌的数量 与 数值数量最少的牌的数量 之差,最终结果为sameCardNum
# result,统计每种数值类型的个数的字典
# 例对于 3, 4, 4, 5, 5 数值数量分别为3:一个,4:两个,5:两个,所以...之差为2-1 = 1,result={3: 1, 4: 2, 5: 2}
# 例对于 4, 4, 4, 4, 5 数值数量分别4:四个,5:一个,所以...之差为4-1 = 3,result={4: 4, 5: 1}
result = dict()
for i in values:
if i not in result.keys():
result[i] = 1
else:
result[i] += 1
sameCardNum = max(result.values()) - min(result.values())
print('玩家' + player + '的牌型是')
if colorsLen == 1 and [1, 10, 11, 12, 13] == sorted(values):
print('大同花顺')
elif colorsLen == 1 and valuesLen == 5 and max(values) - min(values) == 4:
print('同花顺')
elif valuesLen == 2 and result[min(result.keys())] - result[max(result.keys())] == 3:
print('四条')
elif valuesLen == 2 and sameCardNum == 1:
print('满堂红')
elif colorsLen == 1:
print('同花')
elif valuesLen == 5 and max(values) - min(values) == 4:
print('顺子')
elif valuesLen == 3 and sameCardNum == 2:
print('三条')
elif valuesLen == 3 and sameCardNum == 1:
print('两队')
elif valuesLen == 4 and sameCardNum == 1:
print('一对')
else:
print('散牌')
def showAllPlayersCardsType(self):
"""
展示所有玩家的牌型
:return:
"""
for i in self.players.keys():
self.showPlayerCardsType(i)
开始游戏(文件:game.py)
需求分析: 开始游戏的程序千变万化,不做分析
代码分析: 玩家人数需要用户输入,因此加入了排错的代码;选择2和选择4需要用户输入,加入排错代码,此处排错直接使用异常处理,当在字典中查询不到数据时,会报出异常,直接在此处进行捕获处理即可;选择6直接结束本循环即可;选择7除了需要结束本循环以外,还要将外循环的flag修改为False
from play import Play
if __name__ == '__main__':
print('欢迎进入德州扑克游戏!')
flag = True
while flag:
number = int(input('请输入玩家人数(2-9):'))
if number < 2 or number > 10:
print('玩家人数有误,请重新输入:')
continue
play = Play(number)
print('扑克牌已为您分发完成')
while True:
choice = int(input('''请输入您的选择:
1、查看所有玩家 2、查看指定玩家的手牌
3、查看所有玩家的手牌 4、查看指定玩家的手牌类型
5、查看所有玩家的手牌类型 6、重新开始游戏 7、退出游戏
'''))
if choice == 1:
play.showPlayers()
elif choice == 2:
player = input('请输入您要查看的玩家:')
try:
play.showPlayerCard(player)
except:
print('没有该玩家!')
elif choice == 3:
play.showAllPlayersCard()
elif choice == 4:
player = input('请输入您要查看的玩家:')
try:
play.showPlayerCardsType(player)
except:
print('没有该玩家!')
elif choice == 5:
play.showAllPlayersCardsType()
elif choice == 6:
break
elif choice == 7:
flag = False
break
else:
print('您输入的数字有误!')
print('欢迎您下次再来')
致读者:打牌不在笔者的爱好之内且不善python,游戏规则有误或需补充、代码如有不当之处,请及时指出,笔者感激不尽。