1. 程式人生 > >swift--廣東麻將v2.0(帶胡牌、聽牌演算法和自動打牌功能)

swift--廣東麻將v2.0(帶胡牌、聽牌演算法和自動打牌功能)

本程式實現了廣東麻將的全部功能:自動摸牌、打牌、碰、槓、聽牌、胡牌(其中莊家手動打牌,其它電腦玩家自動打牌),具體功能有:

  1. 系統通過骰子確定莊家,然後發牌,最開始從莊家手動打牌。
  2. 可以碰,槓,不能吃牌;沒有癩子。只能自摸。
  3. 所有玩家自動.碰、槓,在某個玩家打牌時,一旦其它玩家可以碰或槓,則自動跳轉到對應玩家進行碰槓再自動判斷是否胡牌後再打牌(其中莊家手動打牌),模擬真實情況。其中槓有3種模式:
    • 暗槓(自己牌面摸了4張一樣的牌槓)、
    • 明槓(先碰,然後摸了一張同樣的槓)、
    • 碰槓(就是自己有3張,其它玩家打了一張而槓)
  4. 其它玩家自動摸牌,自動判斷是否能槓,是否能胡,然後自動打牌,為了簡化,預設“槓”優先,就有同時有槓和胡的話,先槓。
  5. 本遊戲沒有介面,只能在控制檯玩,後期需要新增上介面。本程式中的gamePk()其實就是不斷地呼叫其他類的方法,後期改為使用者按鈕點選事件再呼叫即可。
  6. 本程式是自我學習的一個小程式,程式碼註釋非常清楚,但在架構方面還存在一些問題,自動打牌功能還需要完善,目前還在學習設計模式,後期會繼續修改。

執行效果: 開始建立玩家。。。 建立 4 個玩家,玩家建立結束。。。。 建立麻將成功。。。。 10次洗牌結束。。。 2個骰子分別是:2:4,結果是6 莊家是2… 1號玩家牌面是: [7萬]、[7萬]、[8萬]、[8萬]、[8條]、[9條]、[2筒]、[3筒]、[5筒]、[6筒]、[8筒]、[南]、[北]、 2號玩家牌面是: [1萬]、[3萬]、[5萬]、[8萬]、[2條]、[9條]、[1筒]、[1筒]、[1筒]、[7筒]、[東]、[東]、[北]、[中]、 3號玩家牌面是: [1萬]、[3萬]、[4萬]、[4萬]、[7萬]、[8萬]、[5條]、[3筒]、[4筒]、[5筒]、[9筒]、[發]、[白]、 4號玩家牌面是: [5萬]、[9萬]、[4條]、[4條]、[6條]、[7條]、[7條]、[東]、[西]、[中]、[中]、[發]、[白]、 2號玩家請打牌,請輸入麻將編號:__ Program ended with exit code: 0

下面程式碼是專案中hupai.swift中內容,主要是關於胡牌,聽牌和自動打牌的演算法,其中胡牌和聽牌功能都OK,但自動打牌的演算法很基礎,沒有考慮牌面已打的牌和可能剩餘的牌,專案完整程式碼可在此下載,可直接編譯通過: https://download.csdn.net/download/qq_42439742/10715620

設計思路: // 1.設定4個玩家,位置定義:自己是1,左邊是2,對面是3,右邊是4 建立以下類: MaJiang : 麻將父類; MaJiangNumber : 數字牌類,繼承自麻將父類。 MaJiangWind : 風牌類,繼承自麻將父類。 MaJiangFactory : 麻將工廠類,用於生成麻將 MaJiangType : 列舉型別,設定麻將10種牌型 Tools : 工具類,專門用來進行計算 HuPai : 胡牌類, 實現是否胡牌,聽牌和自動打牌的演算法 Player : 描述玩家; Game:控制遊戲流程 每個方法 和屬性都有詳細註釋 在這裡插入圖片描述

// 廣東麻將簡化版 // Created by terry on 2018/8/21. // 功能: // 1.在控制檯操作。麻將有東西南北中發白,用2顆骰子。 // 2. 建立一幅麻將並洗牌,丟骰子後根據點數發牌,並顯示每個玩家的牌面。按點數確定誰是莊家, // 3.發牌後,對每人的牌面進行排序並顯示出來 // 3.可以碰,槓,不能吃牌;沒有癩子。只能自摸。胡牌後顯示結果。 // 4. IOS10.13+xcode8編譯通過

