对于斗地主残局,用python实现solver
前面的话
前段时间呢,我发了一个朋友圈,是有关一个斗地主残局
刚刚看到有个人实现了残局的solver,缺点是在手牌多的时候时间复杂度还是挺高的。
minimax
代码的核心思想是minimax。minimax可以拆解为两部分,mini和max,分别是最小和最大的意思。
直观的理解是什么呢?就有点像A、B两个人下棋。A现在可以在N个点走棋,假设A在某个点走棋了,使得A的这一步的盘面评估分数最高;但是轮到B下的时候,就一定会朝着让A最不利的方向走,使得A的下一步必然按照B设定的轨迹来,而没法达到A在第一步时估算到这一步的最高盘面评分。
在牌局中是一样的,如果农民的一手牌,让地主无论如何应对都不能赢的话,那么可以说农民有必胜策略;否则,农民必输。
核心逻辑
我们可以用一个函数
hand_out
来模拟一个人的出牌过程。在现实生活中,一个人想要出牌的话,必然需要知道自己手上的所有牌:me_pokers
,也需要知道上一手的出的牌:last_hand
。如果我们要用这个函数来模拟两个人的出牌,则还需要知道对手当前的所有牌:enemy_pokers
。这个函数的返回值,是轮到我
me_pokers
出牌时,是否能够必赢牌。如果能赢则返回真,否则返回假。def hand_out(me_pokers, enemy_pokers, last_hand)
假设轮到我出牌时,如果我手上的牌都出完了,那么我将立刻知道我赢了;反之如果对手的牌都出完了,而我没有,则我失败了。
if not me_pokers:return True if not enemy_pokers:return False
因为现在轮到我出牌,所以我首先需要知道我现在能出的所有手牌组合。注意:这个组合中,包括 过牌(即不出牌)的策略。
all_hands = get_all_hands(me_pokers)
现在我们要对所有可能的手牌组合进行遍历。
首先我需要知道,上一手对方出的牌是什么。
- 如果对方上一手选择过牌,或者没有上一手牌,那么我这一轮必须不能过牌,但是我可以出任意的牌
- 如果对手上一手出了牌,则我必须要出一个比它更大的牌或者选择这一轮直接过牌(不出牌)
关键点来了,在出完我的牌或选择过牌后,我们需要用一个递归调用来模拟对手下一步的行为。如果对手的下一次出牌不能获胜的话,则我这一次的出牌必胜;否则,对于我的每一个出牌选择,对手都能获胜的话,则我必败。
def hand_out(me_pokers, enemy_pokers, last_hand, cache):if not me_pokers:# 我全部过牌,直接获胜return Trueif not enemy_pokers:# 对手全部过牌,我失败return False# 获取我当前可以出的所有手牌组合,包括过牌all_hands = get_all_hands(me_pokers)# 遍历我的所有出牌组合,进行模拟出牌for hand in all_hands:# 如果上一轮对手出了牌,则这一轮我必须要出比对手更大的牌 或者 对手上一轮选择过牌,那么我只需出任意牌,但是不能过牌if (last_hand and can_comb2_beat_comb1(last_hand, hand)) or (not last_hand and hand['type'] != COMB_TYPE.PASS):# 模拟对手出牌,如果对手不能取胜,则我必胜if not hand_out(enemy_pokers, make_hand(me_pokers, hand), hand, cache):return True# 如果上一轮对手出了牌,但我这一轮选择过牌elif last_hand and hand['type'] == COMB_TYPE.PASS:# 模拟对手出牌,如果对手不能取胜,则我必胜if not hand_out(enemy_pokers, me_pokers, None, cache):return True# 如果之前的所有出牌组合均不能必胜,则我必败return False
构建
以上核心逻辑理清楚后,构建破解器将变得十分简单。
首先,我们要用数字来表示牌的大小,这里我们用3表示3,11来表示J,12表示Q,依次类推……
其次,我们需要求出一个手牌的所有出牌组合,这里需要
get_all_hands
函数,具体实现比较繁琐但是很简单,就不在此赘述。然后,我们还需要一个牌力判断函数
can_comb2_beat_comb1(comb1, comb2)
,这个函数用于比较两组手牌的牌力,看是否comb2
可以击败comb1
。唯一需要注意的一点,在斗地主的规则中,除了炸弹外,其他所有牌力均等,只有牌型一样时才能去比较。最后,我们需要一个模拟出牌函数
make_hand(pokers, hand)
,用于求出在手牌为pokers
的情况下打出一手牌hand
后,剩下的手牌,实现也非常简单,只需简单的移除掉那些打出的牌即可。
最后
给出GitHub上有的源码
# -*- coding: UTF-8 -*-# 牌型枚举 class COMB_TYPE:PASS, SINGLE, PAIR, TRIPLE, TRIPLE_ONE, TRIPLE_TWO, FOURTH_TWO_ONES, FOURTH_TWO_PAIRS, STRIGHT, BOMB, KING_PAIR = range(11)# 根据牌,获取此副牌所有可能的牌型 # 牌型数据结构为牌类型,主牌,副牌 def get_all_hands(pokers):if not pokers:return []combs = [{'type':COMB_TYPE.PASS}]dic = {}for poker in pokers:dic[poker] = dic.get(poker, 0) + 1for poker in dic:if dic[poker] >= 1:# 单张combs.append({'type':COMB_TYPE.SINGLE, 'main':poker})if dic[poker] >= 2:# 对子combs.append({'type':COMB_TYPE.PAIR, 'main':poker})if dic[poker] >= 3:# 三带零combs.append({'type':COMB_TYPE.TRIPLE, 'main':poker})for poker2 in dic:if ALLOW_THREE_ONE and dic[poker2] >= 1 and poker2 != poker:# 三带一combs.append({'type':COMB_TYPE.TRIPLE_ONE, 'main':poker, 'sub':poker2})if ALLOW_THREE_TWO and dic[poker2] >= 2 and poker2 != poker:# 三带二combs.append({'type':COMB_TYPE.TRIPLE_TWO, 'main':poker, 'sub':poker2})if dic[poker] == 4:# 炸弹combs.append({'type':COMB_TYPE.BOMB, 'main':poker})if ALLOW_FOUR_TWO:pairs = []ones = []for poker2 in dic:if dic[poker2] == 1:ones.append(poker2)elif dic[poker2] == 2:pairs.append(poker2)for i in xrange(len(ones)):for j in xrange(i + 1, len(ones)):combs.append({'type':COMB_TYPE.FOURTH_TWO_ONES, 'main':poker, 'sub1':ones[i], 'sub2':ones[j]})for i in xrange(len(pairs)):combs.append({'type':COMB_TYPE.FOURTH_TWO_ONES, 'main':poker, 'sub1':pairs[i], 'sub2':pairs[i]})for j in xrange(i + 1, len(pairs)):combs.append({'type':COMB_TYPE.FOURTH_TWO_PAIRS, 'main':poker, 'sub1':pairs[i], 'sub2':pairs[j]})if 16 in pokers and 17 in pokers:# 王炸combs.append({'type':COMB_TYPE.KING_PAIR})# 所有顺子组合distincted_sorted_pokers = sorted(list(set(pokers)))lastPoker = distincted_sorted_pokers[0]sequence_num = 1i = 1while i < len(distincted_sorted_pokers):# 只有3-A能连成顺子if distincted_sorted_pokers[i] <= 14 and distincted_sorted_pokers[i] - lastPoker == 1:sequence_num += 1if sequence_num >= 5:j = 0while sequence_num - j >= 5:# 顺子combs.append({'type':COMB_TYPE.STRIGHT, 'main':sequence_num - j, 'sub':distincted_sorted_pokers[i]})j += 1else:sequence_num = 1lastPoker = distincted_sorted_pokers[i]i += 1return combs# comb1先出,问后出的comb2是否能打过comb1 def can_comb2_beat_comb1(comb1, comb2):if comb2['type'] == COMB_TYPE.PASS:return Falseif not comb1 or comb1['type'] == COMB_TYPE.PASS:return Trueif comb1['type'] == comb2['type']:if comb1['type'] == COMB_TYPE.STRIGHT:if comb1['main'] != comb2['main']:return Falseelse:return comb2['sub'] > comb1['sub']else:if comb1['main'] == comb2['main']:return comb2['sub'] > comb1['sub']else:return comb2['main'] > comb1['main']elif comb2['type'] == COMB_TYPE.BOMB or comb2['type'] == COMB_TYPE.KING_PAIR:return comb2['type'] > comb1['type']return False# 给定牌pokers,求打出手牌hand后的牌 def make_hand(pokers, hand):poker_clone = pokers[:]if hand['type'] == COMB_TYPE.SINGLE:poker_clone.remove(hand['main'])elif hand['type'] == COMB_TYPE.PAIR:poker_clone.remove(hand['main'])poker_clone.remove(hand['main'])elif hand['type'] == COMB_TYPE.TRIPLE:poker_clone.remove(hand['main'])poker_clone.remove(hand['main'])poker_clone.remove(hand['main'])elif hand['type'] == COMB_TYPE.TRIPLE_ONE:poker_clone.remove(hand['main'])poker_clone.remove(hand['main'])poker_clone.remove(hand['main'])poker_clone.remove(hand['sub'])elif hand['type'] == COMB_TYPE.TRIPLE_TWO:poker_clone.remove(hand['main'])poker_clone.remove(hand['main'])poker_clone.remove(hand['main'])poker_clone.remove(hand['sub'])poker_clone.remove(hand['sub'])elif hand['type'] == COMB_TYPE.FOURTH_TWO_ONES:poker_clone.remove(hand['main'])poker_clone.remove(hand['main'])poker_clone.remove(hand['main'])poker_clone.remove(hand['main'])poker_clone.remove(hand['sub1'])poker_clone.remove(hand['sub2'])elif hand['type'] == COMB_TYPE.FOURTH_TWO_PAIRS:poker_clone.remove(hand['main'])poker_clone.remove(hand['main'])poker_clone.remove(hand['main'])poker_clone.remove(hand['main'])poker_clone.remove(hand['sub1'])poker_clone.remove(hand['sub1'])poker_clone.remove(hand['sub2'])poker_clone.remove(hand['sub2'])elif hand['type'] == COMB_TYPE.STRIGHT:for i in xrange(hand['sub'], hand['sub'] - hand['main'], -1):poker_clone.remove(i)elif hand['type'] == COMB_TYPE.BOMB:poker_clone.remove(hand['main'])poker_clone.remove(hand['main'])poker_clone.remove(hand['main'])poker_clone.remove(hand['main'])elif hand['type'] == COMB_TYPE.KING_PAIR:poker_clone.remove(16)poker_clone.remove(17)return poker_clone# 模拟每次出牌,me_pokers为当前我的牌,enemy_pokers为对手的牌 # last_hand为上一手的手牌 def hand_out(me_pokers, enemy_pokers, last_hand, cache):if not me_pokers:return Trueif not enemy_pokers:return Falsekey = str(me_pokers) + str(enemy_pokers) + str(last_hand)if key in cache:return cache[key]all_hands = get_all_hands(me_pokers)for hand in all_hands:if (last_hand and can_comb2_beat_comb1(last_hand, hand)) or (not last_hand and hand['type'] != COMB_TYPE.PASS):if not hand_out(enemy_pokers, make_hand(me_pokers, hand), hand, cache):cache[key] = Truereturn Trueelif last_hand and hand['type'] == COMB_TYPE.PASS:if not hand_out(enemy_pokers, me_pokers, None, cache):cache[key] = Truereturn Truecache[key] = Falsereturn False# 残局1 # 是否允许三带一 ALLOW_THREE_ONE = True # 是否允许三带二 ALLOW_THREE_TWO = False # 是否允许四带二 ALLOW_FOUR_TWO = Truelord = [17,16,11,11,9,9,9] farmer = [3,3,3,3,4,5,6,7,10,10,14,14,14,14] print hand_out(farmer, lord, None, {})# 残局2 # # 是否允许三带一 # ALLOW_THREE_ONE = False # # 是否允许三带二 # ALLOW_THREE_TWO = False # # 是否允许四带二 # ALLOW_FOUR_TWO = True # # lord = [14,14,11,11] # farmer = [16,13,13,13,12,12,12,10,10,9,9,8,8] # print hand_out(farmer, lord, None, {})
对于斗地主残局,用python实现solver相关推荐
- 当Python碰上斗地主残局,该如何破解?AI对AI谁能更胜一筹?
前言 最近刷抖音,老是会碰到那种斗地主残局的直播或者视频,今天刚好礼拜六,又不忙,那反正闲来无事,不如写点东西玩一下.于是我就看到了在斗地主的同事,他每天就只能领低保,反正打不过别人就去破解残局!但是 ...
- 淘宝斗地主残局玩法技术方案总结
游戏互动是淘宝内容化建设的重要一环,其实自研的淘宝斗地主满足了部分人群的简单娱乐需求.本文主要介绍了淘宝斗地主新推出的残局玩法从0到1是如何实现的,笔者针对游戏链路到设计方案和可能出现的问题做了比较细 ...
- 斗地主残局破解算法,斗地主残局暴力求解器算法,秒解各种斗地主残局
斗地主残局破解,斗地主残局暴力求解器,秒解各种斗地主残局 秒解抖音.微信等各大平台的斗地主残局挑战 支持自定义出牌规则 输入双方的牌后单击"开始求解"按钮即可 求解完成后,电脑会自 ...
- C++大战Python - 以C++11重写欢乐斗地主残局解答器
业界传说Python平均一行代码能够顶的上几十行C/C++代码.业界还传说,C++效率能够达到Python的几十倍. 对于以上二者,笔者本来感觉也许差不多只是略夸张.笔者曾经用C++和Python分别 ...
- python斗地主游戏源码_我用tkinter写的一个斗地主练习复盘python程序
python写的斗地主模拟器使用说明,以及python的标准控件库tkinter的使用示例. http://vdisk.weibo.com/s/C5R1f8s9EVq2y 我用python写的一个斗地 ...
- 基于Alpha-Beta剪枝的欢乐斗地主残局辅助
2019年4月17日更新: 将搜索主函数优化为局部记忆化搜索,再次提高若干倍搜索速度 更新了main和player,helper无更新 1 #include "Player-v3.0.cpp ...
- 一份超全的Python学习资料汇总
一.学习Python必备技能图谱 二.0基础如何系统学习Python? 一.Python的普及入门 1.1 Python入门学习须知和书本配套学习建议 1.2 Python简史 1.3 Python的 ...
- 一份超级详细的Python零基础学习资料(仅此一家,可能会被404抓紧收藏)
一.学习Python必备技能图谱 二.0基础如何系统学习Python? 一.Python的普及入门 1.1 Python入门学习须知和书本配套学习建议 1.2 Python简史 1.3 Python的 ...
- 斗地主AI算法——第十七章の总结整理
转载请标明出处:https://blog.csdn.net/sm9sun/article/details/70878001 文章出自:九日王朝 查看全文 http://www.taodudu.cc/ ...
最新文章
- 报错解决:TypeError: Object type class 'str' cannot be passed to C code
- SEO的操作流程梗概
- app里使用163邮箱发送邮件,被163认为是垃圾邮件的坑爹经历!_ !
- ゾーン10進数、パック10進数
- MySQL数据库操作(DDL)
- wave格式分析,wave音频文件格式分析配程序
- figma导出android切图,谁再说Figma没办法导出标注和切图,你把这个插件转发给他...
- MATLAB基本操作(一):MATLAB中变量的文件存储
- js RegExp正则表达式常见用例
- linux这样去掉文件里高亮字体
- nes 红白机模拟器 第4篇 linux 手柄驱动支持
- EasyFlash | 让 Flash 成为小型 KV 数据库
- 把计算机怎么连接手机的网络助手在哪里,怎么将手机网络通过USB共享给电脑
- 计算机的常见故障处理实验报告,微机系统故障与处理-实验报告.doc
- Badboy 录制脚本提示“当前页面的脚本发生错误”解决
- 计算机显示时区怎么更改,电脑时区自动改怎么办
- 操作系统同步互斥问题
- 【Lesson 4】 和弦的大小增减属
- HMI-47-【多媒体】Title界面实现 2
- 数据可视化:科研论文配色