C++帶賴子的麻將聽牌檢測算法實現

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

enum MajiangType:uint8_t
{
	emMJType_Wan	= 1, //萬
	emMJType_Tiao	= 2, //條
	emMJType_Tong	= 3, //筒
	emMJType_Zi	= 4, //字
	emMJType_Hua	= 5  //花
};

constexpr uint8_t MJ(uint8_t m, uint8_t n) {
	return m << 4 | (n & 0x0F);
}

inline MajiangType Majiang_Type(uint8_t m) {
	return MajiangType(m >> 4);
}

inline uint8_t Majiang_Value(uint8_t m) {
	return m & 0x0F;
}

enum emMJ:uint8_t
{
	emMJ_Unknown = 0,
	emMJ_Joker = 0,		//變後的賴子
	emMJ_1Wan = MJ(emMJType_Wan, 1),
	emMJ_2Wan = MJ(emMJType_Wan, 2),
	emMJ_3Wan = MJ(emMJType_Wan, 3),
	emMJ_4Wan = MJ(emMJType_Wan, 4),
	emMJ_5Wan = MJ(emMJType_Wan, 5),
	emMJ_6Wan = MJ(emMJType_Wan, 6),
	emMJ_7Wan = MJ(emMJType_Wan, 7),
	emMJ_8Wan = MJ(emMJType_Wan, 8),
	emMJ_9Wan = MJ(emMJType_Wan, 9),

	emMJ_1Tiao = MJ(emMJType_Tiao, 1),
	emMJ_2Tiao = MJ(emMJType_Tiao, 2),
	emMJ_3Tiao = MJ(emMJType_Tiao, 3),
	emMJ_4Tiao = MJ(emMJType_Tiao, 4),
	emMJ_5Tiao = MJ(emMJType_Tiao, 5),
	emMJ_6Tiao = MJ(emMJType_Tiao, 6),
	emMJ_7Tiao = MJ(emMJType_Tiao, 7),
	emMJ_8Tiao = MJ(emMJType_Tiao, 8),
	emMJ_9Tiao = MJ(emMJType_Tiao, 9),

	emMJ_1Tong = MJ(emMJType_Tong, 1),
	emMJ_2Tong = MJ(emMJType_Tong, 2),
	emMJ_3Tong = MJ(emMJType_Tong, 3),
	emMJ_4Tong = MJ(emMJType_Tong, 4),
	emMJ_5Tong = MJ(emMJType_Tong, 5),
	emMJ_6Tong = MJ(emMJType_Tong, 6),
	emMJ_7Tong = MJ(emMJType_Tong, 7),
	emMJ_8Tong = MJ(emMJType_Tong, 8),
	emMJ_9Tong = MJ(emMJType_Tong, 9),

	emMJ_DongFeng =		MJ(4, 1),//東 1 % 4 = 1
	emMJ_NanFeng =		MJ(4, 2),//南 2 % 4 = 2
	emMJ_XiFeng =		MJ(4, 3),//西 3 % 4 = 3
	emMJ_BeiFeng =		MJ(4, 4),//北 4 % 4 = 0

	emMJ_HongZhong =	MJ(4, 5),//中 5 % 4 = 1
	emMJ_FaCai =		MJ(4, 6),//發 6 % 4 = 2
	emMJ_BaiBan =		MJ(4, 7),//白 7 % 4 = 3

	//一副中花牌各只有一張
	emMJ_Mei =	MJ(5, 1),//梅
	emMJ_Lan =	MJ(5, 3),//蘭
	emMJ_Ju =	MJ(5, 5),//菊
	emMJ_Zhu =	MJ(5, 7),//竹
	emMJ_Chun = 	MJ(5, 9),//春
	emMJ_Xia =	MJ(5, 11),//夏
	emMJ_Qiu =	MJ(5, 13),//秋
	emMJ_Dong = 	MJ(5,15)  //冬
};

const std::set<emMJ> all_majiang_types = {
	emMJ_1Wan,
	emMJ_2Wan,
	emMJ_3Wan,
	emMJ_4Wan,
	emMJ_5Wan,
	emMJ_6Wan,
	emMJ_7Wan,
	emMJ_8Wan,
	emMJ_9Wan,


	emMJ_1Tiao,
	emMJ_2Tiao,
	emMJ_3Tiao,
	emMJ_4Tiao,
	emMJ_5Tiao,
	emMJ_6Tiao,
	emMJ_7Tiao,
	emMJ_8Tiao,
	emMJ_9Tiao,


	emMJ_1Tong,
	emMJ_2Tong,
	emMJ_3Tong,
	emMJ_4Tong,
	emMJ_5Tong,
	emMJ_6Tong,
	emMJ_7Tong,
	emMJ_8Tong,
	emMJ_9Tong,


	emMJ_DongFeng,
	emMJ_NanFeng,
	emMJ_XiFeng,
	emMJ_BeiFeng,
	emMJ_HongZhong,
	emMJ_FaCai,
	emMJ_BaiBan
};

//十三幺牌型:13張再加其中任意一張
static const std::set<emMJ> pattern131 = { emMJ_1Wan,emMJ_9Wan,emMJ_1Tiao,emMJ_9Tiao,emMJ_1Tong,emMJ_9Tong,
emMJ_DongFeng,emMJ_NanFeng,emMJ_XiFeng,emMJ_BeiFeng,emMJ_HongZhong,emMJ_FaCai,emMJ_BaiBan };

using MaJiangPai = std::vector<emMJ>;

template <typename T, typename V>
static T Find_In_Sorted(T begin, T end, V v) {
	auto it = begin;
	while (it != end)
	{
		if (*it == v)
		{
			break;
		}
		else if (*it > v)
		{
			it = end;
			break;
		}
		++it;
	}
	return it;
}

//遞歸拆分手牌
bool ResolvePai(MaJiangPai pai, uint8_t joker_count)
{
	if (pai.empty() && joker_count % 3 == 0)
	{
		return true;
	}
	else if (pai.size() + joker_count < 3)
	{
		return false;
	}

	if (pai.size() >= 3 && pai[0] == pai[2])
	{
		//找到刻子牌並移除
		pai.erase(pai.begin(), pai.begin() + 3);
		if (ResolvePai(pai, joker_count)) {
			return true;
		}
	}
	else if (pai.size() >= 2 && pai[0] == pai[1] && joker_count >= 1)
	{
		--joker_count;

		//找到刻子牌並移除
		pai.erase(pai.begin(), pai.begin() + 2);
		if (ResolvePai(pai, joker_count)) {
			return true;
		}
	}
	else if (pai.size() >= 1 && joker_count >= 2)
	{
		joker_count -= 2;

		//找到刻子牌並移除
		pai.erase(pai.begin(), pai.begin() + 1);
		if (ResolvePai(pai, joker_count)) {
			return true;
		}
	}

	if (Majiang_Type(pai[0]) < emMJType_Zi)
	{
		auto it1 = Find_In_Sorted(pai.begin() + 1, pai.end(), pai[0] + 1);
		if (it1 != pai.end())
		{
			auto it2 = Find_In_Sorted(it1 + 1, pai.end(), pai[0] + 2);
			if (it2 != pai.end())
			{
				//找到順序牌並移除
				pai.erase(it2);
				pai.erase(it1);
				pai.erase(pai.begin());

				if (ResolvePai(pai, joker_count))
					return true;
			}
			else if(joker_count >= 1)
			{
				//找到順序牌並移除
				--joker_count;

				pai.erase(it1);
				pai.erase(pai.begin());

				if (ResolvePai(pai, joker_count))
					return true;
			}
		}
		else if(joker_count >= 1)
		{
			auto it2 = Find_In_Sorted(pai.begin() + 1, pai.end(), pai[0] + 2);
			if (it2 != pai.end())
			{
				//找到順序牌並移除
				--joker_count;

				pai.erase(it2);
				pai.erase(pai.begin());

				if (ResolvePai(pai, joker_count))
					return true;
			}
			else if (joker_count >= 2)
			{
				joker_count -= 2;
				pai.erase(pai.begin());

				if (ResolvePai(pai, joker_count))
					return true;
			}
		}
	}

	return false;
}


//普通和牌類型
bool IsCommonHu(const MaJiangPai& original_pai)
{
	//前提:牌已經排好序,不含已碰牌和已槓牌,所以牌數應該是3n+2
	//過程:先找出一對將牌,然後再尋找刻子牌和順子牌,直到剩餘牌爲0才表示可和牌,否則不能和牌

	//記錄將牌位置
	size_t jiang_location = 0;
	MaJiangPai pai;
	while (true)
	{
		auto i = jiang_location + 1;
		if (i >= original_pai.size())
		{
			return false;
		}

		pai = original_pai;
		if (jiang_location != 0)
		{
			if (pai[i] == pai[jiang_location])
			{
				++i;
			}
		}

		//尋找將牌位置,記錄將牌第二個,並擦除該兩牌
		jiang_location = 0;
		for (; i < pai.size(); ++ i)
		{
			if (pai[i] == pai[i - 1])
			{
				jiang_location = i;
				pai.erase(pai.begin() + i - 1, pai.begin() + i + 1);
				break;
			}
			else if (pai[i] != emMJ_Joker && pai[0] == emMJ_Joker)
			{
				jiang_location = i;
				pai.erase(pai.begin() + i, pai.begin() + i + 1);
				pai.erase(pai.begin());
				break;
			}
		}
		if (jiang_location == 0)
		{
			//沒有將牌,不能和牌
			return false;
		}

		//無賴子時可直接循環拆分,有賴子時較複雜一些,需要遞歸拆分
		auto joker_end = pai.begin();
		while (joker_end != pai.end() && *joker_end == emMJ_Joker)
		{
			++joker_end;
		}
		uint8_t joker_count = joker_end - pai.begin();
		if (joker_count > 0)
		{
			pai.erase(pai.begin(), joker_end);
			if (ResolvePai(pai, joker_count))
			{
				break;
			}
		}
		else
		{
			//剩下的牌數是3的倍數
			//從左起第1張牌開始,它必須能組成刻子牌或者順子牌才能和,否則不能和
			while (pai.size() >= 3)
			{
				if (pai[0] == pai[2])
				{
					//找到刻子牌並移除
					pai.erase(pai.begin(), pai.begin() + 3);
				}
				else if (Majiang_Type(pai[0]) < emMJType_Zi)
				{
					auto it1 = Find_In_Sorted(pai.begin() + 1, pai.end(), pai[0] + 1);
					//auto it1 = std::lower_bound(pai.begin() + 1, pai.end(), pai[0] + 1);
					if (it1 != pai.end())
					{
						auto it2 = Find_In_Sorted(it1 + 1, pai.end(), pai[0] + 2);
						//auto it2 = std::lower_bound(it1 + 1, pai.end(), pai[0] + 2);
						if (it2 != pai.end())
						{
							//找到順序牌並移除
							pai.erase(it2);
							pai.erase(it1);
							pai.erase(pai.begin());
						}
						else
						{
							break;
						}
					}
					else
					{
						break;
					}
				}
				else
				{
					break;
				}
			}

			if (pai.empty())
			{
				break;
			}
		}
	}

	return true;
}

std::set<emMJ> Is131Ting(const MaJiangPai& original_pai)
{
	std::set<emMJ> setTingPai;
	if (original_pai.size() != pattern131.size())
	{
		return setTingPai;
	}

	auto pai_begin = original_pai.begin();
	while (pai_begin != original_pai.end() && *pai_begin == emMJ_Joker)
	{
		++pai_begin;
	}
	uint8_t joker_count = pai_begin - original_pai.begin();

	//先找將牌
	auto it_jiang = pai_begin + 1;
	while (it_jiang != original_pai.end())
	{
		if (*it_jiang == *(it_jiang-1))
		{
			break;
		}
		++it_jiang;
	}
	if (it_jiang == original_pai.end())
	{
		//沒找到將牌,則如果是十三幺就胡13張
		auto it1 = pai_begin;
		auto it2 = pattern131.begin();
		while (it1 != original_pai.end() && it2 != pattern131.end())
		{
			if (*it1 != *it2) {
				if (joker_count == 0)
				{
					return setTingPai;
				}
				--joker_count;
				++it2;
				continue;
			}
			++it1;
			++it2;
		}
		for (const auto& ting : pattern131)
		{
			setTingPai.insert(ting);
		}
		return setTingPai;
	}

	//找到將牌,則如果是十三幺就只能賴子個數加一張
	auto pai = original_pai;
	pai.erase(pai.begin() + (it_jiang - original_pai.begin()));

	auto it1 = pai.cbegin() + joker_count;
	auto it2 = pattern131.cbegin();
	while(it1 != pai.cend() && it2 != pattern131.cend())
	{
		if (*it1 != *it2)
		{
			if (setTingPai.size() > joker_count)
			{
				setTingPai.clear();
				break;
			}
			setTingPai.insert(*it2);
			++it2;
			continue;
		}
		++it1;
		++it2;
	}
	if (it1 == pai.cend() && it2 != pattern131.cend())
	{
		setTingPai.insert(*it2);
	}

	return setTingPai;
}

