之前用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,請檢查。