#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;
}
C++帶賴子的麻將聽牌檢測算法實現
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.