std::set<emMJ> Is7pairsTing(const MaJiangPai& original_pai)
{
	std::set<emMJ> setTingPai;

	if (original_pai.size() == 13)
	{
		auto pai_begin = original_pai.begin();
		while (pai_begin != original_pai.end() && *pai_begin == emMJ_Joker)
		{
			++pai_begin;
		}
		uint8_t joker_count = pai_begin - original_pai.begin();

		for (; pai_begin != original_pai.end(); ++pai_begin)
		{
			if (pai_begin + 1 != original_pai.end() && *pai_begin == *(pai_begin + 1))
			{
				++pai_begin;
			}
			else if(setTingPai.size() > joker_count)
			{
				//還有沒成對的牌時,如果之前沒配對的牌數已經超過賴子數,則組不成小七對
				setTingPai.clear();
				break;
			}
			else
			{
				//不相等時,以賴子抵
				setTingPai.insert(*pai_begin);
			}
		}
		if (pai_begin == original_pai.end() && setTingPai.size() < joker_count)
		{
			//匹配完成後,如果還有剩餘賴子,則可以匹配任何牌,即整幅麻將都可以和
			setTingPai = all_majiang_types;
		}
	}
	return setTingPai;
}

std::set<emMJ> CheckTing(const MaJiangPai& pai)
{
	std::set<emMJ> ting_pai;

	if (pai.size() == 13)
	{
		auto ting_pai = Is131Ting(pai);
		if (!ting_pai.empty())
		{
			//三十幺牌型與其它牌型不兼容,直接返回
			return ting_pai;
		}

		ting_pai = Is7pairsTing(pai);
		//小七對牌型與普通牌型兼容,即可能和小七對,也可能普通和。
	}


	//賴子個數:賴子牌編碼最小,在排好序的隊列前面
	auto joker_end = pai.cbegin();
	while (joker_end != pai.cend() && *joker_end == emMJ_Joker)
	{
		++joker_end;
	}
	uint8_t jocker_count = joker_end - pai.cbegin();

	for (auto i : all_majiang_types)
	{
		//沒有賴子時才過濾,有賴子的時候不能過濾,因爲賴子單調的時候是和所有牌
		if(jocker_count  == 0)
		{
			if (pai.front() - i > 1 || i - pai.back() > 1)
			{
				continue;
			}
			if (Majiang_Type(i) >= emMJType_Zi)
			{
				//字牌必須有相同的纔可能和
				if (!std::binary_search(pai.cbegin(), pai.cend(), i)) {
					continue;
				}
			}
			else
			{
				auto it = std::find_if(pai.cbegin(), pai.cend(), [&i,&jocker_count](const char& c) {
					//萬筒條必須滿足牌的數字相鄰纔有可能和
					return abs(c - i) <= 1;
				});
				if (it == pai.cend()) {
					continue;
				}
			}
		}
		
		auto temp(pai);
		auto range = std::equal_range(temp.begin(), temp.end(), i);
		if (std::distance(range.first, range.second) == 4) {
			//如果已經有四張牌了,不能算聽牌
			continue;
		}
		temp.insert(range.second, i);
		if (IsCommonHu(temp))
		{
			ting_pai.insert(i);
		}
	}
	return ting_pai;
}

int main()
{
    MaJiangPai v = {emMJ_Joker,emMJ_1Wan, emMJ_1Wan, emMJ_2Wan, emMJ_2Wan, emMJ_3Wan, emMJ_3Wan,emMJ_4Wan, emMJ_4Wan, emMJ_5Wan, emMJ_5Wan, emMJ_6Wan, emMJ_6Wan};
    auto ting = CheckTing(v);
    for(auto i : ting){
        std::cout<<(int) i <<std::endl;
    }
    return 0;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章