扑克玩法:炸金花--数据分析
一、前言
又到了一年一度令人期待的年更时刻。今年过年,我彻底过了一把牌瘾。平时我打牌打的很少,但一到过年,只要和亲戚朋友聚在一起,我就像变了个人似的,特别喜欢拉人一起打扑克。
当然,我们只是随便玩玩,纯粹是为了打发时间。今年玩的项目挺多,也比较杂,包括麻将、牛牛、斗地主和炸金花。今年是我第一次比较系统地玩炸金花,之前虽然有所了解,但今年玩得比较久。当然,第一次玩难免会输,这也算是交了学费吧。
炸金花,别称“扎金花”“拖拉机”“赢三张”,通常需要三人及以上参与,起源于安徽省黄山市。游戏中使用一副52张的扑克牌(不含大小王),每位玩家会获得三张暗牌。游戏规则包括底注、看牌、跟注、加注、比牌和弃牌等操作。玩家通过比较手中的牌型来决定胜负,牌型从大到小依次为:豹子、顺金、金花、顺子、对子、散牌,而特殊牌型则超越所有常规牌型。此外,游戏还引入了押满、决战和孤注一掷等增加刺激性的元素。
这种玩法挺有意思的,很有策略性,也很刺激,所以我对它产生了一点兴趣。由于是第一次玩,很多牌抓在手上也不知道大小,容易被老手针对,自然也就容易输。
所以,我找了个时间,写了个程序,把所有的数据都分析了一遍,主要是想了解这种玩法中所有牌型的出现概率,对所有牌型的大小有个直观的了解,以后再也不会轻易被“诈”了。
下面贴出代码源码以及1亿次手牌的结果。
温馨提示:未成年人禁止赌bo。
二、代码
# Encoding: utf-8
# Author: 思必得
# Date: 2025/2/5 13:58
# Project Name: PythonFiles
# IDE: PyCharm
# File Name: 扑克牌
# Version:3.10.2
# Requires:3.10.2
# 模块说明:
"""
"""
# 导入模块:
from threading import Thread
import pickle
import random
from tqdm import trange
from paPath.mdText import ftWordFrequencyEx
# 更新日志:
"""
1、2025/2/5:
a、创建文件
"""
# 待修改:
"""
花色Unicode编码:print(list(map(chr, range(0x2660, 0x2668))))
扑克牌的Unicode编码(大致):print(list(map(chr, range(0x1F0A1, 0x1F0DD))))
"""
class csCard:
def __init__(self, pmPoint, pmFlower, pmMode='炸金花'):
assert pmPoint in list('23456789JQKA小大') + ['10']
assert pmFlower in list('♢♣♡♠')
assert pmMode in ['炸金花', '九点半', '斗地主']
self.abPoint = pmPoint
self.abFlower = pmFlower
self.abMode = pmMode
self._abValue = None # 添加缓存属性
self._abName = None # 添加缓存属性
@property
def abValue(self):
if self._abValue is None:
if self.abMode == '炸金花':
if self.abPoint in [str(x) for x in range(2, 11)]:
self._abValue = int(self.abPoint)
elif self.abPoint in ['小', '大']:
raise NotImplementedError('炸金花模式下,不支持小/大王')
else:
self._abValue = {'J': 11, 'Q': 12, 'K': 13, 'A': 14}[self.abPoint]
return self._abValue
@property
def abName(self):
if self._abName is None:
if self.abPoint in ['小', '大']:
self._abName = f'{self.abPoint}王'
else:
self._abName = f'{self.abPoint}{self.abFlower}'
return self._abName
@property
def abColor(self):
if self.abPoint == '小' or self.abFlower in ['♣', '♠']:
return '黑'
else:
return '红'
class csPlayCards:
def __init__(self, pmMode='炸金花', pmPersons=8, pmTimes=2):
self.abMode = pmMode # 玩法
self.abPersons = pmPersons # 人数
self.abTimes = pmTimes # n副扑克牌
self.abResult = [] # 所有结果
self.abCards = [] # 一副适用于当前玩法的扑克牌
self.mtCreatePairCards() # 初始化时创建扑克牌
@property
def abPersonCards(self): # 每局中,每个人的手牌数
if self.abMode == '炸金花':
return 3
def mtCreatePairCards(self): # 创建一副适用于当前玩法的扑克牌
if self.abMode == '炸金花':
self.abCards = [csCard(Point, Flower, self.abMode) for Point in list('23456789JQKA') + ['10'] for Flower in list('♢♣♡♠')]
return self.abCards
def mtShuffleCards(self, pmCards): # 洗牌
random.shuffle(pmCards)
return pmCards
def thTakeCards(self): # 发牌(整副扑克牌),根据人数和玩法,并把结果存储到self.abResult中
PairCards = self.mtShuffleCards(self.abCards)
while len(PairCards) >= self.abPersons * self.abPersonCards:
group = [] # 本局
for i in range(self.abPersons):
inHand = [] # 手牌
for j in range(self.abPersonCards):
inHand.append(PairCards[j * self.abPersons + i])
group.append(tuple(inHand))
self.abResult.append(group)
PairCards = PairCards[self.abPersons * self.abPersonCards:]
def mtTakeCards(self): # 发牌(整副扑克牌),根据人数和玩法,并把结果存储到self.abResult中
threads = []
for _ in trange(self.abTimes):
t = Thread(target=self.thTakeCards, args=())
threads.append(t)
t.start()
for _ in threads:
_.join() # 如果执行了join代码,则主线程会等待所有的子线程全部执行完毕后才会执行后面的代码。
return self.abResult
def mtDataPersistence(self): # 持久化数据
data = {self.abPersons: self.abPersons, self.abTimes: self.abTimes, 'Datas': self.abResult}
with open(f'data-{self.abPersons}-{self.abTimes}.pik', 'ab') as f: # 使用追加模式批量写入
pickle.dump(data, f)
def mtGetDataFromFile(self): # 从文件获取数据
with open(f'data-{self.abPersons}-{self.abTimes}.pik', 'rb') as f:
return pickle.load(f)['Datas']
def mtIsLeopard_炸金花(self, pmCards: tuple | list): # 判断是否为豹子
if len(set(map(lambda x: x.abValue, pmCards))) == 1:
return True
else:
return False
def mtIsGoldenFlower_炸金花(self, pmCards: tuple | list): # 判断是否为金花
if len(set(map(lambda x: x.abFlower, pmCards))) == 1:
return True
else:
return False
def mtIsSequence_炸金花(self, pmCards: tuple | list): # 判断是否为顺子
values = sorted(map(lambda x: x.abValue, pmCards), reverse=True)
if len(set(values)) != 3:
return False
else:
if values[0] - values[-1] == 2:
return True
else:
return False
def mtIsPair_炸金花(self, pmCards: tuple | list): # 判断是否为对子
if len(set(map(lambda x: x.abValue, pmCards))) == 2:
return True
else:
return False
def mtGetCardValue_炸金花(self, pmCards: tuple | list): # 获取牌的值
if self.mtIsLeopard_炸金花(pmCards):
return '豹子'
elif self.mtIsGoldenFlower_炸金花(pmCards) and self.mtIsSequence_炸金花(pmCards):
return '顺金'
elif self.mtIsGoldenFlower_炸金花(pmCards):
return '金花'
elif self.mtIsSequence_炸金花(pmCards):
return '顺子'
elif self.mtIsPair_炸金花(pmCards):
return '对子'
else:
value = max(map(lambda x: x.abValue, pmCards))
if value <= 10:
return str(value)
else:
return {11: 'J', 12: 'Q', 13: 'K', 14: 'A'}[value]
def mtAnalyze_炸金花(self):
lst1 = []
lst2 = []
result = self.mtTakeCards()
for group in result:
for hand in group:
value = self.mtGetCardValue_炸金花(hand)
lst1.append(value)
if value in ['对子', '金花', '顺子', '豹子', '顺金']:
lst2.append(value)
else:
lst2.append('单张')
ftWordFrequencyEx(lst1)
ftWordFrequencyEx(lst2)
if __name__ == '__main__':
from paBase.mdTools import csTimeit
with csTimeit():
pc = csPlayCards(pmTimes=6250000)
pc.mtAnalyze_炸金花()
# 以下代码用于测试程序是否正常
# pc = csPlayCards()
# Result = pc.mtTakeCards()
# for Group in Result:
# for Hand in Group:
# print([x.abName for x in sorted([x for x in Hand], reverse=True, key=lambda x: x.abValue)], pc.mtGetCardValue_炸金花(Hand))
三、结果及分析
上面的代码中,分析了625万副扑克牌,玩家数设置为8,一共产生了1250万回合的结果。每个回合有8次结果。总共1亿次的手牌结果。
以下为这些数据的分析结果:
目标代码片段开始时间:2025-02-06 11:10:59
100%|██████████| 6250000/6250000 [09:45<00:00, 10671.36it/s]
所有元素数量: 100000000
筛选长度后元素数量: 100000000
筛选长度后非重复元素数量: 15
全部筛选后非重复元素数量: 15
************************************************** 开始显示柱图 **************************************************序号 元素 频次 占比 柱图
1 A 17647739 17.65% --------------------------------------------------
2 对子 16938757 16.94% -----------------------------------------------
3 K 14663201 14.66% -----------------------------------------
4 Q 11945348 11.95% ---------------------------------
5 J 9504064 9.5% --------------------------
6 10 7325493 7.33% --------------------
7 9 5431772 5.43% ---------------
8 金花 4979492 4.98% --------------
9 8 3803200 3.8% ----------
10 顺子 2983865 2.98% --------
11 7 2441350 2.44% ------
12 6 1357287 1.36% —
13 5 544600 0.54% -
14 豹子 234922 0.23%
15 顺金 198910 0.2%************************************************** 结束显示柱图 **************************************************
所有元素数量: 100000000
筛选长度后元素数量: 100000000
筛选长度后非重复元素数量: 6
全部筛选后非重复元素数量: 6
************************************************** 开始显示柱图 **************************************************序号 元素 频次 占比 柱图
1 单张 74664054 74.7% --------------------------------------------------
2 对子 16938757 16.9% -----------
3 金花 4979492 5.0% —
4 顺子 2983865 3.0% -
5 豹子 234922 0.2%
6 顺金 198910 0.2%************************************************** 结束显示柱图 **************************************************