[C++] 麻將胡牌算法



麻將的玩法規則眾多,核心的玩法是一致的,本文將根據聯發科2017年編程挑戰賽的復賽題規則來實現。

牌的表示方式
ABCDEFGHI代表一到九萬,abcdefghi代表一到九條,123456789代表一到九餅

三種胡牌牌型

  • 普通牌型,14張牌,形如:3+3+3+3+2。其中數字2代表兩張相同的牌可成一組,形如XX。數字3代表三張相同或者連續的牌可成一組,形如XXXXYZ
  • 龍七對,14張形如:2+2+2+2+2+2+2
  • 帶杠,即普通牌型里三張一樣的牌XXX可以升級成XXXX,稱為一道杠。每多一道杠,牌總數可以增加一張。最多可以有4道杠,因此帶杠的胡牌,牌總數最多可以達18張。

樣例

  • ABCeee345456DD 胡牌,組牌方式為 ABC+eee+345+456+DD,普通牌型。
  • ABeeee345456DD 炸胡,因為AB兩張牌未能形成組牌。
  • AAAABC123456333 炸胡,雖然看似組牌OK(AAA+ABC+124+345+456+333)但是不符合任何一種胡牌牌型。
  • AADDFF1133aagg 胡牌,暗七對。
  • AAAABBBBCCCCDDDD88 胡牌,3+3+3+3+2牌型,升級了4道杠。
  • AAA123789 炸胡,不符合任何一種牌型。
  • AAA111345666DEF88 炸胡,不符合任何一種牌型。

算法實現思路

1、普通牌型為3n+2的形式,和龍7對均為14張牌,若有杠則最多有18張牌,因此第一步可以判定,如果牌數小於14或者大於18,則必定不能胡牌;

2、對牌進行從小到大排序,方便后續判斷。如果手牌數是14張,可以先判定是否是龍7對(對對胡),其特點是每張奇數位的牌都和它后一位的牌相等。如果不是對對胡,則進入步驟3;

3、3n+2形式的普通牌型里面有一個對子,因此判斷是不是胡牌牌型,可以先找出其中的一個對子。一張牌可能有2張也可能有4張,可以組成對子也可能組成暗杠或者杠,又或者是和后面的牌組成順子。不管情況有多少種,對子一定是出現在重復的牌之中,只要每次遍歷去除一個對子即可。接下來進入步驟4;

4、去除一個對子后,判定是否是3n牌型,即是否是全部由順子或者暗杠組成。由於牌已經經過排序,所以只要觀察第一張牌即可。

  • 如果第一張牌的數量只有一張或者兩張,則這張牌必須和后面的牌組成順子,否則不能胡牌。如果存在這樣的順子,去除這個順子
  • 如果第一張牌的數量有三張或者四張,則可能組成一個暗杠,或者是和后面的牌組成順子(先不考慮有杠的情況),去除這個暗杠(順子)

一直循環以上的判斷,滿足條件則去掉這三張牌,直到牌數為0時,返回“胡牌”,否則回到步驟3中,將之前去除的對子放回,繼續刪除下一個對子。如果步驟3中嘗試過所有的對子還沒能滿足胡牌條件時,則返回“不胡牌”

5、如果牌數為15張,則至少包含一個4張牌的杠,否則不胡牌。如果包含多個杠,則依次遍歷刪除一個杠,再進入步驟3,判斷是不是3n牌型。如果遍歷完所有的杠后還不能胡牌,則返回“不胡牌”;

6、如果牌數是16張,則至少包含2個杠,依次遍歷刪一對杠的組合,余下同步驟5類似。同理,牌數為17張和18張時方法類似。

C++源代碼

#include <iostream>
#include <vector>
#include <algorithm>

using std::vector;
using std::cout;

template <typename T>
void showVector(vector <T> & lst);
vector <int> findReptPos(vector <char> & lst);
bool isDDH(const vector <char> & lst);
bool isHU3N(vector <char> & lst);
vector <char> delDUI(const vector <char> & lst, const int x);
bool is3N(vector <char> lst);
int numOfFirst(vector <char> lst);
bool checkGroup(vector <char> lst, const int num);
void delGroup(vector <char> & lst, const int num);
vector <char> delGANG(const vector <char> & lst, const int x);
vector <int> findGangPos(const vector <char> & lst);


