ios 棋類游戲?qū)?zhàn)的實(shí)現(xiàn)

棋類游戲?qū)?zhàn)的實(shí)現(xiàn)

  • 六洲棋
  • 五子棋
  • AI對(duì)戰(zhàn)
  • 藍(lán)牙對(duì)戰(zhàn)
  • 在線對(duì)戰(zhàn)

六洲棋

六洲棋,又稱:泥棋芯急、插方勺届、來馬、五福棋娶耍,中國(guó)民間傳統(tǒng)棋類體育形式免姿。源于民間,簡(jiǎn)便榕酒、通俗胚膊、易學(xué),在民間廣為流行想鹰,深受社會(huì)底層大眾的喜愛紊婉。龍其在淮河流域的安徽省、河南省杖挣、江蘇省肩榕、以及湖北省、山東省非常普及惩妇,并流傳到中國(guó)各地株汉,包括港、澳歌殃、臺(tái)地區(qū)乔妈。起源于勞動(dòng)人民生活,根植于民間大眾之中氓皱,它簡(jiǎn)捷路召、明快,趣味性波材、競(jìng)技性強(qiáng)股淡,是一項(xiàng)長(zhǎng)期流行于民間,富有傳統(tǒng)文化色彩的競(jìng)技項(xiàng)目廷区。對(duì)于啟迪智慧唯灵,休閑娛樂,增進(jìn)交流非常有益隙轻。列安徽省第二批省級(jí)非物質(zhì)文化遺產(chǎn)埠帕。
6*6縱橫線組成垢揩,共三十六個(gè)棋點(diǎn)。每方十八枚棋子敛瓷,以兩色區(qū)分?jǐn)澄摇?/p>

規(guī)則

對(duì)弈過程分三階段叁巨。(鳳陽(yáng)下法)放子:對(duì)弈雙方依次將己子放入空棋點(diǎn),將手上的棋子放完才開始走子呐籽。逼子:若無棋子被吃锋勺,使得棋子放滿棋盤。則兩人各選對(duì)方一枚敵子移出游戲狡蝶。走子:由后手方開始輪流移動(dòng)己棋宙刘,沿線直橫線一格。吃子:無論是下子或走子階段牢酵,只要己方棋子排成以下排列稱為成城,就要吃掉一定數(shù)量的敵子衙猪,但不可吃掉已成城子的敵棋馍乙。在放子階段,被吃的子先作記號(hào)垫释,等走子階段開始才一齊提取丝格。
成六:六枚棋子以縱、橫和斜3個(gè)方向連成直線(除了四條邊的直線)棵譬。吃掉敵方三子显蝌。

six
six_two

斜五:連子的2頭都靠棋盤邊緣,吃掉敵方兩子订咸。

five

斜四:連子的2頭都靠棋盤邊緣曼尊,吃掉敵方一子。

four

斜三:連子的2頭都靠棋盤邊緣脏嚷,吃掉敵方一子骆撇。

three

成方:四枚棋子組成一個(gè)緊鄰相連的小正方形,吃掉敵方一子父叙。

check

使對(duì)方只剩下三枚以下則獲勝神郊。因?yàn)槭敲耖g文化,各地稍有差異趾唱。

棋型的算法實(shí)現(xiàn)

//是否形成斜子棋(三子棋涌乳,四子棋,五子棋甜癞,六子棋)
   static func isXiZiChess(_ point:SWSPoint,_ chessArray: [[FlagType]]) ->  LianZhuState?{
       let type = chessArray[point.x][point.y]
       let pointLeft = SWSPoint()
       let pointRight = SWSPoint()
       let ponitTop = SWSPoint()
       let pointBottom = SWSPoint()
       
       // 東北方向
       var i = 0
       while point.x - i >= 0 && point.y + i <= 5 && chessArray[point.x - i][point.y + i] == type {
           pointLeft.x = point.x - i
           pointLeft.y = point.y + i
           i += 1
       }
           i = 0
       while point.x + i <= 5 && point.y - i >= 0 && chessArray[point.x + i][point.y - i] == type {
           pointRight.x = point.x + i
           pointRight.y = point.y - i
           i += 1
       }
       
       //西北方向
       i = 0
       while point.x - i >= 0 && point.y - i >= 0 && chessArray[point.x - i][point.y - i] == type {
           ponitTop.x = point.x - i
           ponitTop.y = point.y - i
           i += 1
       }
       i = 0
       while point.x + i <= 5 && point.y + i <= 5 && chessArray[point.x + i][point.y + i] == type {
           pointBottom.x = point.x + i
           pointBottom.y = point.y + i
           i += 1
       }
       print(pointRight.x,pointRight.y,pointLeft.x,pointLeft.y,ponitTop.x,ponitTop.y,pointBottom.x,pointBottom.y)
       let arr = [3,2,1,0]
       for index in arr {
           
           func condition() -> Bool {
               if pointRight.x == 2+index && pointRight.y == 0 && pointLeft.x == 0 && pointLeft.y == 2+index {
                   return true
               }
               if pointRight.x == 5  && pointRight.y == 3 - index && pointLeft.x == 3 - index && pointLeft.y == 5 {
                   return true
               }
               if ponitTop.x == 0 && ponitTop.y == 3-index && pointBottom.x == 2+index && pointBottom.y == 5 {
                   return true
               }
               if ponitTop.x == 3-index && ponitTop.y == 0 && pointBottom.x == 5 && pointBottom.y == 2+index {
                   return true
               }
               return false
           }
           
           if condition() {
               switch index {
               case 0:
                   return .threeChess
               case 1:
                   return .fourChess
               case 2:
                   return .fiveChess
               case 3:
                   return .sixChess
               default:()
               }
           }
       }
       return nil
   }
   
    //是否形成方格棋
   static func isCheckChess(_ point:SWSPoint,_ chessArray: [[FlagType]]) ->LianZhuState? {
       let type = chessArray[point.x][point.y]
       //左上
       if point.x - 1 >= 0 && point.y - 1 >= 0 && chessArray[point.x][point.y-1] == type &&
           chessArray[point.x-1][point.y] == type && chessArray[point.x-1][point.y-1] == type {
           return .checkChess
       }
       //左下
       if point.x - 1 >= 0 && point.y + 1 <= 5 && chessArray[point.x][point.y+1] == type &&
           chessArray[point.x-1][point.y] == type && chessArray[point.x-1][point.y+1] == type {
           return .checkChess
       }
       //右上
       if point.x + 1 <= 5 && point.y - 1 >= 0 && chessArray[point.x][point.y-1] == type &&
           chessArray[point.x+1][point.y] == type && chessArray[point.x+1][point.y-1] == type {
           return .checkChess
       }
       //右下
       if point.x + 1 <= 5 && point.y + 1 <= 5 && chessArray[point.x][point.y+1] == type &&
           chessArray[point.x+1][point.y] == type && chessArray[point.x+1][point.y+1] == type {
           return .checkChess
       }
       return nil
   }
   

小結(jié)

六洲棋夕晓,在我們老家被稱為泥棋,小時(shí)候經(jīng)常玩的一種棋带欢,偶有回憶运授,因此實(shí)現(xiàn)下這個(gè)游戲烤惊,望能找到個(gè)棋友沒事玩玩,這種棋吁朦,玩法多種柒室,很有趣。

五子棋

五子棋五子棋是比較流行的棋類游戲了逗宜,玩法簡(jiǎn)單雄右,基本上人人會(huì)玩,在此就不介紹游戲規(guī)則了纺讲。下面使用 swift實(shí)現(xiàn)五子棋這個(gè)游戲擂仍,主要實(shí)現(xiàn)AI算法,包括極大值極小值算法熬甚,深度搜索算法逢渔,估值函數(shù),Alpha Beta 剪枝算法等等乡括。

 //橫向五子連珠(除去四邊線的五子連珠)
    static func isFiveChess(_ point:SWSPoint,_ chessArray: [[FlagType]]) -> Bool {
        let type = chessArray[point.x][point.y]
        let pointLeft = SWSPoint()
        let pointRight = SWSPoint()
        let pointTop = SWSPoint()
        let pointBottom = SWSPoint()
        let pointLeft45 = SWSPoint()
        let pointRight45 = SWSPoint()
        let pointTop135  = SWSPoint()
        let pointBottom135 = SWSPoint()
        //東西方向
        var i = 0
        while point.x - i >= 0 && chessArray[point.x - i][point.y] == type {
            pointLeft.x = point.x - i
            i += 1
        }
        i = 0
        while point.x + i <= 14 && chessArray[point.x + i][point.y] == type {
            pointRight.x = point.x + i
            i += 1
        }
        
        if pointRight.x - pointLeft.x == 4 && (pointLeft.y != 15 || pointLeft.y != 0){
            return true
        }
        //南北方向
        i = 0
        while point.y - i >= 0 && chessArray[point.x][point.y-i] == type {
            pointTop.y = point.y - i
            i += 1
        }
        i = 0
        while point.y + i <= 14 && chessArray[point.x][point.y+i] == type {
            pointBottom.y = point.y + i
            i += 1
        }
        if pointBottom.y - pointTop.y == 4 && (pointTop.x != 15 || pointTop.x != 0) {
            return true
        }
        
        // 東北方向
         i = 0
        while point.x - i >= 0 && point.y + i <= 14 && chessArray[point.x - i][point.y + i] == type {
            pointLeft45.x = point.x - i
            pointLeft45.y = point.y + i
            i += 1
        }
        i = 0
        while point.x + i <= 14 && point.y - i >= 0 && chessArray[point.x + i][point.y - i] == type {
            pointRight45.x = point.x + i
            pointRight45.y = point.y - i
            i += 1
        }
        
        if pointLeft45.y - pointRight45.y == 4{
            return true
        }
        
        //西北方向
        i = 0
        while point.x - i >= 0 && point.y - i >= 0 && chessArray[point.x - i][point.y - i] == type {
            pointTop135.x = point.x - i
            pointTop135.y = point.y - i
            i += 1
        }
        i = 0
        while point.x + i <= 14 && point.y + i <= 14 && chessArray[point.x + i][point.y + i] == type {
            pointBottom135.x = point.x + i
            pointBottom135.y = point.y + i
            i += 1
        }
        if pointBottom135.y - pointTop135.y == 4{
            return true
        }
        
        return false
    }

demo中實(shí)現(xiàn)了五子棋的AI肃廓、同機(jī)、藍(lán)牙诲泌、在線對(duì)戰(zhàn)盲赊,下面重點(diǎn)介紹AI對(duì)戰(zhàn)。

五子棋的AI算法實(shí)現(xiàn)

2017年互聯(lián)網(wǎng)最火的技術(shù)毫無疑問就是AI了敷扫,在此嘗試寫了個(gè)算法來和人腦來pk哀蘑。五子棋屬于零和游戲:一方勝利代表另一方失敗,而零和游戲的代表算法就是極大值極小值搜索算法葵第。

極大值極小值搜索算法

A绘迁、B二人對(duì)弈,A先走卒密,A始終選擇使局面對(duì)自己最有利的位置脊髓,然后B根據(jù)A的選擇,在剩下的位置中選擇對(duì)A最不利的位置栅受,以此類推下去直到到達(dá)我們定義的最大搜索深度将硝。所以每一層輪流從子節(jié)點(diǎn)選擇最大值-最小值-最大值-最小值...

我們?nèi)绾沃滥膫€(gè)位置最有利和最不利呢?在此我們引入一套評(píng)估函數(shù)屏镊,來對(duì)棋盤上每個(gè)位置進(jìn)行分?jǐn)?shù)評(píng)估

//活一依疼、活二、活三而芥、活四律罢、連五、眠一,眠二误辑、眠三沧踏、眠四
enum FiveChessType:Int {
   case liveOne = 0
   case liveTwo
   case liveThree
   case liveFour
   case liveFive
   case sleepOne
   case sleepTwo
   case sleepThree
   case sleepFour
   case unknown
   var score:Int  {
       switch self {
       case .unknown:
           return un_known
       case .sleepOne:
           return sleep_One
       case .liveOne,.sleepTwo:
           return live_One
       case .liveTwo,.sleepThree:
           return live_Two
       case .liveThree:
           return live_Three
       case .sleepFour:
           return sleep_Four
       case .liveFour:
           return live_Four
       case .liveFive:
           return live_Five
           
       }
   }
   
}
let live_Five = 1000000
let live_Four = 100000
let sleep_Four = 10000
let live_Three = 1000
let live_Two = 100
let sleep_Three = 100
let live_One = 10
let sleep_Two = 10
let sleep_One = 1
let un_known = 0

在使用極大值極小值進(jìn)行深度搜索時(shí),遍歷節(jié)點(diǎn)是指數(shù)增長(zhǎng)的巾钉,如果不進(jìn)行算法優(yōu)化翘狱,將會(huì)導(dǎo)致電腦計(jì)算時(shí)間過長(zhǎng),影響下棋體驗(yàn)砰苍,所以這里引入 Alpha Beta 剪枝原理潦匈。

Alpha Beta 剪枝原理

