1. 程式人生 > >麻將胡牌演算法 極速(速度接近理論極限)

麻將胡牌演算法 極速(速度接近理論極限)

此麻將胡牌演算法優點:

1.可處理多賴子牌(萬能牌)

2.演算法速度極快:1ms可大約計算1W+副手牌是否可胡(帶賴子、0.08us左右),不帶賴子的牌型更快。(最新版的演算法速度感覺已很接近理論極限值)

3.不同玩法的麻將,可用同一套胡牌演算法,載入不同的胡牌配置檔案即可。

4.查bug方便

先講下理論基礎:

將麻將的牌 所有牌值對應的索引設為下面的值

static const BYTE s_HuCardType[34] = 
{
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,//1~9 萬
0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19,//1~9 餅
0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29,//1~9 條
0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37                            //東南西北中發白(風字牌)
};

如果我們有一段陣列來表示各張牌的張數,如

手牌:1萬*3 、3萬*3、 5萬*3、7萬*3、9萬*2 可用如下陣列表示

BYTE byFlag[34] = {3,0,3,0,3,0,3,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};

但我們發現34種牌,每種牌的數量範圍是0~4(3bit),也就是至少需要34*3bit=102bit,int64都得兩個才存得下,十分不方便。有什麼優化方法麼?

優化方法:我們發現麻將不同花色的牌之間不會組成牌型(順子、刻子、將)賴子牌除外。所以各花色之間是相對獨立的。所以我們只需存同一花色的數量就行了

也就是上面的手牌,我們只需如下表示:

BYTE byFlag[9] = {3,0,3,0,3,0,3,0,2};

還有一個問題,許多麻將是存在賴子牌的(萬能牌)我們可以在最後加一位表示賴子牌的數量,那這樣我們只需10*3bit=30bit,一個int32就可以存下了

BYTE byFlag[10] = {3,0,3,0,3,0,3,0,2,0};

但是這樣優化後,會有兩個不好的影響需要處理:

1、風字牌與一般花色略有不同(總數只有7種且一般不能組成順子),得特殊處理

2、各花色的牌型裡,總共只有一對將,而將是比較特別的(將只需2張牌)


下面介紹一種直接查表來判斷是否可胡牌的方法。也就是將所有可胡的牌型存成一個表,是否可胡,去表裡查是否有當前手牌牌型即可。是不是會很快?

那關鍵的關鍵,也就是那張表了,我們要如何去構建這張表呢?如下

如何生成這樣的表呢?

1、生成所有單獨的牌組。如(下圖還沒考慮賴子,算上賴子的話,會更多)

所有順子:

0x01, 0x02, 0x03,
0x02, 0x03, 0x04,
0x03, 0x04, 0x05,
...
0x07, 0x08, 0x09,
所有刻子:
0x01, 0x01, 0x01,
0x02, 0x02, 0x02,
0x03, 0x03, 0x03,
...
0x09, 0x09, 0x09,
所有將:
0x01, 0x01,
0x02, 0x02,
0x03, 0x03,
...
0x09, 0x09,
然後將各牌組及將牌,組合生成所有可胡的手牌,並過濾掉非法牌組再存起來,就生成了我們需要表了。


跟朋友討論,其實還有種陣列指標代替hash表定址的方式,會快幾倍,但代價是多了十幾倍或幾十倍的記憶體(最新版優化後,記憶體也很少不到1M)。
它的優化代價也不小且應用面不大(它僅適合麻將,但本演算法可適用於多種牌類)。

測試效果如下圖:i7cpu 、win7、 隨機了1800W組牌,每組有4個賴子(萬能牌)有約45W組可胡,總共用時2.3秒