C++带赖子的麻将听牌检测算法实现

原创 2017年09月07日 09:51:37
#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;
}

麻将游戏的听牌算法

测试测试!!~~~~这两周都是在测试各种BUG,没事情的时候自己在网上学学新知识,也为下个月的游戏改版预热。最近呢我也开始了我的shader之旅,估计也是这充满神秘和艰辛的旅途吧,哈哈哈!      ...

癞子麻将胡牌以及听牌算法实现

最先实现的就是算法的实现。 需求:碰杠胡  ,不能吃 ,不能听 ,只能自摸胡,其中癞子可以做任意牌但是不能碰和杠。 写的时候还不会玩麻将,还是老板教的。^_^ 最麻烦的是胡牌算法。之前搜到的都是不包...
  • skillart
  • skillart
  • 2014年10月24日 11:03
  • 17571

Delphi7高级应用开发随书源码

  • 2003年04月30日 00:00
  • 676KB
  • 下载

带癞子麻将查表判断胡牌高效率低内存算法

在我未查阅相关资料时,最初我有两种想法(本文只深入讨论第二种想法) * 像我当初做斗地主智能出牌机器人拆解手牌那样,拆解手牌后判定是否符合条件进而判定输赢。 * 组合出所有赢的手牌,构造 map,判定...

带赖子的超高效麻将、跑胡子胡牌算法

速度: 每秒处理100万次四个赖子判胡 文档 github地址 https://github.com/yuanfengyun/qipai/tree/master/doc lua版 https://g...

带赖子的麻将胡牌及其听牌算法研究

癞子麻将胡牌以及听牌算法实现:http://blog.csdn.net/skillart/article/details/40422885 癞子麻将胡牌算法实现:http://www.bkjia.c...

C++实现麻将基本听牌胡牌的算法

c++实现麻将的基本胡牌与听牌算法,包括小七对和十三幺牌型。
  • sdghchj
  • sdghchj
  • 2017年08月03日 14:03
  • 609

麻将的和牌、听牌以及一向听(即能否打一张牌进行立直)的算法。

感觉网上关于麻将的源码资源很少,一般这种算法都是用递归,把牌堆分解成若干子牌堆然后针对2-3张牌的情形给出一个出口。 和牌算法比较常见,毕竟只要是麻将编程都要用到,后面两种虽然普通的麻将编程用不到,...

棋牌麻将 - 无癞子胡牌算法(第三版)

基础知识本文涉及的所有名词均在博文中有说明: http://blog.csdn.net/kunyus/article/details/78644517测试结果测试环境: MEM: 4 GB ...
  • kunyus
  • kunyus
  • 2017年12月04日 19:18
  • 34

带赖子的胡牌算法lua版本

1.GameLayer GameLayer = class("GameLayer",function() return cc.Layer:create() end) -- function ...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:C++带赖子的麻将听牌检测算法实现
举报原因:
原因补充:

(最多只允许输入30个字)