[C++] 麻将胡牌算法


麻将的玩法规则众多,核心的玩法是一致的,本文将根据联发科2017年编程挑战赛的复赛题规则来实现。

牌的表示方式
ABCDEFGHI代表一到九萬,abcdefghi代表一到九条,123456789代表一到九饼

三种胡牌牌型

  • 普通牌型,14张牌,形如:3+3+3+3+2。其中数字2代表两张相同的牌可成一组,形如XX。数字3代表三张相同或者连续的牌可成一组,形如XXXXYZ
  • 龙七对,14张形如:2+2+2+2+2+2+2
  • 带杠,即普通牌型里三张一样的牌XXX可以升级成XXXX,称为一道杠。每多一道杠,牌总数可以增加一张。最多可以有4道杠,因此带杠的胡牌,牌总数最多可以达18张。

样例

  • ABCeee345456DD 胡牌,组牌方式为 ABC+eee+345+456+DD,普通牌型。
  • ABeeee345456DD 炸胡,因为AB两张牌未能形成组牌。
  • AAAABC123456333 炸胡,虽然看似组牌OK(AAA+ABC+124+345+456+333)但是不符合任何一种胡牌牌型。
  • AADDFF1133aagg 胡牌,暗七对。
  • AAAABBBBCCCCDDDD88 胡牌,3+3+3+3+2牌型,升级了4道杠。
  • AAA123789 炸胡,不符合任何一种牌型。
  • AAA111345666DEF88 炸胡,不符合任何一种牌型。

算法实现思路

1、普通牌型为3n+2的形式,和龙7对均为14张牌,若有杠则最多有18张牌,因此第一步可以判定,如果牌数小于14或者大于18,则必定不能胡牌;

2、对牌进行从小到大排序,方便后续判断。如果手牌数是14张,可以先判定是否是龙7对(对对胡),其特点是每张奇数位的牌都和它后一位的牌相等。如果不是对对胡,则进入步骤3;

3、3n+2形式的普通牌型里面有一个对子,因此判断是不是胡牌牌型,可以先找出其中的一个对子。一张牌可能有2张也可能有4张,可以组成对子也可能组成暗杠或者杠,又或者是和后面的牌组成顺子。不管情况有多少种,对子一定是出现在重复的牌之中,只要每次遍历去除一个对子即可。接下来进入步骤4;

4、去除一个对子后,判定是否是3n牌型,即是否是全部由顺子或者暗杠组成。由于牌已经经过排序,所以只要观察第一张牌即可。

  • 如果第一张牌的数量只有一张或者两张,则这张牌必须和后面的牌组成顺子,否则不能胡牌。如果存在这样的顺子,去除这个顺子
  • 如果第一张牌的数量有三张或者四张,则可能组成一个暗杠,或者是和后面的牌组成顺子(先不考虑有杠的情况),去除这个暗杠(顺子)

一直循环以上的判断,满足条件则去掉这三张牌,直到牌数为0时,返回“胡牌”,否则回到步骤3中,将之前去除的对子放回,继续删除下一个对子。如果步骤3中尝试过所有的对子还没能满足胡牌条件时,则返回“不胡牌”

5、如果牌数为15张,则至少包含一个4张牌的杠,否则不胡牌。如果包含多个杠,则依次遍历删除一个杠,再进入步骤3,判断是不是3n牌型。如果遍历完所有的杠后还不能胡牌,则返回“不胡牌”;

6、如果牌数是16张,则至少包含2个杠,依次遍历删一对杠的组合,余下同步骤5类似。同理,牌数为17张和18张时方法类似。

C++源代码

#include <iostream>
#include <vector>
#include <algorithm>

using std::vector;
using std::cout;

template <typename T>
void showVector(vector <T> & lst);
vector <int> findReptPos(vector <char> & lst);
bool isDDH(const vector <char> & lst);
bool isHU3N(vector <char> & lst);
vector <char> delDUI(const vector <char> & lst, const int x);
bool is3N(vector <char> lst);
int numOfFirst(vector <char> lst);
bool checkGroup(vector <char> lst, const int num);
void delGroup(vector <char> & lst, const int num);
vector <char> delGANG(const vector <char> & lst, const int x);
vector <int> findGangPos(const vector <char> & lst);


int main(int argc, char **argv)
{

    char *mahjong = argv[1];
    vector <char> lst;
    
    for (int i = 0; mahjong[i] != '\0'; ++i)
    {
        lst.push_back(mahjong[i]);
    }

    std::sort(lst.begin(), lst.end());  // 从小到大排序
    
    /*cout << "sort: ";
    showVector(lst);*/

    int num = lst.size();  // 麻将牌数量
    
    if ((num < 14) || (num > 18))
    {
        cout << "BAD";
        return 0;
    }

    if (num == 14)  // 14张牌,2种胡牌法
    {
        if (isDDH(lst))  // 如果是对对胡
        {
            cout << "GOOD";
            return 0;
        }
        if (isHU3N(lst))
        {
            cout << "GOOD";
        }
        else
        {
            cout << "BAD";
        }

        return 0;
         
    }

    if (num == 15)
    {
        vector <int> pos = findGangPos(lst);  // 查找杠的位置     

        if (pos.size() < 1)
        {
            cout << "BAD";
        }
        else
        { 
            // 依次删除一个杠,再判断是否是3N牌型
            for (int i = 0; i < pos.size(); ++i)
            {
                vector <char> newLst = delGANG(lst, pos[i]);
                /*cout << "delGANG/";
                showVector(newLst);*/

                if (isHU3N(newLst))
                {
                    cout << "GOOD";
                    return 0;
                }
                
            }

            cout << "BAD";
        }
    }
    if (num == 16)
    {
        vector <int> pos = findGangPos(lst);  // 查找杠的位置     

        if (pos.size() < 2)  // 少于2个杠肯定不胡牌
        {
            cout << "BAD";
        }
        else
        {
            // 依次删除不同的2个杠组合,再判断是否是3N牌型
            for (int i = 0; i < pos.size() - 1; ++i)
            {
                for (int j = i + 1; j < pos.size(); ++j)
                {
                    // 注意先删除后一个杠,否则位置会变动
                    vector <char> newLst1 = delGANG(lst, pos[j]);
                    vector <char> newLst2 = delGANG(newLst1, pos[i]);

                    /*cout << "delGANG/";
                    showVector(newLst2);*/

                    if (isHU3N(newLst2))
                    {
                        cout << "GOOD";
                        return 0;
                    }
                }
            }

            cout << "BAD";
        }
    }
    if (num == 17)
    {
        vector <int> pos = findGangPos(lst);  // 查找杠的位置     

        if (pos.size() < 3)  // 少于3个杠肯定不胡牌
        {
            cout << "BAD";
        }
        else
        {
            // 依次删除不同的3个杠组合,再判断是否是3N牌型
            for (int i = 0; i < pos.size() - 2; ++i)
            {
                for (int j = i + 1; j < pos.size() - 1; ++j)
                {
                    for (int k = j + 1; k < pos.size(); ++k)
                    {
                        // 注意先删除后一个杠,否则位置会变动
                        vector <char> newLst1 = delGANG(lst, pos[k]);
                        vector <char> newLst2 = delGANG(newLst1, pos[j]);
                        vector <char> newLst3 = delGANG(newLst2, pos[i]);

                        /*cout << "delGANG/";
                        showVector(newLst3);*/

                        if (isHU3N(newLst3))
                        {
                            cout << "GOOD";
                            return 0;
                        }
                    }
                }
            }

            cout << "BAD";
        }
    }
    if (num == 18)
    {
        vector <int> pos = findGangPos(lst);  // 查找杠的位置     

        if (pos.size() != 4)  // 不是4个杠肯定不胡牌
        {
            cout << "BAD";
        }
        else
        {
            // 直接删除4个杠
            vector <char> newLst1 = delGANG(lst, pos[3]);
            vector <char> newLst2 = delGANG(newLst1, pos[2]);
            vector <char> newLst3 = delGANG(newLst2, pos[1]);
            vector <char> newLst4 = delGANG(newLst3, pos[0]);

            /*cout << "delGANG/";
            showVector(newLst4);*/

            if (newLst4[0] == newLst4[1])
            {
                cout << "GOOD";
            }
            else
            {
                cout << "BAD";
            }
        }
    }


    return 0;
}


// 显示列表内容
template <typename T>
void showVector(vector <T> & lst)
{
    vector <T>::iterator iter;  // 迭代器
    for (iter = lst.begin(); iter != lst.end(); iter++)
    {
        cout << *iter << " ";
    }
    cout << "\n";
}


// 查找重复牌的位置
vector <int> findReptPos(vector <char> & lst)
{
    vector <int> pos;  // 储存重复牌的位置
    int temp_pos = 0;

    if (lst.size() <= 1)  // 牌数小于等于1,直接返回
    {
        return pos;
    }

    // lst.size() >= 2
    vector <char>::iterator iter2;
    iter2 = lst.begin();
    ++iter2;  // 迭代器不支持算数运算,只能++/--/advance链式操作
    if (lst.front() == *iter2)
    {
        pos.push_back(temp_pos);
    }

    vector <char>::iterator it_front;
    vector <char>::iterator it_back;

    for (auto iter1 = iter2; iter1 != (--lst.end()); iter1++)  // 从第二位到倒数第二位
    {
        ++temp_pos;  // 位置更新

        it_front = iter1; --it_front;
        it_back = iter1; ++it_back;

        // 不等于前面的且等于后面的
        if ((*iter1 != *it_front) && (*iter1 == *it_back))
        {
            pos.push_back(temp_pos);
        }
    }
    

    return pos;
}




// 是否是对对胡
bool isDDH(const vector <char> & lst)
{
    vector <char> newLst = lst;

    for (int i = 0; i <= 12; ++i)
    {
        if (newLst[i] != newLst[i + 1])
        {
            return false;
        }
        ++i;
    }

    return true;
}


// 是否是普通牌型3n
bool isHU3N(vector <char> & lst)
{

    vector <int> pos = findReptPos(lst);  // 找重复牌的位置
    for (int i = 0; i < pos.size(); ++i)  // 依次去掉重复的对子
    {
        vector <char> newLst = delDUI(lst, pos[i]);  // 删除一对牌

        /*cout << "delDUI/";
        showVector(newLst);*/

        if (is3N(newLst))
        {
            return true;
        }
    }
    return false;
}

// 是否是N个顺子或者暗杠
bool is3N(vector <char> lst)
{
    
    if (lst.size() % 3 != 0)
    {
        return false;
    }

    while (lst.size() > 0)
    {
        

        if (lst.size() >= 3)
        {
            int num = numOfFirst(lst);  // 计算第一张牌的重复数量
            
            if (checkGroup(lst, num))  // 检查是否有第一个顺子或者暗杠
            {
                
                delGroup(lst, num);  // 删除这组顺子或者暗杠
                
            }
            else
            {
                return false;
            }

        }
        
        /*cout << "//";
        showVector(lst);*/
    }

    return true;
}


// 检查是否有第一个顺子或者暗杠
bool checkGroup(vector <char> lst, const int num)
{
    if ((num == 1) && (lst[1] == lst[0] + 1))
    {
        // 第二个数可能有重复情况
        for (int i = 2; i < lst.size(); ++i)
        {
            if (lst[i] != lst[1])
            {
                if (lst[i] == lst[0] + 2)
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }
        }
        
    }
    if ((num == 2) && (lst[2] == lst[1] + 1))
    {
        // 第三个数可能有重复情况
        for (int i = 3; i < lst.size(); ++i)
        {
            if (lst[i] != lst[2])
            {
                if (lst[i] == lst[1] + 2)
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }
        }

    }
    if (num >= 3)
    {
        return true;
    }

    return false;
}

// 删除这组顺子或者暗杠
void delGroup(vector <char> & lst, const int num)
{
    
    if (num == 1)
    {
        vector <char>::iterator iter = ++lst.begin();
        for (int i = 2; i < lst.size(); ++i)
        {
            ++iter;
            if (lst[i] == (1 + lst[1]))
            {
                lst.erase(iter);
                lst.erase(lst.begin());
                lst.erase(lst.begin());
                break;
            }
            
        }
    }

    if (num == 2)
    {
        vector <char>::iterator iter = ++(++lst.begin());
        for (int i = 3; i < lst.size(); ++i)
        {
            ++iter;
            if (lst[i] == (1 + lst[2]))
            {
                lst.erase(iter);
                lst.erase(++lst.begin());  // 删除第二位的牌
                lst.erase(++lst.begin());
                break;
            }
        }
    }

    if (num >= 3)
    {
        lst.erase(lst.begin());
        lst.erase(lst.begin());
        lst.erase(lst.begin());
    }
}




// 计算第一张牌的重复数量
int numOfFirst(vector <char> lst)
{
    if (lst[0] != lst[1])
    {
        return 1;
    }
    if ((lst[0] == lst[1]) && (lst[1] != lst[2]))
    {
        return 2;
    }

    if (lst[0] == lst[2])
    {
        if (lst.size() == 3)
        {
            return 3;
        }
        if ((lst.size() >= 4) && (lst[2] != lst[3]))  // lst[3]一定保证size>=4
        {
            return 3;
        }
            
    }
    if ((lst[0] == lst[3]) && (lst.size() >= 4))
    {
        return 4;
    }
    return 0;
}




// 删除x和x+1位置的一对牌
vector <char> delDUI(const vector <char> & lst, const int x)
{
    vector <char> newLst;

    for (int i = 0; i < lst.size(); ++i)
    {
        if ((i != x) && (i != (x + 1)))
        {
            newLst.push_back(lst[i]);
        }
    }


    return newLst;
}


// 删除起始位置为x的杠(4张牌)
vector <char> delGANG(const vector <char> & lst, const int x)
{
    vector <char> newLst;

    for (int i = 0; i < lst.size(); ++i)
    {
        if ((i < x) || (i > x + 3))
        {
            newLst.push_back(lst[i]);
        }
    }

    return newLst;
}


// 查找杠的位置
vector <int> findGangPos(const vector <char> & lst)
{
    vector <int> pos;
    int temp_pos = 0;

    if (lst.size() < 4)
    {
        return pos;
    }

    for (int i = 0; i < lst.size() - 3; ++i)
    {
        
        if (lst[i] == lst[i + 3])
        {
            pos.push_back(temp_pos);
        }

        ++temp_pos;  // 位置更新
    }

    return pos;
}

程序运行结果

程序从命令行参数取得输入数据,数据为一个字符串,代表一副牌。若这副牌达到胡牌条件,输出GOOD,否则输出BAD。
1397676-20180729152721660-1073506704.png

转载于:https://www.cnblogs.com/Ran-Chen/p/9385460.html

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值