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