斗地主AI算法——第四章の权值定义

第一章业务逻辑结尾部分我提到了权值的计算方法:

①每个单牌都有一个基础价值②组合牌型的整体价值与这个基础价值有关,但显然计算规则不完全一样。③整手牌可以分成若干个组合牌,但分法不唯一。

当时,我说了①和②可以直接定义,③需要迭代计算。所以本章的主要内容就是确定基础价值&组合牌型的价值定义


对于牌型权值的定义看似简单,实际却需要大量的推敲。这就跟游戏里不同英雄属性、技能反复修改一样。事实上,直至整个工程开发完毕,我还在修改权值定义,因为这是唯一影响逻辑处理的因素。如果你觉得程序返回的出牌策略不太符合你的想法,那么一定是权值定义这里出现的问题。


首先,网络上有很多文章统计出不同牌型出现的概率,但是我始终觉得,对于斗地主而言,一种牌型出现的概率并不是那么重要,举个例子。我认为33和3的价值是一样的。

因为大多数情况你出了都会被管,若你有飞机,这两者都可以被带出。或许从概率的角度来说能管上33的牌会更少,但事实上,对方究竟管不管你,用什么管你取决于对方的牌型组合。你出33,对方可以出44,但是你出3,对方不一定会出4管你,因为这样他还会剩下一个4,他会用其他的牌管你,并且,若该牌型很难出现,即被管的可能性很低,那么他管上别人的可能性也很低。所以实际上这种牌型的收益可以近似的等于不存在。况且我也不想搞一套很复杂的公式,我的想法是用一些简单的公式得出一个中肯的价值,或许不是那么的公平,但至少程序应对绝大部分局面都能给出比较正确的操作。



首先,我们有出牌轮次的定义,我们尽量使得出牌轮次少一些,还是那句话,所以对于牌型,大家是公平的,你组成这种牌型的几率低,但你管别人的几率也低。你若想出去,还是要用其他的牌抢占先手再打出,所以这种牌型不会给你带来抢占出牌权的收益。那么他的好处或许是能让你快速的出完牌,减少出牌轮次。

然后就是牌的基础价值定义,牌的价值一定有正有负,我也是经过一些测试及计算,确定了价值为0的位置是10。

我们有15种牌型,从值域来说是3~17。那么10属于最中间的位置,还有就是我们经常管10以上的牌称作带“人”的牌(因为JQK有人图案)。我们认为带“人”的牌都是干部,是可以管别人的。

或许有人会说,王比其他牌型少,且人家若成了王炸又不可能拆开出。但实际玩牌的时候王牌管单出现的几率和其他牌差不多,因为其他牌也需要考虑是否组成对牌、三牌、顺子等情况。大家的拆分选择可以认为近似公平的。至于牌少的问题,你想想你一局游戏实际打单牌的次数也没多少次。况且,基础价值是应用于所有类型的牌值的。但他不能说明一切问题。他只是表示当你的牌值大于10以上,你管上别人的概率高,反之,你被管的概率高。

根据这个思路,我们暂定单双三等牌型的价值就是这个基础价值。也就是3的基础价值是-7  大王的基础价值是7

根据这个基础价值,我们便得出控手(轮次)的价值。因为最大负牌值是-7,所以控手价值是7,这个应该不难理解把。因为你若想打出一个3,你就必须得抢占一次控手机会。然后再打出3。同理,一次控手机会可以出一手牌,所以单个轮次的价值也是7,若你的牌型组合价值大于7,则认为你这种牌型可以创造一个轮次,举个简单的例子 王炸。


接下来是顺子的定义。顺子权值的思路我也犹豫了好久,最后决定是其最大牌的单体价值+1,因为2不参与顺子。至于王就不考虑了。原来考虑到是否要采用平均值等,后来发现顺子到底能否被管以及管别人,主要决定于最大的牌,比如说你从3~A和从10~A是一样的。管别人的话也不是越长越好,你长顺子强行管短顺子可能还会多出很多单牌。


对于四带二、飞机等定义,是修改最多的。的确这样的牌很难被管,所以我给了他一个非负价值,但是又发现其价值很大,比如说KKKQQQJJJ他的价值已经飙到11了。

那么程序给出的策略就是 分开打,因为KKKQQQ也是飞机。一个轮次的价值是7,远小于11。所以可以拆成两个轮次。鉴于这一点,我把价值调成为非负且除以2

这样这类牌型的价值永远不会大于7,毕竟这种牌型能一手出完不能分成两手出。而且根据之前的逻辑,你很难被管所以你也很难管别人,那么正价值收益缩减也是很自然的了。

最后说一下炸弹及王炸,炸弹无负价值且+一个轮次7,因为我们认为炸弹大概率能抢占一局轮次。而炸弹及王炸没有正价值收益缩减的原因是处于信仰把,尽量的不要拆分。也符合我们人性。我们都是把炸弹、王炸看成最重要的。

还有,在此之前我有想过权值定义取不拆分和拆分的最大价值。比如说3A带俩2,那么其实俩2的价值也很高,后期很有可能拆开打。但是最后因为引入了手牌轮次参数,所以不考虑拆分价值了。不然四带二的价值爆炸。

