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

上一章已经说明了被动出牌算法基本的出牌思路,且以单牌为例给出了具体的代码。

对牌、三不带牌型实现方法与单牌基本类似。改动的地方主要是枚举牌类型,出牌时value_nPutCardList的处理,回溯时value_aHandCardList和nHandCardCount的变化等几个方面。下面给出完整代码:

 

[cpp] view plain copy
 
  1. /对牌类型  
  2.     else if (clsGameSituation.uctNowCardGroup.cgType == cgDOUBLE)  
  3.     {  
  4.         //剪枝:如果能出去最后一手牌直接出  
  5.         CardGroupData SurCardGroupData = ins_SurCardsType(clsHandCardData.value_aHandCardList);  
  6.         if (SurCardGroupData.cgType != cgERROR)  
  7.         {  
  8.             if (SurCardGroupData.cgType == cgDOUBLE&&SurCardGroupData.nMaxCard>clsGameSituation.uctNowCardGroup.nMaxCard)  
  9.             {  
  10.                 Put_All_SurCards(clsGameSituation, clsHandCardData, SurCardGroupData);  
  11.                 return;  
  12.             }  
  13.             else if (SurCardGroupData.cgType == cgBOMB_CARD || SurCardGroupData.cgType == cgKING_CARD)  
  14.             {  
  15.                 Put_All_SurCards(clsGameSituation, clsHandCardData, SurCardGroupData);  
  16.                 return;  
  17.             }  
  18.         }  
  19.         //-------------------------------------------对牌-------------------------------------------  
  20.   
  21.         //暂存最佳的价值  
  22.         HandCardValue BestHandCardValue = get_HandCardValue(clsHandCardData);  
  23.   
  24.         //我们认为不出牌的话会让对手一个轮次,即加一轮(权值减少7)便于后续的对比参考。  
  25.         BestHandCardValue.NeedRound += 1;  
  26.   
  27.         //暂存最佳的牌号  
  28.         int BestMaxCard = 0;  
  29.         //是否出牌的标志  
  30.         bool PutCards = false;  
  31.   
  32.         for (int i = clsGameSituation.uctNowCardGroup.nMaxCard + 1; i < 18; i++)  
  33.         {  
  34.             if (clsHandCardData.value_aHandCardList[i] > 1)  
  35.             {  
  36.                 //尝试打出一对牌,估算剩余手牌价值  
  37.                 clsHandCardData.value_aHandCardList[i]-=2;  
  38.                 clsHandCardData.nHandCardCount-=2;  
  39.                 HandCardValue tmpHandCardValue = get_HandCardValue(clsHandCardData);  
  40.                 clsHandCardData.value_aHandCardList[i]+=2;  
  41.                 clsHandCardData.nHandCardCount+=2;  
  42.   
  43.                 //选取总权值-轮次*7值最高的策略  因为我们认为剩余的手牌需要n次控手的机会才能出完,若轮次牌型很大(如炸弹) 则其-7的价值也会为正  
  44.                 if ((BestHandCardValue.SumValue - (BestHandCardValue.NeedRound * 7)) <= (tmpHandCardValue.SumValue - (tmpHandCardValue.NeedRound * 7)))  
  45.                 {  
  46.                     BestHandCardValue = tmpHandCardValue;  
  47.                     BestMaxCard = i;  
  48.                     PutCards = true;  
  49.                 }  
  50.   
  51.             }  
  52.         }  
  53.         if (PutCards)  
  54.         {  
  55.             clsHandCardData.value_nPutCardList.push_back(BestMaxCard);  
  56.             clsHandCardData.value_nPutCardList.push_back(BestMaxCard);  
  57.             clsHandCardData.uctPutCardType = clsGameSituation.uctNowCardGroup = get_GroupData(cgDOUBLE, BestMaxCard, 2);  
  58.             return;  
  59.         }  
  60.   
  61.   
  62.         //-------------------------------------------炸弹-------------------------------------------  
  63.   
  64.         for (int i = 3; i < 16; i++)  
  65.         {  
  66.             if (clsHandCardData.value_aHandCardList[i] ==4)  
  67.             {  
  68.   
  69.                 //尝试打出炸弹,估算剩余手牌价值,因为炸弹可以参与顺子,不能因为影响顺子而任意出炸  
  70.                 clsHandCardData.value_aHandCardList[i] -= 4;  
  71.                 clsHandCardData.nHandCardCount -= 4;  
  72.                 HandCardValue tmpHandCardValue = get_HandCardValue(clsHandCardData);  
  73.                 clsHandCardData.value_aHandCardList[i] += 4;  
  74.                 clsHandCardData.nHandCardCount += 4;  
  75.   
  76.                 //选取总权值-轮次*7值最高的策略  因为我们认为剩余的手牌需要n次控手的机会才能出完,若轮次牌型很大(如炸弹) 则其-7的价值也会为正  
  77.                 if ((BestHandCardValue.SumValue - (BestHandCardValue.NeedRound * 7)) <= (tmpHandCardValue.SumValue - (tmpHandCardValue.NeedRound * 7))  
  78.                 //如果剩余手牌价值为正,证明出去的几率很大, 那么可以用炸获得先手  
  79.                     || tmpHandCardValue.SumValue > 0)  
  80.                 {  
  81.                     BestHandCardValue = tmpHandCardValue;  
  82.                     BestMaxCard = i;  
  83.                     PutCards = true;  
  84.                 }  
  85.   
  86.             }  
  87.         }  
  88.         if (PutCards)  
  89.         {  
  90.             clsHandCardData.value_nPutCardList.push_back(BestMaxCard);  
  91.             clsHandCardData.value_nPutCardList.push_back(BestMaxCard);  
  92.             clsHandCardData.value_nPutCardList.push_back(BestMaxCard);  
  93.             clsHandCardData.value_nPutCardList.push_back(BestMaxCard);  
  94.             clsHandCardData.uctPutCardType = clsGameSituation.uctNowCardGroup = get_GroupData(cgBOMB_CARD, BestMaxCard, 4);  
  95.             return;  
  96.         }  
  97.   
  98.         //王炸  
  99.         if (clsHandCardData.value_aHandCardList[17] > 0 && clsHandCardData.value_aHandCardList[16] > 0)  
  100.         {  
  101.             //如果剩余手牌价值为正,证明出去的几率很大,那么可以用炸获得先手,王炸20分  
  102.             if (BestHandCardValue.SumValue > 20)  
  103.             {  
  104.                 clsHandCardData.value_nPutCardList.push_back(17);  
  105.                 clsHandCardData.value_nPutCardList.push_back(16);  
  106.                 clsHandCardData.uctPutCardType = clsGameSituation.uctNowCardGroup = get_GroupData(cgKING_CARD, 17, 2);  
  107.                 return;  
  108.             }  
  109.         }  
  110.   
  111.         //管不上  
  112.         clsHandCardData.uctPutCardType = get_GroupData(cgZERO, 0, 0);  
  113.         return;  
  114.     }  
  115.     //三牌类型  
  116.     else if (clsGameSituation.uctNowCardGroup.cgType == cgTHREE)  
  117.     {  
  118.         //剪枝:如果能出去最后一手牌直接出  
  119.         CardGroupData SurCardGroupData = ins_SurCardsType(clsHandCardData.value_aHandCardList);  
  120.         if (SurCardGroupData.cgType != cgERROR)  
  121.         {  
  122.             if (SurCardGroupData.cgType == cgTHREE&&SurCardGroupData.nMaxCard>clsGameSituation.uctNowCardGroup.nMaxCard)  
  123.             {  
  124.                 Put_All_SurCards(clsGameSituation, clsHandCardData, SurCardGroupData);  
  125.                 return;  
  126.             }  
  127.             else if (SurCardGroupData.cgType == cgBOMB_CARD || SurCardGroupData.cgType == cgKING_CARD)  
  128.             {  
  129.                 Put_All_SurCards(clsGameSituation, clsHandCardData, SurCardGroupData);  
  130.                 return;  
  131.             }  
  132.         }  
  133.         //-------------------------------------------三牌-------------------------------------------  
  134.   
  135.         //暂存最佳的价值  
  136.         HandCardValue BestHandCardValue = get_HandCardValue(clsHandCardData);  
  137.   
  138.   
  139.         //我们认为不出牌的话会让对手一个轮次,即加一轮(权值减少7)便于后续的对比参考。  
  140.         BestHandCardValue.NeedRound += 1;  
  141.   
  142.         //暂存最佳的牌号  
  143.         int BestMaxCard = 0;  
  144.         //是否出牌的标志  
  145.         bool PutCards = false;  
  146.   
  147.         for (int i = clsGameSituation.uctNowCardGroup.nMaxCard + 1; i < 18; i++)  
  148.         {  
  149.             if (clsHandCardData.value_aHandCardList[i] > 2)  
  150.             {  
  151.                 //尝试打出一对牌,估算剩余手牌价值  
  152.                 clsHandCardData.value_aHandCardList[i] -= 3;  
  153.                 clsHandCardData.nHandCardCount -= 3;  
  154.                 HandCardValue tmpHandCardValue = get_HandCardValue(clsHandCardData);  
  155.                 clsHandCardData.value_aHandCardList[i] += 3;  
  156.                 clsHandCardData.nHandCardCount += 3;  
  157.   
  158.                 //选取总权值-轮次*7值最高的策略  因为我们认为剩余的手牌需要n次控手的机会才能出完,若轮次牌型很大(如炸弹) 则其-7的价值也会为正  
  159.                 if ((BestHandCardValue.SumValue - (BestHandCardValue.NeedRound * 7)) <= (tmpHandCardValue.SumValue - (tmpHandCardValue.NeedRound * 7)))  
  160.                 {  
  161.                     BestHandCardValue = tmpHandCardValue;  
  162.                     BestMaxCard = i;  
  163.                     PutCards = true;  
  164.                 }  
  165.   
  166.             }  
  167.         }  
  168.         if (PutCards)  
  169.         {  
  170.             clsHandCardData.value_nPutCardList.push_back(BestMaxCard);  
  171.             clsHandCardData.value_nPutCardList.push_back(BestMaxCard);  
  172.             clsHandCardData.value_nPutCardList.push_back(BestMaxCard);  
  173.             clsHandCardData.uctPutCardType = clsGameSituation.uctNowCardGroup = get_GroupData(cgTHREE, BestMaxCard, 3);  
  174.             return;  
  175.         }  
  176.   
  177.   
  178.         //-------------------------------------------炸弹-------------------------------------------  
  179.   
  180.         for (int i = 3; i < 16; i++)  
  181.         {  
  182.             if (clsHandCardData.value_aHandCardList[i] == 4)  
  183.             {  
  184.   
  185.                 //尝试打出炸弹,估算剩余手牌价值,因为炸弹可以参与顺子,不能因为影响顺子而任意出炸  
  186.                 clsHandCardData.value_aHandCardList[i] -= 4;  
  187.                 clsHandCardData.nHandCardCount -= 4;  
  188.                 HandCardValue tmpHandCardValue = get_HandCardValue(clsHandCardData);  
  189.                 clsHandCardData.value_aHandCardList[i] += 4;  
  190.                 clsHandCardData.nHandCardCount += 4;  
  191.   
  192.                 //选取总权值-轮次*7值最高的策略  因为我们认为剩余的手牌需要n次控手的机会才能出完,若轮次牌型很大(如炸弹) 则其-7的价值也会为正  
  193.                 if ((BestHandCardValue.SumValue - (BestHandCardValue.NeedRound * 7)) <= (tmpHandCardValue.SumValue - (tmpHandCardValue.NeedRound * 7))  
  194.                     //如果剩余手牌价值为正,证明出去的几率很大, 那么可以用炸获得先手  
  195.                     || tmpHandCardValue.SumValue > 0)  
  196.                 {  
  197.                     BestHandCardValue = tmpHandCardValue;  
  198.                     BestMaxCard = i;  
  199.                     PutCards = true;  
  200.                 }  
  201.   
  202.             }  
  203.         }  
  204.         if (PutCards)  
  205.         {  
  206.             clsHandCardData.value_nPutCardList.push_back(BestMaxCard);  
  207.             clsHandCardData.value_nPutCardList.push_back(BestMaxCard);  
  208.             clsHandCardData.value_nPutCardList.push_back(BestMaxCard);  
  209.             clsHandCardData.value_nPutCardList.push_back(BestMaxCard);  
  210.             clsHandCardData.uctPutCardType = clsGameSituation.uctNowCardGroup = get_GroupData(cgBOMB_CARD, BestMaxCard, 4);  
  211.             return;  
  212.         }  
  213.   
  214.         //王炸  
  215.         if (clsHandCardData.value_aHandCardList[17] > 0 && clsHandCardData.value_aHandCardList[16] > 0)  
  216.         {  
  217.             //如果剩余手牌价值为正,证明出去的几率很大,那么可以用炸获得先手,王炸20分  
  218.             if (BestHandCardValue.SumValue > 20)  
  219.             {  
  220.                 clsHandCardData.value_nPutCardList.push_back(17);  
  221.                 clsHandCardData.value_nPutCardList.push_back(16);  
  222.                 clsHandCardData.uctPutCardType = clsGameSituation.uctNowCardGroup = get_GroupData(cgKING_CARD, 17, 2);  
  223.                 return;  
  224.             }  
  225.         }  
  226.   
  227.         //管不上  
  228.         clsHandCardData.uctPutCardType = get_GroupData(cgZERO, 0, 0);  
  229.         return;  
  230.     }  


接下来我们说一下顺子的处理方法。以单顺为例:

 

首先在第一阶段判断是否同类型牌里要额外增加一个条件,即顺子长度要一致

 

[cpp] view plain copy
 
  1. if (SurCardGroupData.cgType == cgSINGLE_LINE&&SurCardGroupData.nMaxCard>clsGameSituation.uctNowCardGroup.nMaxCard  
  2.                 &&SurCardGroupData.nCount== clsGameSituation.uctNowCardGroup.nCount)  
  3.             {  
  4.                 Put_All_SurCards(clsGameSituation, clsHandCardData, SurCardGroupData);  
  5.                 return;  
  6.             }  


然后除了设置BestHandCardValue等变量外,我们需要额外设置几个关于顺子的标志

 

 

[cpp] view plain copy
 
  1. //验证顺子的标志  
  2. int prov = 0;  
  3. //顺子起点  
  4. int start_i = 0;  
  5. //顺子终点  
  6. int end_i = 0;  
  7. //顺子长度  
  8. int length = clsGameSituation.uctNowCardGroup.nCount;  


遍历顺子的方法有点类似于最大子段和问题,大家可以参考下我以前的博客http://blog.csdn.net/sm9sun/article/details/53240992

 

解决思路就是如果出现某张牌个数为0,那么必然不存在经过他的顺子,此时就把计数器置零,如果计数器长度大于等于length,即可以组成顺子,我们以当前下标i为最高标志构造出(i-length+1)~i的顺子。


