斗地主AI算法——第八章の被动出牌(2)

上一章我们已经搭好了被动出牌的基本架子,本章我们主要说明一下被动出牌算法的基本步骤。

我把出牌逻辑分为四个阶段,也就是策略的优先级。分别是:【直接打光手牌】→【同类型牌压制】→【炸弹王炸压制】→【不出】

第一阶段【直接打光手牌】就是说如果我们可以一次性把手牌打出,那就不用考虑接下来价值之类的问题了,因为已经赢了。这种情况可能是对方打出的牌型和你一样且你比他大,或者你剩的牌是炸弹王炸。

我们先以单牌为例:

 

[cpp] view plain copy
 
  1. //剪枝:如果能出去最后一手牌直接出  
  2.         CardGroupData SurCardGroupData = ins_SurCardsType(clsHandCardData.value_aHandCardList);  
  3.         if (SurCardGroupData.cgType != cgERROR)  
  4.         {  
  5.             if (SurCardGroupData.cgType == cgSINGLE&&SurCardGroupData.nMaxCard>clsGameSituation.uctNowCardGroup.nMaxCard)  
  6.             {  
  7.                 Put_All_SurCards(clsGameSituation, clsHandCardData, SurCardGroupData);  
  8.                 return;  
  9.             }  
  10.             else if (SurCardGroupData.cgType == cgBOMB_CARD|| SurCardGroupData.cgType == cgKING_CARD)  
  11.             {  
  12.                 Put_All_SurCards(clsGameSituation, clsHandCardData, SurCardGroupData);  
  13.                 return;  
  14.             }  
  15.         }       


也就是通过调用第六章实现的判定是否为一手牌函数,如果返回的类型与当前牌型相同且nMaxCard值更大一些,或者是炸弹王炸。那么直接打光所有手牌。

 

 

Put_All_SurCards打光所有手牌实现方法:

 

[cpp] view plain copy
 
  1. /*封装好的  将所有的牌都打出*/  
  2. void Put_All_SurCards(GameSituation &clsGameSituation, HandCardData &clsHandCardData, CardGroupData SurCardGroupData)  
  3. {  
  4.     /*全部出完*/  
  5.     for (int i = 0; i < 18; i++)  
  6.         for (int j = 0; j< clsHandCardData.value_aHandCardList[i]; j++)  
  7.             clsHandCardData.value_nPutCardList.push_back(i);  
  8.     /**********/  
  9.     clsHandCardData.uctPutCardType = clsGameSituation.uctNowCardGroup = SurCardGroupData;  
  10.     return;  
  11. }  
  12.   
  13. void Put_All_SurCards( HandCardData &clsHandCardData, CardGroupData SurCardGroupData)  
  14. {  
  15.     /*全部出完*/  
  16.     for (int i = 0; i < 18; i++)  
  17.         for (int j = 0; j< clsHandCardData.value_aHandCardList[i]; j++)  
  18.             clsHandCardData.value_nPutCardList.push_back(i);  
  19.     /**********/  
  20.     clsHandCardData.uctPutCardType  = SurCardGroupData;  
  21.     return;  
  22. }  

 

第二阶段【同类型牌压制】就是需要遍历当前手牌满足可以管上的组合,然后选出最优解。我们先做一些准备工作,因为要考虑出牌和不出牌收益情况,所以我们先计算出当前手牌的价值,之所以把原始牌型轮数+1也是为了在这里若能抢占一轮尽量出牌管上。当然,若管完之后的剩余价值损失的太大就只能算了。

还需要设置暂存最佳牌号的变量、是否出牌的标志。

 

 

[cpp] view plain copy
 
  1. //暂存最佳的价值  
  2.         HandCardValue BestHandCardValue = get_HandCardValue(clsHandCardData);  
  3.   
  4.   
  5.         //我们认为不出牌的话会让对手一个轮次,即加一轮(权值减少7)便于后续的对比参考。  
  6.         BestHandCardValue.NeedRound += 1;  
  7.   
  8.         //暂存最佳的牌号  
  9.         int BestMaxCard=0;  
  10.         //是否出牌的标志  
  11.         bool PutCards = false;  

 

然后就是循环遍历满足条件的若干个选择,选出最优的解决方案。还是以单牌为例:

 