AlphaBeta剪枝算法是一個(gè)搜索算法旨在減少在其搜索樹中,被極大極小算法評(píng)估的節(jié)點(diǎn)數(shù)赚导。
Alpha-Beta只能用遞歸來實(shí)現(xiàn)茬缩。這個(gè)思想是在搜索中傳遞兩個(gè)值,第一個(gè)值是Alpha吼旧,即搜索到的最好值凰锡,任何比它更小的值就沒用了,因?yàn)椴呗跃褪侵繟lpha的值圈暗,任何小于或等于Alpha的值都不會(huì)有所提高寡夹。
第二個(gè)值是Beta,即對(duì)于對(duì)手來說最壞的值厂置。這是對(duì)手所能承受的最壞的結(jié)果,因?yàn)槲覀冎涝趯?duì)手看來魂角,他總是會(huì)找到一個(gè)對(duì)策不比Beta更壞的昵济。如果搜索過程中返回Beta或比Beta更好的值,那就夠好的了野揪,走棋的一方就沒有機(jī)會(huì)使用這種策略了访忿。
在搜索著法時(shí),每個(gè)搜索過的著法都返回跟Alpha和Beta有關(guān)的值斯稳,它們之間的關(guān)系非常重要海铆,或許意味著搜索可以停止并返回。
如果某個(gè)著法的結(jié)果小于或等于Alpha挣惰,那么它就是很差的著法卧斟,因此可以拋棄。因?yàn)槲仪懊嬲f過憎茂,在這個(gè)策略中珍语,局面對(duì)走棋的一方來說是以Alpha為評(píng)價(jià)的。
如果某個(gè)著法的結(jié)果大于或等于Beta竖幔,那么整個(gè)節(jié)點(diǎn)就作廢了板乙,因?yàn)閷?duì)手不希望走到這個(gè)局面,而它有別的著法可以避免到達(dá)這個(gè)局面拳氢。因此如果我們找到的評(píng)價(jià)大于或等于Beta募逞,就證明了這個(gè)結(jié)點(diǎn)是不會(huì)發(fā)生的蛋铆,因此剩下的合理著法沒有必要再搜索。
如果某個(gè)著法的結(jié)果大于Alpha但小于Beta放接,那么這個(gè)著法就是走棋一方可以考慮走的刺啦,除非以后有所變化。因此Alpha會(huì)不斷增加以反映新的情況透乾。有時(shí)候可能一個(gè)合理著法也不超過Alpha洪燥,這在實(shí)戰(zhàn)中是經(jīng)常發(fā)生的,此時(shí)這種局面是不予考慮的乳乌,因此為了避免這樣的局面捧韵,我們必須在博弈樹的上一個(gè)層局面選擇另外一個(gè)著法。鏈接

c代碼實(shí)現(xiàn)原理

int AlphaBeta(int depth, int alpha, int beta) 
{
    if (depth == 0) 
    {
        return Evaluate();
    }
    GenerateLegalMoves();
    while (MovesLeft()) 
    {
        MakeNextMove();
        val = -AlphaBeta(depth - 1, -beta, -alpha);
        UnmakeMove();
        if (val >= beta) 
        {
            return beta;
        }
        if (val > alpha) 
        {
            alpha = val;
        }
    }
    return alpha;
} 

實(shí)際在代碼中的運(yùn)用汉操,代碼比較復(fù)雜請(qǐng)結(jié)合項(xiàng)目理解再来。項(xiàng)目地址

static func getAIPoint(chessArray:inout[[FlagType]],role:FlagType,AIScore:inout [[Int]],humanScore:inout [[Int]],deep:Int) ->(Int,Int,Int)? {
        
        let maxScore = 10*live_Five
        let minScore = -1*maxScore
        let checkmateDeep = self.checkmateDeep
       var total=0, //總節(jié)點(diǎn)數(shù)
        steps=0,  //總步數(shù)
        count = 0,  //每次思考的節(jié)點(diǎn)數(shù)
        ABcut = 0 //AB剪枝次數(shù)
        
        
        func humMax(deep:Int)->(Int,Int,Int)? {
            let points = self.getFiveChessType(chessArray: chessArray, AIScore: &AIScore, humanScore: &humanScore)
            var bestPoint:[(Int,Int)] = []
            var best = minScore
            count = 0
            ABcut = 0
         
            for i in 0..<points.count {
                let p = points[i]
                chessArray[p.x][p.y] = role
                self.updateOneEffectScore(chessArray: chessArray, point: (p.x,p.y), AIScore: &AIScore, humanScore: &humanScore)
                var score = -aiMaxS(deep: deep-1, alpha: -maxScore, beta: -best, role: self.reverseRole(role: role))
                if p.x < 3 || p.x > 11 || p.y < 3 || p.y > 11 {
                    score = score/2
                }
                if TJFTool.equal(a: Float(score), b: Float(best)){
                    bestPoint.append((p.x,p.y))
                }
                if TJFTool.greatThan(a: Float(score), b: Float(best)){
                    best = score
                    bestPoint.removeAll()
                    bestPoint.append((p.x,p.y))
                }
                chessArray[p.x][p.y] = .freeChess
                self.updateOneEffectScore(chessArray: chessArray, point: (p.x,p.y), AIScore: &AIScore, humanScore: &humanScore)
                
            }
            steps += 1
            total += count
            if bestPoint.count > 0 {
                let num = arc4random()%UInt32(bestPoint.count)
                return (bestPoint[Int(num)].0,bestPoint[Int(num)].1,best)
            }
            return nil
           
        }
        
        func aiMaxS(deep:Int,alpha:Int,beta:Int,role:FlagType) -> Int{
            var score = 0
            var aiMax = 0
            var humMax = 0
            var best = minScore
            for i in 0..<15{
                for j in 0..<15{
                    if chessArray[i][j] == .freeChess{
                        aiMax = max(AIScore[i][j], aiMax)
                        humMax = max(humanScore[i][j], humMax)
                    }
                }
            }
            score = (role == .blackChess ? 1 : -1) * (aiMax-humMax)
            count += 1
            if deep <= 0 || TJFTool.greatOrEqualThan(a: Float(score), b: Float(live_Five)){
                return score
            }
            let points =  self.getFiveChessType(chessArray: chessArray, AIScore: &AIScore, humanScore: &humanScore)
            for i in 0..<points.count{
                let p = points[i]
                chessArray[p.x][p.y] = role
                self.updateOneEffectScore(chessArray: chessArray, point: (p.x,p.y), AIScore: &AIScore, humanScore: &humanScore)
                let some = -aiMaxS(deep: deep-1, alpha: -beta, beta: -1 * ( best > alpha ? best : alpha), role: self.reverseRole(role: role)) * deepDecrease
                chessArray[p.x][p.y] = .freeChess
                self.updateOneEffectScore(chessArray: chessArray, point: (p.x,p.y), AIScore: &AIScore, humanScore: &humanScore)
                if TJFTool.greatThan(a: Float(some), b: Float(best)) {
                    best = some
                }
                //在這里進(jìn)行ab 剪枝
                if TJFTool.greatOrEqualThan(a: Float(some), b: Float(beta)){
                    ABcut += 1
                    return some
                }
            }
            
            if (deep == 2 || deep == 3 || deep == 4) && TJFTool.littleThan(a: Float(best), b: Float(sleep_Four)) && TJFTool.greatThan(a: Float(best), b: -(Float)(sleep_Four)){
                
                if let result = self.checkmateDeeping(chessArray: &chessArray, role: role, AIScore: &AIScore, humanScore: &humanScore, deep: checkmateDeep) {
                   return Int(Double(result[0].2) * pow(0.8, Double(result.count)) * (role == .blackChess ? 1:-1))
                }
            }
            return best
        }
        
        var i = 2
        var result:(Int,Int,Int)?
        while i <= deep {
            if let test = humMax(deep: i) {
                result = test
                if TJFTool.greatOrEqualThan(a: Float(test.2), b: Float(live_Four)) {
                    return test
                }
            }
            i += 2
        }
        if result == nil {
            var maxAiScore = 0
            for i in 0..<15{
                for j in 0..<15 {
                    if chessArray[i][j] == .freeChess && maxAiScore < AIScore[i][j] {
                        maxAiScore = AIScore[i][j]
                        result = (i,j,maxAiScore)
                    }
                }
            }
        }
        
        return result
    }

