社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  Python

用python开发一个扑克小游戏,可别玩上瘾了~

Crossin的编程教室 • 4 年前 • 685 次点击  

京东图书-电子工业出版社年终开新盛典正在进行中(12.28~12.30),每满100减50,再送可叠加200减30的优惠券,有想要购买技术书籍的朋友,可在公众号中回复关键字 买书 直达活动页


大家好,欢迎来到 Crossin的编程教室 !

众所周知扑克牌可谓是居家旅行、桌面交友的必备道具,今天我们用 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 万场游戏并对各类牌型进行频率统计,可见与前述排列组合的计算所得概率基本一致。

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)

以上就是这个扑克牌小游戏的全部代码,感兴趣的同学可以自己试着运行及二次修改。

如果文章对你有帮助,欢迎转发/点赞/收藏~

作者:Seon

来源:可以叫我才哥


_往期文章推荐_

斗地主老是输?Python做个自动出牌器




如需了解付费精品课程及教学答疑服务
请在Crossin的编程教室内回复: 666

Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/125103