美文网首页iOS Developer
Swift 简单消消乐demo

Swift 简单消消乐demo

作者: 34码的小孩子 | 来源:发表于2017-04-18 14:31 被阅读140次

    游戏规则:

    • 点击某一方块,当该方块的上下左右四个方向同颜色方块可连续(大于等于2)即可消除。
    • 方块消除后,上面的方块往下掉。
    • 中间整列都空的话,旁边的列往中间靠拢。
    • 如果没有可消除的方块,游戏结束。

    效果如下:

    效果图

    流程:

    1. 使用随机方法产生方块的颜色,然后创建背景色与之相对应的方块。
    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)
        }
       }
    

    用递归方法寻找点击方块的上下左右同颜色的方块,用寻找左侧方向来示例:
    首先需要判断当前方块的左侧方块颜色是否一致。一致,则继续在该方向寻找;否则,该方向的寻找结束。

    private mutating func findSameTypeWithRound(row: Int, column: Int) {
        let isLeft = isLeftSame(row: row, column: column)
        //如果颜色一致,则继续往左边寻找相同颜色的方块
        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)
                        }
                    }
                }
            }
        }
        }
    

    如果消除了之后发现某列空了,则需要判断两侧方块是否需要整列往中间移动。使用二分法,从中间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)
            }
        }
        }
    

    遍历所有的方块,如果没有连续的同颜色方块,则游戏结束。

     mutating func isGameOver() -> Bool {
        //判断是否已经结束游戏了
        for row in 0..<RowCount {
            for column in 0..<ColumnCount {
                if sourceDataArray[row][column] == .clear {
                    //遇到空白方块,不执行下面的内容,继续下一次循环
                    continue;
                }
                
                //先清空,并把当前的方块添加到消除数组
                clearArray = [(Int, Int)]()
                clearArray.append((row, column))
                //在四个方向上寻找同颜色的方块
                findSameTypeWithRound(row: row, column: column)
                
                if clearArray.count >= 2 {
                    //存在连续的同颜色方块,游戏继续
                    return false
                }
            }
        }
        
        return true
    }
    

    viewController

    //显示游戏得分
    @IBOutlet var scoreLabel: UILabel!
    //存储方块的数组
    private var imageArray = [[DiamondsImageView]]()
    //游戏逻辑的引用
    private var diamondsBrain = TwoDimentionalBrain()
    

    在页面加载完成时,创建随机颜色的方块。并且实现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)
        }
        
        //创建方块
        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
        }
    

    创建方块时,根据diamondsBrain的soureDataArray的存储元素决定方块的颜色,而itemIndex由数组的行和列决定 itemIndex = row * ColumnCount + column,并且接收方块点击的block,进行相关的处理。

    private func createImageView() {
        //根据sourceDataArray的颜色创建方块
        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 喜欢的可以去下载。

    相关文章

      网友评论

        本文标题:Swift 简单消消乐demo

        本文链接:https://www.haomeiwen.com/subject/gwmtzttx.html