int main(int argc, char **argv)
{

	char *mahjong = argv[1];
	vector <char> lst;
	
	for (int i = 0; mahjong[i] != '\0'; ++i)
	{
		lst.push_back(mahjong[i]);
	}

	std::sort(lst.begin(), lst.end());  // 從小到大排序
	
	/*cout << "sort: ";
	showVector(lst);*/

	int num = lst.size();  // 麻將牌數量
	
	if ((num < 14) || (num > 18))
	{
		cout << "BAD";
		return 0;
	}

	if (num == 14)  // 14張牌,2種胡牌法
	{
		if (isDDH(lst))  // 如果是對對胡
		{
			cout << "GOOD";
			return 0;
		}
		if (isHU3N(lst))
		{
			cout << "GOOD";
		}
		else
		{
			cout << "BAD";
		}

		return 0;
		 
	}

	if (num == 15)
	{
		vector <int> pos = findGangPos(lst);  // 查找杠的位置		

		if (pos.size() < 1)
		{
			cout << "BAD";
		}
		else
		{ 
			// 依次刪除一個杠,再判斷是否是3N牌型
			for (int i = 0; i < pos.size(); ++i)
			{
				vector <char> newLst = delGANG(lst, pos[i]);
				/*cout << "delGANG/";
				showVector(newLst);*/

				if (isHU3N(newLst))
				{
					cout << "GOOD";
					return 0;
				}
				
			}

			cout << "BAD";
		}
	}
	if (num == 16)
	{
		vector <int> pos = findGangPos(lst);  // 查找杠的位置		

		if (pos.size() < 2)  // 少於2個杠肯定不胡牌
		{
			cout << "BAD";
		}
		else
		{
			// 依次刪除不同的2個杠組合,再判斷是否是3N牌型
			for (int i = 0; i < pos.size() - 1; ++i)
			{
				for (int j = i + 1; j < pos.size(); ++j)
				{
					// 注意先刪除后一個杠,否則位置會變動
					vector <char> newLst1 = delGANG(lst, pos[j]);
					vector <char> newLst2 = delGANG(newLst1, pos[i]);

					/*cout << "delGANG/";
					showVector(newLst2);*/

					if (isHU3N(newLst2))
					{
						cout << "GOOD";
						return 0;
					}
				}
			}

			cout << "BAD";
		}
	}
	if (num == 17)
	{
		vector <int> pos = findGangPos(lst);  // 查找杠的位置		

		if (pos.size() < 3)  // 少於3個杠肯定不胡牌
		{
			cout << "BAD";
		}
		else
		{
			// 依次刪除不同的3個杠組合,再判斷是否是3N牌型
			for (int i = 0; i < pos.size() - 2; ++i)
			{
				for (int j = i + 1; j < pos.size() - 1; ++j)
				{
					for (int k = j + 1; k < pos.size(); ++k)
					{
						// 注意先刪除后一個杠,否則位置會變動
						vector <char> newLst1 = delGANG(lst, pos[k]);
						vector <char> newLst2 = delGANG(newLst1, pos[j]);
						vector <char> newLst3 = delGANG(newLst2, pos[i]);

						/*cout << "delGANG/";
						showVector(newLst3);*/

						if (isHU3N(newLst3))
						{
							cout << "GOOD";
							return 0;
						}
					}
				}
			}

			cout << "BAD";
		}
	}
	if (num == 18)
	{
		vector <int> pos = findGangPos(lst);  // 查找杠的位置		

		if (pos.size() != 4)  // 不是4個杠肯定不胡牌
		{
			cout << "BAD";
		}
		else
		{
			// 直接刪除4個杠
			vector <char> newLst1 = delGANG(lst, pos[3]);
			vector <char> newLst2 = delGANG(newLst1, pos[2]);
			vector <char> newLst3 = delGANG(newLst2, pos[1]);
			vector <char> newLst4 = delGANG(newLst3, pos[0]);

			/*cout << "delGANG/";
			showVector(newLst4);*/

			if (newLst4[0] == newLst4[1])
			{
				cout << "GOOD";
			}
			else
			{
				cout << "BAD";
			}
		}
	}


	return 0;
}


// 顯示列表內容
template <typename T>
void showVector(vector <T> & lst)
{
	vector <T>::iterator iter;  // 迭代器
	for (iter = lst.begin(); iter != lst.end(); iter++)
	{
		cout << *iter << " ";
	}
	cout << "\n";
}


// 查找重復牌的位置
vector <int> findReptPos(vector <char> & lst)
{
	vector <int> pos;  // 儲存重復牌的位置
	int temp_pos = 0;

	if (lst.size() <= 1)  // 牌數小於等於1,直接返回
	{
		return pos;
	}

	// lst.size() >= 2
	vector <char>::iterator iter2;
	iter2 = lst.begin();
	++iter2;  // 迭代器不支持算數運算,只能++/--/advance鏈式操作
	if (lst.front() == *iter2)
	{
		pos.push_back(temp_pos);
	}

	vector <char>::iterator it_front;
	vector <char>::iterator it_back;

	for (auto iter1 = iter2; iter1 != (--lst.end()); iter1++)  // 從第二位到倒數第二位
	{
		++temp_pos;  // 位置更新

		it_front = iter1; --it_front;
		it_back = iter1; ++it_back;

		// 不等於前面的且等於后面的
		if ((*iter1 != *it_front) && (*iter1 == *it_back))
		{
			pos.push_back(temp_pos);
		}
	}
	

	return pos;
}




// 是否是對對胡
bool isDDH(const vector <char> & lst)
{
	vector <char> newLst = lst;

	for (int i = 0; i <= 12; ++i)
	{
		if (newLst[i] != newLst[i + 1])
		{
			return false;
		}
		++i;
	}

	return true;
}


