斗地主
前两天看到一个斗地主残局(斗地主都有残局了..), 如下图. 我便想写个程序算一下结果如何. 记录一下, 锻练一下自己说清楚事情的能力.
想法是用程序模拟两个人对局. 就每人依次出牌. 如果一个人牌打完了, 对手就会要求悔棋. 一直到悔到最后没办法悔了就输了.
每个人都先从最简单的牌开始出,最开始出单张,打到最后发现单张不行,就出对子.
比如说, 农民先出一张3, 打到最后发现没办法赢, 再尝试先出一张4. 打到最后发现单张没办法赢, 就出2张3. 依次继续.
大小顺序我们可以随便定, 我是按这样来的: 单张,一对,三张,三带一,顺子,四带2,普通炸,王炸,Pass
解释一下最后的Pass是什么意思: 竟然想不到简单的例子, 就直接拿上图举例. 农民先出A, 地主小怪. 按顺序的话, 应该是出炸,但最后会发现炸会输,按我们定义好的顺序,接下来就是Pass,也就是说,地主出小怪, 我选择不出牌.
其中几个需要仔细斟酌一下的细节:
- 什么时候需要悔牌?
- 一个人的牌出完了, 对手要求悔牌
- A悔牌之后, 发现自己上一轮是Pass, 那只能继续往前悔牌
- 什么时候算赢?
- 农民第一次出牌已经需要Pass了, 说明他输了
- 地主要求农民悔牌的时候, 农民已经是第一张牌, 说明地主输了
- 需要保存/传递哪些信息
- 按顺序记录当前出过的所有牌
- 当前出牌的人
- 当前桌面上的牌是什么 (可以放第1项里面)
- 如果是刚刚悔牌, 需要保存/传递刚悔的是什么牌
- 是不是处于悔牌状态(具体到我的代码实现中, 这个值是需要传递的. 在你的实现中,可以通过第4项直接得到). 这个关系到下一次可以出什么牌. 比如说桌面是444带J, 如果我不是悔牌, 那我的3张必须比4大,单张随意. 如果我是悔牌, 那么三张可以一样, 但单张必须比之前的大.
简单的伪代码:
while True:
cureent_player.next
if len(paths) == 1 and desktop is Pass:
第一牌就只能Pass,没得打了,判cureent_player输
if destop is Pass and previous_desktop is Pass:
//注意这里是需要从Pass往前悔三次牌, 这种回退是一定可以的
rollback(cureent_player,previous_player, cureent_player)
if cureent_player.win:
if rollback(cureent_player,previous_player) is False:
无牌可悔,cureent_player赢
具体代码见https://github.com/childe/doudizhu
英语太差, 有几个变量名字解释一下:
- Round 是指选手出的一次牌. 比如444带J是一个Round对象. Pass也是一个Round对象
- minimal Two.minimal(cards)本意是指在所有cards里面, 选出最小的对子牌. 这个方法用在对手刚Pass, 而自己悔牌,需要从单张升级到对子的时候. 但可能没有对子,需要继续寻找有没有3张,3带1,顺子等.
就这么多了, 我代码水平太次, 真正实现起来,还是Debug了好久,代码可读性也不是很理想. 另外希望没有打扰到你的思路, 对程序员来说, 递归可能是一种更加自然的思路. 但用Python实现的话,有可能会抛递归层次太深的异常. 毕竟我在i5上面跑了整5分钟, 日志打印了300MB…