這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
先簡單的說明一下,基於上一篇部落格麻將胡牌演算法使用的是Lua
語言,有一些同學私信我,之後部落格能不能使用福士一點的後端語言,所以這篇部落格將使用Google
強力推薦的後端語言Golang
。不過在這裡值得一提的是,編程特別是演算法更應該注重的是思想,程式設計語言本身並不會流露出你的演算法能力和設計思想,語言只是表達你思想的一個工具而已。裝逼到此結束,進入本文我們來討論一下賴子胡牌應該怎麼檢測
當然一些麻將中使用到的基本名詞和胡牌規則,在這裡就不在重複解釋了。如果不瞭解的可以參考上一篇部落格麻將胡牌演算法
賴子胡牌
胡牌規則和普通胡牌一樣,不過出現了一個賴子牌。這張牌可以是任意牌,如果我們依然按照普通胡牌演算法那樣檢測去遍曆的話,即使只算萬
,筒
,條
我們簡單的計算一下麻將共有27
種牌。如果有四個賴子,那麼賴子檢測演算法的時間複雜度將是普通胡牌演算法的27 * 27 * 27 * 27
倍,最壞的情況將是最後一種情況能胡牌,那麼就比普通胡牌演算法多檢測19683
次,按照目前的PC
效能來看其實還是可以接受的,不過我們通過演算法可以去最佳化那何樂而不為呢。
遍曆檢測
在最佳化之前我們還是先說說如何遍曆去檢測賴子胡牌,因為這不是這篇部落格討論的重點,所以只是簡單的介紹一下便利的思想。
去除手牌賴子
// 去除賴子牌func getAndRmoveLaiZiCard(cardList []int) []int { laiZiCardList := make([]int, 0, len(allLaiZiCardList)) for i := 0; i < len(cardList); i++ { for _, laiZiValue := range allLaiZiCardList { if laiZiValue == cardList[i] { laiZiCardList = append(laiZiCardList, cardList[i]) cardList[i] = 0 break } } } return laiZiCardList}
- 這一步主要目的有兩個
- 擷取手上的賴子牌,便於之後將賴子作為任意一張牌放入手牌
- 去除手中的賴子牌,便於加入一種賴子牌組成新的手牌
組合手牌(遍曆賴子胡牌檢測演算法的關鍵)
// 賴子胡牌檢測(遍曆)func checkLaiZiHu(cardList []int, laiZiCount int) bool { for _, mahjongValue := range mahjongValueList { tempCardList := append(cardList, mahjongValue) if laiZiCount == 1 { checkCount++ mahjongMatrix := getMahjongMatrixWithCardList(tempCardList) printCardsInfoByMahjongMatrix(mahjongMatrix) isHu := checkHu(mahjongMatrix) if isHu { return isHu } } else if laiZiCount > 1 { isHu := checkLaiZiHu(tempCardList, laiZiCount-1) if isHu { return isHu } } } return false}
上面我們提到了,胡牌檢測的時候一張賴子牌可以作為任意牌出現在手牌中。一個賴子的時候毫無疑問,賴子組合有27
種(只涉及 萬,筒,條 三種花色)。那麼兩個賴子的時候就是27*27
種組合了。因此上面使用遞迴的演算法去建立每一種組合。
胡牌檢測
因為在上一步已經構造好賴子組合并且加入到手牌中,因此只需要將手牌按照普通胡牌檢測方法檢測即可。
胡牌檢測
// 檢測胡牌func checkHu(mahjongMatrix MahjongMatrix) bool { mahjongMatrixList := getMahjongMatrixListByRemoveTwoCards(mahjongMatrix) for i := 0; i < len(mahjongMatrixList); i++ { removeThreeLinkCards(&mahjongMatrixList[i]) removeTheSameThreeCards(&mahjongMatrixList[i]) isHu := checkMatrixAllElemEqualZero(mahjongMatrixList[i]) if isHu { return isHu } } return false}
去除麻將矩陣中一個將之後的麻將矩陣列表
// 通過去除麻將矩陣中一個將之後的麻將矩陣列表func getMahjongMatrixListByRemoveTwoCards(mahjongMatrix MahjongMatrix) []MahjongMatrix { var mahjongMatrixList []MahjongMatrix for i := 0; i < 3; i++ { for j := 0; j < 12; j++ { if mahjongMatrix[i][j] >= 2 { temp := mahjongMatrix temp[i][j] -= 2 mahjongMatrixList = append(mahjongMatrixList, temp) } } } return mahjongMatrixList}
去除句子
// 去除句子func removeThreeLinkCards(mahjongMatrix *MahjongMatrix) { for i := 0; i < len(mahjongMatrix); i++ { for j := 0; j < len(mahjongMatrix[i])-2; j++ { if mahjongMatrix[i][j] > 0 && mahjongMatrix[i][j+1] > 0 && mahjongMatrix[i][j+2] > 0 { mahjongMatrix[i][j] -= 1 mahjongMatrix[i][j+1] -= 1 mahjongMatrix[i][j+2] -= 1 j-- } } }}
注意:可能存在0x0101
, 0x0102
, 0x0201
, 0x0202
, 0x0301
, 0x0302
這個樣的牌,因此檢測到一個句子之後,需要執行j--
避免漏掉一個句子的檢測
計數檢測
計數檢測,就是對遍曆檢測的一種最佳化。計數的思想就除先去除賴子牌之後剩餘的牌進行胡牌檢測,然後檢測還沒有組成 刻字 順子 將的牌需要多少個賴子牌才能組成 將 賴子 克子。如果需要的個數大於已有賴子個數則不能胡牌,否則可以胡牌。
計數賴子
首先將賴子牌從手牌中移除並且記錄賴子的個數,這個演算法與上面的去除賴子演算法一致,可以參考上面去除賴子演算法。
去除克子和句子
將除去賴子牌後剩餘牌放入麻將矩陣中進行胡牌檢測,演算法與上面的胡牌檢測演算法一致,可以參考上面胡牌檢測演算法。
檢測麻將矩陣中剩餘牌
- 計算將麻將矩陣中剩餘牌湊出一個 將 所需要的賴子個數
- 計算剩餘的牌組成 克子 順子 所需要的賴子個數
- 總共需要賴子個數如果小於等於手中所持有賴子個數則胡牌
func checkLaiZiHu(cardList []int, laiZiCount int) bool { mahjongMatrix := getMahjongMatrixWithCardList(cardList) removeThreeLinkCards(&mahjongMatrix) removeTheSameThreeCards(&mahjongMatrix) for i := 0; i < len(mahjongMatrix); i++ { for j := 0; j < len(mahjongMatrix[i]); j++ { if mahjongMatrix[i][j] > 0 { tempMahjong := mahjongMatrix needLaiZiCount := tempMahjong[i][j] % 2 tempMahjong[i][j] = 0 needLaiZiCount = getNeedLaiZiCountByMahjongMatrix(tempMahjong, needLaiZiCount) if needLaiZiCount <= laiZiCount { return true } } } needLaiZiCount := getNeedLaiZiCountByMahjongMatrix(mahjongMatrix, 2) if needLaiZiCount <= laiZiCount { return true } } return false}// 計算需要賴子的數量func getNeedLaiZiCountByMahjongMatrix(mahjongMatrix MahjongMatrix, needLaiZiCount int) int { minLaiZiCount := needLaiZiCount if !checkMatrixAllElemEqualZero(mahjongMatrix) { for i := 0; i < len(mahjongMatrix); i++ { for j := 0; j < len(mahjongMatrix[i]); j++ { if mahjongMatrix[i][j] <= 0 { continue } if mahjongMatrix[i][j+1] > 0 { mahjongMatrix[i][j]-- mahjongMatrix[i][j+1]-- j-- minLaiZiCount++ continue } if mahjongMatrix[i][j+2] > 0 { mahjongMatrix[i][j]-- mahjongMatrix[i][j+2]-- j-- minLaiZiCount++ continue } if mahjongMatrix[i][j] == 1 { mahjongMatrix[i][j]-- minLaiZiCount += 2 continue } if mahjongMatrix[i][j] == 2 { mahjongMatrix[i][j] -= 2 minLaiZiCount++ } } } } return minLaiZiCount}
注意:在組成 將 的過程中可能一個花色裡面都沒有剩餘牌,此時應該使用兩個賴子組成一個 將
歡迎討論
Email huliuworld@yahoo.com
Github https://github.com/LHCoder2016/MahjongArithmetic.git