zoukankan      html  css  js  c++  java
  • Swift实战之2048小游戏

    上周在图书馆借了一本Swift语言实战入门,入个门玩一玩^_^正好这本书的后面有一个2048小游戏的实例,笔者跟着实战了一把。

    差不多一周的时间,到今天,游戏的基本功能已基本实现,细节我已不打算继续完善,就这么整理一下过程中一些值得记录的点吧。

    用的Swift版本是2.0,原书中的Swift版本会低一些,所以实践起来有些地方语法并不一样。

    一、开始页面

     

    在程序的第一张页面(Main.storyboard)上,只放了一个“开始游戏”按钮,点击按钮,弹出一个提示对话框,确认后,进入游戏页面。

    1  @IBAction func startGame(sender: UIButton) {
    2         let alerController = UIAlertController(title: "开始", message: "游戏就要开始,你准备好了吗?", preferredStyle: UIAlertControllerStyle.Alert)
    3         alerController.addAction(UIAlertAction(title: "Ready Go!", style: UIAlertActionStyle.Default, handler: {
    4             action in
    5             self.presentViewController(MainTabViewController(), animated: true, completion: nil)
    6         }))
    7         self.presentViewController(alerController, animated: true, completion: nil)
    8         
    9     }

    二、游戏页面

    用一个TabViewController来实现,控制游戏页面(MainViewController)、设置页面(SettingViewController)和主题页面(KKColorListViewController)等三个页面的切换。 

     1 import UIKit
     2 
     3 class MainTabViewController: UITabBarController,KKColorListViewControllerDelegate {
     4     var viewMain = MainViewController()
     5     var viewColor = KKColorListViewController(schemeType:KKColorsSchemeType.Crayola)
     6     override func viewDidLoad() {
     7         super.viewDidLoad()
     8 
     9         // Do any additional setup after loading the view.
    10         
    11         viewMain.title = "2048"
    12         let user=UserModel.sharedInstance().user
    13         if let red = user?.red{
    14             let uicolor=UIColor(red: red, green: (user?.green)!, blue: (user?.blue)!, alpha: (user?.alpha)!)
    15             viewMain.view.backgroundColor=uicolor
    16         }
    17         
    18         let viewSetting = SettingViewController()
    19         viewSetting.title = "设置"
    20         
    21         viewColor.title="颜色"
    22         viewColor.headerTitle="选择背景色"
    23         viewColor.delegate=self
    24         
    25         let main = UINavigationController(rootViewController: viewMain)
    26         let setting = UINavigationController(rootViewController: viewSetting)
    27         let color = UINavigationController(rootViewController: viewColor)
    28         
    29         self.viewControllers = [main, setting,color]
    30         self.selectedIndex = 0
    31     }
    32 
    33     override func didReceiveMemoryWarning() {
    34         super.didReceiveMemoryWarning()
    35         // Dispose of any resources that can be recreated.
    36     }
    37     
    38     func colorListController(controller: KKColorListViewController!, didSelectColor color: KKColor!) {
    39         viewMain.view.backgroundColor = color.uiColor()
    40         
    41         UserModel.sharedInstance().saveColor(color.uiColor())
    42         self.selectedIndex=0
    43     }
    44     
    45     func colorListPickerDidComplete(controller: KKColorListViewController!) {
    46         self.selectedIndex=0
    47     }
    48 
    49 }

    (一)主题页面

     其中,主题页面直接使用GitHub上的一个开源项目KKColorListViewController,选中颜色后,改变游戏页面的背景色。

    这个项目可以从GitHub直接下载,但这个项目是用Objective-C写的,所以添加到Swift项目中后,需要新建一个Bridge头文件,这个头文件需要保存在项目文件夹的根目录下,而不是项目文件夹里面的源码文件夹(否则,可能需要自己配置头文件的目录)

    1 #ifndef Bridging_Header_h
    2 #define Bridging_Header_h
    3 #import "KKColorListPicker.h"
    4 
    5 #endif /* Bridging_Header_h */

    另外,添加到项目后,编译时还会有一些文件会报错,需要修改一些细节才能正常使用。

    (1)KKColorsSchemeType.h中需添加

    #import <Foundation/Foundation.h>

    (2)KKColorListViewController中(void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath方法,将最后一句注释掉。否则每次选完颜色,程序就会关闭当前的MainTabViewController而回到开始游戏页面。

    1 - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
    2 {
    3     KKColor *color = self.colors[indexPath.row];
    4     if (self.delegate) {
    5         [self.delegate colorListController:self didSelectColor:color];
    6     }
    7 //    [self dismissViewControllerAnimated:YES completion:nil];
    8 }

    (二)游戏页面

     

    1、ScoreView

    游戏页面,上方有两个自定义的UIView,用于动态显示游戏分数。

      

     1 import UIKit
     2 
     3 enum ScoreType{
     4     case Common
     5     case Best
     6 }
     7 
     8 protocol ScoreViewProtocol{
     9     func changeScore(value s:Int)
    10 }
    11 
    12 class ScoreView: UIView, ScoreViewProtocol {
    13 
    14     var label:UILabel!
    15     let defaultFrame = CGRectMake(0, 0, 100, 30)
    16     var stype:String!
    17     
    18     var score:Int = 0 {
    19         didSet{
    20             label.text = "(stype):(score)"
    21         }
    22     }
    23     
    24     init(stype: ScoreType){
    25         super.init(frame: defaultFrame)
    26         self.stype = (stype == ScoreType.Common ? "分数":"最高分")
    27         
    28         backgroundColor = UIColor.orangeColor()
    29         label = UILabel(frame: defaultFrame)
    30         label.textAlignment = NSTextAlignment.Center
    31         label.font = UIFont(name: "微软雅黑", size: 16)
    32         label.textColor = UIColor.whiteColor()
    33         
    34         self.addSubview(label)
    35         
    36         //布局约束
    37         //必须将该属性值设置为false,否则自己设置的约束和AutoresizingMask生成的约束有冲突,运行时会产生异常
    38         self.translatesAutoresizingMaskIntoConstraints = false
    39         //宽度约束
    40         self.widthAnchor.constraintEqualToConstant(100).active=true
    41         //高度约束
    42         self.heightAnchor.constraintEqualToConstant(30).active=true
    43     }
    44 
    45     required init?(coder aDecoder: NSCoder) {
    46         super.init(coder: aDecoder)
    47     }
    48     
    49     func changeScore(value s: Int) {
    50         self.score = s
    51     }
    52 
    53 }

    上面的ScoreView首先由一个ScoreType来选择显示“分数”还是“最高分”,然后有一个changeScore的方法,可以改变Score属性值,改变该值得时候同时改变Label显示的数字。

    值得一提的是,在该UIView中,添加了布局约束,方便我们把该UIView添加到页面时控制它的布局。在iOS9.0中,多了一个NSLayoutAnchor类,用它来完成布局约束,比原来低版本用的NSLayoutConstraint要更方便一些。

    2、按钮

     

    游戏页面下方是两个按钮,重置清空本次游戏的数字,生成则产生一个数字,这两个按钮主要用于调试。

    按钮是在一个ViewFactory的工厂类中生产的,同样生产时,添加了一些布局约束。

     1 class func createButton(title:String,action:Selector,sender:UIViewController) -> UIButton{
     2         let button = UIButton(frame: CGRectZero)
     3         button.backgroundColor=UIColor.orangeColor()
     4         button.setTitle(title, forState: .Normal)
     5         button.titleLabel!.textColor=UIColor.whiteColor()
     6         button.titleLabel!.font=UIFont.systemFontOfSize(14)
     7         
     8         //布局约束
     9         button.translatesAutoresizingMaskIntoConstraints = false
    10         button.widthAnchor.constraintEqualToConstant(100).active=true
    11         button.heightAnchor.constraintEqualToConstant(30).active=true
    12         
    13         button.addTarget(sender, action: action, forControlEvents: UIControlEvents.TouchUpInside)
    14         return button
    15     }

    3、游戏区域(游戏地图)

    一个5X5的矩阵,首先在所有位置上放置灰色的方块UIView。

     1 var backgrounds:Array<UIView>! //所有方块的背景
     2    func setupGameMap(){
     3         let margins = self.view.layoutMarginsGuide
     4         
     5         for row in 0..<self.dimension {
     6             for col in 0..<self.dimension {
     7                 //放置灰色的方块在对应的矩阵位置上
     8                 let background = UIView(frame: CGRectMake(0, 0, self.width, self.width))
     9                 background.backgroundColor = UIColor.darkGrayColor()
    10                 background.translatesAutoresizingMaskIntoConstraints = false
    11                 background.widthAnchor.constraintEqualToConstant(self.width).active = true
    12                 background.heightAnchor.constraintEqualToConstant(self.width).active = true
    13                 self.view.addSubview(background)
    14                 self.backgrounds.append(background)
    15                 
    16                 //布局约束
    17                 background.translatesAutoresizingMaskIntoConstraints=false
    18                 background.widthAnchor.constraintEqualToConstant(self.width).active=true
    19                 background.heightAnchor.constraintEqualToConstant(self.width).active=true
    20                 
    21                 //用代码进行布局约束
    22                 var centerXConstant:CGFloat
    23                 var centerYConstant:CGFloat
    24                 if self.dimension%2 == 1 {
    25                     centerXConstant = (self.padding+self.width) * CGFloat(col-self.dimension/2)
    26                     centerYConstant = (self.padding+self.width) * CGFloat(row-self.dimension/2)
    27                 }else{
    28                     centerXConstant = (self.padding+self.width) * CGFloat(Double(col-self.dimension/2)+0.5)
    29                     centerYConstant = (self.padding+self.width) * CGFloat(Double(row-self.dimension/2)+0.5)
    30                 }
    31                 
    32                 background.centerXAnchor.constraintEqualToAnchor(margins.centerXAnchor, constant: centerXConstant).active=true
    33                 background.centerYAnchor.constraintEqualToAnchor(margins.centerYAnchor, constant: centerYConstant).active=true
    34 
    35             }
    36         }
    37     }

    然后,添加数字时,再在相应位置上放置数字方块TileView。

     1 var tiles = [NSIndexPath:TileView]() //存储当前的有数字的方块
     2 
     3 //插入一个数字方块
     4     func insertTile(pos:(Int,Int),value:Int){
     5         let (row,col)=pos
     6         
     7         let x=50 + CGFloat(col) * (self.width+self.padding)
     8         let y=150 + CGFloat(row) * (self.width+self.padding)
     9         
    10         let tile=TileView(pos:CGPointMake(x,y),self.width,value:value)
    11       
    12         self.view.addSubview(tile)
    13         self.view.bringSubviewToFront(tile)
    14         
    15         let index = NSIndexPath(forRow: row, inSection: col)
    16         
    17         tiles[index] = tile
    18         
    19         //布局约束
    20         var centerXConstant:CGFloat
    21         var centerYConstant:CGFloat
    22         if self.dimension%2 == 1 {
    23             centerXConstant = (self.padding+self.width) * CGFloat(col-self.dimension/2)
    24             centerYConstant = (self.padding+self.width) * CGFloat(row-self.dimension/2)
    25         }else{
    26             centerXConstant = (self.padding+self.width) * CGFloat(Double(col-self.dimension/2)+0.5)
    27             centerYConstant = (self.padding+self.width) * CGFloat(Double(row-self.dimension/2)+0.5)
    28         }
    29         let margins=self.view.layoutMarginsGuide
    30         tile.centerXAnchor.constraintEqualToAnchor(margins.centerXAnchor, constant: centerXConstant).active=true
    31         tile.centerYAnchor.constraintEqualToAnchor(margins.centerYAnchor, constant: centerYConstant).active=true
    32     }

    上面用了一个数字tiles来保存插入的TileView,以便清除数字时,可以把对应的TileView从界面中移除。

    1     //移除一个数字方块
    2     func clearTile(row:Int,col:Int){
    3         
    4         let index=NSIndexPath(forRow: row, inSection: col)
    5         let tile=tiles[index]!
    6         tile.removeFromSuperview()
    7         tiles.removeValueForKey(index)
    8         
    9     }

    TileView的实现代码如下:

     1 import Foundation
     2 import UIKit
     3 
     4 
     5 class TileView : UIView {
     6     let colorMap=[
     7         2:UIColor.redColor(),
     8         4:UIColor.orangeColor(),
     9         8:UIColor.lightTextColor(),
    10         16:UIColor.greenColor(),
    11         32:UIColor.brownColor(),
    12         64:UIColor.blackColor(),
    13         128:UIColor.purpleColor(),
    14         256:UIColor.lightGrayColor(),
    15         512:UIColor.cyanColor(),
    16         1024:UIColor.magentaColor(),
    17         2048:UIColor.blackColor()
    18     ]
    19     
    20     var value:Int{
    21         didSet{
    22             backgroundColor=colorMap[value]
    23             numberLabel.text="(value)"
    24         }
    25     }
    26     
    27     var numberLabel:UILabel!
    28     
    29     init(pos:CGPoint,CGFloat,value:Int){
    30         self.value=value
    31         
    32         numberLabel=UILabel(frame: CGRectMake(0, 0, width, width))
    33         numberLabel.textColor=UIColor.whiteColor()
    34         numberLabel.textAlignment=NSTextAlignment.Center
    35         numberLabel.minimumScaleFactor=0.5
    36         numberLabel.font=UIFont(name: "微软雅黑", size: 20)
    37         numberLabel.text="(value)"
    38         
    39         super.init(frame: CGRectMake(pos.x, pos.y,width , width))
    40         addSubview(numberLabel)
    41         backgroundColor=colorMap[value]
    42         
    43         //代码约束
    44         self.translatesAutoresizingMaskIntoConstraints = false
    45         self.widthAnchor.constraintEqualToConstant(width).active=true
    46         self.heightAnchor.constraintEqualToConstant(width).active=true
    47         
    48     }
    49 
    50     required init?(coder aDecoder: NSCoder) {
    51         self.value = 2
    52         super.init(coder: aDecoder)
    53     }
    54     
    55     
    56 }
    View Code

    4、游戏模型

    (1)游戏数据存储

    采用一个自定义的矩阵数据结构,存储游戏数据。

    //自定义矩阵,对应2048的游戏面板
    struct Matrix {
        let rows:Int,columns:Int
        var grid:[Int]
        init(rows:Int,columns:Int){
            self.rows=rows
            self.columns=columns
            grid = Array<Int>(count: rows * columns, repeatedValue: 0)
        }
        
        func indexIsValidForRow(row:Int,column:Int)->Bool{
            return (row >= 0) && (row < rows) && (column >= 0) && (column < columns)
        }
        
        subscript(row:Int,column:Int)->Int{
            get{
                assert(indexIsValidForRow(row, column: column),"超出范围")
                return grid[(row * columns)+column]
            }
            set{
                assert(indexIsValidForRow(row, column: column),"超出范围")
                grid[(row * columns)+column]=newValue
            }
        }
        
    //    相等函数,判断两个Matrix是否相等
        func isEqualTo(matrix:Matrix)->Bool{
            if rows != matrix.rows{
                return false
            }
            if columns != matrix.columns{
                return false
            }
            for i in 0..<rows{
                for j in 0..<columns{
                    if self[i,j] != matrix[i,j]{
                        return false
                    }
                }
            }
            return true
        }
    }

    游戏模型中,包含tiles和维度两个属性,用维度来对tiles这个Matrix进行初始化,同时,提供一些方法。

     1 class GameModel{
     2     var dimension:Int = 0{
     3         didSet{
     4             self.tiles = Matrix(rows: self.dimension, columns: self.dimension)
     5         }
     6     }
     7     var tiles:Matrix    
     8     init(dimension:Int){
     9         self.dimension=dimension
    10         self.tiles = Matrix(rows: self.dimension, columns: self.dimension)
    11     }
    12     
    13     func emptyPosition()->[Int]{
    14         var emptytiles=Array<Int>()
    15         for row in 0..<self.dimension{
    16             for col in 0..<self.dimension{
    17                 let val=tiles[row,col]
    18                 if val==0 {
    19                     emptytiles.append(tiles[row,col])
    20                 }
    21             }
    22             
    23         }
    24         return emptytiles
    25     }
    26     
    27     func isFull()->Bool{
    28         if emptyPosition().count==0 {
    29             return true
    30         }
    31         return false
    32     }
    33 //    打印矩阵,调试时使用
    34     func printTiles(){
    35         for i in 0..<self.dimension{
    36             print(tiles.grid[(i*self.dimension)...((i+1)*self.dimension-1)])
    37         }
    38     }
    39     
    40 // 设置某个位置的值
    41     func setPosition(row:Int,col:Int,value:Int)->Bool{
    42         assert(row>=0 && row<dimension)
    43         assert(col>=0 && col<dimension)
    44         
    45         print("值更新之前:")
    46         printTiles()
    47         
    48         tiles[row,col]=value
    49         
    50         print("值更新之后:tiles[(row),(col)]=(tiles[row,col])")
    51         printTiles()
    52         return true
    53     }
    54 // 清除数据
    55     func clearAll(){
    56         tiles=Matrix(rows: self.dimension, columns: self.dimension)
    57     }
    58     
    59 }
    (2)游戏数据变更

     产生一个数字:

     1     //生成新的数字,生成按钮的响应方法
     2     func genNumber(){
     3 ////        随机产生数字2和4,几率为1:4
     4 //        let randv=Int(arc4random_uniform(5))
     5 //        var seed:Int = 2
     6 //        if randv==1 {
     7 //            seed = 4
     8 //        }
     9 
    10         let col=Int(arc4random_uniform(UInt32(dimension)))
    11         let row=Int(arc4random_uniform(UInt32(dimension)))
    12         
    13         if gameModel.isFull(){
    14             print("位置已经满了")
    15             return
    16         }
    17         
    18         if gameModel.tiles[row, col]>0 {
    19             genNumber()
    20             return
    21         }
    22         
    23         let seed=2 //原书的程序,按照一定的几率比来生成2或4。此处改成一直生成2.
    24         gameModel.setPosition(row, col: col, value: seed)
    25         insertTile((row,col), value: seed)
    26         
    27         // 生成数字后,判断一下游戏是否结束
    28         checkGameOver()
    29     }

    每次生成一个数字后,判断一下游戏是否结束。如果矩阵的所有位置都有数字,并且相邻位置的数字都不相同,则本轮游戏结束,弹出一个提示框,重新开始游戏。

     1     //检查游戏是否结束
     2     func checkGameOver(){
     3         if self.gameModel.isFull(){
     4             for row in 0..<self.dimension{
     5                 for col in 0..<self.dimension{
     6                     let val = gameModel.tiles[row,col]
     7                     let left:Int? = (col-1)>=0 ? gameModel.tiles[row,col-1] : nil
     8                     let right:Int? = (col+1)<self.dimension ? gameModel.tiles[row,col+1] : nil
     9                     let up:Int? = (row-1)>=0 ? gameModel.tiles[row-1,col] : nil
    10                     let down:Int? = (row+1)<self.dimension ? gameModel.tiles[row+1,col] : nil
    11                     if (val==left) || (val==right) || (val==up) || (val==down){
    12                         return
    13                     }
    14                     
    15                 }
    16             }
    17             
    18             let alerController = UIAlertController(title: "游戏结束", message: "本轮游戏结束,重新开始吧!", preferredStyle: UIAlertControllerStyle.Alert)
    19             alerController.addAction(UIAlertAction(title: "Ready Go!", style: UIAlertActionStyle.Default, handler: {
    20                 action in
    21                 self.resetGameMap()
    22             }))
    23             self.presentViewController(alerController, animated: true, completion: nil)
    24             return
    25         }
    26     }

    5、游戏效果实现

     

    首先,是添加手势识别器,相当于对手势处理方法进行注册。

     1     //添加滑动的手势识别处理
     2     func setupSwipeGestures(){
     3         let upSwipe=UISwipeGestureRecognizer(target: self, action: Selector("swipeUp"))
     4         upSwipe.numberOfTouchesRequired=1
     5         upSwipe.direction=UISwipeGestureRecognizerDirection.Up
     6         self.view.addGestureRecognizer(upSwipe)
     7         
     8         let downSwipe=UISwipeGestureRecognizer(target: self, action: Selector("swipeDown"))
     9         downSwipe.numberOfTouchesRequired=1
    10         downSwipe.direction=UISwipeGestureRecognizerDirection.Down
    11         self.view.addGestureRecognizer(downSwipe)
    12         
    13         let leftSwipe=UISwipeGestureRecognizer(target: self, action: Selector("swipeLeft"))
    14         leftSwipe.numberOfTouchesRequired=1
    15         leftSwipe.direction=UISwipeGestureRecognizerDirection.Left
    16         self.view.addGestureRecognizer(leftSwipe)
    17         
    18         let rightSwipe=UISwipeGestureRecognizer(target: self, action: Selector("swipeRight"))
    19         rightSwipe.numberOfTouchesRequired=1
    20         rightSwipe.direction=UISwipeGestureRecognizerDirection.Right
    21         self.view.addGestureRecognizer(rightSwipe)
    22         
    23     }

    然后,就是具体的处理方法,上下左右四个方向的滑动,会引起对应的方向上的数字重排和合并。原书中的处理是先将数字重排,然后进行合并。这里采用自己的逻辑方法,用递归的方式,对于值为0的位置,向后检索不为0的数字放入其中,并考虑是否要和相邻位置进行合并。

     1 var tileVals:Matrix //合并数字时所用的游戏数据复本
     2 
     3     //向上滑动
     4     func swipeUp(){
     5         tileVals = gameModel.tiles
     6         
     7         func merge(row row:Int,col:Int){
     8             if row == self.dimension-1{
     9                 return
    10             }
    11             
    12             for i in (row+1)..<self.dimension {
    13                 let valNew = tileVals[i,col]
    14                 if valNew>0 {
    15                     let val = tileVals[row, col]
    16                     
    17                     if val == 0 {
    18                         tileVals[row, col] = valNew
    19                         tileVals[i, col] = 0
    20                         merge(row: row, col: col)
    21                     }else if val == valNew{
    22                         tileVals[row, col] = valNew<<1
    23                         tileVals[i, col] = 0
    24                         if row==0 {
    25                             merge(row: row, col: col)
    26                         }else{
    27                             merge(row: row-1, col: col)
    28                         }
    29 //                        若产生合并,则加分
    30                         changeScore(valNew)
    31                     }else{
    32                         merge(row: row+1, col: col)
    33                     }
    34                     
    35                     break
    36                 }
    37             }
    38         }
    39         
    40         for col in 0..<self.dimension{
    41             merge(row: 0, col: col)
    42         }
    43         
    44 //        如果合并后的结果与原来相同,则不做任何操作
    45         if tileVals.isEqualTo(gameModel.tiles){
    46             return
    47         }
    48 //        显示合并后的结果,并产生新的数字
    49         refresh()
    50         genNumber()
    51     }

    如果过程中产生合并,则进行加分。

    1     //加分
    2     func changeScore(baseNum:Int){
    3         self.scoreView.score += baseNum * 2
    4         
    5     }

    处理时,用了一个临时的Matrix变量tileVals来作处理,以便与未处理的数据进行比较,决定是否要进行后续的操作。如果处理后的数据与处理前不一致,则需要刷新页面中的游戏数据。

     1     //刷新页面
     2     func refresh(){
     3         for i in 0..<self.dimension{
     4             for j in 0..<self.dimension{
     5                 let val = gameModel.tiles[i,j]
     6                 let valNew = tileVals[i,j]
     7                 if valNew != val{
     8                     gameModel.setPosition(i, col: j, value: valNew)
     9                     if valNew>0{
    10                         if val>0{
    11                             clearTile(i, col: j)
    12                         }
    13                         insertTile((i,j) , value: valNew)
    14                         
    15                     }else{
    16                         clearTile(i, col: j)
    17                     }
    18                 }
    19                 
    20             }
    21         }
    22     }

    其他三个方向上的处理方法类似:

      1     //向下滑动
      2     
      3     func swipeDown(){
      4         tileVals = gameModel.tiles
      5         
      6         func merge(row row:Int,col:Int){
      7             if row == 0{
      8                 return
      9             }
     10             
     11             for var i=row-1;i>=0;i-- {
     12                 let valNew=tileVals[i,col]
     13                 if valNew>0 {
     14                     let val=tileVals[row,col]
     15                     
     16                     if val == 0 {
     17                         tileVals[row, col] = valNew
     18                         tileVals[i, col] = 0
     19                         merge(row: row, col: col)
     20                     }else if val == valNew{
     21                         tileVals[row, col] = valNew<<1
     22                         tileVals[i, col] = 0
     23 
     24                         if row==self.dimension-1 {
     25                             merge(row: row, col: col)
     26                         }else{
     27                             merge(row: row+1, col: col)
     28                         }
     29 //                        若产生合并,则加分
     30                         changeScore(valNew)
     31                     }else{
     32                         merge(row: row-1, col: col)
     33                     }
     34                     
     35                     break
     36                 }
     37             }
     38         }
     39         
     40         for col in 0..<self.dimension{
     41             merge(row: self.dimension-1, col: col)
     42         }
     43 //        如果合并后的结果与原来相同,则不做任何操作
     44         if tileVals.isEqualTo(gameModel.tiles){
     45             return
     46         }
     47         
     48         refresh()
     49         genNumber()
     50     }
     51     
     52     //向左滑动
     53     func swipeLeft(){
     54         tileVals = gameModel.tiles
     55         func merge(row row:Int,col:Int){
     56             if col == self.dimension-1{
     57                 return
     58             }
     59             
     60             for i in (col+1)..<self.dimension {
     61                 let valNew=tileVals[row,i]
     62                 if valNew>0 {
     63                     let val=tileVals[row,col]
     64                     
     65                     if val == 0 {
     66                         tileVals[row, col] = valNew
     67                         tileVals[row, i] = 0
     68                         merge(row: row, col: col)
     69                     }else if val == valNew{
     70                         tileVals[row, col] = valNew<<1
     71                         tileVals[row, i] = 0
     72                         if col==0 {
     73                             merge(row: row, col: col)
     74                         }else{
     75                             merge(row: row, col: col-1)
     76                         }
     77 //                        若产生合并,则加分
     78                         changeScore(valNew)
     79                     }else{
     80                         merge(row: row, col: col+1)
     81                     }
     82                     
     83                     break
     84                 }
     85             }
     86         }
     87         
     88         for row in 0..<self.dimension{
     89             merge(row: row, col: 0)
     90         }
     91         
     92 //        如果合并后的结果与原来相同,则不做任何操作
     93         if tileVals.isEqualTo(gameModel.tiles){
     94             return
     95         }
     96         refresh()
     97         genNumber()
     98     }
     99     
    100     //向右滑动
    101     func swipeRight(){
    102         tileVals = gameModel.tiles
    103         func merge(row row:Int,col:Int){
    104             if col == 0{
    105                 return
    106             }
    107             
    108             for var i=col-1;i>=0;i-- {
    109                 let valNew=tileVals[row, i]
    110                 if valNew>0 {
    111                     let val=tileVals[row, col]
    112                     
    113                     if val == 0 {
    114                         tileVals[row, col] = valNew
    115                         tileVals[row, i] = 0
    116                         merge(row: row, col: col)
    117                     }else if val == valNew{
    118                         tileVals[row, col] = valNew<<1
    119                         tileVals[row, i] = 0
    120                         if col==self.dimension-1 {
    121                             merge(row: row, col: col)
    122                         }else{
    123                             merge(row: row, col: col+1)
    124                         }
    125 //                        若产生合并,则加分
    126                         changeScore(valNew)
    127                     }else{
    128                         merge(row: row, col: col-1)
    129                     }
    130                     
    131                     break
    132                 }
    133             }
    134         }
    135         
    136         for row in 0..<self.dimension{
    137             merge(row: row, col: self.dimension-1)
    138         }
    139         
    140 //        如果合并后的结果与原来相同,则不做任何操作
    141         if tileVals.isEqualTo(gameModel.tiles){
    142             return
    143         }
    144         refresh()
    145         genNumber()
    146         
    147     }
    View Code

    (三)设置页面

    设置页面可以设置游戏矩阵的维度。上面的阈值,游戏中数字超过该阈值可以视作通关,不过这个功能并没有实现,因为到后面对这个功能无甚兴趣~(≧▽≦)/~。

    上面的几个控件都是在ViewFactory生产的,大多是一些常用控件,UISegmentedControl我倒是第一次用到,功能上有点像RadioButton,只是外观不一样。

    ViewFactory的代码如下:

     1 import UIKit
     2 
     3 class ViewFactory {
     4     class func getDefaultFrame() -> CGRect {
     5         let defaultFrame = CGRectMake(0, 0, 100, 30)
     6         return defaultFrame
     7     }
     8     
     9     class func createControl(type:String, title:[String],action:Selector,sender:AnyObject)->UIView{
    10         switch(type){
    11             case "label": return ViewFactory.createLabel(title[0])
    12             case "button": return ViewFactory.createButton(title[0], action: action, sender: sender as! UIViewController)
    13             case "text":return ViewFactory.createTextField(title[0], action: action, sender: sender as! UITextFieldDelegate)
    14             case "segment":return ViewFactory.createSegment(title, action: action, sender: sender as! UIViewController)
    15         default: return ViewFactory.createLabel(title[0])
    16         }
    17     }
    18     
    19     class func createLabel(title:String) -> UILabel{
    20         let label = UILabel()
    21         label.textColor = UIColor.blackColor()
    22         label.backgroundColor = UIColor.whiteColor()
    23         label.text = title
    24         label.frame = CGRectZero
    25         label.font = UIFont(name: "HelveticaNeue-Bold", size: 16)
    26         label.translatesAutoresizingMaskIntoConstraints = false
    27         
    28         return label
    29     }
    30     
    31     class func createButton(title:String,action:Selector,sender:UIViewController) -> UIButton{
    32         let button = UIButton(frame: CGRectZero)
    33         button.backgroundColor=UIColor.orangeColor()
    34         button.setTitle(title, forState: .Normal)
    35         button.titleLabel!.textColor=UIColor.whiteColor()
    36         button.titleLabel!.font=UIFont.systemFontOfSize(14)
    37         
    38         //布局约束
    39         button.translatesAutoresizingMaskIntoConstraints = false
    40         button.widthAnchor.constraintEqualToConstant(100).active=true
    41         button.heightAnchor.constraintEqualToConstant(30).active=true
    42         
    43         button.addTarget(sender, action: action, forControlEvents: UIControlEvents.TouchUpInside)
    44         return button
    45     }
    46     
    47     class func createTextField(value:String,action:Selector,sender:UITextFieldDelegate) -> UITextField{
    48         let textField=UITextField(frame: ViewFactory.getDefaultFrame())
    49         textField.backgroundColor=UIColor.clearColor()
    50         textField.textColor=UIColor.blackColor()
    51         textField.text=value
    52         textField.borderStyle=UITextBorderStyle.RoundedRect
    53         textField.adjustsFontSizeToFitWidth=true
    54         textField.delegate=sender
    55         
    56         textField.translatesAutoresizingMaskIntoConstraints = false
    57         return textField
    58     }
    59     
    60     class func createSegment(items:[String],action:Selector,sender:UIViewController)->UISegmentedControl {
    61         let segment=UISegmentedControl(items: items)
    62         segment.frame=ViewFactory.getDefaultFrame()
    63         segment.momentary=false
    64         segment.addTarget(sender, action: action, forControlEvents: UIControlEvents.ValueChanged)
    65         
    66         segment.translatesAutoresizingMaskIntoConstraints = false
    67         return segment
    68         
    69     }
    70 }
    View Code

    SettingViewController的实现:

     1 import UIKit
     2 
     3 class SettingViewController: UIViewController,UITextFieldDelegate {
     4     init(){
     5         super.init(nibName: nil, bundle: nil)
     6     }
     7 
     8     required init?(coder aDecoder: NSCoder) {
     9         super.init(coder: aDecoder)
    10     }
    11     
    12     var main:MainViewController?
    13     override func viewDidLoad() {
    14         super.viewDidLoad()
    15         self.view.backgroundColor=UIColor.whiteColor()
    16         
    17         let views = (self.parentViewController?.parentViewController as! MainTabViewController).viewControllers
    18         main = ((views?[0] as? UINavigationController)?.childViewControllers)?[0] as? MainViewController
    19         setupControls()
    20         // Do any additional setup after loading the view.
    21     }
    22 
    23     override func didReceiveMemoryWarning() {
    24         super.didReceiveMemoryWarning()
    25         // Dispose of any resources that can be recreated.
    26     }
    27     
    28     var segDimension:UISegmentedControl?
    29     func setupControls(){
    30         let segVals = [3,4,5]
    31         let textNum = ViewFactory.createTextField("", action: Selector("numChanged:"), sender: self)
    32         textNum.returnKeyType=UIReturnKeyType.Done
    33         
    34         self.view.addSubview(textNum)
    35         
    36         let labelNum = ViewFactory.createLabel("阈值:")
    37         self.view.addSubview(labelNum)
    38         
    39         segDimension = ViewFactory.createSegment(["3X3","4X4","5X5"], action: "dimensionChanged:", sender: self)
    40         self.view.addSubview(segDimension!)
    41         segDimension!.selectedSegmentIndex=segVals.indexOf((main?.dimension)!)!
    42         
    43         let labelDm=ViewFactory.createLabel("维度:")
    44         self.view.addSubview(labelDm)
    45         
    46         //布局约束
    47         let margins=self.view.layoutMarginsGuide
    48         labelNum.widthAnchor.constraintEqualToConstant(60).active=true
    49         labelNum.heightAnchor.constraintEqualToConstant(30).active=true
    50         labelNum.leadingAnchor.constraintEqualToAnchor(margins.leadingAnchor, constant: 20).active=true
    51         labelNum.topAnchor.constraintEqualToAnchor(margins.topAnchor, constant: 100).active=true
    52         
    53         textNum.widthAnchor.constraintEqualToConstant(200).active=true
    54         textNum.heightAnchor.constraintEqualToConstant(30).active=true
    55         textNum.leadingAnchor.constraintEqualToAnchor(labelNum.trailingAnchor).active=true
    56         textNum.topAnchor.constraintEqualToAnchor(labelNum.topAnchor).active=true
    57         
    58         labelDm.widthAnchor.constraintEqualToConstant(60).active=true
    59         labelDm.heightAnchor.constraintEqualToConstant(30).active=true
    60         labelDm.leadingAnchor.constraintEqualToAnchor(labelNum.leadingAnchor).active=true
    61         labelDm.topAnchor.constraintEqualToAnchor(textNum.topAnchor, constant: 100).active=true
    62         
    63         segDimension?.widthAnchor.constraintEqualToConstant(200).active=true
    64         segDimension?.heightAnchor.constraintEqualToConstant(30).active=true
    65         segDimension?.leadingAnchor.constraintEqualToAnchor(labelDm.trailingAnchor).active=true
    66         segDimension?.topAnchor.constraintEqualToAnchor(labelDm.topAnchor).active=true
    67         
    68         
    69     }
    70     
    71     //
    72     func numChanged(textField:UITextField)->Bool{
    73         textField.resignFirstResponder()
    74         //改变游戏页面
    75         if (textField.text != "(main?.maxnumber)"){
    76             let num = Int(textField.text!)
    77             main?.maxnumber = num!
    78             
    79         }
    80         //存储到本地数据库
    81         UserModel.sharedInstance().saveMaxnum((main?.maxnumber)!)
    82         return true
    83     }
    84     
    85     func dimensionChanged(sender:SettingViewController){
    86         let segVals = [3,4,5]
    87         //改变游戏页面
    88         main?.dimension = segVals[segDimension!.selectedSegmentIndex]
    89         main?.resetGameMap()
    90         //存储到本地数据库
    91         UserModel.sharedInstance().saveDimesion((main?.dimension)!)
    92     }
    93 
    94 }

    设置页面不仅要对游戏页面的刷新,同时也对本地数据库的游戏参数进行变更。

    1、游戏界面刷新

     1     //游戏面板重绘
     2     func resetGameMap(){
     3         UserModel.sharedInstance().saveBestScore(self.scoreView.score)
     4         for view in self.view.subviews{
     5             view.removeFromSuperview()
     6         }
     7         backgrounds.removeAll()
     8         tiles.removeAll()
     9         gameModel.clearAll()
    10         gameModel.dimension=self.dimension
    11         setupScoreLables()
    12         setupGameMap()
    13         setupButton()
    14         genNumber()
    15         
    16     }

    刷新界面时,先保存当前分数,然后再对界面进行重绘。

    2、游戏参数保存

    原书使用SQLite作为本地数据库,笔者个人觉得SQLite的使用挺麻烦的(偶是个没有用过SQLite的菜鸟O(∩_∩)O~),无意间找到了一个相对使用更为方便的数据库Realm。

    官网地址:https://realm.io,在首页上就可以下载Objective-C、Swift或Android等三个版本的Realm。首页下方有Realm的介绍,标题就是这么一句:

    Realm is a replacement for SQLite & Core Data

    Realm的中文资料不是很多,但因为其使用方便,现有资料也已经足够。这里有一篇关于Realm的教程:Realm数据库基础教程,具体配置可以参考,不过由于版本差异可能会有些不一样的地方。

    笔者下载的版本是realm-swift-0.95.2,下载后自动解压。

    (1)在iOS/swift-2.0中找到Realm.framework和RealmSwift.framework,把它们拖到项目中,一定要确保勾选了 Copy Items if needed 选项。

    (2)将链接库配置如下:

    下面是基于Realm的数据模型。UserMode使用单例模式保证数据库的唯一性,并提供方法来对Realm数据库中的user的属性值进行改写。

      1 import Foundation
      2 import UIKit
      3 import RealmSwift
      4 import Realm
      5 
      6 // 继承Object的用户数据模型
      7 // 属性包括用户id、游戏维度、通过数值、背景颜色参数、不同维度对应的最高分数
      8 class UserObject:Object{
      9     // 所有属性必须明确数据类型并且初始化
     10     dynamic var userid:String = ""
     11     dynamic var dimension:Int = 0
     12     dynamic var maxnum:Int = 0
     13     
     14     dynamic var red:CGFloat = 0.0
     15     dynamic var green:CGFloat = 0.0
     16     dynamic var blue:CGFloat = 0.0
     17     dynamic var alpha:CGFloat = 0.0
     18     
     19     dynamic var bestScoreForD3:Int = 0
     20     dynamic var bestScoreForD4:Int = 0
     21     dynamic var bestScoreForD5:Int = 0
     22 
     23     // 自定义的初始化方法
     24     init(userid:String,dimension:Int,maxnum:Int,backgroundColor:UIColor){
     25         
     26         let cicolor = CIColor(color:backgroundColor)
     27         red = cicolor.red
     28         green = cicolor.green
     29         blue = cicolor.blue
     30         alpha = cicolor.alpha
     31         
     32         self.dimension = dimension
     33         self.maxnum = maxnum
     34         self.userid = userid
     35         
     36         super.init()
     37     }
     38 
     39     required init() {
     40         super.init()
     41     }
     42     
     43     override init(realm: RLMRealm, schema: RLMObjectSchema) {
     44         super.init(realm: realm, schema: schema)
     45     }
     46     
     47     // 将userid作为primaryKey,这个功能不是必要的,因为userid都是同一个。
     48     // 只是看了Realm的文档后,试着添加进来而已
     49     override static func primaryKey() -> String?{
     50         return "userid"
     51     }
     52 }
     53 
     54                  
     55 class UserModel
     56 {
     57     // 产生userid
     58     class func get_uuid()->String{
     59         let userid = NSUserDefaults.standardUserDefaults().stringForKey("swift2048user")
     60         if userid == nil {
     61             let uuid_ref=CFUUIDCreate(nil)
     62             let uuid_string_ref=CFUUIDCreateString(nil, uuid_ref)
     63             let uuid:String = uuid_string_ref as String
     64             NSUserDefaults.standardUserDefaults().setObject(uuid, forKey: "swift2048user")
     65             return uuid
     66         }
     67         return userid!
     68     }
     69     
     70     var realm:Realm!
     71     let userid:String
     72     var user:UserObject?
     73     required init(){
     74         //------ Realm 配置 --------------
     75         // 这里涉及到Realm的Migrations。
     76         // 因为我对UserObject的架构进行更改过,必须配置,才能使数据库中的旧的UserObject更新。
     77         var config = Realm.Configuration()
     78         
     79         // schemaVersion,这可以看做UserObject的版本
     80         // 原始值为0,这是第二次更改,所以要赋值为2
     81         config.schemaVersion = 2
     82         config.migrationBlock = {
     83             migration,oldSchemaVersion in
     84             if oldSchemaVersion < 2{
     85                 
     86             }
     87         }
     88         Realm.Configuration.defaultConfiguration = config
     89         //------ Realm 配置 Over --------------
     90         
     91         
     92         realm = try! Realm()
     93         userid = UserModel.get_uuid()
     94         let objects = realm.objects(UserObject).filter("userid == '(userid)'") //检索对象
     95         //如果用户不存在
     96         if objects.count == 0 {
     97             realm.beginWrite()
     98             //根据默认的数据创建游戏
     99             user = UserObject(userid: userid, dimension: 3, maxnum: 0, backgroundColor: UIColor.darkGrayColor())
    100             realm.add(user!)
    101             realm.commitWrite()
    102         }else{
    103             user = objects.first
    104         }
    105     }
    106     
    107 //    单例模式
    108     struct Static {
    109         static var instance:UserModel? = nil
    110         static var token: dispatch_once_t = 0
    111     }
    112     class func sharedInstance()->UserModel {
    113         dispatch_once(&Static.token){
    114             print("UserModel - Dispatch once")
    115             Static.instance = self.init()
    116         }
    117         return Static.instance!
    118     }
    119     
    120 //    返回属性值,调试可用
    121     func getUserdata()->[String:String]{
    122         let dic:[String:String] = ["maxnum":"(self.user?.maxnum)",
    123             "dimension":"(self.user?.dimension)",
    124             "red":"(self.user?.red)",
    125             "green":"(self.user?.green)",
    126             "blue":"(self.user?.blue)",
    127             "alpha":"(self.user?.alpha)"]
    128         return dic
    129         
    130     }
    131     
    132 //    更改属性值
    133     func saveDimesion(dimension:Int){
    134         realm.write({
    135             self.user?.dimension = dimension
    136         })
    137         
    138     }
    139     
    140     func saveMaxnum(maxnum:Int){
    141         realm.write({
    142             self.user?.maxnum = maxnum
    143         })
    144     }
    145     
    146     func saveColor(backgroundColor:UIColor){
    147         realm.write({
    148             let cicolor = CIColor(color: backgroundColor)
    149             self.user?.red = cicolor.red
    150             self.user?.green = cicolor.green
    151             self.user?.blue = cicolor.alpha
    152         })
    153     }
    154     
    155     func saveBestScore(score:Int){
    156         realm.write({
    157             if (self.user?.dimension==3) && (score>self.user?.bestScoreForD3){
    158                 self.user?.bestScoreForD3 = score
    159                 return
    160             }
    161             
    162             if (self.user?.dimension==4) && (score>self.user?.bestScoreForD4){
    163                 self.user?.bestScoreForD4 = score
    164                 return
    165             }
    166             
    167             if (self.user?.dimension==5) && (score>self.user?.bestScoreForD5){
    168                 self.user?.bestScoreForD5 = score
    169                 return
    170             }
    171         })
    172     }
    173 }

     至此,整个项目的完成过程介绍完毕。

    关于游戏成品:功能不算丰富,细节不算完美,个别地方只是在原书的基础上做了更改,如有兴趣,可以自行完善。

    最后,附上MainViewController的完整代码。

      1 import UIKit
      2 import RealmSwift
      3 
      4 class MainViewController: UIViewController {
      5 
      6   
      7     var dimension:Int = 4  //矩阵的维度,即游戏面板的大小
      8     var maxnumber:Int = 2048  //游戏通关阈值,该功能没有实现
      9     var gameModel:GameModel  //游戏数据模型
     10     
     11     var CGFloat = 50  //方块的宽度,用于布局
     12     var padding:CGFloat = 6 //方块之间的间隔宽度,用于布局
     13     
     14     var backgrounds:Array<UIView>! //所有方块的背景
     15     
     16     var tiles = [NSIndexPath:TileView]() //存储当前的有数字的方块
     17     var tileVals:Matrix //合并数字时所用的游戏数据复本
     18     
     19     
     20     init(){
     21         self.dimension = (UserModel.sharedInstance().user?.dimension)!  //从数据库中取出维度
     22         self.backgrounds = Array<UIView> ()
     23         self.gameModel = GameModel(dimension: self.dimension)
     24         self.tileVals = Matrix(rows: self.dimension, columns: self.dimension)
     25         
     26         print("dimension:(self.dimension)")
     27         super.init(nibName: nil, bundle: nil)
     28     }
     29 
     30     required init?(coder aDecoder: NSCoder) {
     31         self.gameModel = GameModel(dimension: dimension)
     32         self.tileVals = Matrix(rows: dimension, columns: dimension)
     33         super.init(coder: aDecoder)
     34     }
     35     
     36     
     37     override func viewDidLoad() {
     38         super.viewDidLoad()
     39         
     40         // Do any additional setup after loading the view.
     41         self.view.backgroundColor = UIColor.whiteColor()
     42         setupScoreLables()
     43         setupGameMap()
     44         setupButton()
     45         setupSwipeGestures()
     46         
     47         genNumber()
     48         
     49     }
     50     
     51     override func didReceiveMemoryWarning() {
     52         super.didReceiveMemoryWarning()
     53         // Dispose of any resources that can be recreated.
     54     }
     55     
     56 //---------------------------初始化游戏页面-------------------------
     57     func setupGameMap(){
     58         let margins = self.view.layoutMarginsGuide
     59         
     60         for row in 0..<self.dimension {
     61             for col in 0..<self.dimension {
     62                 //放置灰色的方块在对应的矩阵位置上
     63                 let background = UIView(frame: CGRectMake(0, 0, self.width, self.width))
     64                 background.backgroundColor = UIColor.darkGrayColor()
     65                 background.translatesAutoresizingMaskIntoConstraints = false
     66                 background.widthAnchor.constraintEqualToConstant(self.width).active = true
     67                 background.heightAnchor.constraintEqualToConstant(self.width).active = true
     68                 self.view.addSubview(background)
     69                 self.backgrounds.append(background)
     70                 
     71                 //布局约束
     72                 background.translatesAutoresizingMaskIntoConstraints=false
     73                 background.widthAnchor.constraintEqualToConstant(self.width).active=true
     74                 background.heightAnchor.constraintEqualToConstant(self.width).active=true
     75                 
     76                 //用代码进行布局约束
     77                 var centerXConstant:CGFloat
     78                 var centerYConstant:CGFloat
     79                 if self.dimension%2 == 1 {
     80                     centerXConstant = (self.padding+self.width) * CGFloat(col-self.dimension/2)
     81                     centerYConstant = (self.padding+self.width) * CGFloat(row-self.dimension/2)
     82                 }else{
     83                     centerXConstant = (self.padding+self.width) * CGFloat(Double(col-self.dimension/2)+0.5)
     84                     centerYConstant = (self.padding+self.width) * CGFloat(Double(row-self.dimension/2)+0.5)
     85                 }
     86                 
     87                 background.centerXAnchor.constraintEqualToAnchor(margins.centerXAnchor, constant: centerXConstant).active=true
     88                 background.centerYAnchor.constraintEqualToAnchor(margins.centerYAnchor, constant: centerYConstant).active=true
     89 
     90             }
     91         }
     92     }
     93     
     94     let scoreView = ScoreView(stype:ScoreType.Common)
     95     let bestScoreView = ScoreView(stype: ScoreType.Best)
     96     func setupScoreLables(){
     97         
     98         scoreView.changeScore(value: 0)
     99         self.view.addSubview(scoreView)
    100         
    101         let val=(self.dimension==3) ? (UserModel.sharedInstance().user?.bestScoreForD3)! : ((self.dimension==4) ? (UserModel.sharedInstance().user?.bestScoreForD4)! : (UserModel.sharedInstance().user?.bestScoreForD5)!)
    102         bestScoreView.changeScore(value: val)
    103         self.view.addSubview(bestScoreView)
    104         
    105         //布局约束
    106         let margins = self.view.layoutMarginsGuide
    107         scoreView.trailingAnchor.constraintEqualToAnchor(margins.centerXAnchor, constant: -30).active=true
    108         scoreView.topAnchor.constraintEqualToAnchor(margins.topAnchor, constant: 100).active=true
    109         //
    110         bestScoreView.leadingAnchor.constraintEqualToAnchor(margins.centerXAnchor, constant: 30).active=true
    111         bestScoreView.topAnchor.constraintEqualToAnchor(scoreView.topAnchor).active=true
    112         
    113     }
    114     
    115     func setupButton(){
    116         let clrBtn=ViewFactory.createButton("重置", action: Selector("clearNumber"), sender: self)
    117         self.view.addSubview(clrBtn)
    118         
    119         let genBtn=ViewFactory.createButton("生成", action: Selector("genNumber"), sender: self)
    120         self.view.addSubview(genBtn)
    121         
    122         
    123         //布局约束
    124         let margins = self.view.layoutMarginsGuide
    125         //
    126         clrBtn.trailingAnchor.constraintEqualToAnchor(margins.centerXAnchor, constant: -30).active = true
    127         clrBtn.bottomAnchor.constraintEqualToAnchor(margins.bottomAnchor, constant:-100).active=true
    128         //
    129         genBtn.leadingAnchor.constraintEqualToAnchor(margins.centerXAnchor, constant: 30).active = true
    130         genBtn.bottomAnchor.constraintEqualToAnchor(margins.bottomAnchor, constant:-100).active=true
    131         //
    132     }
    133     
    134 //---------------------------游戏逻辑实现-------------------------
    135     
    136     //游戏面板重绘
    137     func resetGameMap(){
    138         UserModel.sharedInstance().saveBestScore(self.scoreView.score)
    139         for view in self.view.subviews{
    140             view.removeFromSuperview()
    141         }
    142         backgrounds.removeAll()
    143         tiles.removeAll()
    144         gameModel.clearAll()
    145         gameModel.dimension=self.dimension
    146         setupScoreLables()
    147         setupGameMap()
    148         setupButton()
    149         genNumber()
    150         
    151     }
    152     
    153     // 清除所有数字,重置按钮的响应方法
    154     func clearNumber(){
    155         gameModel.clearAll()
    156         for (_,tile) in tiles{
    157             tile.removeFromSuperview()
    158         }
    159         tiles.removeAll()
    160         
    161     }
    162     
    163     //生成新的数字,生成按钮的响应方法
    164     func genNumber(){
    165 ////        随机产生数字2和4,几率为1:4
    166 //        let randv=Int(arc4random_uniform(5))
    167 //        var seed:Int = 2
    168 //        if randv==1 {
    169 //            seed = 4
    170 //        }
    171 
    172         let col=Int(arc4random_uniform(UInt32(dimension)))
    173         let row=Int(arc4random_uniform(UInt32(dimension)))
    174         
    175         if gameModel.isFull(){
    176             print("位置已经满了")
    177             return
    178         }
    179         
    180         if gameModel.tiles[row, col]>0 {
    181             genNumber()
    182             return
    183         }
    184         
    185         let seed=2 //原书的程序,按照一定的几率比来生成2或4。此处改成一直生成2.
    186         gameModel.setPosition(row, col: col, value: seed)
    187         insertTile((row,col), value: seed)
    188         
    189         // 生成数字后,判断一下游戏是否结束
    190         checkGameOver()
    191     }
    192     
    193     //插入一个数字方块
    194     func insertTile(pos:(Int,Int),value:Int){
    195         let (row,col)=pos
    196         
    197         let x=50 + CGFloat(col) * (self.width+self.padding)
    198         let y=150 + CGFloat(row) * (self.width+self.padding)
    199         
    200         let tile=TileView(pos:CGPointMake(x,y),self.width,value:value)
    201       
    202         self.view.addSubview(tile)
    203         self.view.bringSubviewToFront(tile)
    204         
    205         let index = NSIndexPath(forRow: row, inSection: col)
    206         
    207         tiles[index] = tile
    208         
    209         //布局约束
    210         var centerXConstant:CGFloat
    211         var centerYConstant:CGFloat
    212         if self.dimension%2 == 1 {
    213             centerXConstant = (self.padding+self.width) * CGFloat(col-self.dimension/2)
    214             centerYConstant = (self.padding+self.width) * CGFloat(row-self.dimension/2)
    215         }else{
    216             centerXConstant = (self.padding+self.width) * CGFloat(Double(col-self.dimension/2)+0.5)
    217             centerYConstant = (self.padding+self.width) * CGFloat(Double(row-self.dimension/2)+0.5)
    218         }
    219         let margins=self.view.layoutMarginsGuide
    220         tile.centerXAnchor.constraintEqualToAnchor(margins.centerXAnchor, constant: centerXConstant).active=true
    221         tile.centerYAnchor.constraintEqualToAnchor(margins.centerYAnchor, constant: centerYConstant).active=true
    222         //原书中的动画效果
    223 //        tile.layer.setAffineTransform(CGAffineTransformMakeScale(0.1, 0.1))
    224         
    225 //        UIView.animateWithDuration(0.5, delay: 0.1, options: UIViewAnimationOptions.TransitionNone, animations: { () -> Void in
    226 //            tile.layer.setAffineTransform(CGAffineTransformMakeRotation(90))
    227 //            }, completion: { (Bool) -> Void in
    228 //                UIView.animateWithDuration(0.5, animations: { () -> Void in
    229 //                    tile.layer.setAffineTransform(CGAffineTransformIdentity)
    230 //                })
    231 //        })
    232         
    233 //        tile.alpha=0
    234 //        UIView.animateWithDuration(0.5, delay: 0.01, options: UIViewAnimationOptions.CurveEaseInOut, animations: { () -> Void in
    235 //            }) { (Bool) -> Void in
    236 //                UIView.animateWithDuration(1, animations: { () -> Void in
    237 //                    tile.alpha = 1
    238 //                })
    239 //        }
    240         
    241 //        UIView.beginAnimations("animation", context: nil)
    242 //        UIView.setAnimationDuration(2)
    243 //        UIView.setAnimationCurve(UIViewAnimationCurve.EaseInOut)
    244 //        UIView.setAnimationTransition(UIViewAnimationTransition.FlipFromLeft, forView: self.view, cache: false)
    245 //        UIView.commitAnimations()
    246     }
    247     
    248     //移除一个数字方块
    249     func clearTile(row:Int,col:Int){
    250         
    251         let index=NSIndexPath(forRow: row, inSection: col)
    252         let tile=tiles[index]!
    253         tile.removeFromSuperview()
    254         tiles.removeValueForKey(index)
    255         
    256     }
    257     
    258     //添加滑动的手势识别处理
    259     func setupSwipeGestures(){
    260         let upSwipe=UISwipeGestureRecognizer(target: self, action: Selector("swipeUp"))
    261         upSwipe.numberOfTouchesRequired=1
    262         upSwipe.direction=UISwipeGestureRecognizerDirection.Up
    263         self.view.addGestureRecognizer(upSwipe)
    264         
    265         let downSwipe=UISwipeGestureRecognizer(target: self, action: Selector("swipeDown"))
    266         downSwipe.numberOfTouchesRequired=1
    267         downSwipe.direction=UISwipeGestureRecognizerDirection.Down
    268         self.view.addGestureRecognizer(downSwipe)
    269         
    270         let leftSwipe=UISwipeGestureRecognizer(target: self, action: Selector("swipeLeft"))
    271         leftSwipe.numberOfTouchesRequired=1
    272         leftSwipe.direction=UISwipeGestureRecognizerDirection.Left
    273         self.view.addGestureRecognizer(leftSwipe)
    274         
    275         let rightSwipe=UISwipeGestureRecognizer(target: self, action: Selector("swipeRight"))
    276         rightSwipe.numberOfTouchesRequired=1
    277         rightSwipe.direction=UISwipeGestureRecognizerDirection.Right
    278         self.view.addGestureRecognizer(rightSwipe)
    279         
    280     }
    281     
    282     //向上滑动
    283     func swipeUp(){
    284         tileVals = gameModel.tiles
    285         
    286         func merge(row row:Int,col:Int){
    287             if row == self.dimension-1{
    288                 return
    289             }
    290             
    291             for i in (row+1)..<self.dimension {
    292                 let valNew = tileVals[i,col]
    293                 if valNew>0 {
    294                     let val = tileVals[row, col]
    295                     
    296                     if val == 0 {
    297                         tileVals[row, col] = valNew
    298                         tileVals[i, col] = 0
    299                         merge(row: row, col: col)
    300                     }else if val == valNew{
    301                         tileVals[row, col] = valNew<<1
    302                         tileVals[i, col] = 0
    303                         if row==0 {
    304                             merge(row: row, col: col)
    305                         }else{
    306                             merge(row: row-1, col: col)
    307                         }
    308 //                        若产生合并,则加分
    309                         changeScore(valNew)
    310                     }else{
    311                         merge(row: row+1, col: col)
    312                     }
    313                     
    314                     break
    315                 }
    316             }
    317         }
    318         
    319         for col in 0..<self.dimension{
    320             merge(row: 0, col: col)
    321         }
    322         
    323 //        如果合并后的结果与原来相同,则不做任何操作
    324         if tileVals.isEqualTo(gameModel.tiles){
    325             return
    326         }
    327 //        显示合并后的结果,并产生新的数字
    328         refresh()
    329         genNumber()
    330     }
    331     
    332     //向下滑动
    333     
    334     func swipeDown(){
    335         tileVals = gameModel.tiles
    336         
    337         func merge(row row:Int,col:Int){
    338             if row == 0{
    339                 return
    340             }
    341             
    342             for var i=row-1;i>=0;i-- {
    343                 let valNew=tileVals[i,col]
    344                 if valNew>0 {
    345                     let val=tileVals[row,col]
    346                     
    347                     if val == 0 {
    348                         tileVals[row, col] = valNew
    349                         tileVals[i, col] = 0
    350                         merge(row: row, col: col)
    351                     }else if val == valNew{
    352                         tileVals[row, col] = valNew<<1
    353                         tileVals[i, col] = 0
    354 
    355                         if row==self.dimension-1 {
    356                             merge(row: row, col: col)
    357                         }else{
    358                             merge(row: row+1, col: col)
    359                         }
    360 //                        若产生合并,则加分
    361                         changeScore(valNew)
    362                     }else{
    363                         merge(row: row-1, col: col)
    364                     }
    365                     
    366                     break
    367                 }
    368             }
    369         }
    370         
    371         for col in 0..<self.dimension{
    372             merge(row: self.dimension-1, col: col)
    373         }
    374 //        如果合并后的结果与原来相同,则不做任何操作
    375         if tileVals.isEqualTo(gameModel.tiles){
    376             return
    377         }
    378         
    379         refresh()
    380         genNumber()
    381     }
    382     
    383     //向左滑动
    384     func swipeLeft(){
    385         tileVals = gameModel.tiles
    386         func merge(row row:Int,col:Int){
    387             if col == self.dimension-1{
    388                 return
    389             }
    390             
    391             for i in (col+1)..<self.dimension {
    392                 let valNew=tileVals[row,i]
    393                 if valNew>0 {
    394                     let val=tileVals[row,col]
    395                     
    396                     if val == 0 {
    397                         tileVals[row, col] = valNew
    398                         tileVals[row, i] = 0
    399                         merge(row: row, col: col)
    400                     }else if val == valNew{
    401                         tileVals[row, col] = valNew<<1
    402                         tileVals[row, i] = 0
    403                         if col==0 {
    404                             merge(row: row, col: col)
    405                         }else{
    406                             merge(row: row, col: col-1)
    407                         }
    408 //                        若产生合并,则加分
    409                         changeScore(valNew)
    410                     }else{
    411                         merge(row: row, col: col+1)
    412                     }
    413                     
    414                     break
    415                 }
    416             }
    417         }
    418         
    419         for row in 0..<self.dimension{
    420             merge(row: row, col: 0)
    421         }
    422         
    423 //        如果合并后的结果与原来相同,则不做任何操作
    424         if tileVals.isEqualTo(gameModel.tiles){
    425             return
    426         }
    427         refresh()
    428         genNumber()
    429     }
    430     
    431     //向右滑动
    432     func swipeRight(){
    433         tileVals = gameModel.tiles
    434         func merge(row row:Int,col:Int){
    435             if col == 0{
    436                 return
    437             }
    438             
    439             for var i=col-1;i>=0;i-- {
    440                 let valNew=tileVals[row, i]
    441                 if valNew>0 {
    442                     let val=tileVals[row, col]
    443                     
    444                     if val == 0 {
    445                         tileVals[row, col] = valNew
    446                         tileVals[row, i] = 0
    447                         merge(row: row, col: col)
    448                     }else if val == valNew{
    449                         tileVals[row, col] = valNew<<1
    450                         tileVals[row, i] = 0
    451                         if col==self.dimension-1 {
    452                             merge(row: row, col: col)
    453                         }else{
    454                             merge(row: row, col: col+1)
    455                         }
    456 //                        若产生合并,则加分
    457                         changeScore(valNew)
    458                     }else{
    459                         merge(row: row, col: col-1)
    460                     }
    461                     
    462                     break
    463                 }
    464             }
    465         }
    466         
    467         for row in 0..<self.dimension{
    468             merge(row: row, col: self.dimension-1)
    469         }
    470         
    471 //        如果合并后的结果与原来相同,则不做任何操作
    472         if tileVals.isEqualTo(gameModel.tiles){
    473             return
    474         }
    475         refresh()
    476         genNumber()
    477         
    478     }
    479     
    480     //刷新页面
    481     func refresh(){
    482         for i in 0..<self.dimension{
    483             for j in 0..<self.dimension{
    484                 let val = gameModel.tiles[i,j]
    485                 let valNew = tileVals[i,j]
    486                 if valNew != val{
    487                     gameModel.setPosition(i, col: j, value: valNew)
    488                     if valNew>0{
    489                         if val>0{
    490                             clearTile(i, col: j)
    491                         }
    492                         insertTile((i,j) , value: valNew)
    493                         
    494                     }else{
    495                         clearTile(i, col: j)
    496                     }
    497                 }
    498                 
    499             }
    500         }
    501     }
    502     
    503     //加分
    504     func changeScore(baseNum:Int){
    505         self.scoreView.score += baseNum * 2
    506         
    507     }
    508     
    509     //检查游戏是否结束
    510     func checkGameOver(){
    511         if self.gameModel.isFull(){
    512             for row in 0..<self.dimension{
    513                 for col in 0..<self.dimension{
    514                     let val = gameModel.tiles[row,col]
    515                     let left:Int? = (col-1)>=0 ? gameModel.tiles[row,col-1] : nil
    516                     let right:Int? = (col+1)<self.dimension ? gameModel.tiles[row,col+1] : nil
    517                     let up:Int? = (row-1)>=0 ? gameModel.tiles[row-1,col] : nil
    518                     let down:Int? = (row+1)<self.dimension ? gameModel.tiles[row+1,col] : nil
    519                     if (val==left) || (val==right) || (val==up) || (val==down){
    520                         return
    521                     }
    522                     
    523                 }
    524             }
    525             
    526             let alerController = UIAlertController(title: "游戏结束", message: "本轮游戏结束,重新开始吧!", preferredStyle: UIAlertControllerStyle.Alert)
    527             alerController.addAction(UIAlertAction(title: "Ready Go!", style: UIAlertActionStyle.Default, handler: {
    528                 action in
    529                 self.resetGameMap()
    530             }))
    531             self.presentViewController(alerController, animated: true, completion: nil)
    532             return
    533         }
    534     }
    535     
    536 }
    View Code
  • 相关阅读:
    【LeetCode-字符串】重构字符串
    【LeetCode-二叉树】填充每个节点的下一个右侧节点指针
    【LeetCode-回溯】分割回文串
    【LeetCode-字符串】最后一个单词的长度
    【LeetCode-数组】生命游戏
    【LeetCode-链表】奇偶链表
    【LeetCode-字符串】反转字符串
    【LeetCode-数学】打乱数组
    Java中实现多线程的3种方法介绍和比较
    oracle 临时表空间、数据表空间、创建用户名与密码、赋予用户权限
  • 原文地址:https://www.cnblogs.com/tt2015-sz/p/4843858.html
Copyright © 2011-2022 走看看