下面是程式碼: 其中關於majiang類的定義在本文最後面

/**
 //  HuPai.swift
 //  用於廣東麻將的胡牌、聽牌和打牌演算法
 //  要和主程式完全解耦
 備註:
 對子:2張相同的牌,如1萬1萬
 順子:3張連續的牌,如1萬2萬3萬
 暗子:3張同樣的牌,如1萬1萬1萬
 暗槓:自己摸了4張同樣的牌而槓
 碰槓:自己牌面已有3張同樣的牌,有人打了一張同樣的牌開槓
 摸槓:自己已經碰了一個暗刻,又摸了1張同樣的牌開槓

//本程式使用靜態方法,並單獨寫在本檔案HuPai.swift中
 /// 本例採用陣列形式存放所有胡牌的可能性,第3版將採用"樹"來存放。
 
 使用方法:
1.在主程式中直接呼叫huPai.Ok_HuPai返回是否胡牌。
 2.在主程式中直接呼叫huPai.Ok_TingPai返回是否聽牌和聽哪些牌
 //  Created by terry on 2018/9/17.
 **/
 
class HuPai {
    //禁止例項化,全部使用靜態方法
    private init() {   
    }
    
    /// 是否聽牌
    ///
    /// - Parameter allCards: 玩家的手牌,可能有碰槓而不是13張
    /// - Returns: 是否聽牌; 聽牌的陣列:哪些牌可以胡
    static   func is_TingPai(allCards:[MaJiang])->(isTingPai:Bool,cards_TingPai:[MaJiang]) {
        
        var cards_TingPai:[MaJiang] = [] //返回聽牌的那些牌
        var isTingpai = false // 是否聽牌
        let clonePais = creatAllClonePai() //建立一幅單張麻將
        
        //將單張麻將中的每一張牌挨個放進去測試是否胡牌,如果胡牌則可以聽牌。
        //一幅完整的牌可以同時聽幾張牌。
        for clonePai in clonePais {
            var clone_allCards =  allCards //生成一幅完整的14張牌
            clone_allCards.append(clonePai)
            
            if is_HuPai(allCards: clone_allCards) == true {
                isTingpai = true   //只要有一張可以胡,就返回已聽牌
                cards_TingPai.append(clonePai) //將這些牌放入聽牌陣列中
            }
        }
        
        return (isTingpai,cards_TingPai) ///聽牌結果
    }
    
    /// 是否胡牌
    ///
    /// - Parameters:
    ///   - allCards: 判斷是否胡牌的手牌,可能有碰槓而不是14張

    static func  is_HuPai(allCards:[MaJiang])->Bool {
        
        var sortCards = sort(maJiangs: allCards)  //排序後的牌
        
        let allDuiZhi = getDuiZhi(cards:sortCards)  //所有對子的陣列,存放的是對子中的單牌,接下來將逐一刪除自己作"將",剩下的就是3n張牌(0<=n<=4),必須全部成ABC或AAA
        
        // 如果沒有對子,則直接不能胡排
        if  allDuiZhi.isEmpty {
            return false
        }
        
        //如果有對子,刪除對子得到的陣列
        var yuPai:[MaJiang] = []
        //分別刪除對子得到所有的陣列
        for oneDuiZhi in allDuiZhi{
            sortCards=sort(maJiangs: allCards)  //重新生成完整的排序後的牌
            yuPai = delDuiZhiPai(duiZhi: oneDuiZhi, allCards: sortCards)
            //12張牌如果全部是順子或者暗子則胡牌,只要有一種能胡牌,就返回true.如果失敗則繼續刪除下一個對子測試是否能胡
            if isShunZhi_Or_AnZhi(yuPai: yuPai) {
                return true
            }
        }
        return false
    }
    
    
    
    
    /// 12張餘牌是否全是順子或者暗子,遞迴
    ///
    /// - Parameter newCards: 刪除某對對子後的12張牌
    /// - Returns: 是否胡牌
    private static   func isShunZhi_Or_AnZhi(yuPai:[MaJiang]) -> Bool {
        //如果陣列為空,則胡牌
        if yuPai.isEmpty {
            return true
        }
        var newYuPai = yuPai //將成為暗子或順子的牌刪除後的新餘牌
        //由第1,2,3組成一個臨時陣列,用於計算是否暗子或者順子,如果是則刪除自己,如果都不是,則返回false
        let cards_3 = [yuPai[0],yuPai[1],yuPai[2]]
        if isShunZhi(cards: cards_3) || isAnKe(cards: cards_3) {
            newYuPai.remove(at: 0)
            newYuPai.remove(at: 0)
            newYuPai.remove(at: 0)
            
            //進行遞迴,將刪除3張後的新陣列傳入
            if  isShunZhi_Or_AnZhi(yuPai: newYuPai) == true{
                return true
            }
            else{
                return false
            }
        }
        else{
            return false
        }
    }
   
    /// 在玩家麻將中分別刪除對子牌
    ///
    /// - Parameters:
    ///   - duiZhi: <#duiZhi description#>
    ///   - allCards: <#allCards description#>
    /// - Returns: <#return value description#>
    private static    func delDuiZhiPai(duiZhi:MaJiang,allCards:[MaJiang]) -> [MaJiang] {
        var tmpAllCards = allCards
        //遍歷,如果相等則連續刪除2張對子牌
        for i in 0..<13 {
            if isEquals(majiang_1: duiZhi, majiang_2: tmpAllCards[i]) {
                tmpAllCards.remove(at: i)
                tmpAllCards.remove(at: i)
                break
            } 
        }
        return tmpAllCards
    }
    
    /// 返回所有為對子的牌
    ///
    /// - Parameter cards: <#cards description#>
    /// - Returns: <#return value description#>
    private static func getDuiZhi(cards:[MaJiang]) -> [MaJiang] {
        var result_DuiZhi:[MaJiang]=[]
        //考慮去重
        for i  in 0..<cards.count-1 {
            //從第1張開始,當前後2張相等時
            if isEquals(majiang_1: cards[i], majiang_2: cards[i+1]) {
                //如果陣列為空,則直接增加
                if result_DuiZhi.isEmpty{
                    result_DuiZhi.append(cards[i])
                }
                else {
                    //如果 和陣列中最後一個牌不相等,則增加。如果相等則放棄--為了針對連續3張牌相等的情況。如果連續4張相等,則進行暗槓
                    if !isEquals(majiang_1: cards[i], majiang_2: result_DuiZhi[result_DuiZhi.count-1]) {
                        result_DuiZhi.append(cards[i])
                    }
                }
            }
        }
        return result_DuiZhi
    }
    
    
    /// 返回3張牌是否成順子
    ///
    /// - Parameter cards: <#cards description#>
    /// - Returns: <#return value description#>
    private static  func isShunZhi(cards:[MaJiang]) -> Bool {
        //3張牌裡只要有一張是風,就無法形成一順子。
        if cards[0].getType().rawValue>3 || cards[1].getType().rawValue>3 || cards[2].getType().rawValue>3 {
            return false
        }
        //3張牌只要有一張花色不一樣,就無法形成順子
        if cards[0].getType() != cards[1].getType() || cards[1].getType() != cards[2].getType() {
            return false
        }
        
        if (cards[0] as! MaJiangNumber).number + 1 == (cards[1] as! MaJiangNumber).number   {
            if (cards[1] as! MaJiangNumber).number + 1 == (cards[2] as! MaJiangNumber).number {
                return true
            }
        }
        
        return false
    }
    
