社区微信群开通啦,扫一扫抢先加入社区官方微信群
社区微信群
最近做的一个麻将里面有双鬼牌的玩法,即为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下载地址:麻将高效鬼牌胡牌算法
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!