python麻將和牌算法


之前用vba寫過,當時的思路不清楚,也沒有python這樣強大的工具,寫了好長時間。

現在想想,真的是太太太落后了。磨刀不誤砍柴工,學習還是大有裨益的。

麻將和牌規則:

胡牌的基礎牌型:
(1)11、123、123、123、123
(2)11、123、123、123、111(1111,下同)
(3)11、123、123、111、111
(4)11、123、111、111、111
(5)11、111、111、111、111
胡牌的特殊牌型:
11、11、11、11、11、11、11(七對)

這里先判斷7對的牌型,剩下的和牌牌型,包括基礎牌型,也可以是吃碰之后的牌,所以數量可能少於14張。

為保證函數solid,成為不需要維護的代碼,那么就需要進行參數檢查。
考慮數字是否在定義的規則范圍內;
考慮參數的數量是否合規;
考慮參數是否包含一個必備選項:對子;
考慮是否是特殊和牌類型:7對;
常規和牌判斷;

錯誤回顧:
1,首次制作,未考慮到遍歷完成,未和牌的情況,導致11,235,678 這樣的未和牌的,沒有返回數據。
2,首次制作的時候,對於順子的判斷出現了錯誤。比如:112233,一個牌型可能有多張,排序完成后,123在數字上連續,但是位置上不一定相鄰。所以不能直接用切片判斷,應該用 a[0]+1 in a 這樣的方式進行判斷。
3,和牌考慮是否超過了4張。

沒想到做一個小的項目要考慮諸多問題,如何防止此類錯誤的發生?
1,事先把限制條件都列出來,然后在做代碼的時候,考慮進去,測試時再檢查一遍。
2,做分枝或判斷時,要確定優先次序,徹底思考每一個步驟,是否考慮了所有的情況。

 

#coding='utf-8'
#author='小羅QQ1271801445'
#麻將胡牌算法
#判定規則:n*(abc)+m*(ddd)+ee
#特殊牌型:7*(ee),7對。

##規則:
##胡牌的基礎牌型:
##(1)11、123、123、123、123
##(2)11、123、123、123、111(1111,下同)
##(3)11、123、123、111、111
##(4)11、123、111、111、111
##(5)11、111、111、111、111
##胡牌的特殊牌型:
##11、11、11、11、11、11、11(七對)
#國士無雙、十三幺九。由1筒(餅)、9筒(餅)、1條(索)、9條(索)、1萬、9萬、東、南、西、北、中、發、白十三種牌統稱幺九牌。
#胡牌時這十三種牌某一種有兩枚,而另十二種各一枚,共計十四枚,即構成十三幺。

##其中:1=單張 11=將、對子 111=刻子 1111=杠 123=順子

##設計:
##牌型:萬條餅,東西南北風,中發白。
##萬:1-9
##條:11-19
##餅:21-29
##東西南北風:31,33,35,37
##中發白:41,43,45

##這樣定義,方便進行連續性計算,防止 東西南風,這樣湊成一組牌。
##思路,先判斷7對子,然后進行常規判斷。遍歷和剪枝。
##先找到數量大於等於2的牌,然后去掉,那么剩下的牌,要么連續的3張,或是相同的3張。
#3張相同的,肯定是相連的3張。
#3張連續的,可能是相鄰的,也可能有跳躍,比如11,2222,33,結果是22,123,123,和牌成功。如果是只做相鄰檢測,則結果會不對。



##為保證函數solid,成為不需要維護的代碼,那么就需要進行參數檢查。
##考慮數字是否在定義的規則范圍內;
##考慮參數的數量是否合規;
##考慮參數是否包含一個必備選項:對子;
##考慮是否是特殊和牌類型:7對;
##常規和牌判斷;
##
##錯誤回顧:
##1,首次制作,未考慮到遍歷完成,未和牌的情況,導致11,235,678 這樣的未和牌的,沒有返回數據。
##2,首次制作的時候,對於順子的判斷出現了錯誤。比如:112233,一個牌型可能有多張,排序完成后,123在數字上連續,但是位置上不一定相鄰。所以不能直接用切片判斷,應該用 a[0]+1 in a 這樣的方式進行判斷。
##3,另外一個重大錯誤,舉例:345,55,567,88,檢查和牌項時,58都是和牌項,但是5明細已經沒有牌了。所以要做數量的限制。

##知識點:通過減少print,可以大幅度減少運行時間。


import time,sys
import random #用於測試。
#公共參數,1套牌庫,注意總共是4套。
pais=list(range(1,10))+list(range(11,20))+list(range(21,30))+list(range(31,38,2))+list(range(41,46,2))