    /// 返回這3張牌是否暗子
    ///
    /// - Parameter cards: <#cards description#>
    /// - Returns: <#return value description#>
    private static func isAnKe(cards:[MaJiang]) -> Bool {
        if isEquals(majiang_1: cards[0], majiang_2: cards[1]) && isEquals(majiang_1: cards[1], majiang_2: cards[2]){
            return true
        } else {
            return false
        }
    }
    
    
    /// 電腦自動打牌演算法
    ///
    /// - Parameter allCard: 玩家摸牌後的手牌,可能有碰槓而不是14張
    /// - Returns: 要打出的那一張麻將
    static  func daPai_Auto(allCards:[MaJiang]) -> MaJiang {
        //TODO:打牌演算法
        /*       聽牌優先,暫不考慮牌河中的牌。
         先打出所有前後無關的牌
         上一聽  打一張是否能聽牌?是-》打哪幾張可以聽牌-》打出導致聽牌張數最少的那張牌
         | 否
         上二聽  再打一張是否能聽牌?是-》打出導致聽牌張數最少的那張牌
         |否
         上三聽  再打一張是否能聽牌?是-》打出導致聽牌張數最少的那張牌
         |否
         直接打出最後那張牌
         */
    //   Tools.echo_SetLog(nowLog: String(allCards.count))
        
        
        var myAllCards  = Tools.player_MaJiangs_Sort(maJiangs: allCards)// 用來計算
        //從小到大排序。如果有1張和前面的不相關,則打這一張。
        //從最後一張開始--因為風牌在最後面,要優先打出去
        var i=myAllCards.count-1
        while i>=1 {
            if !isXiangGuan(card1: myAllCards[i-1], card2:myAllCards[i] ){
                return myAllCards[i]  //為了避免程式碼過長,此處直接返回
            }
            i -= 2  //如果2張相關聯,則下標向前移2個位。//如。。。。東,南,西,西。當【12】【13】相關時直接向前跳2個位。
        }
        Tools.echo_SetLog(nowLog: String(myAllCards.count))
        
        //下面已經全部是互相關聯的牌
        //上一聽,打一張能否聽牌?
        //從第一張開始,依次刪除一張牌,是否能聽牌,將每種能聽牌的張數記錄下來,打出聽牌張數最少的牌
        let result = is_ShangYiTing(allcards: myAllCards)
        if result.isTingPai == true{
            return result.daPaicard
        }
        else{
            //從[0]開始更換一張牌,如果從0-13每個位都換了一遍還是不能聽牌,則更換下一張
            /////////此處為遞迴呼叫
            return  is_ShangLiangTing(huanPaiId: 0, allcards: myAllCards).daPaicard
            
        }
    }

    
    /// 上一聽---打出一張牌後能否聽牌,相當於上一張牌後可以聽牌,是打出聽牌張數最多的牌
    ///
    /// - Parameter allcards: 手牌
    /// - Returns: .0:是否可以聽牌,.1:綜合考慮後打出的麻將--打出能聽牌最多的那張牌
    private static func is_ShangYiTing(allcards:[MaJiang])->(isTingPai:Bool,daPaicard:MaJiang){
        
        var tingPai:[(dapai:MaJiang,tingpais:[MaJiang])] = []  //元組陣列存放聽牌結果。.0:打出的某張牌。.1:對應胡的幾張牌
        
        for i in 0..<allcards.count{
           var newAllCards = sort(maJiangs: allcards) //重新生成完整手牌
            newAllCards.remove(at: i)   //刪除一張牌,能否聽牌?
            
            let result = is_TingPai(allCards: newAllCards) //將刪了一張牌的13張牌是否能聽牌(或少於13張)
            //如果可以聽牌,則存入陣列。//此處不去重也可以,原因見後面的迴圈
            if result.isTingPai {
                tingPai.append((dapai:allcards[i],tingpais:result.cards_TingPai))
            }
        }
        //如果tingpai[]裡有值--就是可以聽牌,則找出能聽牌張數最多的那張打出
        if !tingPai.isEmpty {
            //如果只有一張牌打出後可以聽牌,則打出這些牌 -- 聽牌優先
            if tingPai.count == 1 {
                return (true,tingPai[0].dapai)
            }
            //有多張牌打出後都可以聽牌,則算出聽牌張數最多所對應打出的那種張
            var tmp = tingPai[0]
            for  i in 0..<tingPai.count-1 {
                if tmp.tingpais.count <= tingPai[i+1].tingpais.count {
                    tmp = tingPai[i+1]
                }
            }
            return (true, tmp.dapai)
        }
        
        return (false,allcards[0]) //為否時打牌值無關緊要,因為要進行後續的迴圈
    
    }

    
    /// 上二聽(遞迴使用)---打出二張牌後能否聽牌(更換1張牌), 相當於上2張牌後可以聽牌,是打出聽牌張數最多的牌
    ///
    /// - Parameters:
    ///   - huanPaiId: 從第幾個位置開換一張牌,可用於遞迴操作。預設從第【0】張開始
    ///   - allcards: 手上的牌
    /// - Returns: 0:是否可以聽牌,.1:綜合考慮後打出的麻將
    private static func is_ShangLiangTing(huanPaiId:Int,allcards:[MaJiang])->(isTingPai:Bool,daPaicard:MaJiang){
       
        //TODO:當huanPaiId>0時說明是遞迴,tingPai需要增加遞迴方法的返回值
        Tools.echo_SetLog(nowLog: "正在進行is_shangLiangTing第\(huanPaiId)次遞迴...\n")
        
        if huanPaiId>=8 {
            return(false,allcards[allcards.count-1])
        }
         //元組陣列存放聽牌結果。0.第一次換牌的pai(暫取消) .1:打出的牌。.2:對應上一聽打哪幾張牌可以聽牌的牌  
        var tingPai:[(dapai:MaJiang,shangyiting:[MaJiang])] = []
        
        var shangyiting:[MaJiang] = [] //元組中shengyiting的變數
        
        //從第huanPaiId張開始迴圈換牌,初始值huanPaiId=0
        //如果遞迴迴圈6輪都還沒有可以聽牌的,則打出最後一張
        
        var newAllCards:[MaJiang] = []//換牌後的新手牌
        
        for i in 0..<allcards.count{
            
            var tmpAllCards = sort(maJiangs: allcards) //重新生成完整手牌
            tmpAllCards.remove(at: i)   //刪除一張牌,再補一張進來,能否上一聽?
            
            //如果前後都是萬,則只需生成"萬"即可。
            //這樣可以大大的優化效能,避免無效的迴圈
            
            //可以繼續縮小範圍,以減少迴圈次數
            //TODO:從遞迴開始,對於更換的牌,要用36單張
            let clonePais:[MaJiang] = creatAllClonePai_36()
  
            //從clonepais中的第一和開始順序代入
            for clonePai in clonePais {
                newAllCards = tmpAllCards  //重新賦值給tmpappcards進行運算
                newAllCards.append(clonePai)  //順序加入一張牌,是否能上一聽?
                newAllCards = sort(maJiangs: tmpAllCards)  //重新排序
                
                let result = is_ShangYiTing(allcards: newAllCards)
                //如果可以聽牌,則存入陣列
                //如:更換[0]後,打出【1】【2】聽牌
                //   更換[1]後,打出【5】【6】【8】可以聽牌
                //   則打出[1]
                //如:當把[0]換成1萬後可以打一張就聽牌,則要先打掉更換前的牌allcards[0]
                //如果更換哪張牌後,可以讓上一聽的數量最多,則打哪張牌
                
                if result.isTingPai {
                    shangyiting.append(result.daPaicard)  //將shangyiting的結果儲存起來
                    //去重,防止原牌為對子暗子時增加2次。
                    if tingPai.isEmpty {
                        tingPai.append((dapai:allcards[i],shangyiting:shangyiting))
                    }
                    else{
                        if  !isEquals(majiang_1: allcards[i], majiang_2: tingPai[tingPai.count-1].dapai){
                            tingPai.append((dapai:allcards[i],shangyiting:shangyiting))
                        }
                    }
                    
                }
            }
        }
        
        /***
         邏輯問題:有沒有可能換上的牌正好被打出去?
         例如:a,b,c,d,......,用1萬換下a:
         變成:1萬,b,c,d,,,,,,
         由於"上一聽"為否,即打了a不能聽牌,則打了1萬同樣不能聽牌--因為打了a和打了1萬後剩餘的牌是一樣的,都不能聽牌。
         所以不存在換上來的牌正好被打出去的問題
         */
        
        //如果tingpai[]裡有值--就是可以聽牌,則找出能聽牌張數最多的那張打出
        if !tingPai.isEmpty {
            
            //如果只有一張牌打出後可以聽牌,則打出這些牌 -- 聽牌優先
            if tingPai.count == 1 {
                return (true,tingPai[0].dapai)
            }
            //有多張牌打出後都可以聽牌,則算出聽牌張數最多所對應打出的那種張
            var tmp = tingPai[0]
            for  i in 0..<tingPai.count-1 {
                if tmp.shangyiting.count <= tingPai[i+1].shangyiting.count {
                }
            }
            return (true, tmp.dapai)
         }
            
        //如果『上二聽』全部迴圈完了還不能聽牌,如"上三聽(先更換第一張牌,然後整體進行『上二聽』進行遞迴)"
        else{
            //遞迴的換牌Id+1
           // let result_DiGui = is_ShangLiangTing(huanPaiId: huanPaiId+1, allcards: newAllCards)
           // return (isTingPai:result_DiGui.isTingPai,daPaicard:result_DiGui.daPaicard)
            
            for i in 0..<allcards.count{
                
                var tmpAllCards = sort(maJiangs: allcards) //重新生成完整手牌
                tmpAllCards.remove(at: i)   //刪除一張牌,再補一張進來,能否上2聽?
                
                //TODO:此處因為是更換,前後需要要用36張牌
                var clonePais:[MaJiang] = creatAallClonePai(i: i, allcards: tmpAllCards)
                
                //從clonepais中的第一和開始順序代入
                for clonePai in clonePais {
                    var newAllCards = tmpAllCards  //重新賦值給tmpappcards進行運算
                    newAllCards.append(clonePai)  //順序加入一張牌,是否能上2聽?
                    newAllCards = sort(maJiangs: tmpAllCards)  //重新排序
                  
                    ////////////////遞迴
                    //第一輪遞迴:此時已經更換了一張,再進行遞迴相當於更換第二張
                    //第二輪遞迴:此時已經更換了2張,再進行遞迴相當於更換第3張
                    let result = is_ShangLiangTing(huanPaiId: huanPaiId+1, allcards: newAllCards)
                    
                    //TODO:如何在上層方法中加上下層方法返回的dapaicard
                    if result.isTingPai {
                       return (true,result.daPaicard)
                    }
                    else{
                      return (false,allcards[allcards.count-1])  //預設值
                    }
                    
                    
                }
            }
            
        
        //如果失敗,則返回false
         return (false,allcards[allcards.count-1])  //如果還是不能聽牌為false,則繼續迴圈,打牌返回值無意義

        }
        //如果全部迴圈完了還不能聽牌,則更換一張牌後再進行"上二聽"--遞歸向後
    }

      /// 前後2張牌是否有相關性(數字牌不能大於或小於2,風牌要相同)
    ///
    /// - Parameters:
    ///   - card1: 第一張牌,從小到大排序在前面
    ///   - card2: 第二張牌,在後面
    /// - Returns: 有則返回true,沒有則返回false
    private static  func isXiangGuan(card1:MaJiang,card2:MaJiang) -> Bool {
        //如果2張牌都是萬條筒,且花色相同
        if card1.getType().rawValue<=3 && card2.getType().rawValue<=3 && card1.getType() == card2.getType(){
            //如果數字相差<=2,則說明有相關性,例如,1萬1萬,1萬2萬,1萬3萬,但1萬4萬就不行
            if (card2 as! MaJiangNumber).number - (card1 as! MaJiangNumber).number <= 2 {
                return true
            }
        }
        //如果2張牌都是風
        if card1.getType().rawValue>3 && card2.getType().rawValue>3 {
            if card1.getType() == card2.getType() {
                return true
            }
        }
        
        return false
    }
    
    /// 比較2個麻將是否相等
    ///
    /// - Parameters:
    ///   - majiang_1: 第一個牌
    ///   - majiang_2: 第二個牌
    /// - Returns: 相等則是true,返之是 false
    private static  func isEquals(majiang_1:MaJiang,majiang_2:MaJiang) -> Bool {
        //如果是風牌
        if majiang_1.getType().rawValue>3{
            if majiang_1.getType().rawValue == majiang_2.getType().rawValue    {
                return true
            }
        }
        //如果是數字牌,則數字相等+花色相等    //注意:開始時只判斷數字相等,但沒有判斷花色相等出錯。
        if majiang_1.getType().rawValue<4 && majiang_2.getType().rawValue<4{
            if (majiang_1 as! MaJiangNumber).number == (majiang_2 as! MaJiangNumber).number && (majiang_1.getType().rawValue == majiang_2.getType().rawValue){
                return true
            }
            
        }
        return false
    }
    
    
    /// 某個玩家牌面的排序
    ///
    /// - Parameter player: 某個玩家
    private static func sort(maJiangs:[MaJiang])->[MaJiang]  {
        var tmpMaJiangs:[MaJiang] = []//臨時陣列,
        var resultMaJiangs:[MaJiang] = [] //某個玩家最後的排序結果
        //按萬條筒。。。。。,10種花色依次排序
        for j in 1...10{
            tmpMaJiangs=getHuaSeAndSort(type: j, maJiangs: maJiangs) //返回的是某個花色排??後的陣列
            resultMaJiangs += tmpMaJiangs
        }
        return resultMaJiangs
        
    }
    
    /// 返回某一花色的牌,同時按從小到大排序 --用於將某玩家牌排序
    ///
    /// - Parameters:
    ///   - type: 花色型別
    ///   - maJiangs: 某位玩家的牌
    /// - Returns: 根據type返回那個花色的牌。
    private static func getHuaSeAndSort(type:Int,maJiangs:[MaJiang]) -> [MaJiang] {
        var tmp:[MaJiang]=[] //臨時陣列
        //1.先選出這種花色的牌
        for x in 0..<maJiangs.count{
            if maJiangs[x].getType().rawValue==type{
                tmp.append(maJiangs[x])
            }
        }
        
        //2.進行排序
        if tmp.isEmpty  || tmp.count==1 {
            return tmp //如果為空或只有1個則直接返回
        }
        if tmp[0].getType().rawValue>3{
            return tmp   //如果為風直接返回
        }
        
        //冒泡法排序
        var tmp1:MaJiang
        for x in 0..<(tmp.count-1){
            for y in 0..<(tmp.count-1-x){
                if (tmp[y] as! MaJiangNumber).number > (tmp[y+1] as!MaJiangNumber).number{
                    tmp1=tmp[y]
                    tmp[y]=tmp[y+1]
                    tmp[y+1]=tmp1
                }
            }
        }
        return tmp
    }
    
    
   /// 建立一整幅單張麻將,整幅麻將全部是單張,用於窮舉測試聽牌
   ///
   /// - Returns: <#return value description#>
   private static func creatAllClonePai() -> [MaJiang] {
        var tmptype:MaJiangType = MaJiangType.Wan //預設值
        var tmpMajiang:MaJiang
        let maJiangFactory = MaJiangFactory()  //麻將工廠
        var result:[MaJiang] = []  //返回的所有單牌麻將
        
        //建立數字牌,x為萬條筒,數字number 為1-9,每張牌有1張
        for x in 1...3 {
            if x==1 { tmptype=MaJiangType.Wan}
            if x==2 { tmptype=MaJiangType.Tiao}
            if x==3 { tmptype=MaJiangType.Tong}
            for number in 1...9{
               tmpMajiang=maJiangFactory.creatMajiang(type: tmptype, number: number)
               result.append(tmpMajiang)
            }
        }
        for x in 4...10{
            if x==4 { tmptype=MaJiangType.Dong}
            if x==5 { tmptype=MaJiangType.Nan}
            if x==6 { tmptype=MaJiangType.Xi}
            if x==7 { tmptype=MaJiangType.Bei}
            if x==8 { tmptype=MaJiangType.HongZhong}
            if x==9 { tmptype=MaJiangType.FaCai}
            if x==10 { tmptype=MaJiangType.BaiBan}
            
                tmpMajiang = maJiangFactory.createMajiang(type: tmptype)
                result.append(tmpMajiang)
            
        }
      //  print("建立單牌麻將成功。。。。")
     //   Tools.printMaJiangs_Test(maJiangs: result)
        return result
        
    }
    
    /// 建立某張牌對應花色的所有麻將,用於窮舉法測試"上二聽"
    ///
    /// - Parameters:
    /// - Returns: 對應花色的所有單張
    private static func creatClonePai(type:MaJiangType) -> [MaJiang] {
        
        let maJiangFactory = MaJiangFactory()  //麻將工廠
        var result:[MaJiang] = []  //返回的所有單牌麻將
        
        if type.rawValue<=3{
            for number in 1...9{
                result.append(maJiangFactory.creatMajiang(type: type, number: number))
                return result
            }
        }
        //如果 是風牌
        result.append( maJiangFactory.createMajiang(type: type))
        return result
    
    }
    
    /// 建立36張的完整單牌麻將,用於窮舉法測試"上二聽"
    ///
    /// - Parameters:
    /// - Returns: 對應花色的所有單張
    private static func creatAllClonePai_36() -> [MaJiang] {
        
        var result:[MaJiang] = []  //返回的所有單牌麻將
        
      
        result += creatClonePai(type:MaJiangType.Wan)
        result += creatClonePai(type:MaJiangType.Tiao)
        result += creatClonePai(type:MaJiangType.Tong)
        result += creatClonePai(type:MaJiangType.Dong)
        result += creatClonePai(type:MaJiangType.Nan)
        result += creatClonePai(type:MaJiangType.Xi)
        result += creatClonePai(type:MaJiangType.Bei)
        result += creatClonePai(type:MaJiangType.HongZhong)
        result += creatClonePai(type:MaJiangType.FaCai)
        result += creatClonePai(type:MaJiangType.BaiBan)
        

        return result
        
    }
    
    /// 建立對應牌相關聯的單牌麻將,用於窮舉法測試"上二聽"
    ///
    /// - Parameters:
    ///   - i: 第幾張
    ///   - allcars: 手牌
    /// - Returns: 有關聯的所有單張
    private static func creatAallClonePai(i:Int,allcards:[MaJiang])->[MaJiang]{
    
        var clonePais:[MaJiang] = []
        if i==0 {
            //根據後位的牌型生成有關聯牌型的所有單張。如果後張為"萬",則這裡全部生成『萬』
            clonePais = creatClonePai(type: allcards[i+1].getType())
        }
        else if i==allcards.count-1{
            //如果 是最後一位,則根據前位的牌型生成
            clonePais = creatClonePai(type: allcards[i-1].getType()) //根據前後位的牌型生成有關聯的牌型
            
        }
        else{
            //如果是中間位,則根據前+後位生成
            //如果前後位型別不同,則分別生成前後型別的所有牌相加
            if allcards[i+1].getType().rawValue != allcards[i-1].getType().rawValue{
                clonePais = creatClonePai(type: allcards[i+1].getType()) + creatClonePai(type: allcards[i-1].getType())
            }
            else{
                clonePais =  creatClonePai(type: allcards[i+1].getType())
            }
        }
        return clonePais
    }
    
    
}


下面是關於麻將類的定義:


/// 麻將的型別
///
/// - Wan: 萬
/// - Tiao: 條
/// - Tong: 筒
/// - Dong: 東風
/// - Nan: 南風
/// - Xi: 西風
/// - Bei: 北分
/// - HongZhong: 紅中
/// - FaCai: 發財
/// - BaiBan: 白板
enum MaJiangType:Int{
    case Wan=1
    case Tiao=2
    case Tong=3
    case Dong=4
    case Nan=5
    case Xi=6
    case Bei=7
    case HongZhong=8
    case FaCai=9
    case BaiBan=10
    
}

/// 碰或者槓的型別
///
/// - Pend: 碰
/// - Gang: 槓
/// - No: 沒有碰或者槓
/// <#Description#>
///
/// - Pend: <#Pend description#>
/// - Gang: <#Gang description#>
/// - No: <#No description#>
enum PengGang_Type:Int{
    case Pend=1 //碰
    case Gang=2  //槓
    case No=0    //沒有碰或者槓
}

/// 麻將的父類
class MaJiang {
    var type:MaJiangType
    var number = -1  //-1為風牌,為了解決風牌向父類轉型時丟失資料問題
    
    /// 得到麻將的型別
    ///
    /// - Returns: 返回麻將的型別--列舉值
    func getType() -> MaJiangType {
        return type
    }
    
    init(type:MaJiangType) {
        self.type=type
    }
    
    func toString() -> String {
        return ("[ \(type.rawValue) ]")
    }
}

/// 數字麻將類=數字+型別
class MaJiangNumber: MaJiang {
    
    func setNumber(number:Int)  {
        self.number = number
    }
    
    /// 顯示牌面
    ///
    /// - Returns: 牌面
    override func toString() -> String {
        var str:String = "["+String( self.number)
        switch self.getType() {
        case MaJiangType.Wan:
            str = str + "萬"
        case MaJiangType.Tiao:
            str = str + "條"
        case MaJiangType.Tong:
            str = str + "筒"
        default:
            str="error"
        } 
        return str+"]、"
    }
    
}

/// 風牌類,此類沒有數字 ,只有型別
class MaJiangWind: MaJiang {
    
    /// 返回一張風牌
    ///
    /// - Returns:
     override func toString() -> String {
        var str:String = "["
        switch self.getType() {
        case MaJiangType.Dong:
            str=str+"東"
        case MaJiangType.Nan:
            str=str+"南"
        case MaJiangType.Xi:
            str=str+"西"
        case MaJiangType.Bei:
            str=str+"北"
        case MaJiangType.HongZhong:
            str=str+"中"
        case MaJiangType.FaCai:
            str=str+"發"
        case MaJiangType.BaiBan:
            str=str+"白"
        default:
            str="error"
        }
        str=str+"]、"
        return str
    }
}
/// 麻將工廠類
class MaJiangFactory{
   
    
    /// 麻將工廠類的生成方法,一次只能生成一張麻將
    ///
    /// - Parameters:
    ///   - type: 麻將型別
    ///   - number: 麻將的數字
    /// - Returns: 麻將
    func creatMajiang(type:MaJiangType,number:Int = -1) ->MaJiang {
        //一次只能生成一張麻將
        var maJiang:MaJiang
        if type.rawValue<=3{
            maJiang =  MaJiangNumber(type: type)
            (maJiang as! MaJiangNumber).setNumber(number: number)
        }
        else{
            maJiang = MaJiangWind(type:type)
        }
        return maJiang
    }
}