// 是否是普通牌型3n
bool isHU3N(vector <char> & lst)
{

	vector <int> pos = findReptPos(lst);  // 找重復牌的位置
	for (int i = 0; i < pos.size(); ++i)  // 依次去掉重復的對子
	{
		vector <char> newLst = delDUI(lst, pos[i]);  // 刪除一對牌

		/*cout << "delDUI/";
		showVector(newLst);*/

		if (is3N(newLst))
		{
			return true;
		}
	}
	return false;
}

// 是否是N個順子或者暗杠
bool is3N(vector <char> lst)
{
	
	if (lst.size() % 3 != 0)
	{
		return false;
	}

	while (lst.size() > 0)
	{
		

		if (lst.size() >= 3)
		{
			int num = numOfFirst(lst);  // 計算第一張牌的重復數量
			
			if (checkGroup(lst, num))  // 檢查是否有第一個順子或者暗杠
			{
				
				delGroup(lst, num);  // 刪除這組順子或者暗杠
				
			}
			else
			{
				return false;
			}

		}
		
		/*cout << "//";
		showVector(lst);*/
	}

	return true;
}


// 檢查是否有第一個順子或者暗杠
bool checkGroup(vector <char> lst, const int num)
{
	if ((num == 1) && (lst[1] == lst[0] + 1))
	{
		// 第二個數可能有重復情況
		for (int i = 2; i < lst.size(); ++i)
		{
			if (lst[i] != lst[1])
			{
				if (lst[i] == lst[0] + 2)
				{
					return true;
				}
				else
				{
					return false;
				}
			}
		}
		
	}
	if ((num == 2) && (lst[2] == lst[1] + 1))
	{
		// 第三個數可能有重復情況
		for (int i = 3; i < lst.size(); ++i)
		{
			if (lst[i] != lst[2])
			{
				if (lst[i] == lst[1] + 2)
				{
					return true;
				}
				else
				{
					return false;
				}
			}
		}

	}
	if (num >= 3)
	{
		return true;
	}

	return false;
}

// 刪除這組順子或者暗杠
void delGroup(vector <char> & lst, const int num)
{
	
	if (num == 1)
	{
		vector <char>::iterator iter = ++lst.begin();
		for (int i = 2; i < lst.size(); ++i)
		{
			++iter;
			if (lst[i] == (1 + lst[1]))
			{
				lst.erase(iter);
				lst.erase(lst.begin());
				lst.erase(lst.begin());
				break;
			}
			
		}
	}

	if (num == 2)
	{
		vector <char>::iterator iter = ++(++lst.begin());
		for (int i = 3; i < lst.size(); ++i)
		{
			++iter;
			if (lst[i] == (1 + lst[2]))
			{
				lst.erase(iter);
				lst.erase(++lst.begin());  // 刪除第二位的牌
				lst.erase(++lst.begin());
				break;
			}
		}
	}

	if (num >= 3)
	{
		lst.erase(lst.begin());
		lst.erase(lst.begin());
		lst.erase(lst.begin());
	}
}




// 計算第一張牌的重復數量
int numOfFirst(vector <char> lst)
{
	if (lst[0] != lst[1])
	{
		return 1;
	}
	if ((lst[0] == lst[1]) && (lst[1] != lst[2]))
	{
		return 2;
	}

	if (lst[0] == lst[2])
	{
		if (lst.size() == 3)
		{
			return 3;
		}
		if ((lst.size() >= 4) && (lst[2] != lst[3]))  // lst[3]一定保證size>=4
		{
			return 3;
		}
			
	}
	if ((lst[0] == lst[3]) && (lst.size() >= 4))
	{
		return 4;
	}
	return 0;
}




// 刪除x和x+1位置的一對牌
vector <char> delDUI(const vector <char> & lst, const int x)
{
	vector <char> newLst;

	for (int i = 0; i < lst.size(); ++i)
	{
		if ((i != x) && (i != (x + 1)))
		{
			newLst.push_back(lst[i]);
		}
	}


	return newLst;
}


// 刪除起始位置為x的杠(4張牌)
vector <char> delGANG(const vector <char> & lst, const int x)
{
	vector <char> newLst;

	for (int i = 0; i < lst.size(); ++i)
	{
		if ((i < x) || (i > x + 3))
		{
			newLst.push_back(lst[i]);
		}
	}

	return newLst;
}


// 查找杠的位置
vector <int> findGangPos(const vector <char> & lst)
{
	vector <int> pos;
	int temp_pos = 0;

	if (lst.size() < 4)
	{
		return pos;
	}

	for (int i = 0; i < lst.size() - 3; ++i)
	{
		
		if (lst[i] == lst[i + 3])
		{
			pos.push_back(temp_pos);
		}

		++temp_pos;  // 位置更新
	}

	return pos;
}

程序運行結果

程序從命令行參數取得輸入數據,數據為一個字符串,代表一副牌。若這副牌達到胡牌條件,輸出GOOD,否則輸出BAD。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM