麻將向聽(tīng)數(shù)的計(jì)算與編程實(shí)現(xiàn)

首先說(shuō)明一下本文采用的牌表示方式:
萬(wàn):一二三四五六七八九
索:123456789
餅:①②③④⑤⑥⑦⑧⑨
字:東南西北白發(fā)中
向聽(tīng)數(shù)表示至少需要替換多少?gòu)埮撇拍苓M(jìn)入聽(tīng)牌狀態(tài)。
特別的,本文中的向聽(tīng)數(shù)=-1表示和牌。
為了計(jì)算向聽(tīng)數(shù),我們首先需要這么幾個(gè)參數(shù):
G3:面子(順子或刻子)的總數(shù)。--?group 3
G2:搭子(差一張牌就能變成面子)的總數(shù)(不能是臟搭子)。-- group 2
DG2:臟搭子(組成搭子的兩張牌都處于爆滿狀態(tài))的數(shù)量。--?dirty group 2
P:雀頭(兩張一樣的牌)的數(shù)量,只能是0或1。-- pair
DN:臟數(shù)牌(孤立的數(shù)牌,并處于爆滿狀態(tài))的數(shù)量。--?dirty number
DZ:臟字牌(孤立的字牌,并處于爆滿狀態(tài))的數(shù)量。-- dirty kanji
R:剩余的牌的數(shù)量。--?remaining
N:手牌的數(shù)量,取值為2、5、8、11、14,且N=3*G3+2*(G2+DG2+P)+DN+DZ+R。
K:K=(N-2)/3,取值為0、1、2、3、4。
爆滿狀態(tài):
例如11119,可以拆成[111, 1, 9],由于不存在第5張1,就稱1處于爆滿狀態(tài),那個(gè)單張1就叫做臟數(shù)牌。同理11112222,可以拆成[111, 222, 12],對(duì)于12這個(gè)搭子,12都處于爆滿狀態(tài),所以稱12是臟搭子。
關(guān)于雀頭:
對(duì)于兩張相同的牌,應(yīng)優(yōu)先把它當(dāng)成雀頭,而不是搭子。
關(guān)于臟搭子:
臟搭子只能是順子搭子,而不能是刻子搭子。因?yàn)槿绻诒闅v的過(guò)程中發(fā)現(xiàn)了能成為刻子的臟搭子,那么應(yīng)該將它歸類為2個(gè)臟牌(臟數(shù)牌或臟字牌),畢竟它已經(jīng)不可能成為刻子了。
關(guān)于臟數(shù)牌和臟字牌:
嚴(yán)格來(lái)說(shuō)這不是字面意思,數(shù)牌也可能成為臟字牌。例如11119,且自己杠了2,手牌可以拆成[111, 1, 9],那么此時(shí)這個(gè)單張1應(yīng)該認(rèn)為是臟字牌,因?yàn)樗豢赡艹蔀轫樧恿恕?/p>
下面來(lái)開(kāi)始計(jì)算向聽(tīng)數(shù)。
令s=-1。
首先我們要確保G3 + G2 + DG2 ≤?K,如果不滿足這個(gè)條件,那么進(jìn)行以下步驟:
優(yōu)先轉(zhuǎn)移G2到R,G2每減1那么R加2,直到滿足條件或G2=0終止循環(huán)。
若條件依然不滿足,轉(zhuǎn)移DG2到DN,DG2每減1那么DN加2,直到滿足條件終止循環(huán)。
將所有的DG2轉(zhuǎn)移到G2,即G2 += DG2,DG2 = 0。
如果P=0,那么我們要湊一個(gè)雀頭出來(lái),進(jìn)行如下步驟:
我們的目標(biāo)是盡量用最少的步數(shù)湊出雀頭來(lái),同時(shí)希望盡量消耗臟牌,因?yàn)楹竺媾K牌的存在會(huì)導(dǎo)致湊面子需要花費(fèi)很多步數(shù)。
如果R>0:
如果DZ>0,那么DZ -= 1,R -= 1,s += 1,跳轉(zhuǎn)到5。
如果DN>0,那么DN -= 1,R -= 1,s += 1,跳轉(zhuǎn)到5。
R -= 2,s += 1,跳轉(zhuǎn)到5。
如果DZ>0(此時(shí)說(shuō)明R=0):
如果DZ≥2,那么DZ -= 2,s += 2,跳轉(zhuǎn)到5。
DZ -= 1,DN -= 1,s += 1,跳轉(zhuǎn)到5。
DN -= 2,跳轉(zhuǎn)到5。(可以保證這里DN≥2)
R += DN,DZ -=?G2 + 2 * R。
如果DZ>0,那么s += DZ / 3。
返回2 * (K - G3) - G2 + s,所返回的值即向聽(tīng)數(shù)。

下面將用C#代碼實(shí)現(xiàn)這一算法。
測(cè)試代碼:
代碼輸出:
