麻将的玩法规则众多,核心的玩法是一致的,本文将根据联发科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。

[C++] 麻将胡牌算法的更多相关文章

  1. 【洛谷5279】[ZJOI2019] 麻将(“胡牌自动机”上DP)

    点此看题面 大致题意: 给你13张麻将牌,问你期望再摸多少张牌可以满足存在一个胡的子集. 似乎ZJOI2019Day1的最大收获是知道了什么是胡牌? 一个显然的性质 首先我们要知道一个显然的性质,即对 ...

  2. 洗牌算法Fisher_Yates原理

    1.算法 http://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle 简单的原理如下图所示: 2.原理 总结下,洗牌算法Fisher_Yates ...

  3. C# 洗牌算法

    最近悟出来一个道理,在这儿分享给大家:学历代表你的过去,能力代表你的现在,学习代表你的将来. 十年河东十年河西,莫欺少年穷 学无止境,精益求精   C#洗牌算法如下: class Program { ...

  4. js 随机数 洗牌算法

    function shuffle(arr){ var len = arr.length; for(var i = 0;i<len -1;i++) { var idx = Math.floor(M ...

  5. Fisher–Yates shuffle 洗牌算法(zz)

    1,缘起 最近工作上遇到一个问题,即将一组数据,比如[A,B,C,D,E]其中的两个B,E按随机排列,其他的仍在原来的位置: 原始数组:[A,B,C,D,E] 随机字母:[B,D] 可能结果:[A,B ...

  6. 519. Random Flip Matrix(Fisher-Yates洗牌算法)

    1. 问题 给定一个全零矩阵的行和列,实现flip函数随机把一个0变成1并返回索引,实现rest函数将所有数归零. 2. 思路 拒绝采样 (1)先计算矩阵的元素个数(行乘以列),记作n,那么[0, n ...

  7. 《Algorithms算法》笔记:元素排序(3)——洗牌算法

    <Algorithms算法>笔记:元素排序(3)——洗牌算法 Algorithms算法笔记元素排序3洗牌算法 洗牌算法 排序洗牌 Knuth洗牌 Knuth洗牌代码 洗牌算法 洗牌的思想很 ...

  8. 洗牌算法shuffle

    对这个问题的研究始于一次在群里看到朋友发的洗牌面试题.当时也不知道具体的解法如何,于是随口回了一句:每次从剩下的数字中随机一个.过后找相关资料了解了下,洗牌算法大致有3种,按发明时间先后顺序如下: 一 ...

  9. 随机洗牌算法Knuth Shuffle和错排公式

    Knuth随机洗牌算法:譬如现在有54张牌,如何洗牌才能保证随机性.可以这么考虑,从最末尾一张牌开始洗,对于每一张牌,编号在该牌前面的牌中任意一张选一张和当前牌进行交换,直至洗到第一张牌为止.参考代码 ...

随机推荐

  1. JavaScript中trim 方法实现

    Java中的 String 类有个trim() 能够删除字符串前后的空格字符.jQuery中也有trim()方法能够删除字符变量前后的字符串. 可是JavaScript中却没有对应的trim() 方法 ...

  2. git 显示多个url地址推送

    前提 一般来说,我们为git增加远程库,一般都是git remote add origin <url> ( 你可以使用真实的地址来代替 \<url\> ) 但是你可能想要把你的 ...

  3. db2 blob EMPTY_BLOB()

    --针对BLOG类型的列,写入一个0长度的字符串

  4. CMake error:System Error:No such file or directory CMake error:Could not open file for write in copy operation xxxx.ros_Config.cmake.tmp.

    微型电脑或嵌入式与电脑还是有点不同的,在微型电脑上ros indigo 版本下利用catkin编译如果你遇到如下错误: CMake error:System Error:No such file or ...

  5. ASCII码表(0-255)

  6. Javascript间歇调用和超时调用

    间歇调用:每隔指定的时间就执行一次代码 超时调用:在指定的时间过后执行代码  HTML Code  12345678910111213141516171819   <!DOCTYPE html& ...

  7. 1624 取余最长路(set)

    1624 取余最长路 基准时间限制:1 秒 空间限制:131072 KB 分值: 40 难度:4级算法题 佳佳有一个n*m的带权矩阵,她想从(1,1)出发走到(n,m)且只能往右往下移动,她能得到的娱 ...

  8. The E-pang Palace(暴力几何)

    //暴力的几何题,问,n个点可以组成的矩形,不相交,可包含的情况下,最大的面积,还有就是边一定与 x y 轴平行,所以比较简单了 //暴力遍历对角线,搜出所有可能的矩形,然后二重循环所有矩形,判断一下 ...

  9. 字符串 转 时间戳 unix_timestamp('2018-07-01 0:0:0')

    SELECT FROM_UNIXTIME(createtime,'%Y%m%d') AS d, s.app,SUM(o.val) FROM orders o LEFT JOIN app_type s ...

  10. 在Nuxt中使用 Highcharts

    npm进行highchars的导入,导入完成后就可以进行highchars的可视化组件开发了 npm install highcharts --save 1.components目录下新建一个char ...