利用Python破解斗地主残局详解


Posted in Python onJune 30, 2017

前言

相信大家都玩过斗地主,规则就不再介绍了。

直接上一张朋友圈看到的残局图:

利用Python破解斗地主残局详解

这道题我刚看到时,曾尝试用手工来破解,每次都以为找到了农民的必胜策略时,最后都发现其实农民跑不掉。由于手工破解无法穷尽所有可能性,所以这道题究竟农民有没有妙手跑掉呢,只能通过代码来帮助我们运算了。

本文将简要讲述怎么通过代码来求解此类问题,在最后会公布残局的最后结果,并开源代码以供大家吐槽。

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 True
 if 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后,剩下的手牌,实现也非常简单,只需简单的移除掉那些打出的牌即可。

效率

由于一副牌的可能手牌巨大,导致递归的分支数巨大。所以时间开销非常大,为阶乘级O(N!),根据斯特林公式,大约为O(N^N)。

由于可能会有很多重复的牌面出现,导致了很多重复的递归调用。所以加一个缓存能极大提升效率。

即对我方手牌和敌方手牌和上一轮手牌的描述(str(me_pokers)+str(enemy_pokers)+str(last_hand))为键,将求出的结果存进缓存字典中。下一次遇到相同的局面时,即可直接从缓存字典中取出,而无需再次重复计算。时间复杂度优化为指数级O(C^N)。

结果

代码运算出来的结果是,农民没有必胜策略。换言之,只要地主会玩,农民不可能赢。阶级固化已经如斯了么……

开源

代码放于Github: doudizhu_solver,或者大家可以本地下载,MIT协议,随便玩。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对三水点靠木的支持。

Python 相关文章推荐
Python基于有道实现英汉字典功能
Jul 25 Python
python相似模块用例
Mar 04 Python
Python中的if、else、elif语句用法简明讲解
Mar 11 Python
Python下载指定页面上图片的方法
May 12 Python
Python机器学习算法之k均值聚类(k-means)
Feb 23 Python
python3中获取文件当前绝对路径的两种方法
Apr 26 Python
Python3.6使用tesseract-ocr的正确方法
Oct 17 Python
Python中numpy模块常见用法demo实例小结
Mar 16 Python
python制作填词游戏步骤详解
May 05 Python
PIL对上传到Django的图片进行处理并保存的实例
Aug 07 Python
tensorflow没有output结点,存储成pb文件的例子
Jan 04 Python
python爬虫用scrapy获取影片的实例分析
Nov 23 Python
Python实现的文本编辑器功能示例
Jun 30 #Python
Python构建XML树结构的方法示例
Jun 30 #Python
基于python的Tkinter编写登陆注册界面
Jun 30 #Python
Python使用微信SDK实现的微信支付功能示例
Jun 30 #Python
python实现的二叉树定义与遍历算法实例
Jun 30 #Python
Python使用openpyxl读写excel文件的方法
Jun 30 #Python
python中关于for循环的碎碎念
Jun 30 #Python
You might like
关于php操作mysql执行数据库查询的一些常用操作汇总
2013/06/24 PHP
PHP使用SOAP调用.net的WebService数据
2013/11/12 PHP
php strnatcmp()函数的用法总结
2013/11/27 PHP
2014过年倒计时示例
2014/01/31 PHP
php实现的click captcha点击验证码类实例
2014/09/23 PHP
理解JavaScript中的事件
2006/09/23 Javascript
JavaScript 获取用户客户端操作系统版本
2009/08/25 Javascript
jQuery live( type, fn ) 委派事件实现
2009/10/11 Javascript
利用javascript的面向对象的特性实现限制试用期
2011/08/04 Javascript
理解JAVASCRIPT中hasOwnProperty()的作用
2013/06/05 Javascript
20条学习javascript的编程规范的建议
2014/11/28 Javascript
jQuery实现html表格动态添加新行的方法
2015/05/28 Javascript
JavaScript基础知识点归纳(推荐)
2016/07/09 Javascript
AngularJS中$http服务常用的应用及参数
2016/08/22 Javascript
解决Node.js使用MySQL出现connect ECONNREFUSED 127.0.0.1:3306的问题
2017/03/09 Javascript
JS实现带动画的回到顶部效果
2017/12/28 Javascript
微信小程序实现手指触摸画板
2018/07/09 Javascript
JS中超越现实的匿名函数用法实例分析
2019/06/21 Javascript
Django Admin实现上传图片校验功能
2016/03/06 Python
python调用外部程序的实操步骤
2019/03/04 Python
Tensorflow tf.nn.atrous_conv2d如何实现空洞卷积的
2020/04/20 Python
Python如何实现机器人聊天
2020/09/10 Python
加拿大当代时尚服饰、配饰和鞋类专业零售商和制造商:LE CHÂTEAU
2017/10/06 全球购物
《草原的早晨》教学反思
2014/04/08 职场文书
放飞梦想演讲稿800字
2014/08/26 职场文书
解放思想演讲稿
2014/09/11 职场文书
机械设计专业大学生职业生涯规划书范文
2014/09/13 职场文书
2014国庆节商场促销活动策划方案
2014/09/16 职场文书
工作失职检讨书(精华篇)
2014/10/15 职场文书
信仰纪录片观后感
2015/06/08 职场文书
无婚姻登记记录证明
2015/06/18 职场文书
高中数学教学反思范文
2016/02/18 职场文书
MySQL update set 和 and的区别
2021/05/08 MySQL
奥特曼十大神器:奥特手镯在榜,第一是贝利亚的神器
2022/03/18 日漫
经典《舰娘》游改全新动画预告 预定11月开播
2022/04/01 日漫
SQL Server中锁的用法
2022/05/20 SQL Server