[cpp] view plain copy
 
  1. for (int i = clsGameSituation.uctNowCardGroup.nMaxCard + 1; i < 18; i++)  
  2. {  
  3.     if (clsHandCardData.value_aHandCardList[i] > 0)  
  4.     {  
  5.         //尝试打出一张牌,估算剩余手牌价值  
  6.         clsHandCardData.value_aHandCardList[i]--;  
  7.         clsHandCardData.nHandCardCount--;  
  8.         HandCardValue tmpHandCardValue=get_HandCardValue(clsHandCardData);  
  9.         clsHandCardData.value_aHandCardList[i]++;  
  10.         clsHandCardData.nHandCardCount++;  
  11.   
  12.         //选取总权值-轮次*7值最高的策略  因为我们认为剩余的手牌需要n次控手的机会才能出完,若轮次牌型很大(如炸弹) 则其-7的价值也会为正  
  13.         if ((BestHandCardValue.SumValue-(BestHandCardValue.NeedRound*7)) <= (tmpHandCardValue.SumValue-(tmpHandCardValue.NeedRound*7)))  
  14.         {  
  15.             BestHandCardValue = tmpHandCardValue;  
  16.             BestMaxCard = i;  
  17.             PutCards = true;  
  18.         }  
  19.   
  20.     }  
  21. }  
  22. if (PutCards)  
  23. {  
  24.     clsHandCardData.value_nPutCardList.push_back(BestMaxCard);  
  25.     clsHandCardData.uctPutCardType = clsGameSituation.uctNowCardGroup = get_GroupData(cgSINGLE, BestMaxCard, 1);  
  26.     return;  
  27. }  


按照之前价值的定义,我们对比的公式为总价值-轮数*7。

 

 

第三阶段【炸弹王炸压制】的策略与上文逻辑类似,唯一的区别就是加了一个手牌剩余价值的判定,就是如果我出完炸剩余手牌价值还蛮可观的话,我们就可以任性的炸出,

毕竟此时我们获胜的几率很大。

 

[cpp] view plain copy
 
  1. for (int i = 3; i < 16; i++)  
  2.         {  
  3.             if (clsHandCardData.value_aHandCardList[i] == 4)  
  4.             {  
  5.   
  6.                 //尝试打出炸弹,估算剩余手牌价值,因为炸弹可以参与顺子,不能因为影响顺子而任意出炸  
  7.                 clsHandCardData.value_aHandCardList[i] -= 4;  
  8.                 clsHandCardData.nHandCardCount -= 4;  
  9.                 HandCardValue tmpHandCardValue = get_HandCardValue(clsHandCardData);  
  10.                 clsHandCardData.value_aHandCardList[i] += 4;  
  11.                 clsHandCardData.nHandCardCount += 4;  
  12.   
  13.                 //选取总权值-轮次*7值最高的策略  因为我们认为剩余的手牌需要n次控手的机会才能出完,若轮次牌型很大(如炸弹) 则其-7的价值也会为正  
  14.                 if ((BestHandCardValue.SumValue - (BestHandCardValue.NeedRound * 7)) <= (tmpHandCardValue.SumValue - (tmpHandCardValue.NeedRound * 7))  
  15.                     //如果剩余手牌价值为正,证明出去的几率很大, 那么可以用炸获得先手  
  16.                     || tmpHandCardValue.SumValue > 0)  
  17.                 {  
  18.                     BestHandCardValue = tmpHandCardValue;  
  19.                     BestMaxCard = i;  
  20.                     PutCards = true;  
  21.                 }  
  22.   
  23.             }  
  24.         }  
  25.         if (PutCards)  
  26.         {  
  27.             clsHandCardData.value_nPutCardList.push_back(BestMaxCard);  
  28.             clsHandCardData.value_nPutCardList.push_back(BestMaxCard);  
  29.             clsHandCardData.value_nPutCardList.push_back(BestMaxCard);  
  30.             clsHandCardData.value_nPutCardList.push_back(BestMaxCard);  
  31.             clsHandCardData.uctPutCardType = clsGameSituation.uctNowCardGroup = get_GroupData(cgBOMB_CARD, BestMaxCard, 4);  
  32.             return;  
  33.         }  
  34.   
  35.         //王炸  
  36.         if (clsHandCardData.value_aHandCardList[17] > 0 && clsHandCardData.value_aHandCardList[16] > 0)  
  37.         {  
  38.             //如果剩余手牌价值为正,证明出去的几率很大,那么可以用炸获得先手,王炸20分  
  39.             if (BestHandCardValue.SumValue > 20)  
  40.             {  
  41.                 clsHandCardData.value_nPutCardList.push_back(17);  
  42.                 clsHandCardData.value_nPutCardList.push_back(16);  
  43.                 clsHandCardData.uctPutCardType = clsGameSituation.uctNowCardGroup = get_GroupData(cgKING_CARD, 17, 2);  
  44.                 return;  
  45.             }  
  46.         }  


若以上三个阶段都没有return的话,就进入我们第四阶段了。

 

 

第四阶段【不出】

 

[cpp] view plain copy
 
  1. //管不上  
  2.     clsHandCardData.uctPutCardType = get_GroupData(cgZERO, 0, 0);  
  3.     return;  


这也就是我们被动出牌算法的基本步骤,本章是以单牌为例,后续文章会进而补充其他的牌型。

 

 

敬请关注下一章:斗地主AI算法——第九章の被动出牌(3)

 

posted on 2018-06-12 22:38  &大飞  阅读(447)  评论(0编辑  收藏  举报

导航