麻将胡牌算法(C++版)

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;
}

  • 1
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在贵阳麻将游戏中,判断胡牌需要遵循以下规则: 1. 手牌中有七对牌,即七对同样的牌,可以胡牌。 2. 手牌中有四副牌,即三张相同的牌加一张相同序数的牌,可以胡牌。 3. 手牌中有将牌,即两张相同的牌,如果能组成四副牌,可以胡牌。 4. 手牌中有顺子,即三张不同序数但同花色的牌,如果能组成四副牌,可以胡牌。 5. 手牌中有刻子,即三张相同的牌,如果能组成四副牌,可以胡牌。 在实现胡牌算法时,可以先对手牌进行排序,然后遍历每张牌,递归判断是否能组成胡牌形式。为了避免重复计算,可以使用记忆化搜索进行优化。 下面是一个简单的胡牌算法实现: ```c++ bool canHu(vector<int>& cards) { int size = cards.size(); if (size == 0) { return true; } if (size % 3 != 2) { return false; } sort(cards.begin(), cards.end()); if (is7Pairs(cards)) { return true; } for (int i = 0; i < size; i++) { if (i > 0 && cards[i] == cards[i-1]) { continue; } if (isKeZi(cards, i) || isShunZi(cards, i) || isJiang(cards, i)) { vector<int> tmp(cards.begin(), cards.end()); tmp.erase(tmp.begin()+i, tmp.begin()+i+3); if (canHu(tmp)) { return true; } } } return false; } bool is7Pairs(vector<int>& cards) { int size = cards.size(); if (size != 14) { return false; } for (int i = 0; i < size-1; i+=2) { if (cards[i] != cards[i+1]) { return false; } } return true; } bool isKeZi(vector<int>& cards, int i) { if (i + 2 >= cards.size()) { return false; } return cards[i] == cards[i+1] && cards[i] == cards[i+2]; } bool isShunZi(vector<int>& cards, int i) { if (i + 2 >= cards.size()) { return false; } return cards[i] + 1 == cards[i+1] && cards[i] + 2 == cards[i+2] && cards[i] / 10 == cards[i+1] / 10 && cards[i] / 10 == cards[i+2] / 10; } bool isJiang(vector<int>& cards, int i) { if (i + 1 >= cards.size()) { return false; } return cards[i] == cards[i+1]; } ``` 在上述实现中,`canHu`函数用于递归判断是否能胡牌,`is7Pairs`函数用于判断是否有七对牌,`isKeZi`函数用于判断是否有刻子,`isShunZi`函数用于判断是否有顺子,`isJiang`函数用于判断是否有将牌。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值