大家好,我是才哥。
众所周知扑克牌可谓是居家旅行、桌面交友的必备道具,今天我们用 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 resultpokers = 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 samplefrom collections import Counterdef 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 resultdef 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_grpdef 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写得很不错,感觉自己的编程技能都提升了。
有6位网友表示赞同!
第一次用Python做游戏,居然这么简单就完成了,激动死了!
有16位网友表示赞同!
好玩到停不下来,不过还是要注意不要沉迷哦。
有11位网友表示赞同!
简单易懂的操作,跟现实中的炸金花玩法差不多。
有16位网友表示赞同!
用Python开发小游戏太有趣了,以后可以试试其他语言吗?
有7位网友表示赞同!
这个游戏的设计挺有创意的,界面也很清爽。
有12位网友表示赞同!
平时学到的编程知识现在能用在实战中了,成就感满满。
有11位网友表示赞同!
炸金花小游戏里面的各种牌面很有细节感呢!
有16位网友表示赞同!
和朋友一起玩这游戏,气氛相当热烈哦。
有13位网友表示赞同!
简单又刺激,我已经连赢好几局了,哈哈。
有13位网友表示赞同!
虽然只是个小游戏,但开发的过程真的锻炼了我的耐心。
有9位网友表示赞同!
炸金花规则都弄得很清楚,不会让新手感到迷失呢!
有6位网友表示赞同!
用Python做游戏原来这么有成就感,感觉自己的编程更上一层楼了。
有10位网友表示赞同!
这游戏有点像小时候玩纸牌游戏的回忆,简单又好玩。
有5位网友表示赞同!
这个小游戏让人有了进一步练习Python的动力了。
有8位网友表示赞同!
别看是小游戏,里面有很多编程技巧可以学呢!
有20位网友表示赞同!
玩了这么多次,发现每次玩的感觉都不一样。
有12位网友表示赞同!
虽然小,但感觉每局都充满了挑战性。
有12位网友表示赞同!
炸金花这个小游戏还带了个提醒,让我记得不要沉迷,挺贴心的。
有15位网友表示赞同!