编程语言
首页 > 编程语言> > 【Python】05 当我旁观诈鸡时,我在想些什么

【Python】05 当我旁观诈鸡时,我在想些什么

作者:互联网

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

  1. 单牌: A > K > Q > . . . > 2 A>K>Q>...>2 A>K>Q>...>2
  2. 牌型:豹子(三个头) > > >顺金(同花顺) > > >金花 > > >顺子(拖拉机) > > >对子 > > >散牌(单张)
  3. 不比花色
  4. 对于牌型相同情况,将判平局。

2 概率计算

从一副牌中任意抽取三张牌,牌型共有 C 52 3 = 22100 C_{52}^3=22100 C523​=22100种可能,下面利用古典概型计算各牌型概率4

3 程序模拟

采用随机抽样的方式,当样本容量足够大时,计算各牌型频率,可近似为概率。
程序设计的关键在于识别各种牌型,基本算法如下:

  1. 定义两个列表,分别存放牌号和花色;
  2. 从两个列表随机抽取一个数字,组合成一张牌
  3. 按此方法抽取不完全相同的三张牌
  4. 判断牌型
  5. 记录牌型到频数字典中
  6. 重复步骤2-5,直到达到设定的样本容量
  7. 统计频数,计算各牌型频率
  8. 绘制频率分布直方图
    模拟1000万次,各牌型出现频率如下图
    在这里插入图片描述

4 牌型胜率

每种牌型胜率的理论计算涉及到条件概率,较为复杂,采用程序模拟。
基本算法如下:

  1. 设定玩家人数
  2. 随机发牌
  3. 记录场上出现的牌型到出场频数字典中,重复的牌型都要计入
  4. 按照规则比较场上牌型大小,记录下最大的牌型到胜利频数字典中,相同牌型只记录一次
  5. 胜利频数字典对应牌型频数除以出场频数字典对应牌型频数,即为该牌型胜率
  6. 绘制各牌型胜率直方图
    随机发牌1000万次6位玩家各牌型胜率如下:
    在这里插入图片描述
    8位玩家各牌型胜率如下:
    在这里插入图片描述
    10位玩家各牌型胜率如下:
    在这里插入图片描述

5 结论

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) #绘制牌型胜率直方图

参考资料


  1. 诈金花百度百科 ↩︎

  2. 炸金花游戏(1)–炸金花游戏的模型设计和牌力评估 ↩︎

  3. 游戏:炸金花(理论分析+py3模拟) ↩︎

  4. 排列组合百度百科 ↩︎

标签:22100,C41,05,Python,牌型,52,cards,type,诈鸡时
来源: https://blog.csdn.net/weixin_43012724/article/details/104206959