經(jīng)過Alpha Beta剪枝后,優(yōu)化效果應(yīng)該達(dá)到 1/2 次方磷瘤,也就是說原來需要遍歷XY個(gè)節(jié)點(diǎn)芒篷,現(xiàn)在只需要遍歷X(Y/2)個(gè)節(jié)點(diǎn),相比之前已經(jīng)有了極大的提升采缚。
不過即使經(jīng)過了Alpha Beta 剪枝针炉,思考層數(shù)也只能達(dá)到四層,也就是一個(gè)不怎么會(huì)玩五子棋的普通玩家的水平扳抽。而且每增加一層篡帕,所需要的時(shí)間或者說計(jì)算的節(jié)點(diǎn)數(shù)量是指數(shù)級(jí)增加的。所以目前的代碼想計(jì)算到第六層是很困難的贸呢。
我們的時(shí)間復(fù)雜度是一個(gè)指數(shù)函數(shù) X^Y镰烧,其中底數(shù)X是每一層節(jié)點(diǎn)的子節(jié)點(diǎn)數(shù),Y 是思考的層數(shù)楞陷。我們的剪枝算法能剪掉很多不用的分支怔鳖,相當(dāng)于減少了 Y,那么下一步我們需要減少 X固蛾,如果能把 X 減少一半结执,那么四層平均思考的時(shí)間能降低到 0.5^4 = 0.06 倍,也就是能從10秒降低到1秒以內(nèi)艾凯。
如何減少X呢昌犹?我們知道五子棋中,成五览芳、活四斜姥、雙三、雙眠四、眠四活三是必殺棋铸敏,于是我們遇到后就不用再往下搜索了缚忧。代碼如下:

static func getFiveChessType(chessArray:[[FlagType]],AIScore:inout [[Int]],humanScore:inout [[Int]]) ->[(x:Int,y:Int)]{
        var twos:[(Int,Int)] = []
        var threes:[(Int,Int)] = []
        var doubleThrees:[(Int,Int)] = []
        var sleepFours:[(Int,Int)] = []
        var fours:[(Int,Int)] = []
        var fives:[(Int,Int)] = []
        var oters:[(Int,Int)] = []
        for i in 0..<15{
            for j in 0..<15{
                if chessArray[i][j] == .freeChess && self.effectivePoint(chessArray: chessArray, point: (x: i, y: j)) {
                    let aiScore = AIScore[i][j]
                    let humScore = humanScore[i][j]
                    if aiScore>=live_Five {
                        return[(i,j)]
                    }else if humScore >= live_Five {
                        fives.append((i,j))
                    }else if aiScore >= live_Four {
                        fours.insert((i,j), at: 0)
                    }else if humScore >= live_Four {
                        fours.append((i,j))
                    }else if aiScore >= sleep_Four{
                        sleepFours.insert((i,j), at: 0)
                    }else if humScore >= sleep_Four{
                        sleepFours.append((i,j))
                    }else if aiScore >= 2*live_Three{
                        doubleThrees.insert((i,j), at: 0)
                    }else if humScore >= 2*live_Three{
                        doubleThrees.append((i,j))
                    }else if aiScore >= live_Three {
                        threes.insert((i,j), at: 0)
                    }else if humScore >= live_Three {
                        threes.append((i, j))
                    }else if aiScore >= live_Two{
                        twos.insert((i,j), at: 0)
                    }else if humScore >= live_Two{
                        twos.append((i,j))
                    }else {
                        oters.append((i,j))
                    }
                }
            }
        }
        
        if fives.count > 0 {
            return [fives[0]]
        }
        if fours.count > 0 {
            return fours
        }
        if sleepFours.count > 0{
            return [sleepFours[0]]
        }
        if doubleThrees.count > 0{
            return doubleThrees + threes
        }
        let result = threes + twos + oters
        var realy:[(Int,Int)] = []
        if result.count > limitNum {
            realy += result.prefix(limitNum)
            return realy
        }
        return result
    }

五子棋是一種進(jìn)攻優(yōu)勢(shì)的棋,依靠連續(xù)不斷地活三或者沖四進(jìn)攻杈笔,最后很容易會(huì)形成必殺棋闪水,所以在進(jìn)行深度搜索時(shí),我們另開一種連續(xù)進(jìn)攻的搜索蒙具,如果球榆,電腦可以依靠連續(xù)進(jìn)攻獲得勝利,我們可以直接走這條路勁禁筏。這條路勁持钉,其實(shí)也是極大值極小值搜索算法的一種,只不過是只考慮活三沖四這兩種棋型篱昔,指數(shù)的底數(shù)較小每强,搜索的節(jié)點(diǎn)比較少,因此是效率很高的算法州刽。代碼如下:

//有限考慮ai成五
 static func findMaxScore(chessArray:[[FlagType]],role:FlagType,aiScore:[[Int]],humanScore:[[Int]],score:Int)->[(Int,Int,Int)]{
        var result:[(Int,Int,Int)] = []
        for i in 0..<15{
            for j in 0..<15{
                if chessArray[i][j] == .freeChess {
                    if self.effectivePoint(chessArray: chessArray, point: (i,j),chessCount: 1) {
                        let score1 =  role == .blackChess ?  aiScore[i][j] : humanScore[i][j]
                        if score1 >= live_Five {
                            return [(i,j,score1)]
                        }
                        if score1 >= score {
                            result.append((i,j,score1))
                            
                        }
                    }
                }
            }
        }
      return  result.sorted { (a, b) -> Bool in
            return b.2 > a.2
        }
        
    }
    //考慮活三空执,沖四
    static func findEnemyMaxScore(chessArray:[[FlagType]],role:FlagType,aiScore:[[Int]],humanScore:[[Int]],score:Int)->[(Int,Int,Int)]{
        var result:[(Int,Int,Int)] = []
        var fours:[(Int,Int,Int)] = []
        var fives:[(Int,Int,Int)] = []
        for i in 0..<15{
            for j in 0..<15{
                if chessArray[i][j] == .freeChess {
                    if  self.effectivePoint(chessArray: chessArray, point: (i,j),chessCount: 1) {
                        let score1 =  role == .blackChess ?  aiScore[i][j] : humanScore[i][j]
                        let score2 = role == .blackChess ?  humanScore[i][j] : aiScore[i][j]
                        if score1 >= live_Five {
                            return [(i,j,-score1)]
                        }
                        if score1 >= live_Four {
                            fours.insert((i,j,-score1), at: 0)
                            continue
                        }
                        if score2 >= live_Five {
                         fives.append((i,j,score2))
                            continue
                        }
                        if score2 >= live_Four{
                            fours.append((i,j,score2))
                            continue
                        }
                        if score1 > score || score2 > score {
                            result.append((i,j,score1))
                        }
                    }
                }
            }
        }
        if fives.count > 0 {
            return [fives[0]]
        }
        if fours.count > 0 {
            return [fours[0]]
        }
      return  result.sorted { (a, b) -> Bool in
            return abs(b.2) > abs(a.2)
        }
    }

