用python开发一个炸金花小游戏

众所周知扑克牌可谓是居家旅行、桌面交友的必备道具,

今天我们用 Python 来实现一个类似炸金花的扑克牌小游戏,先来看一下基本的游戏规则。

炸(诈)金花又叫三张牌,是在全国广泛流传的一种民间多人纸牌游戏。游戏使用一副除去大小王的扑克牌,共 4 个花色 52 张牌,各个玩家从中抽取 3 张牌,比较大小。各种牌型的大小顺序如下(按照全排列组合中出现的概率越小,牌型分数奖励越大):1、同花顺:三张同样花色且点数连续的牌,如红心2、红心3、红心4;2、豹子:三张点数一样的牌,如 AAA、222;3、顺子:三张点数连续的牌,如红心2、黑桃3、方块4;4、金花:三张同样花色的牌,如红心2、红心5、红心8;5、对子:两张点数一样的牌,如红心2、黑桃2;6、单张:2~10 < J < Q < K < A。以下概率截自百度百科:

注:本文所述游戏规则与实际有所不同,主要基于对不同牌型的比较进行设计

一、游戏流程实现

1、准备扑克牌

开始游戏前,需要先生成一副满足要求的扑克牌,牌友们都知道,扑克牌有以下四种花色,每种花色有 A、2~10、J、Q、K 等 13 张牌。

复制代码
suit = ["黑桃", "红心", "方块", "梅花"]
num = [str(i) for i in range(2, 11)] + ["J", "Q", "K", "A"]

为了便于后续算分,先给每一个单张赋予相应的点数。

复制代码
score_map = {}  # 单张点数映射表
for s in suit:
    count = 2
    for n in num:
        score_map[f"{s}{n}"] = count
        count += 1

扑克牌点数预览如下:

score_map = {'黑桃2': 2, '黑桃3': 3, '黑桃4': 4, '黑桃5': 5, '黑桃6': 6, '黑桃7': 7, '黑桃8': 8, '黑桃9': 9, '黑桃10': 10, '黑桃J': 11, '黑桃Q': 12, '黑桃K': 13, '黑桃A': 14, '红心2': 2, ... }

2、玩家入场

以 p1、p2 等名称对玩家进行区分,我们先邀请 5 个玩家入场。

复制代码
players = [f"p{i}" for i in range(1, 6)]

3、发牌

将玩家和扑克牌列表作为参数,传入发牌器。发牌器在扑克牌中进行不放回抽取,为每个玩家随机抽取 3 张牌,并记下玩家名称及其对应牌组。

复制代码
def get_pk_lst(pls, pks):
    result = []
    for p in pls:
        pk = sample(pks, 3)
        for _pk in pk:
            pks.remove(_pk)
        result.append({"name": p, "poker": pk})
    return result

pokers = list(score_map.keys())  # 去掉大小王的一幅扑克
poker_grp = get_pk_lst(players, pokers)  # 发牌

发牌预览如下:

result = [{'name': 'p1', 'poker': ['方块5', '梅花3', '方块A']}, {'name': 'p2', 'poker': ['黑桃4', '方块8', '黑桃J']}, {'name': 'p3', 'poker': ['红心10', '红心K', '方块7']}, {'name': 'p4', 'poker': ['方块4', '梅花6', '方块J']}, {'name': 'p5', 'poker': ['红心5', '梅花10', '黑桃A']}]

4、判断牌型及算分

在算分之前先按之前的映射字典,将 pk_lst 里的 3 张扑克牌转换成对应的点数。

复制代码
n_lst = list(map(lambda x: score_map[x], pk_lst))  # 点数映射

接下来截取花色部分的文本,利用集合去重后判断是否为三张同花。

复制代码
same_suit = len(set([pk[:2] for pk in pk_lst])) == 1  # 是否同花色

再对点数部分进行排序,与依靠点数的最值生成的顺序列表进行比较,判断是否为连续的点数。要注意的是,A23 与 QKA 一样被视作顺子。

复制代码
continuity = sorted(n_lst) == [i for i in range(min(n_lst), max(n_lst) + 1)] or set(n_lst) == {14, 2, 3}  # 是否连续

别忘了考虑对子和豹子的检查方式。

复制代码
check = len(set(n_lst))  # 重复情况

那么正式开始判断牌型和算分吧!首先是单张,非同花、非顺子、三张点数不一。得分以 3 个单张点数相加。

复制代码
if not same_suit and not continuity and check == 3:
    return sum(n_lst), "单张"

其次是对子,非同花,有且仅有两张点数一致。得分中对于构成对子的部分给予 2 倍奖励。

复制代码
if not same_suit and check == 2:
    w = [i for i in n_lst if n_lst.count(i) == 2][0]
    single = [i for i in n_lst if i != w][0]
    return w*2*2 + single, "对子"

金花,即同花而非顺子,给予 9 倍奖励。

复制代码
if same_suit and not continuity:
    return sum(n_lst)*9, "金花"

顺子,即点数连续而非同花,给予 81 倍奖励。

