您的位置:首页 > 编程语言 > C语言/C++

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

2017-09-07 09:51 531 查看
#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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息