Swift 簡單消消樂demo

游戲規(guī)則:

  • 點擊某一方塊,當該方塊的上下左右四個方向同顏色方塊可連續(xù)(大于等于2)即可消除。
  • 方塊消除后涎才,上面的方塊往下掉。
  • 中間整列都空的話力九,旁邊的列往中間靠攏耍铜。
  • 如果沒有可消除的方塊,游戲結束跌前。

效果如下:

效果圖

流程:

  1. 使用隨機方法產生方塊的顏色棕兼,然后創(chuàng)建背景色與之相對應的方塊。
  2. 判斷游戲是否結束抵乓。
  3. 用戶點擊方塊伴挚,判斷該方塊的上下左右方向上是否存在同顏色的方塊靶衍。有,把方塊保存起來茎芋,進入第四步颅眶,沒有,不做任何響應田弥,并等待用戶點擊涛酗。
  4. 清除方塊,向下移動方塊偷厦。存在中間某列空了商叹,兩側的列往中間移動。跳轉到第二步只泼。
  5. 用戶點擊重新開始按鈕剖笙,跳轉到第一步。

實現:

先來看一下demo的文件結構:


文件結構

其中:TwoDimentionalBrain是demo的邏輯處理请唱,DiamondsImageView是繼承UIImageView, 是帶有點擊功能的方塊枯途。

DiamondsImageView 類

class DiamondsImageView: UIImageView {
var backgroundType : backgroundType = .clear
var itemIndex : Int = 0

//起始位置和將要移動時的位置,動畫效果
var currentLocation : CGPoint?
var toLocation : CGPoint?

typealias returnIndexAndType = (Int, backgroundType) -> ()
var returnTuple : returnIndexAndType?
}

方塊在demo中是使用一個二維數組存儲籍滴,itemIndex代表該數組的index(i * row + column), backgroundType表示該方塊的顏色值酪夷。

enum backgroundType {
case yellow
case blue
case red
case green
case clear
}

而returnTuple是一個block, 主要是在點擊方塊時,在單擊響應方法里將該方塊的的itemIndex和backgroundType傳遞給viewController.

func touchInside(_ sender: UITapGestureRecognizer) {
    //點擊空白方塊孽惰,則不響應
    if backgroundType == .clear {
        print("不能點擊")
        return
    }
    
    //點擊顏色方塊晚岭,將方塊信息傳遞給viewController
    if let tuple = returnTuple {
        tuple(itemIndex, backgroundType)
    }
    }

TwoDimentionalBrain 結構體

struct TwoDimentionalBrain {
//存儲方塊顏色
private var sourceDataArray = [[backgroundType]]()
//存儲消除方塊的單個分數值(count, value)
private let scoreArray = [(0, 5), (5, 8), (10, 10), (13, 12), (15, 13), (100, 15)]
//存儲需要清除的方塊 1:清除, 0:不清除
private var clearArray = [(Int, Int)]()
//存儲需要移動的方塊列
private var emptyColumnArray = Array<Int>(repeating: 0, count: ColumnCount)
//存儲游戲分數
var score = 0;

//同列需要往下掉的方塊勋功,傳遞給viewController
typealias exchangeRowInColumn = (Int, Int, Int) -> ()
var itemMoveDown: exchangeRowInColumn?

//需要整列移動的方塊坦报,傳遞給viewController
typealias exchangeColumn = (Int, Int) -> ()
var itemChangeColumn: exchangeColumn?
}

隨機產生一行的顏色排列,返回的數組添加到sourceDataArray里面去狂鞋。

private mutating func setOneArray() -> [backgroundType] {
    var array = [backgroundType]()
    for _ in 0..<ColumnCount {
        let data = arc4random() % 4
        
        switch data {
        case 0:
            array.append(.yellow)
        case 1:
            array.append(.blue)
        case 2:
            array.append(.red)
        case 3:
            array.append(.green)
        default:
            array.append(.clear)
        }
    }
    
    return array
    }

mutating func setSourceDataArray() {
    //先清空
    sourceDataArray = [[backgroundType]]()
    
    for _ in 0..<RowCount {
        let array = setOneArray()
        
        sourceDataArray.append(array)
    }
   }

用遞歸方法尋找點擊方塊的上下左右同顏色的方塊片择,用尋找左側方向來示例:
首先需要判斷當前方塊的左側方塊顏色是否一致。一致骚揍,則繼續(xù)在該方向尋找字管;否則,該方向的尋找結束信不。

private mutating func findSameTypeWithRound(row: Int, column: Int) {
    let isLeft = isLeftSame(row: row, column: column)
    //如果顏色一致嘲叔,則繼續(xù)往左邊尋找相同顏色的方塊
    if isLeft {
        findSameTypeWithRound(row: row, column: column - 1)
    }
    }

判斷當前方塊的左側方塊顏色是否一致的方法如下:

private mutating func isLeftSame(row: Int, column: Int) -> Bool {
    if column <= 0 || isVisited(row: row, column: column - 1) {
        //如果已經是最左邊或者已經訪問過了
        return false
    }
    
    if sourceDataArray[row][column - 1] == .clear {
        //如果已經是空白方塊
        return false
    }
    
    if sourceDataArray[row][column - 1] == sourceDataArray[row][column] {
        //左側方塊和當前方塊的顏色一致,則將左側方塊的行與列坐標添加到clearArray
        clearArray.append((row, column - 1))
        return true
    }
    
    return false
    }

其他方向的尋找類似抽活,就不多陳述了硫戈。如果顏色一致,則添加到clearArray下硕,在四個方向都尋找結束之后丁逝,根據clearArray的數據計算分數和對方塊進行消除汁胆。

if clearArray.count < 2 {
        clearArray = [(Int, Int)]()
        return;
    }
    
    for (row, column) in clearArray {  
        //將需要消除的方塊的顏色設置為透明
        sourceDataArray[row][column] = .clear
    }
    
    //計算分數
    let count = clearArray.count
    
    for (item, value) in scoreArray {
        if count >= item {
            score += value * count;
            break;
        }
    }

方塊往下掉:遍歷sourceDataArray數組,如果方塊的顏色不是.clear 就在該方塊所在列的下一行遞歸尋找.clear的方塊霜幼,直到碰到有顏色的方塊或者是邊界沦泌。使用count存儲兩者的行數的間隔:

private func getUnClearUpCount(row: Int, column: Int) -> Int {
    if row < 0 {
        //遇到邊界
        return -1
    }
    
    var count = 0
    //遍歷該列下方的所有方塊
    for index in 0...row {
        if sourceDataArray[row - index][column] == .clear {
            //遇到空白方塊,則間隔+1
            count = count + 1
        }
        else {
            //遇到有顏色的方塊辛掠,則返回
            return count
        }
    }
    
    return count
    }

private mutating func moveDown() {
    for row in 0..<RowCount {
        for column in 0..<ColumnCount {
            if sourceDataArray[row][column] != .clear {
                //尋找同列的下方是否有空的方塊可以進行移動
                let count = getUnClearUpCount(row: row - 1, column: column)
                
                if count > 0 {
                    //有谢谦,進行移動。
                    sourceDataArray[row - count][column] = sourceDataArray[row][column]
                    sourceDataArray[row][column] = .clear
                    
                    if let itemMD = itemMoveDown {
                        //傳遞給viewController itemMD(所在列, 原來的行, 需要移動到的行)
                        itemMD(column, row, row - count)
                    }
                }
            }
        }
    }
    }

如果消除了之后發(fā)現某列空了萝衩,則需要判斷兩側方塊是否需要整列往中間移動回挽。使用二分法,從中間let centerColumn = ColumnCount / 2 分界猩谊。左邊部分:從centerColumn ~ 0 進行遍歷千劈,如果當前列不空,并且右側有空的牌捷,則整列向右移動墙牌;右邊部分:從centerColumn + 1 到右側邊界ColumnCount。如果當前列不空暗甥,并且左側有空的喜滨,則整列往左移動。