小結(jié)

本次編寫的AI還是比較強(qiáng)的,我勝利的機(jī)會(huì)很少穗椅,但還是存在贏的時(shí)候辨绊,因此AI算法還存在漏洞,主要表現(xiàn)在評(píng)分標(biāo)準(zhǔn)不準(zhǔn)確和搜索深度不夠問題上匹表,如何優(yōu)化評(píng)分標(biāo)準(zhǔn)和搜索算法门坷,是實(shí)現(xiàn)AI無敵的關(guān)鍵工作。
另外桑孩,在增加搜索深度的同時(shí),遍歷的節(jié)點(diǎn)指數(shù)增長(zhǎng)框冀,計(jì)算時(shí)間增長(zhǎng)流椒,可以結(jié)合哈希算法,保存每次的棋盤評(píng)分明也,一定程度上提高計(jì)算時(shí)間宣虾,這也只是治標(biāo)不治本的做法。

藍(lán)牙對(duì)戰(zhàn)

MultipeerConnectivity框架的使用

MultipeerConnectivity通過WiFi温数、P2P WiFi以及藍(lán)牙個(gè)人局域網(wǎng)進(jìn)行通信的框架绣硝,從而無需聯(lián)網(wǎng)手機(jī)間就能傳遞消息。其原理是通過廣播作為服務(wù)器去發(fā)現(xiàn)附近的節(jié)點(diǎn)撑刺,每個(gè)節(jié)點(diǎn)都以設(shè)備名稱為標(biāo)識(shí)鹉胖。

   myPeer = MCPeerID.init(displayName: UIDevice.current.name)
   session = MCSession.init(peer: myPeer!, securityIdentity: nil, encryptionPreference: .none)
   session?.delegate = self

MCSession的幾個(gè)代理方法必須實(shí)現(xiàn),否則無法建立連接

    //監(jiān)聽連接狀態(tài)
   func session(_ session: MCSession, peer peerID: MCPeerID, didChange state: MCSessionState) {
        switch state {
        case .notConnected:
            print("未連接")
        case .connecting:
            print("正在連接中")
        case .connected:
            print("連接成功")
        }
    }
    
    //發(fā)送Dada數(shù)據(jù)
    func sendData(_ messageVo: GPBMessage, successBlock:(()->())?,errorBlock:((NSError)->())?) {
        guard let session = session else {
            return
        }
        guard let data = NSDataTool.shareInstance().returnData(messageVo, messageId: 0) else {return}
        
        do {
          try session.send(data as Data , toPeers: session.connectedPeers, with: .reliable)
        }catch let error as NSError {
            errorBlock?(error)
            return
        }
        successBlock?()
    }
    
    //接收到的Data數(shù)據(jù)
    func session(_ session: MCSession, didReceive data: Data, fromPeer peerID: MCPeerID) {
       // 解析出過來的data數(shù)據(jù)包
      NSDataTool.shareInstance().startParse(data) { (gpbMessage) in
         self.getMessageBlock?(gpbMessage)
        }
        
    }
    //接收到的流數(shù)據(jù)
     func session(_ session: MCSession, didReceive stream: InputStream, withName streamName: String, fromPeer peerID: MCPeerID) {
        print("streamName")
    }
    //接收到的文件類型數(shù)據(jù)
    func session(_ session: MCSession, didStartReceivingResourceWithName resourceName: String, fromPeer peerID: MCPeerID, with progress: Progress) {
         print("resourceName")
    }
    //接收到的文件類型數(shù)據(jù),可將文件換路勁
    func session(_ session: MCSession, didFinishReceivingResourceWithName resourceName: String, fromPeer peerID: MCPeerID, at localURL: URL, withError error: Error?) {
        
    }

我們通過MCAdvertiserAssistant(廣播)開啟搜索服務(wù)

    advertiser = MCAdvertiserAssistant.init(serviceType: serviceStr, discoveryInfo: nil, session: session!)
    //發(fā)出廣播
    advertiser?.start()
    advertiser?.delegate = self

MCBrowserViewControllerDelegate代理方法

 /// 發(fā)出廣播請(qǐng)求
    func advertiserAssistantWillPresentInvitation(_ advertiserAssistant: MCAdvertiserAssistant) {
        print("advertiserAssistantWillPresentInvitation")
    }
    /// 結(jié)束廣播請(qǐng)求
    func advertiserAssistantDidDismissInvitation(_ advertiserAssistant: MCAdvertiserAssistant) {
        print("advertiserAssistantDidDismissInvitation")
    } 

設(shè)置藍(lán)牙連接頁(yè)面甫菠,顯示效果如圖所示:

  func setupBrowserVC() {
        guard let session = session else {
            return
        }
        browser = MCBrowserViewController.init(serviceType: serviceStr,  session: session)
        browser?.delegate = self
    }
image

實(shí)現(xiàn)MCBrowserViewControllerDelegate代理方法

 func browserViewControllerDidFinish(_ browserViewController: MCBrowserViewController) {
        print("藍(lán)牙連接完成")
        browser?.dismiss(animated: true, completion: { [weak self] in
             self?.browserBlock?()
            
        })
       
    }
    
    func browserViewControllerWasCancelled(_ browserViewController: MCBrowserViewController) {
        print("取消藍(lán)牙連接")
        browser?.dismiss(animated: true, completion: nil)
    }

小結(jié)

使用藍(lán)牙技術(shù)進(jìn)行傳輸數(shù)據(jù)挠铲,盡管不需要連接網(wǎng)絡(luò)服務(wù),但是真實(shí)因?yàn)檫@樣存在著許多安全隱患寂诱,為此我們引入Google Protobuf框架進(jìn)行數(shù)據(jù)傳輸拂苹。下章會(huì)對(duì)該技術(shù)的運(yùn)用進(jìn)行詳解。

protobuf在iOS中的運(yùn)用

protocolbuffer(以下簡(jiǎn)稱protobuf)是google 的一種數(shù)據(jù)交換的格式痰洒,它獨(dú)立于語(yǔ)言瓢棒,獨(dú)立于平臺(tái)。google 提供了多種語(yǔ)言的實(shí)現(xiàn):java丘喻、c#脯宿、c++、oc仓犬、go 和 python嗅绰,每一種實(shí)現(xiàn)都包含了相應(yīng)語(yǔ)言的編譯器以及庫(kù)文件。由于它是一種二進(jìn)制的格式搀继,比使用 xml和json 進(jìn)行數(shù)據(jù)交換快許多窘面。可以把它用于分布式應(yīng)用之間的數(shù)據(jù)通信或者異構(gòu)環(huán)境下的數(shù)據(jù)交換叽躯。作為一種效率和兼容性都很優(yōu)秀的二進(jìn)制數(shù)據(jù)傳輸格式财边,可以用于諸如網(wǎng)絡(luò)傳輸、配置文件点骑、數(shù)據(jù)存儲(chǔ)等諸多領(lǐng)域酣难。
我們重點(diǎn)介紹protobuf在iOS中的運(yùn)用,官方文檔

protobuf使用步驟

  • 定義.proto文件
  • 配置protobuf環(huán)境
  • 映射相應(yīng)語(yǔ)言的文件
  • 導(dǎo)入第三方庫(kù)protobuf

.proto文件的定義

該文件主要是用來作為你傳遞數(shù)據(jù)的數(shù)據(jù)結(jié)構(gòu)的文檔黑滴,然后通過終端命令生成我們相應(yīng)語(yǔ)言的model類憨募,導(dǎo)入項(xiàng)目中使用。
.proto的定義語(yǔ)法有官方文檔自己學(xué)習(xí)袁辈,在此不過多介紹菜谣,在此一定要注意的是,一定要使用proto3來定義晚缩,proto2已經(jīng)在很多第三方庫(kù)中被淘汰使用(以前用的都是proto2尾膊,Proto3出來并不了解,報(bào)錯(cuò)信息一度讓我懷疑人生)荞彼。定義文件類似下圖所示:

配置protobuf環(huán)境

使用homebrew進(jìn)行配置(如果沒安裝冈敛,自己谷歌安裝)

映射相應(yīng)語(yǔ)言的文件

  • cd 到.proto文件的路勁中
  • protoc --plugin=/usr/local/bin/protoc-gen-objc test.proto --objc_out=.
    此為生成oc類的命令,其中test.proto是自己生成的proto文件的名字鸣皂。相應(yīng)swift類的命令為:
    protoc --plugin=/usr/local/bin/protoc-gen-swift test.proto --swift_out=.
  • 將生成的文件導(dǎo)入項(xiàng)目中

導(dǎo)入第三方庫(kù)protobuf

這里建議使用pod管理:pod 'Protobuf'

Protobuf庫(kù)的使用

一般就是將Data類型的數(shù)據(jù)映射成model和將model生成data類型數(shù)據(jù)兩個(gè)方法抓谴,他們分別是
使用GPBMessage中的倆個(gè)方法


+ (instancetype)parseFromData:(NSData *)data error:(NSError **)errorPtr {
  return [self parseFromData:data extensionRegistry:nil error:errorPtr];
}

- (nullable NSData *)data;

小結(jié)

使用protobuf傳輸還是存在安全問題和數(shù)據(jù)比較大時(shí)的耗能問題暮蹂,于是我們想到了,在直播領(lǐng)域應(yīng)用很普遍的RTMP協(xié)議齐邦。下章詳細(xì)講解椎侠,使用分包思想拆解數(shù)據(jù)包進(jìn)行數(shù)據(jù)傳輸。

RTMP協(xié)議藍(lán)牙傳輸數(shù)據(jù)

RTMP傳統(tǒng)定義

rtmp協(xié)議中基本的數(shù)據(jù)單元被稱為消息(message)結(jié)構(gòu)一般為:

  • 時(shí)戳:4 byte措拇,單位毫秒我纪。超過最大值后會(huì)翻轉(zhuǎn)。
  • 長(zhǎng)度:消息負(fù)載的長(zhǎng)度丐吓。
  • 類型ID:Type Id 一部分ID范圍用于rtmp的控制信令浅悉。還有一部分可以供上層使用,rtmp只是透 傳券犁。這樣可以方便的在rtmp上進(jìn)行擴(kuò)展术健。
  • 消息流ID:Message Stream ID,用于區(qū)分不同流的消息粘衬。

消息在網(wǎng)絡(luò)中傳輸時(shí)荞估,會(huì)被分割成很多小的消息塊,進(jìn)行傳輸稚新,增加傳輸?shù)男士彼牛@些消息塊是由消息頭+消息體組成,消息頭就是制定的標(biāo)識(shí)消息的協(xié)議褂删,消息體就是所傳輸?shù)南?nèi)容飞醉。

RTMP在藍(lán)牙中的定義

手機(jī)藍(lán)牙傳輸數(shù)據(jù),無法保證雙方手機(jī)時(shí)間同步屯阀,因此刨除時(shí)間戳定義改為固定字符串缅帘,因此messageHeader定義為:

 struct message_header
{
   uint32_t magic;//magic number, 0x98765432
   uint32_t total;//包長(zhǎng)度,從這一字段頭算起
   uint32_t msgid;//消息ID
   uint32_t seqnum;//客戶端使用,自增量
   uint32_t version;//協(xié)議版本难衰,目前為1
   
};

將需要傳輸?shù)臄?shù)據(jù)添加message_header

  //GPBMEssage為protobuf庫(kù)里的類,請(qǐng)參考上篇文章
  -(NSMutableData*)returnData:(GPBMessage*)req messageId:(int)messageId {
    NSString *header=[NSString stringWithFormat:@"98765432%08lx%08x%08lx00000001",(unsigned long)req.data.length+20,messageId,(unsigned long)++self.header_count];
    Byte bytes[40];
    int j=0;
    for(int i=0;i*2+1<header.length;i++)
    {
        int int_ch;  /// 兩位16進(jìn)制數(shù)轉(zhuǎn)化后的10進(jìn)制數(shù)
        const char* hex_char=[[header substringWithRange:NSMakeRange(i*2, 2)] UTF8String];
        int_ch = (int)strtoul(hex_char, 0, 16);
        //        DLog(@"int_ch=%d",int_ch);
        bytes[j] = int_ch;  ///將轉(zhuǎn)化后的數(shù)放入Byte數(shù)組里
        j++;
    }
    NSMutableData *data = [[NSMutableData alloc] init];
    [data appendBytes:bytes length:j];
    [data appendData:req.data];
    return data;
}

接受到數(shù)據(jù)后钦无,需要把長(zhǎng)度小于message_header長(zhǎng)度的數(shù)據(jù)進(jìn)行拼包,并解析message_header結(jié)構(gòu)

  //解析數(shù)據(jù)message_header結(jié)構(gòu)
   -(void)parseSocketReceiveData:(NSData*)data result:(void (^)(NSData*result ,int messageId,int hearderId))resultBlock finish:(void(^)())finishBlockMessage{
    
    if (_halfData.length>0) {
        [_halfData appendData:data];
        data=[_halfData copy];
        _halfData =[[NSMutableData alloc]init];
    }else{
        data=[data copy];
    }
    
    if (data.length<20) {
        [_halfData appendData:data];
        if (finishBlockMessage) {
            finishBlockMessage();
        }
        return;
    }
    Byte *testByte = (Byte*)[data bytes];
    
    int length=(int) ((testByte[4] & 0xFF<<24)
                      | ((testByte[5] & 0xFF)<<16)
                      | ((testByte[6] & 0xFF)<<8)
                      | ((testByte[7] & 0xFF)));
    
    int messageId=(int) ((testByte[8] & 0xFF<<24)
                         | ((testByte[9] & 0xFF)<<16)
                         | ((testByte[10] & 0xFF)<<8)
                         | ((testByte[11] & 0xFF)));
    int headerId=(int)((testByte[12] & 0xFF<<24)
                       | ((testByte[13] & 0xFF)<<16)
                       | ((testByte[14] & 0xFF)<<8)
                       | ((testByte[15] & 0xFF)));
    if(length==data.length){
        if (resultBlock) {
            resultBlock([data subdataWithRange:NSMakeRange(20, length-20)],messageId,headerId);
        }
        if (finishBlockMessage) {
            finishBlockMessage();
        }
    }else if(length<data.length){
        if (resultBlock) {
            resultBlock([data subdataWithRange:NSMakeRange(20, length-20)],messageId,headerId);
        }
        [self parseSocketReceiveData:[data subdataWithRange:NSMakeRange(length, data.length-length)] result:resultBlock finish:            finishBlockMessage];
    }else{
        
        [_halfData appendData:data];
        if (finishBlockMessage) {
            finishBlockMessage();
        }
    }
}

小結(jié)

rtmp協(xié)議雖然加快了數(shù)據(jù)傳輸?shù)男矢窍欢ǔ潭壬系陌踩г荩遣⒉皇翘貏e的安全,為避免攻擊者攻擊苍凛,一些安全措施還是有必要的趣席,在這里不過多介紹兵志,有興趣自己調(diào)研醇蝴。

在線對(duì)戰(zhàn)

IM采用的是環(huán)信SDK,環(huán)信作為免費(fèi)的socket服務(wù)想罕,相對(duì)已經(jīng)很好了悠栓,功能也挺全面霉涨,但是,如果作為嚴(yán)謹(jǐn)?shù)墓δ荛_發(fā)惭适,他所暴露出來的api是遠(yuǎn)遠(yuǎn)不夠的笙瑟,如傳輸?shù)臄?shù)據(jù)必須是它定好的結(jié)構(gòu),雖然有個(gè)自定義字典可以傳輸?shù)邱荆@個(gè)字典也是僅僅限于幾種數(shù)據(jù)類型(做主要的DATA類型不接受)往枷。導(dǎo)入SDK官方文檔

環(huán)信的主要用到的API

環(huán)信的主要用到的API需要實(shí)現(xiàn)的代理

    //在初始化是設(shè)置代理
    private override init() {
        super.init()
        EMClient.shared().add(self, delegateQueue: nil)
        EMClient.shared().chatManager.add(self, delegateQueue: nil)
        EMClient.shared().contactManager.add(self, delegateQueue: nil)
        EMClient.shared().groupManager.add(self, delegateQueue: nil)
        EMClient.shared().roomManager.add(self, delegateQueue: nil)
       
    }  
    //在對(duì)象釋放時(shí),釋放代理對(duì)象
    deinit {
        EMClient.shared().removeDelegate(self)
        EMClient.shared().chatManager.remove(self)
        EMClient.shared().contactManager.removeDelegate(self)
        EMClient.shared().groupManager.removeDelegate(self)
        EMClient.shared().roomManager.remove(self)
    }
 

實(shí)現(xiàn)登錄異常的代理:服務(wù)器斷開凄杯,開啟定時(shí)器定時(shí)重連(環(huán)信并沒有給出重連的api错洁,我發(fā)現(xiàn)調(diào)用環(huán)信的需要連接服務(wù)器的api,sdk會(huì)自動(dòng)重連服務(wù)器戒突,所以斷開服務(wù)器屯碴,定時(shí)調(diào)用上傳錯(cuò)誤日志的api,機(jī)制吧膊存。)

extension ChatHelpTool: EMClientDelegate{
    //主要處理斷開服務(wù)器重連機(jī)制
    func connectionStateDidChange(_ aConnectionState: EMConnectionState) {
        networkState?(aConnectionState)
        switch aConnectionState {
        case EMConnectionConnected:
            print("服務(wù)器已經(jīng)連上")
            if reconnectTimer != nil {
                reconnectTimer.invalidate()
                reconnectTimer = nil
            }
           
        case EMConnectionDisconnected:
            print("服務(wù)器已斷開")
            if reconnectTimer != nil {
                reconnectTimer.invalidate()
                reconnectTimer = nil
            }
            
            DispatchQueue.global().async {
                self.reconnectTimer = Timer.weak_scheduledTimerWithTimeInterval(2, selector: { [weak self] in
                    self?.reconnectNetwork()
                    
                    }, repeats: true)
                self.reconnectTimer.fire()
                RunLoop.current.add(self.reconnectTimer, forMode: RunLoopMode.defaultRunLoopMode)
                RunLoop.current.run()
            }
           
        
            
        default:
            ()
        }
    }
    
    func autoLoginDidCompleteWithError(_ aError: EMError!) {
        if let error = aError {
            TJFTool.errorForCode(code: error.code)
            TJFTool.loginOutMessage(message: "自動(dòng)登錄失敗导而,請(qǐng)重新登錄。")
        }else {
             PAMBManager.sharedInstance.showBriefMessage(message: "自動(dòng)登錄成功")
        }
    }
    //異地登錄
    func userAccountDidLoginFromOtherDevice() {
       TJFTool.loginOutMessage(message: "該賬號(hào)在其他設(shè)備上登錄,請(qǐng)重新登錄隔崎。")
    }
    
    func userAccountDidRemoveFromServer() {
        TJFTool.loginOutMessage(message: "當(dāng)前登錄賬號(hào)已經(jīng)被從服務(wù)器端刪除,請(qǐng)重新登錄")
    }
    
    func userDidForbidByServer() {
        TJFTool.loginOutMessage(message: "服務(wù)被禁用,請(qǐng)重新登錄")
    }
}

實(shí)現(xiàn)發(fā)送消息的方法:因?yàn)槭亲远x的數(shù)據(jù)結(jié)構(gòu)今艺,所以使用消息的擴(kuò)展,自定義字典傳遞數(shù)據(jù)仍稀。

  //發(fā)送消息
extension ChatHelpTool {
   // 定義消息model EMMessage
  static func sendTextMessage(text:String,toUser:String,messageType:EMChatType,messageExt:[String:Any]?) ->EMMessage?{
     let body = EMTextMessageBody.init(text: text)
     let from = EMClient.shared().currentUsername
     let message  = EMMessage.init(conversationID: toUser, from: from, to: toUser, body: body, ext: messageExt)
       message?.chatType = messageType
       return message
   }
 //發(fā)送消息
 static  func senMessage(aMessage:EMMessage,progress aProgressBlock:(( _ progres: Int32)->())?,completion aCompletionBlock:((_ message:EMMessage?,_ error:EMError?)->())?) {
       
       DispatchQueue.global().async {
          EMClient.shared().chatManager.send(aMessage, progress: aProgressBlock,completion:aCompletionBlock)
       }
       
   }
}

實(shí)現(xiàn)接收消息的代理

extension ChatHelpTool: EMChatManagerDelegate{
    //會(huì)話列表發(fā)生變化<EMConversation>
    func conversationListDidUpdate(_ aConversationList: [Any]!) {
         print("會(huì)話列表發(fā)生變化")
    }
    //收到消息
    func messagesDidReceive(_ aMessages: [Any]!) {
        aMessages.forEach { (message) in
            if let message = message as? EMMessage {
              
                if  let data = message.ext as? [String:Any] {
                    let model = MessageModel.init(dictionary: data)
                    if model.gameType == "1" {
                    self.letterOfChallengeAction(["userName":message.from,"message":(model.challengeList?.message).noneNull,"chessType":model.chessType.noneNull])
                    
                    }else if model.gameType == "2" {
                        var role:Role = .blacker
                        var gameType:GameType = .LiuZhouChess
                        if model.chessType == "1" {
                            role = .whiter
                            gameType = .fiveInRowChess
                        }
                        TJFTool.pushToChessChatRoom(message.from,role,chessType: gameType)
                     
                    }else {
                       self.buZiChessMessage?(message)
                    }
                }
                
            }
        }
    }
    //收到已讀回執(zhí)
    func messagesDidRead(_ aMessages: [Any]!) {
        print("收到已讀回執(zhí)")
    }
    //收到消息送達(dá)回執(zhí)
    func messagesDidDeliver(_ aMessages: [Any]!) {
        print("收到消息送達(dá)回執(zhí)")
        aMessages.forEach { (message) in
            if let message = message as? EMMessage {
                if  let data = message.ext as? [String:Any] {
                    let model = MessageModel.init(dictionary: data)
                     if model.gameType == "3" {
                       
                    }
                }
               print(message.messageId)
               print(TJFTool.timeWithTimeInterVal(time: message.timestamp),TJFTool.timeWithTimeInterVal(time: message.localTime))
                
            }
        }
    }
    //消息狀態(tài)發(fā)生變化
    func messageStatusDidChange(_ aMessage: EMMessage!, error aError: EMError!){
         print("消息狀態(tài)發(fā)生變化")
    }
     
}

小結(jié)

IM在沒有服務(wù)器的情況下洼滚,使用第三方免費(fèi)的最方便,但是同時(shí)并不能滿足產(chǎn)品的需求技潘,有機(jī)會(huì)遥巴,我會(huì)為大家分享一篇自定義socket服務(wù)器下的即時(shí)通信結(jié)構(gòu)和邏輯的設(shè)定。

最后

代碼具體實(shí)現(xiàn)地址
代碼中具體實(shí)現(xiàn)了兩個(gè)棋類游戲(有時(shí)間會(huì)持續(xù)添加游戲種類)享幽,包括在線對(duì)戰(zhàn)铲掐,人機(jī)對(duì)戰(zhàn)(算法不錯(cuò)哦),藍(lán)牙對(duì)戰(zhàn)值桩。
代碼編寫不易摆霉,喜歡的請(qǐng)點(diǎn)贊,謝謝奔坟!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末携栋,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子咳秉,更是在濱河造成了極大的恐慌婉支,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,000評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件澜建,死亡現(xiàn)場(chǎng)離奇詭異向挖,居然都是意外死亡蝌以,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,745評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門何之,熙熙樓的掌柜王于貴愁眉苦臉地迎上來跟畅,“玉大人,你說我怎么就攤上這事溶推』布” “怎么了呜舒?”我有些...
    開封第一講書人閱讀 168,561評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵俗扇,是天一觀的道長(zhǎng)蹦渣。 經(jīng)常有香客問我往果,道長(zhǎng)压储,這世上最難降的妖魔是什么则酝? 我笑而不...
    開封第一講書人閱讀 59,782評(píng)論 1 298
  • 正文 為了忘掉前任扎狱,我火速辦了婚禮蝶怔,結(jié)果婚禮上占拍,老公的妹妹穿的比我還像新娘略就。我一直安慰自己,他們只是感情好晃酒,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,798評(píng)論 6 397
  • 文/花漫 我一把揭開白布表牢。 她就那樣靜靜地躺著,像睡著了一般贝次。 火紅的嫁衣襯著肌膚如雪崔兴。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,394評(píng)論 1 310
  • 那天蛔翅,我揣著相機(jī)與錄音敲茄,去河邊找鬼。 笑死山析,一個(gè)胖子當(dāng)著我的面吹牛堰燎,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播笋轨,決...
    沈念sama閱讀 40,952評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼秆剪,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了爵政?” 一聲冷哼從身側(cè)響起仅讽,我...
    開封第一講書人閱讀 39,852評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎钾挟,沒想到半個(gè)月后洁灵,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,409評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡等龙,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,483評(píng)論 3 341
  • 正文 我和宋清朗相戀三年处渣,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蛛砰。...
    茶點(diǎn)故事閱讀 40,615評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡罐栈,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出泥畅,到底是詐尸還是另有隱情荠诬,我是刑警寧澤,帶...
    沈念sama閱讀 36,303評(píng)論 5 350
  • 正文 年R本政府宣布位仁,位于F島的核電站柑贞,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏聂抢。R本人自食惡果不足惜钧嘶,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,979評(píng)論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望琳疏。 院中可真熱鬧有决,春花似錦、人聲如沸空盼。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,470評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)揽趾。三九已至台汇,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間篱瞎,已是汗流浹背苟呐。 一陣腳步聲響...
    開封第一講書人閱讀 33,571評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留俐筋,地道東北人掠抬。 一個(gè)月前我還...
    沈念sama閱讀 49,041評(píng)論 3 377
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像校哎,于是被迫代替她去往敵國(guó)和親两波。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,630評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容

  • 五子棋 五子棋五子棋是比較流行的棋類游戲了闷哆,玩法簡(jiǎn)單腰奋,基本上人人會(huì)玩,在此就不介紹游戲規(guī)則了抱怔。下面使用 swift...
    天機(jī)否閱讀 20,517評(píng)論 3 29
  • 實(shí)時(shí)消息協(xié)議---流的分塊 版權(quán)聲明: 版權(quán)(c)2009 Adobe系統(tǒng)有限公司劣坊。全權(quán)所有。 摘要: 本備忘錄描...
    一個(gè)人zy閱讀 1,908評(píng)論 0 9
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理屈留,服務(wù)發(fā)現(xiàn)局冰,斷路器测蘑,智...
    卡卡羅2017閱讀 134,704評(píng)論 18 139
  • 針對(duì)曾經(jīng)火爆的2048游戲,有人實(shí)現(xiàn)了一個(gè)AI程序康二,可以以較大概率(高于90%)贏得游戲碳胳,并且作者在stackov...
    GarfieldEr007閱讀 2,722評(píng)論 1 18
  • 01砍掉中間環(huán)節(jié)▲ 曾經(jīng)挨约,我有一個(gè)愛看書的同事。 我也挺愛看書产雹,不同的是诫惭,她分享好書給我時(shí),我總是把書名寫在紙上蔓挖,...
    三人行必有吳師閱讀 519評(píng)論 0 4