斗地主AI算法——第十一章の被动出牌(5)

本文深入探讨斗地主AI算法中飞机和炸弹类型的处理策略,详细解析了如何通过多层循环和条件判断,寻找最优出牌方案,确保在被动出牌情况下最大化胜率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


本章是被动出牌的最后一章,截止目前,我们已经解决了大部分牌型。只剩下飞机和炸弹了。

飞机无疑是最复杂的类型,他等于顺子和三带的结合体,但又增加了很多难度。

根据上一章的算法,我们可以大概想到,若是带出去一张我就加一个循环,若是带出去两张我就加俩循环,但是这个飞机长度不一致,带出去的牌个数也就不一致,这TM怎么加啊!!我一开始的想法是外置一个全排列函数,给定count个数,然后列举所有的方案去改变value_aHandCardList数组再筛选出最优解。但这样做有坏处,第一,这个外置函数非常的麻烦,因为他全排列的全集和子集个数都不确定。第二,影响了程序分支一致性,别的模块都差不多,就飞机这里搞特殊化不太好。第三,不一定安全,因为这种做法意味着我又要多了一个可以影响value_aHandCardList的模块,我并不希望这样。

所以思考了很久,我觉得宁愿多做几个分支,毕竟飞机的个数还是可控的,就2-4。虽然这种代码写起来跟看起来都很傻逼,但是也没有办法。。。