let centerColumn = ColumnCount / 2
    //存儲需要移動兩列的列數間隔
    var count = 0
    for column in centerColumn + 1..<ColumnCount {
        if emptyColumnArray[column] == 1 {
            //如果是空的話撤防,就加一
            count += 1
        }
        else if (count > 0) {
            //如果當前列不空虽风,并且左側有空的,則整列移動
            for i in 0..<ColumnCount - centerColumn {
                if column + i < ColumnCount {
                    moveColumnToAnother(fromColumn: column + i, toColumn: column - count + i)
                }
            }
            
            count = 0
        }
        else {
            //當前列不空寄月,并且左側沒有空的辜膝,則什么都不做
        }

整列移動的方法如下:

private mutating func moveColumnToAnother(fromColumn: Int, toColumn: Int) {
    for row in 0..<sourceDataArray.count {
        sourceDataArray[row][toColumn] = sourceDataArray[row][fromColumn]
        sourceDataArray[row][fromColumn] = .clear
        
        emptyColumnArray[fromColumn] = 1
        emptyColumnArray[toColumn] = 0
        
        //傳遞給viewController
        if let changeColumn = itemChangeColumn {
            //changeColumn(當前的列, 移動后的列)
            changeColumn(fromColumn, toColumn)
        }
    }
    }

遍歷所有的方塊,如果沒有連續(xù)的同顏色方塊漾肮,則游戲結束厂抖。

 mutating func isGameOver() -> Bool {
    //判斷是否已經結束游戲了
    for row in 0..<RowCount {
        for column in 0..<ColumnCount {
            if sourceDataArray[row][column] == .clear {
                //遇到空白方塊,不執(zhí)行下面的內容克懊,繼續(xù)下一次循環(huán)
                continue;
            }
            
            //先清空忱辅,并把當前的方塊添加到消除數組
            clearArray = [(Int, Int)]()
            clearArray.append((row, column))
            //在四個方向上尋找同顏色的方塊
            findSameTypeWithRound(row: row, column: column)
            
            if clearArray.count >= 2 {
                //存在連續(xù)的同顏色方塊,游戲繼續(xù)
                return false
            }
        }
    }
    
    return true
}

viewController

//顯示游戲得分
@IBOutlet var scoreLabel: UILabel!
//存儲方塊的數組
private var imageArray = [[DiamondsImageView]]()
//游戲邏輯的引用
private var diamondsBrain = TwoDimentionalBrain()

在頁面加載完成時保檐,創(chuàng)建隨機顏色的方塊耕蝉。并且實現TwoDimentionalBrain結構體中兩個移動方塊的block崔梗。

override func viewDidLoad() {
    super.viewDidLoad()
    //獲取隨機方塊顏色
    diamondsBrain.setSourceDataArray()
    
    //方塊移動的實現方法
    weak var weakSelf = self
    diamondsBrain.itemMoveDown = {(column, fromRow, toRow) in
        weakSelf?.imageViewMoveDown(column: column, fromRow: fromRow, toRow: toRow)
    }
    diamondsBrain.itemChangeColumn = {(fromColunm, toColumn) in
        weakSelf?.imageViewExchangeColumn(fromColumn: fromColunm, toColumn: toColumn)
    }
    
    //創(chuàng)建方塊
    createImageView()
    
    if diamondsBrain.isGameOver() {
        print("Game Over!")
    }
    }

//同列的兩個方塊進行交換
private func imageViewMoveDown(column: Int, fromRow: Int, toRow: Int) {
    let fromImage = imageArray[fromRow][column]
    let toImage = imageArray[toRow][column]
    
    exchangeImage(fromImage: fromImage, toImage: toImage)
    
    imageArray[fromRow][column] = toImage
    imageArray[toRow][column] = fromImage
}

//交換兩列的方塊
private func imageViewExchangeColumn(fromColumn: Int, toColumn: Int) {
    for row in 0..<RowCount {
        if imageArray[row][fromColumn].backgroundType == .clear {
            return
        }
        else {
            let fromImage = imageArray[row][fromColumn]
            let toImage = imageArray[row][toColumn]
            exchangeImage(fromImage: fromImage, toImage: toImage)
            
            imageArray[row][fromColumn] = toImage
            imageArray[row][toColumn] = fromImage
        }
    }
}

使用UIView動畫夜只,展示兩個方塊交換的過程(已經清楚的方塊設置成空白,所以只看到有顏色的方塊在移動)蒜魄。

 private func exchangeImage(fromImage: DiamondsImageView, toImage: DiamondsImageView) {
    UIView.animate(withDuration: 0.2, animations: {
        let origin = fromImage.frame.origin
        fromImage.frame.origin = toImage.frame.origin
        toImage.frame.origin = origin
        
        })
    
    let index = fromImage.itemIndex
    fromImage.itemIndex = toImage.itemIndex
    toImage.itemIndex = index
    }

創(chuàng)建方塊時扔亥,根據diamondsBrain的soureDataArray的存儲元素決定方塊的顏色场躯,而itemIndex由數組的行和列決定 itemIndex = row * ColumnCount + column,并且接收方塊點擊的block旅挤,進行相關的處理踢关。

private func createImageView() {
    //根據sourceDataArray的顏色創(chuàng)建方塊
    let dataArray = diamondsBrain.getSourceArray()

    for (row, itemArray) in dataArray.enumerated() {
        var rowImageArray = [DiamondsImageView]()
        
        for (column, item) in itemArray.enumerated() {
            let originX = space + CGFloat(column) * (width + ImageSpace)
            let originY = height - CGFloat(row + 1) * (width + ImageSpace)
            let rect = CGRect(x: originX, y: originY, width: width, height: width)
            let imageView = DiamondsImageView(frame: rect)
            imageView.backgroundType = item
            imageView.itemIndex = row * ColumnCount + column
            
            weak var weakSelf = self
            imageView.returnTuple = {(index, type) in
                let column = index % ColumnCount
                let row = index / ColumnCount
                weakSelf?.clearItem(row: row, column: column)
            }
            
            rowImageArray.append(imageView)
            self.view.addSubview(imageView)
        }
        
        imageArray.append(rowImageArray)
    }
    
    updateUI()
    }

在用戶點擊了方塊之后,根據傳過來的itemIndex確定點擊的方塊的行和列粘茄,然后調用diamondsBrain.getClearItem方法消除方塊签舞。消除方塊之后再判斷游戲是否結束。

private func clearItem(row: Int, column: Int) {
    diamondsBrain.getClearItem(row: row, column: column)
    let dataArray = diamondsBrain.getSourceArray()
    
    for (row, itemArray) in dataArray.enumerated() {
        let rowImageArray = imageArray[row]
        
        for (column, item) in itemArray.enumerated() {
            rowImageArray[column].backgroundType = item
        }
    }
    
    updateUI()
    
    if diamondsBrain.isGameOver() {
        print("Game Over!")
    }
    }

根據方塊backgroundTyped對方塊的背景顏色進行賦值

private func updateUI() {
    let score = diamondsBrain.score;
    scoreLabel.text = String.init(stringInterpolationSegment: score)
    
    for (_, itemArray) in imageArray.enumerated() {
        for (_, item) in itemArray.enumerated() {
            switch item.backgroundType {
            case .green:
                item.backgroundColor = UIColor.green
            case .red:
                item.backgroundColor = UIColor.red
            case .blue:
                item.backgroundColor = UIColor.blue
            case .yellow:
                item.backgroundColor = UIColor.yellow
            case .clear:
                item.backgroundColor = UIColor.clear
            }
        }
    }
    }

重新開始游戲時柒瓣,使用隨機方法產生方塊顏色儒搭,然后對ImageArray的方塊的背景色進行賦值。

@IBAction func resetGame(_ sender: UIButton) {
    diamondsBrain.setSourceDataArray()
    diamondsBrain.score = 0;
    
    let dataArray = diamondsBrain.getSourceArray()
    
    for (row, itemArray) in dataArray.enumerated() {
        var rowImageArray = imageArray[row]
        
        for (column, item) in itemArray.enumerated() {
            let originX = space + CGFloat(column) * (width + ImageSpace)
            let originY = height - CGFloat(row + 1) * (width + ImageSpace)
            let rect = CGRect(x: originX, y: originY, width: width, height: width)
            let imageView = rowImageArray[column]
            imageView.frame = rect
            imageView.backgroundType = item
            imageView.itemIndex = row * ColumnCount + column
            
            rowImageArray[column] = imageView
        }
        
        imageArray[row] = rowImageArray
    }

    updateUI()
    }

感覺都在用代碼說話芙贫,包涵一下搂鲫,貼個demo放在百度云ClearGame 喜歡的可以去下載。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末磺平,一起剝皮案震驚了整個濱河市魂仍,隨后出現的幾起案子,更是在濱河造成了極大的恐慌拣挪,老刑警劉巖擦酌,帶你破解...
    沈念sama閱讀 212,884評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異菠劝,居然都是意外死亡仑氛,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 90,755評論 3 385
  • 文/潘曉璐 我一進店門闸英,熙熙樓的掌柜王于貴愁眉苦臉地迎上來锯岖,“玉大人,你說我怎么就攤上這事甫何〕龃担” “怎么了?”我有些...
    開封第一講書人閱讀 158,369評論 0 348
  • 文/不壞的土叔 我叫張陵辙喂,是天一觀的道長捶牢。 經常有香客問我,道長巍耗,這世上最難降的妖魔是什么秋麸? 我笑而不...
    開封第一講書人閱讀 56,799評論 1 285
  • 正文 為了忘掉前任,我火速辦了婚禮炬太,結果婚禮上灸蟆,老公的妹妹穿的比我還像新娘。我一直安慰自己亲族,他們只是感情好炒考,可當我...
    茶點故事閱讀 65,910評論 6 386
  • 文/花漫 我一把揭開白布可缚。 她就那樣靜靜地躺著,像睡著了一般斋枢。 火紅的嫁衣襯著肌膚如雪帘靡。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 50,096評論 1 291
  • 那天瓤帚,我揣著相機與錄音描姚,去河邊找鬼。 笑死戈次,一個胖子當著我的面吹牛轰胁,可吹牛的內容都是我干的。 我是一名探鬼主播朝扼,決...
    沈念sama閱讀 39,159評論 3 411
  • 文/蒼蘭香墨 我猛地睜開眼赃阀,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了擎颖?” 一聲冷哼從身側響起榛斯,我...
    開封第一講書人閱讀 37,917評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎搂捧,沒想到半個月后驮俗,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 44,360評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡允跑,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,673評論 2 327
  • 正文 我和宋清朗相戀三年王凑,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片聋丝。...
    茶點故事閱讀 38,814評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡索烹,死狀恐怖,靈堂內的尸體忽然破棺而出弱睦,到底是詐尸還是另有隱情百姓,我是刑警寧澤,帶...
    沈念sama閱讀 34,509評論 4 334
  • 正文 年R本政府宣布况木,位于F島的核電站垒拢,受9級特大地震影響,放射性物質發(fā)生泄漏火惊。R本人自食惡果不足惜求类,卻給世界環(huán)境...
    茶點故事閱讀 40,156評論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望屹耐。 院中可真熱鬧尸疆,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽俗他。三九已至脖捻,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間兆衅,已是汗流浹背地沮。 一陣腳步聲響...
    開封第一講書人閱讀 32,123評論 1 267
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留羡亩,地道東北人摩疑。 一個月前我還...
    沈念sama閱讀 46,641評論 2 362
  • 正文 我出身青樓,卻偏偏與公主長得像畏铆,于是被迫代替她去往敵國和親雷袋。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,728評論 2 351

推薦閱讀更多精彩內容