复制代码
if continuity and not same_suit:
    return sum(n_lst)*81, "顺子"

豹子,即三张点数一致,这不得刷个 666 嘛。

复制代码
if check == 1:
    return sum(n_lst)*666, "豹子"

同花顺,同花色且点数连续,绝了,赌神一个技能 999 伤害。

复制代码
if continuity and same_suit:
    return sum(n_lst)*999, "同花顺"

5、决出胜负

一组玩家、抽牌、算分、牌型记录如下:

pk_grp = [{'name': 'p1', 'poker': ['方块5', '梅花3', '方块A'], 'score': 22, 'type': '单张'}, {'name': 'p2', 'poker': ['黑桃4', '方块8', '黑桃J'], 'score': 23, 'type': '单张'}, {'name': 'p3', 'poker': ['红心10', '红心K', '方块7'], 'score': 30, 'type': '单张'}, {'name': 'p4', 'poker': ['方块4', '梅花6', '方块J'], 'score': 21, 'type': '单张'}, {'name': 'p5', 'poker': ['红心5', '梅花10', '黑桃A'], 'score': 29, 'type': '单张'}]

利用 max 函数找出来谁是最棒的,公布名字!

复制代码
best = max(pk_grp, key=lambda x: x["score"])["name"]

赢家是------ p3

好啦,又可以开始下一场愉快的游戏了~

二、统计及源码

1、牌型统计

进行了 10 万场游戏并对各类牌型进行频率统计,可见与前述排列组合的计算所得概率基本一致。另外,搜索公众号Linux就该这样学后台回复"猴子",获取一份惊喜礼包。

复制代码
Counter({'单张': 371856, '对子': 84773, '金花': 24833, '顺子': 16239, '豹子': 1179, '同花顺': 1120})
单张频率:74.37%
对子频率:16.95%
金花频率:4.97%
顺子频率:3.25%
豹子频率:0.24%
同花顺频率:0.22%

2、牌局案例

各类牌型的局面和结果如下:

复制代码
开牌结果------
{'name': 'p1', 'poker': ['方块5', '梅花3', '方块A'], 'score': 22, 'type': '单张'}
{'name': 'p2', 'poker': ['黑桃4', '方块8', '黑桃J'], 'score': 23, 'type': '单张'}
{'name': 'p3', 'poker': ['红心10', '红心K', '方块7'], 'score': 30, 'type': '单张'}
{'name': 'p4', 'poker': ['方块4', '梅花6', '方块J'], 'score': 21, 'type': '单张'}
{'name': 'p5', 'poker': ['红心5', '梅花10', '黑桃A'], 'score': 29, 'type': '单张'}
赢家是------
p3


开牌结果------
{'name': 'p1', 'poker': ['方块Q', '黑桃5', '黑桃K'], 'score': 30, 'type': '单张'}
{'name': 'p2', 'poker': ['黑桃2', '方块2', '红心10'], 'score': 18, 'type': '对子'}
{'name': 'p3', 'poker': ['梅花2', '黑桃4', '梅花J'], 'score': 17, 'type': '单张'}
{'name': 'p4', 'poker': ['红心K', '梅花7', '红心6'], 'score': 26, 'type': '单张'}
{'name': 'p5', 'poker': ['方块A', '方块6', '红心4'], 'score': 24, 'type': '单张'}
赢家是------
p1


开牌结果------
{'name': 'p1', 'poker': ['黑桃J', '黑桃5', '黑桃4'], 'score': 180, 'type': '金花'}
{'name': 'p2', 'poker': ['梅花7', '红心4', '梅花5'], 'score': 16, 'type': '单张'}
{'name': 'p3', 'poker': ['方块5', '黑桃9', '梅花10'], 'score': 24, 'type': '单张'}
{'name': 'p4', 'poker': ['黑桃Q', '梅花9', '黑桃10'], 'score': 31, 'type': '单张'}
{'name': 'p5', 'poker': ['红心9', '方块9', '红心A'], 'score': 50, 'type': '对子'}
赢家是------
p1


开牌结果------
{'name': 'p1', 'poker': ['方块8', '黑桃10', '方块9'], 'score': 2187, 'type': '顺子'}
{'name': 'p2', 'poker': ['梅花9', '红心Q', '黑桃3'], 'score': 24, 'type': '单张'}
{'name': 'p3', 'poker': ['方块A', '梅花K', '黑桃4'], 'score': 31, 'type': '单张'}
{'name': 'p4', 'poker': ['方块J', '红心J', '红心6'], 'score': 50, 'type': '对子'}
{'name': 'p5', 'poker': ['梅花5', '黑桃K', '方块3'], 'score': 21, 'type': '单张'}
赢家是------
p1


