0 前言
每逢春节,诈金花1作为一种休闲益智类扑克游戏,深受亲朋好友的热爱,以小博大,紧张刺激。下面尝试模拟诈金花游戏,比较各牌型胜率,给充满不确定性的游戏做个参考。
1 游戏规则
诈金花又称诈鸡、三张牌,偏重于运气和心理对抗2,游戏使用一副除去大小王的扑克牌,即 A , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 , J , Q , K A, 2, 3, 4, 5 ,6, 7, 8, 9, 10, J, Q, K A,2,3,4,5,6,7,8,9,10,J,Q,K共计 13 种牌型,每种牌型 4 种花色,总计 52 张牌。比牌规则3为
- 单牌: A > K > Q > . . . > 2 A>K>Q>...>2 A>K>Q>...>2
- 牌型:豹子(三个头) > > >顺金(同花顺) > > >金花 > > >顺子(拖拉机) > > >对子 > > >散牌(单张)
- 不比花色
- 对于牌型相同情况,将判平局。
2 概率计算
从一副牌中任意抽取三张牌,牌型共有 C 52 3 = 22100 C_{52}^3=22100 C523=22100种可能,下面利用古典概型计算各牌型概率4。
- 豹子(三个头): P 0 = 13 ∗ C 4 3 C 52 3 = 52 22100 = 0.2353 % P_0=\frac{13*C_4^3}{C_{52}^3}=\frac{52}{22100}=0.2353\% P0=C52313∗C43=2210052=0.2353%
- 顺金(同花顺): P 1 = 12 ∗ C 4 1 C 52 3 = 48 22100 = 0.2172 % P_1=\frac{12*C_4^1}{C_{52}^3}=\frac{48}{22100}=0.2172\% P1=C52312∗C41=2210048=0.2172%
- 金花: P 2 = C 13 3 C 4 1 − 12 ∗ C 4 1 C 52 3 = 1096 22100 = 4.9593 % P_2=\frac{C_{13}^3C_4^1-12*C_4^1}{C_{52}^3}=\frac{1096}{22100}=4.9593\% P2=C523C133C41−12∗C41=221001096=4.9593%
- 顺子(拖拉机): P 3 = 12 ∗ ( C 4 1 ∗ C 4 1 ∗ C 4 1 − C 4 1 ) C 52 3 = 720 22100 = 3.2579 % P_3=\frac{12*(C_4^1*C_4^1*C_4^1-C_4^1)}{C_{52}^3}=\frac{720}{22100}=3.2579\% P3=C52312∗(C41∗C41∗C41−C41)=22100720=3.2579%
- 对子: P 4 = C 13 1 ∗ C 4 2 ∗ C 12 1 ∗ C 4 1 C 52 3 = 3744 22100 = 16.9412 % P_4=\frac{C_{13}^1*C_4^2*C_{12}^1*C_4^1}{C_{52}^3}=\frac{3744}{22100}=16.9412\% P4=C523C131∗C42∗C121∗C41=221003744=16.9412%
- 单张: P 单 = C 52 3 − [ 13 ∗ C 4 3 + 12 ∗ C 4 1 + ( C 13 3 C 4 1 − 12 ∗ C 4 1 ) + 12 ∗ ( C 4 1 ∗ C 4 1 ∗ C 4 1 − C 4 1 ) + C 13 1 ∗ C 4 2 ∗ C 12 1 ∗ C 4 1 ] C 52 3 = 16440 22100 = 74.3891 % P_单=\frac{C_{52}^3-[13*C_4^3+12*C_4^1+(C_{13}^3C_4^1-12*C_4^1)+12*(C_4^1*C_4^1*C_4^1-C_4^1)+C_{13}^1*C_4^2*C_{12}^1*C_4^1]}{C_{52}^3}=\frac{16440}{22100}=74.3891\% P单=C523C523−[13∗C43+12∗C41+(C133C41−12∗C41)+12∗(C41∗C41∗C41−C41)+C131∗C42∗C121∗C41]=2210016440=74.3891%
- 单A: P A = C 4 1 ∗ C 12 2 ∗ C 4 1 ∗ C 4 1 − 2 ∗ C 4 1 ∗ C 4 1 ∗ C 4 1 − ( C 4 1 ∗ C 12 2 − 2 ∗ C 4 1 ) C 52 3 = 3840 22100 = 17.3756 % P_A=\frac{C_4^1*C_{12}^2*C_4^1*C_4^1-2*C_4^1*C_4^1*C_4^1-(C_4^1*C_{12}^2-2*C_4^1)}{C_{52}^3}=\frac{3840}{22100}=17.3756\% PA=C523C41∗C122∗C41∗C41−2∗C41∗C41∗C41−(C41∗C122−2∗C41)=221003840=17.3756%
- 单K: P K = C 4 1 ∗ C 11 2 ∗ C 4 1 ∗ C 4 1 − C 4 1 ∗ C 4 1 ∗ C 4 1 − ( C 4 1 ∗ C 11 2 − C 4 1 ) C 52 3 = 3240 22100 = 14.6606 % P_K=\frac{C_4^1*C_{11}^2*C_4^1*C_4^1-C_4^1*C_4^1*C_4^1-(C_4^1*C_{11}^2-C_4^1)}{C_{52}^3}=\frac{3240}{22100}=14.6606\% PK=C523C41∗C112∗C41∗C41−C41∗C41∗C41−(C41∗C112−C41)=221003240=14.6606%
- 单Q: P Q = C 4 1 ∗ C 10 2 ∗ C 4 1 ∗ C 4 1 − C 4 1 ∗ C 4 1 ∗ C 4 1 − ( C 4 1 ∗ C 10 2 − C 4 1 ) C 52 3 = 2640 22100 = 11.9457 % P_Q=\frac{C_4^1*C_{10}^2*C_4^1*C_4^1-C_4^1*C_4^1*C_4^1-(C_4^1*C_{10}^2-C_4^1)}{C_{52}^3}=\frac{2640}{22100}=11.9457\% PQ=C523C41∗C102∗C41∗C41−C41∗C41∗C41−(C41∗C102−C41)=221002640=11.9457%
- 单J: P J = C 4 1 ∗ C 9 2 ∗ C 4 1 ∗ C 4 1 − C 4 1 ∗ C 4 1 ∗ C 4 1 − ( C 4 1 ∗ C 9 2 − C 4 1 ) C 52 3 = 2100 22100 = 9.5023 % P_J=\frac{C_4^1*C_{9}^2*C_4^1*C_4^1-C_4^1*C_4^1*C_4^1-(C_4^1*C_{9}^2-C_4^1)}{C_{52}^3}=\frac{2100}{22100}=9.5023\% PJ=C523C41∗C92∗C41∗C41−C41∗C41∗C41−(C41∗C92−C41)=221002100=9.5023%
- 单10: P 10 = C 4 1 ∗ C 8 2 ∗ C 4 1 ∗ C 4 1 − C 4 1 ∗ C 4 1 ∗ C 4 1 − ( C 4 1 ∗ C 8 2 − C 4 1 ) C 52 3 = 1620 22100 = 7.3303 % P_{10}=\frac{C_4^1*C_{8}^2*C_4^1*C_4^1-C_4^1*C_4^1*C_4^1-(C_4^1*C_{8}^2-C_4^1)}{C_{52}^3}=\frac{1620}{22100}=7.3303\% P10=C523C41∗C82∗C41∗C41−C41∗C41∗C41−(C41∗C82−C41)=221001620=7.3303%
- 单9: P 9 = C 4 1 ∗ C 7 2 ∗ C 4 1 ∗ C 4 1 − C 4 1 ∗ C 4 1 ∗ C 4 1 − ( C 4 1 ∗ C 7 2 − C 4 1 ) C 52 3 = 1200 22100 = 5.4299 % P_9=\frac{C_4^1*C_{7}^2*C_4^1*C_4^1-C_4^1*C_4^1*C_4^1-(C_4^1*C_{7}^2-C_4^1)}{C_{52}^3}=\frac{1200}{22100}=5.4299\% P9=C523C41∗C72∗C41∗C41−C41∗C41∗C41−(C41∗C72−C41)=221001200=5.4299%
- 单8: P 8 = C 4 1 ∗ C 6 2 ∗ C 4 1 ∗ C 4 1 − C 4 1 ∗ C 4 1 ∗ C 4 1 − ( C 4 1 ∗ C 6 2 − C 4 1 ) C 52 3 = 840 22100 = 3.8009 % P_8=\frac{C_4^1*C_{6}^2*C_4^1*C_4^1-C_4^1*C_4^1*C_4^1-(C_4^1*C_{6}^2-C_4^1)}{C_{52}^3}=\frac{840}{22100}=3.8009\% P8=C523C41∗C62∗C41∗C41−C41∗C41∗C41−(C41∗C62−C41)=22100840=3.8009%
- 单7: P 7 = C 4 1 ∗ C 5 2 ∗ C 4 1 ∗ C 4 1 − C 4 1 ∗ C 4 1 ∗ C 4 1 − ( C 4 1 ∗ C 5 2 − C 4 1 ) C 52 3 = 540 22100 = 2.4434 % P_7=\frac{C_4^1*C_{5}^2*C_4^1*C_4^1-C_4^1*C_4^1*C_4^1-(C_4^1*C_{5}^2-C_4^1)}{C_{52}^3}=\frac{540}{22100}=2.4434\% P7=C523C41∗C52∗C41∗C41−C41∗C41∗C41−(C41∗C52−C41)=22100540=2.4434%
- 单6: P 6 = C 4 1 ∗ C 4 2 ∗ C 4 1 ∗ C 4 1 − C 4 1 ∗ C 4 1 ∗ C 4 1 − ( C 4 1 ∗ C 4 2 − C 4 1 ) C 52 3 = 300 22100 = 1.3575 % P_6=\frac{C_4^1*C_{4}^2*C_4^1*C_4^1-C_4^1*C_4^1*C_4^1-(C_4^1*C_{4}^2-C_4^1)}{C_{52}^3}=\frac{300}{22100}=1.3575\% P6=C523C41∗C42∗C41∗C41−C41∗C41∗C41−(C41∗C42−C41)=22100300=1.3575%
- 单5:
P
5
=
C
4
1
∗
C
3
2
∗
C
4
1
∗
C
4
1
−
C
4
1
∗
C
4
1
∗
C
4
1
−
(
C
4
1
∗
C
3
2
−
C
4
1
)
C
52
3
=
120
22100
=
0.5430
%
P_5=\frac{C_4^1*C_{3}^2*C_4^1*C_4^1-C_4^1*C_4^1*C_4^1-(C_4^1*C_{3}^2-C_4^1)}{C_{52}^3}=\frac{120}{22100}=0.5430\%
P5=C523C41∗C32∗C41∗C41−C41∗C41∗C41−(C41∗C32−C41)=22100120=0.5430%
不存在单4及其以下的单张牌型。
3 程序模拟
采用随机抽样的方式,当样本容量足够大时,计算各牌型频率,可近似为概率。
程序设计的关键在于识别各种牌型,基本算法如下:
- 定义两个列表,分别存放牌号和花色;
- 从两个列表随机抽取一个数字,组合成一张牌
- 按此方法抽取不完全相同的三张牌
- 判断牌型
- 记录牌型到频数字典中
- 重复步骤2-5,直到达到设定的样本容量
- 统计频数,计算各牌型频率
- 绘制频率分布直方图
模拟1000万次,各牌型出现频率如下图
4 牌型胜率
每种牌型胜率的理论计算涉及到条件概率,较为复杂,采用程序模拟。
基本算法如下:
- 设定玩家人数
- 随机发牌
- 记录场上出现的牌型到出场频数字典中,重复的牌型都要计入
- 按照规则比较场上牌型大小,记录下最大的牌型到胜利频数字典中,相同牌型只记录一次
- 胜利频数字典对应牌型频数除以出场频数字典对应牌型频数,即为该牌型胜率
- 绘制各牌型胜率直方图
随机发牌1000万次,6位玩家各牌型胜率如下:
8位玩家各牌型胜率如下:
10位玩家各牌型胜率如下:
5 结论
- 豹子比顺金出现的概率大,金花比顺子出现的概率大,但是豹子>顺金,金花>顺子
- 所有牌型中,单A是最容易出现的,其次是对子,再次是单K
- 单张牌型中,从单A到单5,出现的概率是递减的
- 随着玩家人数增多,各种牌型胜率都在减小,金花的胜率约为80%,存在较大不确定性,这可能是该游戏叫做诈金花的原因
6 代码
#创建时间:2021/2/20
#修改时间:2021/2/23
#程序目标:计算牌型出现概率及胜率
import random
import matplotlib.pyplot as plt
import numpy
import copy
name = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13] #牌名,分别表示A,2,3...J,Q,K
suit = [1, 2, 3, 4] #花色,分别表示红桃、梅花、方块、黑桃
poker = [] #一副牌,共52张,由牌名和花色组成
for i in name:
for j in suit:
poker.append([i,j])
poker_type = ["单A", "单2", "单3", "单4", "单5", "单6", "单7", "单8", "单9", "单10", '单J', '单Q', '单K']
card_1 = []
card_2 = []
card_3 = []
cards = [] #三张牌
Frequency = {"单5":0, "单6":0, "单7":0, "单8":0, "单9":0, "单10":0, "单J":0, "单Q":0, "单K":0, "单A":0, "对子":0, "顺子":0, "金花":0, "顺金":0, "豹子":0} #频数字典
FrequencyGroup = copy.deepcopy(Frequency) #出场频数字典
FrequencyVictory = copy.deepcopy(Frequency) #胜利频数字典
cards_type_rate = copy.deepcopy(Frequency) #胜率字典
rate = {} #频率字典
times = int(1e7) #抽牌次数
N = 8 #玩家数量
#随机抽取三张牌
def DrawCards(poker):
cards = random.sample(poker, 3)
return cards
#多人抽取三张牌
def DrawCardsGroup(N):
poker_temp = poker[:] #临时扑克牌组 注意:这里不能直接用poker_temp = poker,否则只是引用
cards_group = [] #多人的三张牌
for j in range(N):
cards_group.append(DrawCards(poker_temp)) #存放各人的三张牌列表
for card in cards_group[-1]: #去掉已抽取的牌
poker_temp.remove(card)
return cards_group
#判断牌型
def ClassifyCards(cards):
cards_name = [cards[0][0], cards[1][0], cards[2][0]] #牌名
cards_suit = [cards[0][1], cards[1][1], cards[2][1]] #花色
cards_name.sort()
cards_suit.sort()
cards_type = ""
if cards_suit[0] == cards_suit[2]: #三张牌花色相同,为顺金或金花
if (cards_name[2]-cards_name[1]==1 and cards_name[1]-cards_name[0]==1) or (cards_name[0]==1 and cards_name[1]==12 and cards_name[2]==13): #三张牌成等差数列即为顺金,否则为金花
cards_type = "顺金"
else:
cards_type = "金花"
elif (cards_name[2]-cards_name[1]==1 and cards_name[1]-cards_name[0]==1) or (cards_name[0]==1 and cards_name[1]==12 and cards_name[2]==13): #三张牌成等差数列,但花色不同
cards_type = "顺子"
elif (cards_name[0] == cards_name[1]) or (cards_name[1] == cards_name[2]): #牌名存在对子
if cards_name[0] == cards_name[2]: #三张牌牌名相同,即为豹子
cards_type = "豹子"
else:
cards_type = "对子"
else:
if cards_name[0] == 1: #单A
cards_type = "单A"
else:
cards_type = poker_type[cards_name[2]-1]
return cards_type
def CountCards(cards_type): #计算频数
Frequency[cards_type] = Frequency.get(cards_type, 0) + 1 #记录牌型频数到频数字典中
return Frequency
def CardsPower(cards_type): #确定牌型牌力
cards_power = list(Frequency.keys()).index(cards_type) #以列表索引代表牌力
return cards_power
def CardsType(cards_power): #根据牌力确定牌型
cards_type = list(Frequency.keys())[cards_power]
return cards_type
def CountCardsGroup(cards_group):
cards_power_group = [] #多人牌力列表
for cards in cards_group: #遍历所有人的手牌
cards_type = ClassifyCards(cards) #判断牌型
FrequencyGroup[cards_type] = FrequencyGroup.get(cards_type, 0) + 1 #记录牌型频数到出场频数字典中
cards_power = CardsPower(cards_type) #计算牌力
cards_power_group.append(cards_power) #添加牌力到列表中
cards_power_group.sort() #牌力排序,默认从小到大
cards_type = CardsType(cards_power_group[-1]) #判断胜利牌型
FrequencyVictory[cards_type] = FrequencyVictory.get(cards_type, 0) + 1 #记录胜利牌型频数到胜利频数字典中
return FrequencyGroup, FrequencyVictory
def CardsTypeRate(FrequencyGroup, FrequencyVictory): #计算牌型胜率
for key in FrequencyGroup:
if FrequencyGroup[key] != 0:
cards_type_rate[key] = FrequencyVictory[key] / FrequencyGroup[key] *100 #计算胜率
return cards_type_rate
#绘制各牌型频率分布直方图
def DrawRate(Frequency):
for key in Frequency:
rate[key] = Frequency[key] / times *100 #计算频率
card_type = list(rate.keys()) #牌型
card_rate = list(rate.values()) #频率
plt.rcParams["font.sans-serif"] = ["KaiTi"] #中文乱码处理
plt.rcParams['axes.unicode_minus'] = False
plt.bar(range(len(card_rate)),card_rate, align = "center",color = [numpy.random.random(3) for i in range(len(card_rate))],alpha = 0.6)
plt.ylabel("概率(%)")
plt.ylim([0,20])
plt.xticks(range(len(card_type)), card_type)
plt.xlabel("牌型")
plt.title("牌型频率分布直方图")
for x,y in enumerate(card_rate):
plt.text(x,y+0.5,'%s' %round(y,4),ha='center')# y+0.5 标签的坐标
plt.savefig(r"D:\program\MyPythonSpace\rate.png")
plt.show()
#绘制牌型胜率直方图
def DrawVictoryRate(rate):
card_type = list(rate.keys()) #牌型
card_rate = list(rate.values()) #胜率
plt.rcParams["font.sans-serif"] = ["KaiTi"] #中文乱码处理
plt.rcParams['axes.unicode_minus'] = False
plt.bar(range(len(card_rate)),card_rate, align = "center",color = [numpy.random.random(3) for i in range(len(card_rate))],alpha = 0.6)
plt.ylabel("胜率(%)")
plt.ylim([0,100])
plt.xticks(range(len(card_type)), card_type)
plt.xlabel("牌型")
plt.title("牌型胜率直方图")
for x,y in enumerate(card_rate):
plt.text(x,y+3,'%s' %round(y,4),ha='center')# y+300 标签的坐标
plt.savefig(r"D:\program\MyPythonSpace\victory_rate.png")
plt.show()
#主函数
for i in range(times):
cards = DrawCards(poker) #从一副扑克牌中随机抽取三张牌
cards_type = ClassifyCards(cards) #确定手牌牌型
Frequency = CountCards(cards_type) #记录频数
cards_group = DrawCardsGroup(N) #从一副扑克牌中随机抽取N个人的三张牌
FrequencyGroup, FrequencyVictory = CountCardsGroup(cards_group) #记录出场频数字典和胜利频数字典
#绘制直方图
cards_type_rate = CardsTypeRate(FrequencyGroup, FrequencyVictory) #计算牌型胜率
DrawRate(Frequency) #绘制牌型频率分布直方图
DrawVictoryRate(cards_type_rate) #绘制牌型胜率直方图