举个例子:对方牌型为34567,我从4遍历至8,若满足,此时end_i=8,即45678,继续走到9,若还满足,end_i=9。即56789,若没有10,则prov归零,下一次循环若11存在,则prov=1。

 

 

[cpp] view plain copy
 
  1. for (int i = clsGameSituation.uctNowCardGroup.nMaxCard - length + 2; i < 15; i++)  
  2. {  
  3.     if (clsHandCardData.value_aHandCardList[i] > 0)  
  4.     {  
  5.         prov++;  
  6.     }  
  7.     else  
  8.     {  
  9.         prov = 0;  
  10.     }  
  11.     if (prov >= length)  
  12.     {  
  13.         end_i = i;  
  14.         start_i = i - length + 1;  
  15.   
  16.         for (int j = start_i; j <= end_i; j++)  
  17.         {  
  18.             clsHandCardData.value_aHandCardList[j] --;  
  19.         }  
  20.         clsHandCardData.nHandCardCount -= clsGameSituation.uctNowCardGroup.nCount;  
  21.         HandCardValue tmpHandCardValue = get_HandCardValue(clsHandCardData);  
  22.         for (int j = start_i; j <= end_i; j++)  
  23.         {  
  24.             clsHandCardData.value_aHandCardList[j] ++;  
  25.         }  
  26.         clsHandCardData.nHandCardCount += clsGameSituation.uctNowCardGroup.nCount;  
  27.   
  28.         //选取总权值-轮次*7值最高的策略  因为我们认为剩余的手牌需要n次控手的机会才能出完,若轮次牌型很大(如炸弹) 则其-7的价值也会为正  
  29.         if ((BestHandCardValue.SumValue - (BestHandCardValue.NeedRound * 7)) <= (tmpHandCardValue.SumValue - (tmpHandCardValue.NeedRound * 7)))  
  30.         {  
  31.             BestHandCardValue = tmpHandCardValue;  
  32.             BestMaxCard = end_i;  
  33.             PutCards = true;  
  34.         }  
  35.   
  36.     }  
  37. }  


最后打出顺子的话在start_i和 end_i区间内依次减一即可。

 

 

[cpp] view plain copy
 
  1. if (PutCards)  
  2. {  
  3.     for (int j = start_i; j <= end_i; j++)  
  4.     {  
  5.         clsHandCardData.value_nPutCardList.push_back(j);  
  6.     }         
  7.     clsHandCardData.uctPutCardType = clsGameSituation.uctNowCardGroup = get_GroupData(cgSINGLE_LINE, BestMaxCard, clsGameSituation.uctNowCardGroup.nCount);  
  8.     return;  
  9. }  


以上就是单顺的处理方法,下一章我们继续填充其他牌型的出牌方法。

 

 

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

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

导航