麻將胡牌演算法研究

上校發表於2017-03-26

麻將通常有13張牌,在打的時候隨著吃,碰越來越少。總數應該維持在3*n + 1,n=0~4,比如1張,4張,7張或10張。胡牌時,加一張(可能是自摸或吃碰來的一張)組成n個順子或暗刻,外加一個麻將對。

這裡要研究的要點是:

1. 給出3n + 2張牌如何判斷是否已經胡牌,所有的胡牌可能組合;

2. 如果給出3n+1張牌如何判斷是否已經挺牌,挺哪些牌。

這兩個問題其實主要是第一個問題,也就是如何判斷3n +2 張牌是否胡牌的問題。後者可以簡單地通過實驗加34種麻將牌之一看是否胡牌來判斷是否挺牌,以及挺哪些牌。

 

如何判斷3n +2張牌是否胡牌

麻將牌包括:

1條~9條

1萬~9萬

1餅~9餅

東西南北中發白

34種牌,34×4=136張牌。

給每張牌設一個編號

1條~9條     -------à 0 ~8

1萬~9萬     ------à 9~17

1餅~9餅     ------à 18~26

東西南北中發白    -----à 27~33

 

1 牌的儲存

設一個巨集,就是牌的種類

#define MAX_TILE_INDEX 34

所摸的牌就可以存在一個長度為34的陣列中

int tiles[MAX_TILE_INDEX];

陣列的每個成員最大值為4,因為每張牌的總數為4,就算摸到暗槓也不過是4. 所有陣列成員加起來應該是3n + 2

 

2 結果的儲存

每個胡牌必定是若干順子/暗刻,外加一個麻將對,用一個簡單的結構或類大概就是:

{

         int nSequence[4][3];

         int nPair[2];  //或直接nPair,不用陣列就可以表示麻將對了

}

 

給定的3n + 2張牌普通只胡一種情況,但特殊情況也可能有多種胡法,比如4個一萬,4個兩萬,4個3萬,2個四萬

胡牌至少可以:

i. 4個1萬,2萬和3萬的順子,外加4萬的麻將對;

ii. 1個1萬,2萬和3萬的順子,1萬暗刻,2萬暗刻,3萬暗刻,以及4萬麻將對。

這些結果都應該被存起來,胡牌應該以最大番數計算。

C++可以用一個vector模板來實現結果列表,java可以考慮用雜湊表。

 

3. 胡牌判斷演算法

判斷胡牌與否

判斷胡牌與否的過程:

3.1. 首先判斷總牌數是否為3n + 2,如果不是肯定不胡牌

3.2. 遍歷所有牌,找到所有一種牌數量大於2的情況,也就是ntile[index] >= 2,然後把這兩張牌(對子)去掉,記錄到結果中的nPair。然後就剩下3n張牌了,再判斷剩下的3n張牌是否能組成順子或是暗刻,如果可以全部組成,那麼這把牌就胡了,否則需要遍歷一下把其他牌當對子的可能性。

關於第二步中如何判斷3n 張牌可否組成順子或暗刻

從第一張(種)牌開始往後檢查,每張牌有5種可能, 0, 1, 2,3,4。如果是0,直接檢查下一張(種);

i. 如果是1張或兩張,要胡牌的話他(們)必須和後面兩張組成順子,如果不能組成順子,肯定不胡。如果可以組成順子,把順子牌取出,存入臨時結果,接著處理剩下的牌;

ii. 如果是3張,要胡牌有兩種可能,一是3張當作一個暗刻,還有一種可能是這3張都與後面的牌組成順子。這裡有檢查這兩種情況。比如3個一萬,3個兩萬和3個三萬。既可以以三暗刻算,也可以按三個順子算。這兩種在最後算番的時候不一樣。

iii. 如果是4張,胡牌的話必須要跟後面兩張牌組成一個順子,然後本張(種)就剩3種了,然後繼續2.2的步驟就可以了。

整個檢查過程可以用一個函式遞迴呼叫就可以了,每次處理一張,如果不能湊成順子或暗刻的話就推出返回錯誤,如果函式處理時總牌數為0,則所有牌都已處理完了,返回成功,結果也已經存在結果裡了,把結果加入結果列表。

4. 挺牌檢查

挺牌時應該是3*n + 1張牌。遍歷34種牌,加入3n +1 ,這時就是3n+2了,根據剛才的演算法算是否胡牌,如果胡牌,剛加入的那張牌就是挺牌。

 

5. 測試過程與結果

1. 挺牌檢查 –選用了非常複雜的九蓮寶燈,也就是3個一萬(或條,餅),3個九萬(或條餅),其他2萬到8萬(或條餅)。這種牌是挺從1萬到9萬,共9張挺牌。演算法可以正確算出。

2. 胡牌檢查 –選用了4個一萬,4個二萬,4個三萬,以及2個4萬,演算法正確列出了3種胡牌結果。

相關文章