下面给出目前暂定的定义方案


  
  
  1. /*评分逻辑思维:
  2. 0.由于新策略引入手牌轮次参数,所以不再考虑拆分价值。
  3. 1.牌力基础价值:我们认为10属于中等位置,即<10单牌价值为负,大于10的单牌价值为正
  4. 2.控手的价值定义:我们认为一次控手权可以抵消一次手中最小牌型,最小牌型(3)的价值为-7,即我们定义一次控手权的价值为7
  5. 3.单牌的价值定义:该牌的基础价值
  6. 4.对牌的价值定义:我们认为对牌与单牌价值相等(均可以被三牌带出)故其价值为该牌的基础价值
  7. 5.三牌的价值定义:
  8. 三不带: 该牌的基础价值
  9. 三带一: 我们认为通常带出去的牌价值一定无正价值故其价值为该牌的基础价值
  10. 三带二: 我们认为通常带出去的牌价值一定无正价值故其价值为该牌的基础价值
  11. 6.四牌的价值定义:
  12. 炸弹: 我们认为炸弹会享有一次控手权利且炸弹被管的概率极小,故其无负价值,非负基础价值+7
  13. 四带二单: 我们认为四带二单管人与被管的概率极小,故其无负价值,其价值为非负基础价值/2(该值最大为6小于一个轮次7)
  14. 四带二对: 我们认为四带二对管人与被管的概率极小,故其无负价值,其价值为非负基础价值/2(该值最大为6小于一个轮次7)
  15. 7.王炸的价值定义:已知炸2价值为15-3+7=19分,故王炸价值为20分。
  16. 8.顺子的价值定义:
  17. 单顺: 我们认为单顺的价值等于其最大牌的单体价值,且2不参与顺子,故顺子的权值依次提升1
  18. 双顺: 我们认为双顺的价值等于其最大牌的单体价值,且2不参与顺子,故顺子的权值依次提升1
  19. 飞机不带: 由于飞机牌型管人与被管的概率极小,故其无负价值,其价值为非负基础价值/2(该值最大为6小于一个轮次7)
  20. 飞机带双翅: 由于飞机牌型管人与被管的概率极小,故其无负价值,其价值为非负基础价值/2(该值最大为6小于一个轮次7)
  21. 飞机带单翅: 由于飞机牌型管人与被管的概率极小,故其无负价值,其价值为非负基础价值/2(该值最大为6小于一个轮次7)
  22. 9.根据数值分布,我们暂定: <10不叫分,10-14叫一分,15-19叫两分,20以上叫三分
  23. PS.以上逻辑纯属扯淡,谁信谁SB。。。。。
  24. */

以及封装好的获取价值函数


  
  
  1. /*封装好的获取各类牌型组合结构函数
  2. CardGroupType cgType:牌型
  3. int MaxCard:决定大小的牌值
  4. int Count:牌数
  5. 返回值:CardGroupData
  6. */
  7. CardGroupData get_GroupData(CardGroupType cgType, int MaxCard, int Count)
  8. {
  9. CardGroupData uctCardGroupData;
  10. uctCardGroupData.cgType = cgType;
  11. uctCardGroupData.nCount = Count;
  12. uctCardGroupData.nMaxCard = MaxCard;
  13. //不出牌型
  14. if (cgType == cgZERO)
  15. uctCardGroupData.nValue = 0;
  16. //单牌类型
  17. else if (cgType == cgSINGLE)
  18. uctCardGroupData.nValue = MaxCard - 10;
  19. //对牌类型
  20. else if (cgType == cgDOUBLE)
  21. uctCardGroupData.nValue = MaxCard - 10;
  22. //三条类型
  23. else if (cgType == cgTHREE)
  24. uctCardGroupData.nValue = MaxCard - 10;
  25. //单连类型
  26. else if (cgType == cgSINGLE_LINE)
  27. uctCardGroupData.nValue = MaxCard - 10 + 1;
  28. //对连类型
  29. else if (cgType == cgDOUBLE_LINE)
  30. uctCardGroupData.nValue = MaxCard - 10 + 1;
  31. //三连类型
  32. else if (cgType == cgTHREE_LINE)
  33. uctCardGroupData.nValue = (MaxCard - 3 + 1)/ 2;
  34. //三带一单
  35. else if (cgType == cgTHREE_TAKE_ONE)
  36. uctCardGroupData.nValue = MaxCard - 10;
  37. //三带一对
  38. else if (cgType == cgTHREE_TAKE_TWO)
  39. uctCardGroupData.nValue = MaxCard - 10;
  40. //三带一单连
  41. else if (cgType == cgTHREE_TAKE_ONE_LINE)
  42. uctCardGroupData.nValue = (MaxCard - 3 + 1) / 2;
  43. //三带一对连
  44. else if (cgType == cgTHREE_TAKE_TWO_LINE)
  45. uctCardGroupData.nValue = (MaxCard - 3 + 1) / 2;
  46. //四带两单
  47. else if (cgType == cgFOUR_TAKE_ONE)
  48. uctCardGroupData.nValue = (MaxCard - 3 ) / 2;
  49. //四带两对
  50. else if (cgType == cgFOUR_TAKE_TWO)
  51. uctCardGroupData.nValue = (MaxCard - 3 ) / 2;
  52. //炸弹类型
  53. else if (cgType == cgBOMB_CARD)
  54. uctCardGroupData.nValue = MaxCard - 3 + 7;
  55. //王炸类型
  56. else if (cgType == cgKING_CARD)
  57. uctCardGroupData.nValue = 20;
  58. //错误牌型
  59. else
  60. uctCardGroupData.nValue = 0;
  61. return uctCardGroupData;
  62. }

注意!!!以上价值定义是作者本人主观意愿,并非斗地主游戏最佳策略,请大家遵从自己的内心适当修改~~


好了,权值定义做完后,我们便可以实现比较重要的一个模块了,即手牌总价值计算函数,也就是我一直说的F()算法函数。


敬请关注下一章:斗地主AI算法——第五章の总值计算



猜你喜欢

转载自blog.csdn.net/King_fengzi/article/details/89326682