1、麻将的基本规则
常见的麻将一共有三种花色,万、条、筒。每种花色的牌都有1~9个数值,每种数值的牌有4张,总共有493=108张牌。胡牌时手牌必须满足(不包含特殊牌型,如对对胡,大对子等)mABC+nDDD+EE(m >=0,n>=0),即一对将牌除开,剩余的牌全能组成顺子(ABC)或者刻子(DDD)。
2、胡牌算法解析
注:以下分析不包含特殊牌型,如对对胡,大对子等。
现有以下已胡手牌,14张:
六条做将牌,剩余的牌:一条 二条 三条 + 七条 八条 九条 + 三筒 三筒 三筒 + 四筒 五筒 六筒;
剔除将牌(既一对EE),剩余的牌都是顺子加刻子的组合,并且每种花色的牌也是顺子加刻子的组合。所以剔除将牌后,只要分析每种花色牌是否都满足mABC+nDDD即可。不管是顺子还是刻子,都是3张牌一组为最小单位,所以从有序手牌牌堆每次依次取三张牌分析是否满足ABC或DDD。
3、麻将的表示方法
一副完整的麻将表示方法
一张麻将由花色+数值确定,如一万,花色是万,数值1;所以可以用两位16进制数表示一张麻将。高位表示花色,低位表示数值大小。所以一副完整的扑克用数组表示如下:
//扑克数据
const BYTE m_cbCardDataArray[108]=
{
0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09, //万子
0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09, //万子
0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09, //万子
0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09, //万子
0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19, //条子
0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19, //条子
0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19, //条子
0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19, //条子
0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29, //同子
0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29, //同子
0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29, //同子
0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29, //同子
};
手牌表示方法
一副手牌正常情况下一共14张,一种是用一个数组HandCard[14]表示;如果我们用一个HandCard[14]的数组表示手牌,再实际摸牌出牌过程中就会进行频繁的增删操作,不仅浪费时间还麻烦;所以我们对手牌进行一次转换,定义一个数组m_cbCardIndex[27(3*9)],用来存储每种牌的张数,例如:手牌一万的牌有2张,则
m_cbCardIndex[0] = 2,以此类推;
//扑克数据,***每种麻将对应的下标***
const BYTE m_cbCardIndex[27]=
{
/*0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09, //万子 下标0-8
0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19, //条子 下标9-17
0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29*/ //同子 下标18-27
};
每种牌与下标对应的关系:例,二条Card=0x12,花色值Color=(Card&0xF0)>>4=1,数值Val=(Card&0xF0)=2;二条对应的下标Index =Color9 +Val -1=19+2-1=10 :
得出手牌HandCard[14]与m_cbCardIndex[27]扑克转换函数:
#define MASK_COLOR 0xF0 //花色掩码
#define MASK_VALUE 0x0F //数值掩码
//扑克转换函数:
BYTE SwitchToCardIndex(BYTE cbCardData)
{
return ((cbCardData&MASK_COLOR)>>4)*9+(cbCardData&MASK_VALUE)-1;
}
4、核心代码
//平胡
bool AnalyseHuCard(BYTE cbCardIndex[27])
{
/*
基本思路:
分单花色判断是否时候满足n*ABC+z*EEE+DD
如果全满足并且只有一对将牌及为胡牌
*/
ASSERT(cbCardIndex != nullptr);
BYTE cbCardNum[3] = { 0,0 ,0};
BYTE cbAllCardNum = 0;
BYTE cbCardIndexTemp1[27];
ZeroMemory(&cbCardIndexTemp1,sizeof(cbCardIndexTemp1));
CopyMemory(cbCardIndexTemp1, cbCardIndex, sizeof(cbCardIndexTemp1));
BYTE cbJiangPai = 0; //记录将牌数
//获取每种牌的数量,万 条 同
for (int i = 0;i < 3;i++)
{
//用户手里每种花色(筒、条、万)牌数量
cbCardNum[i] = GetCardCount(cbCardIndexTemp1 + i * 9);
cbAllCardNum += cbCardNum[i];
}
if (cbAllCardNum == 0) return false; //没有手牌数据
//按花色判断满足n*ABC+z*EEE+DD
for (int i = 0;i < 3;i++)
{
if (cbCardNum[i] == 0) continue; //无此种花色手牌
//判断有无将牌
//牌数量3n+2,有将牌
if ((cbCardNum[i] >= 2) &&(cbCardNum[i] - 2) % 3 == 0)
{
for (int g = 0; g < 9; g++)
{
ZeroMemory(&cbCardIndexTemp1, sizeof(cbCardIndexTemp1));
CopyMemory(cbCardIndexTemp1,cbCardIndex,sizeof(cbCardIndexTemp1));//每次循环重选将牌,重置cbCardIndexTemp[i * 9],重新判断
if (AnalyseJiangCardByColor(cbCardIndexTemp1 + i * 9, g) && AnalysePerutCardByColor(cbCardIndexTemp1 + i * 9))//选取将牌,判断是否满足n*ABC+z*EEE
{
cbJiangPai++;break; //记录所有花色中可以做将牌数量
}
}
}
//牌数量3n,无将牌
else if (cbCardNum[i] % 3 == 0)
{
if (!AnalysePerutCardByColor(&cbCardIndexTemp1[i * 9])) return false;
}
//牌数量不满足3n+2或3n一定不能胡牌
else
{
return false;
}
}
//说明所有将牌组合动不能满足n*ABC+z*EEE,或者花色(万条同)能做将牌的不止一对
if (cbJiangPai != 1) return false;
return true;
}
//扑克数目
BYTE GetCardCount(const BYTE cbCardIndex[9])
{
//数目统计
BYTE cbCardCount=0;
for (BYTE i=0;i<9;i++)
cbCardCount+=cbCardIndex[i];
return cbCardCount;
}
//分析牌
bool AnalysePerutCardByColor(BYTE cbCardIndexTemp[9])
{
INT j = 0;
while (j <9)
{
//对应牌数量为零跳过
if (cbCardIndexTemp[j] == 0)
{
j++;
continue;
}
/*if (j > 6 && cbCardIndexTemp[j] != 3) return false;*/
//从第一个牌数量不为0开始判断
else if (cbCardIndexTemp[j] >= 4)
{
//如果之后相连的两张牌数量大于0则可组成ABC形式
if (cbCardIndexTemp[j + 1] >0 && cbCardIndexTemp[j + 2] > 0)
{
//从牌堆中减去满足要求的牌
cbCardIndexTemp[j] -= 4;
cbCardIndexTemp[j + 1] -= 1;
cbCardIndexTemp[j + 2] -= 1;
j++;
}
else
{
return false;
}
}
//如果第一个牌数量>=3,只能以AAA形式存在
else if (cbCardIndexTemp[j] == 3)
{
//从牌堆中减去满足要求的牌
cbCardIndexTemp[j] -= 3;
j++;
}
//如果第一个牌数量<=2,只能以之后相连的2张牌组成ABC形式存在
else if (cbCardIndexTemp[j] == 2)
{
//如果之后相连的两张牌数量大于0则可组成ABC形式
if (j > 6) return false;
if (cbCardIndexTemp[j + 1] >= 2 && cbCardIndexTemp[j + 2] >= 2)
{
//从牌堆中减去满足要求的牌
cbCardIndexTemp[j] -= 2;
cbCardIndexTemp[j + 1] -= 2;
cbCardIndexTemp[j + 2] -= 2;
j++;
}
else
{
return false;
}
}
else if (cbCardIndexTemp[j] == 1)
{
//如果之后相连的两张牌数量大于0则可组成ABC形式
if (j > 6) return false;
if (cbCardIndexTemp[j + 1] >= 1 && cbCardIndexTemp[j + 2] >= 1)
{
//从牌堆中减去满足要求的牌
cbCardIndexTemp[j] -= 1;
cbCardIndexTemp[j + 1] -= 1;
cbCardIndexTemp[j + 2] -= 1;
j++;
}
else
{
return false;
}
}
}
return true;
}
//分析将牌
bool AnalyseJiangCardByColor(BYTE cbCardIndexTemp[9], BYTE cbSubscript)
{
//判断是否可为将牌
if (cbCardIndexTemp[cbSubscript] >= 2)
{
//减去将牌
cbCardIndexTemp[cbSubscript] -= 2;
return true;
}
return false;
}