[Python小项目] 扑克玩法:炸金花--数据分析

扑克玩法:炸金花--数据分析

一、前言

又到了一年一度令人期待的年更时刻。今年过年,我彻底过了一把牌瘾。平时我打牌打的很少,但一到过年,只要和亲戚朋友聚在一起,我就像变了个人似的,特别喜欢拉人一起打扑克。
当然,我们只是随便玩玩,纯粹是为了打发时间。今年玩的项目挺多,也比较杂,包括麻将、牛牛、斗地主和炸金花。今年是我第一次比较系统地玩炸金花,之前虽然有所了解,但今年玩得比较久。当然,第一次玩难免会输,这也算是交了学费吧。
炸金花,别称“扎金花”“拖拉机”“赢三张”,通常需要三人及以上参与,起源于安徽省黄山市。游戏中使用一副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%

************************************************** 结束显示柱图 **************************************************

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

0思必得0

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值