飞机带单:


 
  1. //暂存最佳的价值
  2. HandCardValue BestHandCardValue = get_HandCardValue(clsHandCardData);
  3. //我们认为不出牌的话会让对手一个轮次,即加一轮(权值减少7)便于后续的对比参考。
  4. BestHandCardValue.NeedRound += 1;
  5. //暂存最佳的牌号
  6. int BestMaxCard = 0;
  7. //是否出牌的标志
  8. bool PutCards = false;
  9. //验证顺子的标志
  10. int prov = 0;
  11. //顺子起点
  12. int start_i = 0;
  13. //顺子终点
  14. int end_i = 0;
  15. //顺子长度
  16. int length = clsGameSituation.uctNowCardGroup.nCount / 4;
  17. int tmp_1 = 0;
  18. int tmp_2 = 0;
  19. int tmp_3 = 0;
  20. int tmp_4 = 0;
  21. //2与王不参与顺子,从当前已打出的顺子最小牌值+1开始遍历
  22. for ( int i = clsGameSituation.uctNowCardGroup.nMaxCard - length + 2; i < 15; i++)
  23. {
  24. if (clsHandCardData.value_aHandCardList[i] > 2)
  25. {
  26. prov++;
  27. }
  28. else
  29. {
  30. prov = 0;
  31. }
  32. if (prov >= length)
  33. {
  34. end_i = i;
  35. start_i = i - length + 1;
  36. for ( int j = start_i; j <= end_i; j++)
  37. {
  38. clsHandCardData.value_aHandCardList[j] -= 3;
  39. }
  40. clsHandCardData.nHandCardCount -= clsGameSituation.uctNowCardGroup.nCount;
  41. /*本来想做全排列选取带出的牌然后枚举出最高价值的,但考虑到当飞机长度也就是在2-4之间
  42. 所以干脆做三个分支处理算了*/
  43. //为两连飞机
  44. if (length == 2)
  45. {
  46. for ( int j = 3; j < 18; j++)
  47. {
  48. if (clsHandCardData.value_aHandCardList[j] > 0)
  49. {
  50. clsHandCardData.value_aHandCardList[j] -= 1;
  51. for ( int k = 3; k < 18; k++)
  52. {
  53. if (clsHandCardData.value_aHandCardList[k] > 0)
  54. {
  55. clsHandCardData.value_aHandCardList[k] -= 1;
  56. HandCardValue tmpHandCardValue = get_HandCardValue(clsHandCardData);
  57. clsHandCardData.value_aHandCardList[k] += 1;
  58. //选取总权值-轮次*7值最高的策略 因为我们认为剩余的手牌需要n次控手的机会才能出完,若轮次牌型很大(如炸弹) 则其-7的价值也会为正
  59. if ((BestHandCardValue.SumValue - (BestHandCardValue.NeedRound * 7)) <= (tmpHandCardValue.SumValue - (tmpHandCardValue.NeedRound * 7)))
  60. {
  61. BestHandCardValue = tmpHandCardValue;
  62. BestMaxCard = end_i;
  63. tmp_1 = j;
  64. tmp_2 = k;
  65. PutCards = true;
  66. }
  67. }
  68. }
  69. clsHandCardData.value_aHandCardList[j] += 1;
  70. }
  71. }
  72. }
  73. //为三连飞机
  74. if (length == 3)
  75. {
  76. for ( int j = 3; j < 18; j++)
  77. {
  78. if (clsHandCardData.value_aHandCardList[j] > 0)
  79. {
  80. clsHandCardData.value_aHandCardList[j] -= 1;
  81. for ( int k = 3; k < 18; k++)
  82. {
  83. if (clsHandCardData.value_aHandCardList[k] > 0)
  84. {
  85. clsHandCardData.value_aHandCardList[k] -= 1;
  86. for ( int l = 3; l < 18; l++)
  87. {
  88. if (clsHandCardData.value_aHandCardList[l] > 0)
  89. {
  90. clsHandCardData.value_aHandCardList[l] -= 1;
  91. HandCardValue tmpHandCardValue = get_HandCardValue(clsHandCardData);
  92. //选取总权值-轮次*7值最高的策略 因为我们认为剩余的手牌需要n次控手的机会才能出完,若轮次牌型很大(如炸弹) 则其-7的价值也会为正
  93. if ((BestHandCardValue.SumValue - (BestHandCardValue.NeedRound * 7)) <= (tmpHandCardValue.SumValue - (tmpHandCardValue.NeedRound * 7)))
  94. {
  95. BestHandCardValue = tmpHandCardValue;
  96. BestMaxCard = end_i;
  97. tmp_1 = j;
  98. tmp_2 = k;
  99. tmp_3 = l;
  100. PutCards = true;
  101. }
  102. clsHandCardData.value_aHandCardList[l] += 1;
  103. }
  104. }
  105. clsHandCardData.value_aHandCardList[k] += 1;
  106. }
  107. }
  108. clsHandCardData.value_aHandCardList[j] += 1;
  109. }
  110. }
  111. }
  112. //为四连飞机
  113. if (length == 4)
  114. {
  115. for ( int j = 3; j < 18; j++)
  116. {
  117. if (clsHandCardData.value_aHandCardList[j] > 0)
  118. {
  119. clsHandCardData.value_aHandCardList[j] -= 1;
  120. for ( int k = 3; k < 18; k++)
  121. {
  122. if (clsHandCardData.value_aHandCardList[k] > 0)
  123. {
  124. clsHandCardData.value_aHandCardList[k] -= 1;
  125. for ( int l = 3; l < 18; l++)
  126. {
  127. if (clsHandCardData.value_aHandCardList[l] > 0)
  128. {
  129. clsHandCardData.value_aHandCardList[l] -= 1;
  130. for ( int m = 3; m < 18; m++)
  131. {
  132. if (clsHandCardData.value_aHandCardList[m] > 0)
  133. {
  134. clsHandCardData.value_aHandCardList[m] -= 1;
  135. HandCardValue tmpHandCardValue = get_HandCardValue(clsHandCardData);
  136. //选取总权值-轮次*7值最高的策略 因为我们认为剩余的手牌需要n次控手的机会才能出完,若轮次牌型很大(如炸弹) 则其-7的价值也会为正
  137. if ((BestHandCardValue.SumValue - (BestHandCardValue.NeedRound * 7)) <= (tmpHandCardValue.SumValue - (tmpHandCardValue.NeedRound * 7)))
  138. {
  139. BestHandCardValue = tmpHandCardValue;
  140. BestMaxCard = end_i;
  141. tmp_1 = j;
  142. tmp_2 = k;
  143. tmp_3 = l;
  144. tmp_4 = m;
  145. PutCards = true;
  146. }
  147. clsHandCardData.value_aHandCardList[m] += 1;
  148. }
  149. }
  150. clsHandCardData.value_aHandCardList[l] += 1;
  151. }
  152. }
  153. clsHandCardData.value_aHandCardList[k] += 1;
  154. }
  155. }
  156. clsHandCardData.value_aHandCardList[j] += 1;
  157. }
  158. }
  159. }
  160. for ( int j = start_i; j <= end_i; j++)
  161. {
  162. clsHandCardData.value_aHandCardList[j] += 3;
  163. }
  164. clsHandCardData.nHandCardCount += clsGameSituation.uctNowCardGroup.nCount;
  165. }
  166. }
  167. if (PutCards)
  168. {
  169. for ( int j = start_i; j <= end_i; j++)
  170. {
  171. clsHandCardData.value_nPutCardList.push_back(j);
  172. clsHandCardData.value_nPutCardList.push_back(j);
  173. clsHandCardData.value_nPutCardList.push_back(j);
  174. }
  175. if (length == 2)
  176. {
  177. clsHandCardData.value_nPutCardList.push_back(tmp_1);
  178. clsHandCardData.value_nPutCardList.push_back(tmp_2);
  179. }
  180. if (length == 3)
  181. {
  182. clsHandCardData.value_nPutCardList.push_back(tmp_1);
  183. clsHandCardData.value_nPutCardList.push_back(tmp_2);
  184. clsHandCardData.value_nPutCardList.push_back(tmp_3);
  185. }
  186. if (length == 4)
  187. {
  188. clsHandCardData.value_nPutCardList.push_back(tmp_1);
  189. clsHandCardData.value_nPutCardList.push_back(tmp_2);
  190. clsHandCardData.value_nPutCardList.push_back(tmp_3);
  191. clsHandCardData.value_nPutCardList.push_back(tmp_4);
  192. }
  193. clsHandCardData.uctPutCardType = clsGameSituation.uctNowCardGroup = get_GroupData(cgTHREE_TAKE_ONE_LINE, BestMaxCard, clsGameSituation.uctNowCardGroup.nCount);
  194. return;
  195. }