def hepai(a:list):
    '''Judge cards hepai. For excample:a=[1,2,3,4,4]

a=list,萬:1-9,條:11-19,餅:21-29,東西南北風:31,33,35,37,中發白:41,43,45。'''
    a=sorted(a)
    #print(a)

    #牌面檢查,是否屬於本函數規定的范圍內。
    #pais=list(range(1,10))+list(range(11,20))+list(range(21,30))+list(range(31,38,2))+list(range(41,46,2))
    #print(pais)
    for x in set(a):
        if a.count(x)>4:#某張牌的數量超過了4,是不正確的。
            return False
        if x not in pais:
            print('參數錯誤:輸入的牌型{}不在范圍內。\n萬:1-9,條:11-19,餅:21-29,東西南北風:31,33,35,37,中發白:41,43,45。'.format(x))
            return False

    #牌數檢查。
    if len(a)%3!=2:
        print('和牌失敗:牌數不正確。')
        return False
    
    #是否有對子檢查。
    double=[]
    for x in set(a):
        if a.count(x)>=2:
            double.append(x)
    #print(double)
    if len(double)==0:
        #print('和牌失敗:無對子')
        return False
    
    #7對子檢查(由於不常見,可以放到后面進行判斷)
    #對子的檢查,特征1:必須是14張;特征2:一個牌型,有2張,或4張。特別注意有4張的情況。
    if len(a)==14:
        for x in set(a):
            if a.count(x) not in [2,4]:
                break
        else:
##            print('和牌:7對子。',a)
            return True

    #十三幺檢查。
    if len(a)==14:
        gtws=[1, 9, 11, 19, 21, 29, 31, 33, 35, 37, 41, 43, 45] #[1,9,11,19,21,29]+list(range(31,38,2))+list(range(41,46,2)) #用固定的表示方法,計算速度回加快。
        #print(gtws)
        for x in gtws:
            if 1<=a.count(x)<=2:
                pass
            else:
                break
        else:
            print('和牌:國土無雙,十三幺!')
            return True

    #常規和牌檢測。
    a1=a.copy()
    a2=[] #a2用來存放和牌后分組的結果。
    for x in double:
        #print('double',x)
        #print(a1[0] in a1 and (a1[0]+1) in a1 and (a1[0]+2) in a1)
        a1.remove(x)
        a1.remove(x)
        a2.append((x,x))
        for i in range(int(len(a1)/3)):
            #print('i-',i)
            if a1.count(a1[0])==3:
                #列表移除,可以使用remove,pop,和切片,這里切片更加實用。
                a2.append((a1[0],)*3)
                a1=a1[3:]
                #print(a1)
            elif a1[0] in a1 and a1[0]+1 in a1 and a1[0]+2 in a1:#這里注意,11,2222,33,和牌結果22,123,123,則連續的3個可能不是相鄰的。
                a2.append((a1[0],a1[0]+1,a1[0]+2))
                a1.remove(a1[0]+2)
                a1.remove(a1[0]+1)
                a1.remove(a1[0])
                #print(a1)

            else:
                a1=a.copy()
                a2=[]
                #print('重置')
                break
        else:
            #print('和牌成功,結果:',a2)
            return True
    
    #如果上述沒有返回和牌成功,這里需要返回和牌失敗。
    else:
        #print('和牌失敗:遍歷完成。')
        return False

#單元測試:
#assert hepai([1,1,2,2,3,3,4,4,5,5,6,6,7,7])==True,'7對和牌'


try:
    print('單元測試開始:',
    hepai([1,1,2,2,3,3,4,4,5,5,6,6,7,7])==True,#7對和牌。
    hepai([1,1,1,1,13,13,4,4,5,5,6,6,17,17]),#含4個一樣牌的7對。
    hepai([1,9,11,19,21,29]+list(range(31,38,2))+list(range(41,46,2))+[29,])==True, #十三幺測試。
    hepai([1,1,2,2,2,2,3,3])==True,#不連續和牌。首次寫的時候,這里給忽略掉了。
    hepai([1,1,1,2,2,2,3,3,3,4,5,17,18,19])==True, #正常和牌。
    hepai([18,18,31,33,35,31,31,33,33,35,35])==True, #東西南北風
    hepai([33,34,35,36,36])==False, #參數錯誤。
    hepai([1,2,3,4])==False, #數量不正確。
    hepai([1,2,3,4,5])==False, #無對子。
    hepai([1,1,2,3,5,6,7,8])==False) #遍歷完成,不和牌。
    print('單元測試結束,如果有False,請檢查。')
    print('*'*50)
except:
    print('運行測試未通過,請檢查。')

 

 

 
運行結果:
和牌成功,結果: [(1, 1), (2, 3, 4), (2, 3, 4), (5, 6, 7), (5, 6, 7)]
和牌成功,結果: [(2, 2), (1, 2, 3), (1, 2, 3)]
和牌成功,結果: [(3, 3), (1, 1, 1), (2, 2, 2), (3, 4, 5), (17, 18, 19)]
和牌成功,結果: [(18, 18), (31, 31, 31), (33, 33, 33), (35, 35, 35)]
參數錯誤:輸入的牌型不在范圍內。
萬:1-9,條:11-19,餅:21-29,東西南北風:31,33,35,37,中發白:41,43,45。
和牌失敗:牌數不正確
和牌失敗:無對子
和牌失敗:遍歷完成。
單元測試開始: True True True True True True True True
單元測試結束,如果有False,請檢查。

 

 

 
       


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM