前面的话

前段时间呢,我发了一个朋友圈,是有关一个斗地主残局

刚刚看到有个人实现了残局的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相关推荐

  1. 当Python碰上斗地主残局,该如何破解?AI对AI谁能更胜一筹?

    前言 最近刷抖音,老是会碰到那种斗地主残局的直播或者视频,今天刚好礼拜六,又不忙,那反正闲来无事,不如写点东西玩一下.于是我就看到了在斗地主的同事,他每天就只能领低保,反正打不过别人就去破解残局!但是 ...

  2. 淘宝斗地主残局玩法技术方案总结

    游戏互动是淘宝内容化建设的重要一环,其实自研的淘宝斗地主满足了部分人群的简单娱乐需求.本文主要介绍了淘宝斗地主新推出的残局玩法从0到1是如何实现的,笔者针对游戏链路到设计方案和可能出现的问题做了比较细 ...

  3. 斗地主残局破解算法,斗地主残局暴力求解器算法,秒解各种斗地主残局

    斗地主残局破解,斗地主残局暴力求解器,秒解各种斗地主残局 秒解抖音.微信等各大平台的斗地主残局挑战 支持自定义出牌规则 输入双方的牌后单击"开始求解"按钮即可 求解完成后,电脑会自 ...

  4. C++大战Python - 以C++11重写欢乐斗地主残局解答器

    业界传说Python平均一行代码能够顶的上几十行C/C++代码.业界还传说,C++效率能够达到Python的几十倍. 对于以上二者,笔者本来感觉也许差不多只是略夸张.笔者曾经用C++和Python分别 ...

  5. python斗地主游戏源码_我用tkinter写的一个斗地主练习复盘python程序

    python写的斗地主模拟器使用说明,以及python的标准控件库tkinter的使用示例. http://vdisk.weibo.com/s/C5R1f8s9EVq2y 我用python写的一个斗地 ...

  6. 基于Alpha-Beta剪枝的欢乐斗地主残局辅助

    2019年4月17日更新: 将搜索主函数优化为局部记忆化搜索,再次提高若干倍搜索速度 更新了main和player,helper无更新 1 #include "Player-v3.0.cpp ...

  7. 一份超全的Python学习资料汇总

    一.学习Python必备技能图谱 二.0基础如何系统学习Python? 一.Python的普及入门 1.1 Python入门学习须知和书本配套学习建议 1.2 Python简史 1.3 Python的 ...

  8. 一份超级详细的Python零基础学习资料(仅此一家,可能会被404抓紧收藏)

    一.学习Python必备技能图谱 二.0基础如何系统学习Python? 一.Python的普及入门 1.1 Python入门学习须知和书本配套学习建议 1.2 Python简史 1.3 Python的 ...

  9. 斗地主AI算法——第十七章の总结整理

    转载请标明出处:https://blog.csdn.net/sm9sun/article/details/70878001  文章出自:九日王朝 查看全文 http://www.taodudu.cc/ ...

最新文章

  1. 报错解决:TypeError: Object type class 'str' cannot be passed to C code
  2. SEO的操作流程梗概
  3. app里使用163邮箱发送邮件,被163认为是垃圾邮件的坑爹经历!_ !
  4. ゾーン10進数、パック10進数
  5. MySQL数据库操作(DDL)
  6. wave格式分析,wave音频文件格式分析配程序
  7. figma导出android切图,谁再说Figma没办法导出标注和切图,你把这个插件转发给他...
  8. MATLAB基本操作(一):MATLAB中变量的文件存储
  9. js RegExp正则表达式常见用例
  10. linux这样去掉文件里高亮字体
  11. nes 红白机模拟器 第4篇 linux 手柄驱动支持
  12. EasyFlash | 让 Flash 成为小型 KV 数据库
  13. 把计算机怎么连接手机的网络助手在哪里,怎么将手机网络通过USB共享给电脑
  14. 计算机的常见故障处理实验报告,微机系统故障与处理-实验报告.doc
  15. Badboy 录制脚本提示“当前页面的脚本发生错误”解决
  16. 计算机显示时区怎么更改,电脑时区自动改怎么办
  17. 操作系统同步互斥问题
  18. 【Lesson 4】 和弦的大小增减属
  19. HMI-47-【多媒体】Title界面实现 2
  20. 数据可视化:科研论文配色

热门文章

  1. 视频播放器开发 - 基本原理
  2. iPhone4升级到iOS5.0.1越狱后出现Cydia使用闪退的修复方法
  3. 手机HCI日志抓取教程
  4. (大数据技术)欢迎参加2020信息技术新工科产学研联盟师资培训班!
  5. 日粮碳水化合物驱动反刍研究进展 国稻种芯百团计划行动
  6. 武田旗下的增长与新兴市场业务部门力争在未来10年实现双位数营收增长
  7. wift密码 java
  8. 整点没用的——Knuth洗牌算法
  9. 配置Apache+Tomcat+mod_jk软件环境
  10. SaaSBase:推荐一些超好用的SCRM社交客户管理软件(上篇)