大家可以看到我回溯的处理方式和之前的不一样了,因为飞机类型很有可能把对牌当成两个单牌带出,甚至可以拆炸弹。所以每个循环内当确定了一个点就先处理value_aHandCardList状态,这样也相对安全,上一章中在四带二环节我也有提到过这方面。


飞机带对类似,而且这里是被动出牌,所以不存在4连飞机的情况,因为4连飞机带对的话就有20张牌了。只考虑2连和3连就可以了。



最后再说一下炸弹,这个炸弹就厉害了,我给的策略就是————————————

直接炸丫的!不要怂!!



 
  1. else if (clsGameSituation.uctNowCardGroup.cgType == cgBOMB_CARD)
  2. {
  3. //更大的炸弹——这里直接炸,不考虑拆分后果。因为信仰。
  4. for ( int i = clsGameSituation.uctNowCardGroup.nMaxCard + 1; i < 16; i++)
  5. {
  6. if (clsHandCardData.value_aHandCardList[i] == 4)
  7. {
  8. clsHandCardData.value_nPutCardList.push_back(i);
  9. clsHandCardData.value_nPutCardList.push_back(i);
  10. clsHandCardData.value_nPutCardList.push_back(i);
  11. clsHandCardData.value_nPutCardList.push_back(i);
  12. clsHandCardData.uctPutCardType = clsGameSituation.uctNowCardGroup = get_GroupData(cgBOMB_CARD, i, 4);
  13. return;
  14. }
  15. }
  16. //王炸
  17. if (clsHandCardData.value_aHandCardList[ 17] > 0 && clsHandCardData.value_aHandCardList[ 16] > 0)
  18. {
  19. clsHandCardData.value_nPutCardList.push_back( 17);
  20. clsHandCardData.value_nPutCardList.push_back( 16);
  21. clsHandCardData.uctPutCardType = clsGameSituation.uctNowCardGroup = get_GroupData(cgKING_CARD, 17, 2);
  22. return;
  23. }
  24. //管不上
  25. clsHandCardData.uctPutCardType = get_GroupData(cgZERO, 0, 0);
  26. return;
  27. }

当然也可以改成考虑拆分后果什么的,或者如果你手上有多个炸弹是否对比一下出那个接下来更好 等等逻辑。

不过对于我来说,你都有俩炸弹了,还怕什么,肯定都是要炸的!宁输不拆!就是这么浪!



好了至此被动出牌模块就全部写完了,从下一章开始,我们讲主动出牌。


敬请关注下一章:斗地主AI算法——第十二章の主动出牌(1)





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值