聲明: 算法並非原創 , 但是來源已經忘記了 , 當時考慮算法的時候看了比較多的麻將胡牌算法 , 想尋找自己比較容易理解的 , 找了幾篇,所以算法的出處已然忘記,不過還是感謝下原創吧 .
算法理解之后就不難了 , 下面開始詳細的闡述了.
1. 將麻將抽象為數字
數字 {01 ~ 09} 表示 {1 ~ 9} 筒
數字 {11 ~ 19} 表示 {1 ~ 9} 條
數字 {21 ~ 29} 表示 {1 ~ 9} 萬
數字 {31 33 35 37 } 表示 { 東 南 西 北 }
數字 {41 43 45} 表示 {中 發 白}
數字10 20 30 32 34 36 40 42 44 46 空出來不代表任何麻將牌 這樣設計的好處就是使得能夠形成順子的牌在用數字表示出來的時候剛好也是連着的 , 而不能夠形成順子的牌,在用數字表示的時候並不是順子 . 便於以后使用代碼進行判斷
2. 算法的核心流程
玩過麻將的都知道麻將玩家手上一般有兩種牌 一種是手牌 一種是已經碰牌或者吃牌或者杠牌之后已經明了的牌 . 現在以玩家A為例 , 把牌分的細一點
a. 玩家A的手牌 (數量1 + 3*n n < N , n < 4 )
b. 其他玩家打出來的牌 (數量 1)
c. 玩家A從牌面上取出來的牌 (數量 1)
d. 玩家A吃碰杠的牌 (3*n + 4*m)
能否胡牌主要是看手牌a 和b/c 的組合能否形成一對加n條順子和m條克子 . 能則能胡 反則不能.
如上圖 用數字表示為 {1,1,2,2,2,3,4,11,12,12,13,13,14,1} 前13張牌為手牌,最后一張二條為玩家A從牌面上取出的牌
OK, 現在只需要先取出一對將,然后判斷剩下的牌能否全部形成順子或者克子,現在對牌面按照相對應的數字進行從小到大的排序 . 現在從剩余的牌中最左邊的牌開始 , 如果只有一張這樣的牌那么這張牌A就只能當作順子的開頭 ; 如果有兩張這樣的牌 , 因為已經有了一對將而這兩張也不能組成克子 , 所以這兩張只能當作兩個順子的開頭 ; 如果有三張這樣的牌 , 可以組成克子 , 但是如果讓他組成順子則要求為 AAABBBCCC 與后面的三張也能組成克子 所以組成順子或者克子本質是相同的 但是組成克子AAA的通用性要高於組成順子AAABBBCCC 所以當有三個及以上這樣牌的時候優先組成克子AAA ; 如果有四張這樣的牌,要能胡牌則需要 AAAABBBBCCCC 或者 AAAABC ,對於是先組一個順子還是一個克子都會回到上述的情況 .
(這里沒有對七對等各類大胡作出判斷)
步驟一:從上述數組中找到一對做"將",並從數組中移除 , 這里共有4對牌所以要分成4種情況
1. {1,1}(將牌) , {1,2,2,2,3,4,11,12,12,13,13,14}(余牌)
2. {2,2}(將牌) , {1,1,1,2,3,4,11,12,12,13,13,14}(余牌)
3. {12,12}(將牌) , {1,1,1,2,2,2,3,4,11,13,13,14}(余牌)
4. {13,13}(將牌) , {1,1,1,2,2,2,3,4,11,12,12,14}(余牌)
依次進行步驟二的檢查 檢查完最后一種情況而沒有返回 "能胡牌" 則返回 不能胡牌
步驟二: 余牌數量為0 則返回 "能胡牌" 否則進入下一步 .
步驟三: 判斷余牌前三張是否相同 相同-> 步驟四 ; 不同 -> 步驟五.
步驟四: 移除余牌中的前三張牌 , 返回步驟二.
步驟五: 若余牌中第一個數為N , 則判斷是否有N + 1 與 N + 2 同時存在與余牌中 , 有將N , n+1 , n+2 從余牌中移除並返回 步驟二 , 否則返回 步驟一
演示如下
1. {1,1}(將牌) , {1,2,2,2,3,4,11,12,12,13,13,14}(余牌)
步驟二 --> 步驟三 --> 步驟五 == {2,2,4,11,12,12,13,13,14}(余牌) -->
步驟二 --> 步驟三 --> 步驟五 --> 步驟一
2. {2,2}(將牌) , {1,1,1,2,3,4,11,12,12,13,13,14}(余牌)
步驟二 --> 步驟三 --> 步驟四 == {2,3,4,11,12,12,13,13,14}(余牌) -->
步驟二 --> 步驟三 --> 步驟五 == {11,12,12,13,13,14}(余牌)-->
步驟二 --> 步驟三 --> 步驟五 == {12,13,14}(余牌)-->
步驟二 --> 步驟三 --> 步驟五 == {}(余牌) -->
步驟二 "能胡牌"
代碼如下
public static bool IsCanHU(List<int> mah, int ID) { List<int> pais = new List<int>(mah); pais.Add(ID); //只有兩張牌 if (pais.Count == 2) { return pais[0] == pais[1]; } //先排序 pais.Sort(); //依據牌的順序從左到右依次分出將牌 for (int i = 0; i < pais.Count; i++) { List<int> paiT = new List<int>(pais); List<int> ds = pais.FindAll(delegate (int d) { return pais[i] == d; }); //判斷是否能做將牌 if (ds.Count >= 2) { //移除兩張將牌 paiT.Remove(pais[i]); paiT.Remove(pais[i]); //避免重復運算 將光標移到其他牌上 i += ds.Count; if (HuPaiPanDin(paiT)) { return true; } } } return false; } private static bool HuPaiPanDin(List<int> mahs) { if (mahs.Count == 0) { return true; } List<int> fs = mahs.FindAll(delegate (int a) { return mahs[0] == a; }); //組成克子 if (fs.Count == 3) { mahs.Remove(mahs[0]); mahs.Remove(mahs[0]); mahs.Remove(mahs[0]); return HuPaiPanDin(mahs); } else { //組成順子 if (mahs.Contains(mahs[0] + 1) && mahs.Contains(mahs[0] + 2)) { mahs.Remove(mahs[0] + 2); mahs.Remove(mahs[0] + 1); mahs.Remove(mahs[0]); return HuPaiPanDin(mahs); } return false; } }