上周在图书馆借了一本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),width: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,width: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 }
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 }
(三)设置页面
设置页面可以设置游戏矩阵的维度。上面的阈值,游戏中数字超过该阈值可以视作通关,不过这个功能并没有实现,因为到后面对这个功能无甚兴趣~\(≧▽≦)/~。
上面的几个控件都是在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 }
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 width: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),width: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 }