麻将高效鬼牌胡牌算法(C++版) - Go语言中文社区

麻将高效鬼牌胡牌算法(C++版)


最近做的一个麻将里面有双鬼牌的玩法,即为8张鬼牌,第一反应是采用N重循环把鬼牌变成一张张牌去遍历是否能胡牌,实际测试在5张牌鬼牌的情况得出结果就需要差不多5,6分钟。

因此自己做了一套用凑牌的方式去判断胡牌,原理是尝试把手牌中的任意一张当做对子(将牌),如果不够两张则用鬼牌去补一张,最后再提取出对子,判断剩下的手牌组成横或者顺子需要多少鬼牌,如果大于剩余的鬼牌数量,则本次检查胡牌失败,把对子还原到手牌中,尝试提取下一个对子继续检测。

我这里的实现方式C++,逻辑并不复杂可以简单的转成lua
首先传入的参数分别是手牌的索引(已经去掉鬼牌),和手牌的数量,以及鬼牌的数量
手牌的索引实际上是一个size大小为34(因为麻将的牌除去重复的一共34张)的char数组HandCard,其中HandCard[i]中的i代表牌值(0-34分别为1-9万,1-9条,1-9筒,东南西北中发白按顺序排列),HandCard[i]的值为手牌中这个牌的数量

bool CheckCanHu(char const HandCard[MAX_INDEX], size_t HandCardSize,size_t GhostCount)
{
	char TotalCount = HandCardSize + GhostCount;
	if ((TotalCount - 2) % 3 != 0 || TotalCount > MAX_HANDCOUNT)
	{
		//牌数,不满足3n+2,或大于14张
		return false;
	}
	//当该手牌中只有万能牌时
	if (TotalCount == GhostCount)
	{
		return true;
	}
	char CardIndexTemp[MAX_INDEX];
	memcpy(CardIndexTemp, HandCard, sizeof(CardIndexTemp));
	std::vector<char> PairsInfo;  //用于存放所有能构成胡牌的对子的值
	char CheckZero = ((GhostCount < 2) ? 2 : 0);//(0-未检查 1-成功 2-失败 )
	//1.把手牌中的每一张当做对子(将牌),然后检查余下的牌是否能达到胡牌条件(组成顺子或者横)
	for (char i = 0; i < MAX_INDEX; ++i)
	{
		if (0 == CardIndexTemp[i])
		{
			//尝试检测一次鬼牌变成手牌里没有的牌
			if (CheckZero == 0)  
			{
				//CheckZero不为0说明检测过了 不再检测
				GhostCount -= 2;
				if (CheckCanContinuity(CardIndexTemp, GhostCount))
				{
					CheckZero = 1;
					//PairsInfo.clear();
					//PairsInfo.push_back(-1);
					//如果变换成手中没有的牌都能胡牌 则说明可以胡任意牌,无需再继续检测
					return true;
				}
				else
				{
					CheckZero = 2;
				}
				//检测失败,还原牌,进行下一轮检测
				GhostCount += 2;
			}
			continue;
		}
		if (CardIndexTemp[i] > 4)
		{
			//牌数超过4张
			return false;
		}
		if (CardIndexTemp[i] >= 2)
		{
			CardIndexTemp[i] -= 2;
			if (CheckCanContinuity(CardIndexTemp, GhostCount))
			{
				//PairsInfo.push_back(i);//如果需要知道详细的胡那些对子,则此处不要返回
				return true;
			}
			//检测失败,还原牌,进行下一轮检测
			CardIndexTemp[i] += 2;
		}
		else if (GhostCount > 0)  //只有一张牌,那么就要使用一张鬼牌的来代替
		{

			--GhostCount;
			--CardIndexTemp[i];
			if (CheckCanContinuity(CardIndexTemp, GhostCount))
			{
				//PairsInfo.push_back(i);  //如果需要知道详细的胡那些对子,则此处不要返回
				return true;
			}
			//检测失败,还原牌,进行下一轮检测
			++CardIndexTemp[i];
			++GhostCount; 
		}
	}
	return false;
	//return (PairsInfo.size() > 0);
}

上面就是循环提取对子的过程,接下来是检查剩余的牌能组成顺和横

bool CheckCanContinuity(char const CardIndex[MAX_INDEX],  char GhostCount)
{
	char CardIndexTemp[MAX_INDEX] = { 0 };
	memcpy(CardIndexTemp, CardIndex, sizeof(CardIndexTemp));

	char GhostCardNeeded = 0;
	for (char i = 0; i < MAX_INDEX; ++i)
	{
		if (0 == CardIndexTemp[i])
			continue;
		if (CardIndexTemp[i] < 0)
		{
			GhostCardNeeded -= CardIndexTemp[i];  //因为牌数为负 所以用减
			CardIndexTemp[i] = 0;
			continue;
		}
		if ((i >= 0 && i <= 6) || (i >= 9 && i <= 15) || (i >= 18 && i <= 24))  //这个判断是用在牌值在(1-7)之间(可以组成[i,i+1,i+2]的顺子)
		{
			switch (CardIndexTemp[i])
			{
				case 1:
				case 4:
				{
					//组成顺
					--CardIndexTemp[i + 1];
					--CardIndexTemp[i + 2];
					break;
				}
				case 2:
				{
					if ((CardIndexTemp[i + 1] < 2 && CardIndexTemp[i + 2] < 2)
						|| (3 == CardIndexTemp[i + 1] && 0 == CardIndexTemp[i + 2])
						|| (0 == CardIndexTemp[i + 1] && 3 == CardIndexTemp[i + 2]))
					{
						//组成横
						++GhostCardNeeded;
					}
					else
					{
						//组成顺
						CardIndexTemp[i + 1] -= 2;
						CardIndexTemp[i + 2] -= 2;
					}
					break;
				}
				case 3:
				{
					break;
				}
			}
		}
		else if (7 == i || 8 == i || 16 == i || 17 == i || 25 == i || 26 == i)//这个判断是用在牌值在(8-9)之间(不可以组成[i,i+1,i+2]的顺子,可以组成横或者i为8的情况下[i-1,i,i+1])
		{
			switch (CardIndexTemp[i])
			{
				case 1:
				case 4:
				{
					if ((7 == i || 16 == i || 25 == i) && CardIndexTemp[i + 1] > 0)
					{
						--CardIndexTemp[i + 1];
						++GhostCardNeeded;
					}
					else
					{
						//如果i+1为0 则直接组成横
						GhostCardNeeded += 2;
					}
					break;
				}
				case 2:
				{
					++GhostCardNeeded; 
					break;
				}
				case 3:
				{
					break;
				}
			}
		}
		else
		{
			//字牌和风牌不能成顺 只能组成横
			if (CardIndexTemp[i] < 3)
			{
				GhostCardNeeded += (3 - CardIndexTemp[i]);
			}
			if (CardIndexTemp[i] > 3)
			{
				return false;
			}
		}
		CardIndexTemp[i] = 0;
	}

	//检查需要鬼牌数量和实际鬼牌的数量
	if (GhostCardNeeded > GhostCount)
	{
		return false;
	}
	else
	{
		return true;
	}

}

最后我们来构造几组牌来测试一下
在这里插入图片描述
完整的demo请参见git_hub:麻将高效鬼牌胡牌算法
CSDN下载地址:麻将高效鬼牌胡牌算法

版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/weixin_42800954/article/details/103061934
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2020-06-28 01:48:23
  • 阅读 ( 1850 )
  • 分类:算法

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