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

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值