ITPub博客

首页 > IT职业 > IT生活 > 斗地主AI算法——第九章の被动出牌(3)

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

IT生活 作者:samenzhou 时间:2021-09-09 09:33:40 0 删除 编辑

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

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


/对牌类型	else if (clsGameSituation.uctNowCardGroup.cgType == cgDOUBLE)	{		//剪枝:如果能出去最后一手牌直接出		CardGroupData SurCardGroupData = ins_SurCardsType(clsHandCardData.value_aHandCardList);		if (SurCardGroupData.cgType != cgERROR)		{			if (SurCardGroupData.cgType == cgDOUBLE&&SurCardGroupData.nMaxCard>clsGameSituation.uctNowCardGroup.nMaxCard)			{				Put_All_SurCards(clsGameSituation, clsHandCardData, SurCardGroupData);				return;			}			else if (SurCardGroupData.cgType == cgBOMB_CARD || SurCardGroupData.cgType == cgKING_CARD)			{				Put_All_SurCards(clsGameSituation, clsHandCardData, SurCardGroupData);				return;			}		}		//-------------------------------------------对牌-------------------------------------------		//暂存最佳的价值		HandCardValue BestHandCardValue = get_HandCardValue(clsHandCardData);		//我们认为不出牌的话会让对手一个轮次,即加一轮(权值减少7)便于后续的对比参考。		BestHandCardValue.NeedRound += 1;		//暂存最佳的牌号		int BestMaxCard = 0;		//是否出牌的标志		bool PutCards = false;		for (int i = clsGameSituation.uctNowCardGroup.nMaxCard + 1; i  1)			{				//尝试打出一对牌,估算剩余手牌价值				clsHandCardData.value_aHandCardList[i]-=2;				clsHandCardData.nHandCardCount-=2;				HandCardValue tmpHandCardValue = get_HandCardValue(clsHandCardData);				clsHandCardData.value_aHandCardList[i]+=2;				clsHandCardData.nHandCardCount+=2;				//选取总权值-轮次*7值最高的策略  因为我们认为剩余的手牌需要n次控手的机会才能出完,若轮次牌型很大(如炸弹) 则其-7的价值也会为正				if ((BestHandCardValue.SumValue - (BestHandCardValue.NeedRound * 7))  0)				{					BestHandCardValue = tmpHandCardValue;					BestMaxCard = i;					PutCards = true;				}			}		}		if (PutCards)		{		clsHandCardData.value_nPutCardList.push_back(BestMaxCard);			clsHandCardData.value_nPutCardList.push_back(BestMaxCard);			clsHandCardData.value_nPutCardList.push_back(BestMaxCard);			clsHandCardData.value_nPutCardList.push_back(BestMaxCard);			clsHandCardData.uctPutCardType = clsGameSituation.uctNowCardGroup = get_GroupData(cgBOMB_CARD, BestMaxCard, 4);			return;		}		//王炸		if (clsHandCardData.value_aHandCardList[17] > 0 && clsHandCardData.value_aHandCardList[16] > 0)		{			//如果剩余手牌价值为正,证明出去的几率很大,那么可以用炸获得先手,王炸20分			if (BestHandCardValue.SumValue > 20)			{				clsHandCardData.value_nPutCardList.push_back(17);				clsHandCardData.value_nPutCardList.push_back(16);				clsHandCardData.uctPutCardType = clsGameSituation.uctNowCardGroup = get_GroupData(cgKING_CARD, 17, 2);				return;			}		}		//管不上		clsHandCardData.uctPutCardType = get_GroupData(cgZERO, 0, 0);		return;	}	//三牌类型	else if (clsGameSituation.uctNowCardGroup.cgType == cgTHREE)	{		//剪枝:如果能出去最后一手牌直接出		CardGroupData SurCardGroupData = ins_SurCardsType(clsHandCardData.value_aHandCardList);		if (SurCardGroupData.cgType != cgERROR)		{			if (SurCardGroupData.cgType == cgTHREE&&SurCardGroupData.nMaxCard>clsGameSituation.uctNowCardGroup.nMaxCard)			{				Put_All_SurCards(clsGameSituation, clsHandCardData, SurCardGroupData);				return;			}			else if (SurCardGroupData.cgType == cgBOMB_CARD || SurCardGroupData.cgType == cgKING_CARD)			{				Put_All_SurCards(clsGameSituation, clsHandCardData, SurCardGroupData);				return;			}		}		//-------------------------------------------三牌-------------------------------------------		//暂存最佳的价值		HandCardValue BestHandCardValue = get_HandCardValue(clsHandCardData);		//我们认为不出牌的话会让对手一个轮次,即加一轮(权值减少7)便于后续的对比参考。		BestHandCardValue.NeedRound += 1;		//暂存最佳的牌号		int BestMaxCard = 0;		//是否出牌的标志		bool PutCards = false;		for (int i = clsGameSituation.uctNowCardGroup.nMaxCard + 1; i  2)			{				//尝试打出一对牌,估算剩余手牌价值				clsHandCardData.value_aHandCardList[i] -= 3;				clsHandCardData.nHandCardCount -= 3;				HandCardValue tmpHandCardValue = get_HandCardValue(clsHandCardData);				clsHandCardData.value_aHandCardList[i] += 3;				clsHandCardData.nHandCardCount += 3;				//选取总权值-轮次*7值最高的策略  因为我们认为剩余的手牌需要n次控手的机会才能出完,若轮次牌型很大(如炸弹) 则其-7的价值也会为正				if ((BestHandCardValue.SumValue - (BestHandCardValue.NeedRound * 7))  0)				{					BestHandCardValue = tmpHandCardValue;					BestMaxCard = i;					PutCards = true;				}			}		}		if (PutCards)		{			clsHandCardData.value_nPutCardList.push_back(BestMaxCard);			clsHandCardData.value_nPutCardList.push_back(BestMaxCard);			clsHandCardData.value_nPutCardList.push_back(BestMaxCard);			clsHandCardData.value_nPutCardList.push_back(BestMaxCard);			clsHandCardData.uctPutCardType = clsGameSituation.uctNowCardGroup = get_GroupData(cgBOMB_CARD, BestMaxCard, 4);			return;		}		//王炸		if (clsHandCardData.value_aHandCardList[17] > 0 && clsHandCardData.value_aHandCardList[16] > 0)		{			//如果剩余手牌价值为正,证明出去的几率很大,那么可以用炸获得先手,王炸20分			if (BestHandCardValue.SumValue > 20)			{				clsHandCardData.value_nPutCardList.push_back(17);				clsHandCardData.value_nPutCardList.push_back(16);				clsHandCardData.uctPutCardType = clsGameSituation.uctNowCardGroup = get_GroupData(cgKING_CARD, 17, 2);				return;			}		}		//管不上		clsHandCardData.uctPutCardType = get_GroupData(cgZERO, 0, 0);		return;	}


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


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


if (SurCardGroupData.cgType == cgSINGLE_LINE&&SurCardGroupData.nMaxCard>clsGameSituation.uctNowCardGroup.nMaxCard				&&SurCardGroupData.nCount== clsGameSituation.uctNowCardGroup.nCount)			{				Put_All_SurCards(clsGameSituation, clsHandCardData, SurCardGroupData);				return;			}


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



		//验证顺子的标志		int prov = 0;		//顺子起点		int start_i = 0;		//顺子终点		int end_i = 0;		//顺子长度		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。



		for (int i = clsGameSituation.uctNowCardGroup.nMaxCard - length + 2; i  0)			{				prov++;			}			else			{				prov = 0;			}			if (prov >= length)			{				end_i = i;				start_i = i - length + 1;				for (int j = start_i; j 


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



		if (PutCards)		{			for (int j = start_i; j 


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



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


来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/912/viewspace-2804692/ ,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论

注册时间:2019-08-19

  • 博文量
    104
  • 访问量
    1057