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

来源:互联网 发布:ios mvvm的数据绑定 编辑:程序博客网 时间:2024/04/29 08:17
#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 = 1emMJ_NanFeng =MJ(4, 2),//南 2 % 4 = 2emMJ_XiFeng =MJ(4, 3),//西 3 % 4 = 3emMJ_BeiFeng =MJ(4, 4),//北 4 % 4 = 0emMJ_HongZhong =MJ(4, 5),//中 5 % 4 = 1emMJ_FaCai =MJ(4, 6),//发 6 % 4 = 2emMJ_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;}