开牌结果------
{'name': 'p1', 'poker': ['黑桃Q', '黑桃8', '梅花6'], 'score': 26, 'type': '单张'}
{'name': 'p2', 'poker': ['红心3', '梅花3', '黑桃3'], 'score': 5994, 'type': '豹子'}
{'name': 'p3', 'poker': ['红心A', '红心6', '方块5'], 'score': 25, 'type': '单张'}
{'name': 'p4', 'poker': ['黑桃4', '梅花A', '方块2'], 'score': 20, 'type': '单张'}
{'name': 'p5', 'poker': ['梅花7', '黑桃6', '梅花8'], 'score': 1701, 'type': '顺子'}
赢家是------
p2


开牌结果------
{'name': 'p1', 'poker': ['黑桃5', '梅花9', '方块9'], 'score': 41, 'type': '对子'}
{'name': 'p2', 'poker': ['黑桃Q', '黑桃2', '红心Q'], 'score': 50, 'type': '对子'}
{'name': 'p3', 'poker': ['红心2', '黑桃7', '红心5'], 'score': 14, 'type': '单张'}
{'name': 'p4', 'poker': ['梅花3', '方块10', '黑桃A'], 'score': 27, 'type': '单张'}
{'name': 'p5', 'poker': ['黑桃9', '黑桃J', '黑桃10'], 'score': 29970, 'type': '同花顺'}
赢家是------
p5

3、完整代码

复制代码
# @Seon
# 炸金花

from random import sample
from collections import Counter


def get_pk_lst(pls, pks):  # 发牌
    result = []
    for p in pls:
        pk = sample(pks, 3)
        for _pk in pk:
            pks.remove(_pk)
        result.append({"name": p, "poker": pk})
    return result


def calculate(_score_map, pk_lst):  # 返回得分和牌型
    n_lst = list(map(lambda x: _score_map[x], pk_lst))  # 点数映射
    same_suit = len(set([pk[:2] for pk in pk_lst])) == 1  # 是否同花色
    continuity = sorted(n_lst) == [i for i in range(min(n_lst), max(n_lst) + 1)] or set(n_lst) == {14, 2, 3}  # 是否连续
    check = len(set(n_lst))  # 重复情况
    if not same_suit and not continuity and check == 3:
        return sum(n_lst), "单张"
    if not same_suit and check == 2:
        w = [i for i in n_lst if n_lst.count(i) == 2][0]
        single = [i for i in n_lst if i != w][0]
        return w*2*2 + single, "对子"
    if same_suit and not continuity:
        return sum(n_lst)*9, "金花"
    if continuity and not same_suit:
        return sum(n_lst)*81, "顺子"
    if check == 1:
        return sum(n_lst)*666, "豹子"
    if continuity and same_suit:
        return sum(n_lst)*999, "同花顺"


def compare(_score_map, pk_grp):  # 比大小
    for p in pk_grp:
        p["score"], p["type"] = calculate(_score_map, p["poker"])
    print("开牌结果------")
    for p in pk_grp:
        print(p)
    print("赢家是------")
    best = max(pk_grp, key=lambda x: x["score"])["name"]
    print(best)
    return pk_grp


def show(_score_map, _players):   # 开局
    pokers = list(_score_map.keys())
    poker_grp = get_pk_lst(_players, pokers)
    return compare(_score_map, poker_grp)


def start_game(_score_map, _players, freq=1):   # 游戏和统计
    type_lst = []
    for i in range(freq):
        grp = show(_score_map, _players)
        type_lst = type_lst + [t["type"] for t in grp]
    c = Counter(type_lst)
    print(c)
    total = sum(c.values())
    for item in c.items():
        print(f"{item[0]}频率:{item[1]/total:.2%}")


if __name__ == '__main__':
    # 准备扑克牌
    suit = ["黑桃", "红心", "方块", "梅花"]
    num = [str(i) for i in range(2, 11)] + ["J", "Q", "K", "A"]
    score_map = {}  # 单张点数映射表
    for s in suit:
        count = 2
        for n in num:
            score_map[f"{s}{n}"] = count
            count += 1
    # 5个玩家入场
    players = [f"p{i}" for i in range(1, 6)]
    # 开始游戏
    start_game(score_map, players, freq=100000)
复制代码
相关推荐
小兔崽子去哪了7 分钟前
RFM 模型 项目实战
python
昨天那个谁谁19 分钟前
ROS2运行时报无法加载create_key等符号错误
c++·python·ros2
nju_spy1 小时前
python 算法题基础常用总结(比赛 or 机试 or 面试)
python·记忆化搜索·位运算·二分查找 - bisect·排序与lambda·最短路和最小生成树·堆与优先队列
Deng8723473481 小时前
自动化极验3点选验证码的识别与验证方案
运维·python·自动化
川石课堂软件测试1 小时前
自动化测试的基本概念及常用框架
数据库·python·功能测试·测试工具·单元测试·自动化·流程图
灰勒塔德1 小时前
jetson orin nano super开发指南
linux·服务器·python
8278209372 小时前
python scp 备份
开发语言·python
poggioxay2 小时前
JAVA零基础入门知识3(持续更新中)
java·开发语言·python
serve the people2 小时前
TensorFlow 基础训练循环(简化版 + 补全代码)
人工智能·python·tensorflow
木里先森2 小时前
解决报错:/lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.32‘ not found
linux·python