麻將的玩法規則眾多,核心的玩法是一致的,本文將根據聯發科2017年編程挑戰賽的復賽題規則來實現。
牌的表示方式
ABCDEFGHI
代表一到九萬,abcdefghi
代表一到九條,123456789
代表一到九餅
三種胡牌牌型
- 普通牌型,14張牌,形如:
3+3+3+3+2
。其中數字2
代表兩張相同的牌可成一組,形如XX
。數字3
代表三張相同或者連續的牌可成一組,形如XXX
、XYZ
。- 龍七對,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。