• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    迪恩网络公众号

Swift实战之2048小游戏

原作者: [db:作者] 来自: [db:来源] 收藏 邀请

上周在图书馆借了一本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 }
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      

鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
上一篇:
[Swift]八大排序算法(五):插入排序发布时间:2022-07-13
下一篇:
iOS appleID 登录 (swift/OC)发布时间:2022-07-13
热门推荐
热门话题
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap