上篇地址:swift实战入门之手把手教你编写2048(一)
github地址:https://github.com/scarlettbai/2048.git。
上篇文章已经中已经把2048的游戏区块画好了,这篇来加入计分板以及往游戏面板中插入数字块
计分板同样作为一个view,我们新建一个ScoreView.swift文件,代码如下:
import UIKit
//这里协议的作用是方便别的类中调用计分板的scoreChanged方法
protocol ScoreProtocol{
func scoreChanged(newScore s : Int)
}
class ScoreView : UIView , ScoreProtocol{
//计分板本身是个lable,作用是显示分数
var lable : UILabel
//分数
var score : Int = 0{
didSet{
lable.text = "SCORE:\(score)"
}
}
let defaultFrame = CGRectMake(0, 0, 140, 40)
init(backgroundColor bgColor : UIColor, textColor tColor : UIColor , font : UIFont){
lable = UILabel(frame : defaultFrame)
lable.textAlignment = NSTextAlignment.Center
super.init(frame : defaultFrame)
backgroundColor = bgColor
lable.textColor = tColor
lable.font = font
lable.layer.cornerRadius = 6
self.addSubview(lable)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func scoreChanged(newScore s : Int){
score = s
}
}
其中引入了swift中的Protocol
这个概念,即协议,其作用类似于Java中的接口,就是方便别的地方调用其中的对外暴露的方法。
加入了ScoreView之后,我们再在主控制器NumbertailGameController中初始化一个计分板即可,代码如下:
func setupGame(){
//...此处省略之前代码
//初始化一个ScoreView
let scoreView = ScoreView(
backgroundColor: UIColor(red : 0xA2/255, green : 0x94/255, blue : 0x5E/255, alpha : 1),
textColor: UIColor(red : 0xF3/255, green : 0xF1/255, blue : 0x1A/255, alpha : 0.5),
font: UIFont(name: "HelveticaNeue-Bold", size: 16.0) ?? UIFont.systemFontOfSize(16.0)
)
let views = [scoreView , gamebord]
//定位其在主面板中左上角的绝对位置
var f = scoreView.frame
f.origin.x = xposition2Center(view: scoreView)
f.origin.y = yposition2Center(0, views: views)
scoreView.frame = f
//调用其自身方法来初始化一个分数
scoreView.scoreChanged(newScore: 13631488)
}
运行结果如下:
可以看到计分板已经出现在游戏中了,接下来我们往游戏中加入数字块,一个数字块其实也就是一个view,所以同样我们新建一个TileView.swift文件,代码如下:
import UIKit
class TileView : UIView{
//数字块中的值
var value : Int = 0 {
didSet{
backgroundColor = delegate.tileColor(value)
lable.textColor = delegate.numberColor(value)
lable.text = "\(value)"
}
}
//提供颜色选择
unowned let delegate : AppearanceProviderProtocol
//一个数字块也就是一个lable
var lable : UILabel
init(position : CGPoint, width : CGFloat, value : Int, delegate d: AppearanceProviderProtocol){
delegate = d
lable = UILabel(frame : CGRectMake(0 , 0 , width , width))
lable.textAlignment = NSTextAlignment.Center
lable.minimumScaleFactor = 0.5
lable.font = UIFont(name: "HelveticaNeue-Bold", size: 15) ?? UIFont.systemFontOfSize(15)
super.init(frame: CGRectMake(position.x, position.y, width, width))
addSubview(lable)
lable.layer.cornerRadius = 6
self.value = value
backgroundColor = delegate.tileColor(value)
lable.textColor = delegate.numberColor(value)
lable.text = "\(value)"
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
这里的AppearanceProviderProtocol其实就是里面定义了一些颜色,可以根据当前的数字值来取不同的颜色,内容很简单,为了不占篇幅,此处就省略了,大家可以在github上下载源码来看下。
同样的,加了视图后,我们需要将其初始化出来,这里由于数字块是在游戏面板中的,所以我们将初始化方法放入GamebordView类中,在GamebordView.swift中新增如下方法:
func insertTile(pos : (Int , Int) , value : Int) {
assert(positionIsValied(pos))
let (row , col) = pos
//取出当前数字块的左上角坐标(相对于游戏区块)
let x = tilePadding + CGFloat(row)*(tilePadding + tileWidth)
let y = tilePadding + CGFloat(col)*(tilePadding + tileWidth)
let tileView = TileView(position : CGPointMake(x, y), width: tileWidth, value: value, delegate: provider)
addSubview(tileView)
bringSubviewToFront(tileView)
tiles[NSIndexPath(forRow : row , inSection: col)] = tileView
//这里就是一些动画效果,如果有兴趣可以研究下,不影响功能
UIView.animateWithDuration(tileExpandTime, delay: tilePopDelay, options: UIViewAnimationOptions.TransitionNone,
animations: {
tileView.layer.setAffineTransform(CGAffineTransformMakeScale(self.tilePopMaxScale, self.tilePopMaxScale))
},
completion: { finished in
UIView.animateWithDuration(self.tileContractTime, animations: { () -> Void in
tileView.layer.setAffineTransform(CGAffineTransformIdentity)
})
})
}
func positionIsValied(position : (Int , Int)) -> Bool{
let (x , y) = position
return x >= 0 && x < dimension && y >= 0 && y < dimension
}
上面代码很简单,就是取出数字块相对于游戏区块的坐标,然后初始化出数字块,添加到游戏面板中,且将其置于上层。
接下来我们在主控制器NumbertailGameController
中调用此方法,就可以看到效果了,代码如下:
var bord : GamebordView?
func setupGame(){
//...此处省略之前代码
gamebord.insertTile((3,1) , value : 2)
gamebord.insertTile((1,3) , value : 2)
}
func insertTile(pos : (Int , Int) , value : Int){
assert(bord != nil)
let b = bord!
b.insertTile(pos, value: value)
}
运行看效果如下:
可以看到游戏区块中已经有数字块了,接下来会面临一个问题,就是我们的2048游戏,是在每次滑动之后,随机向空余的地方插入一个数字块,那么,这里我们需要提供一个随机向空余地方插入数字块的方法,这里选用一个我们选用一个数组结构来存储已经添加进游戏的数字块,数组中存储当前块的值,我们通过当前值是否为空来判断这个位置是否空闲,首先我们新建一个BaseModle.swift文件,代码如下:
import Foundation
//数组中存放的枚举,要么空要么一个带值的Tile
enum TileEnum {
case Empty
case Tile(Int)
}
struct SequenceGamebord<T> {
var demision : Int
//存放实际值的数组
var tileArray : [T]
init(demision d : Int , initValue : T ){
self.demision = d
tileArray = [T](count : d*d , repeatedValue : initValue)
}
//通过当前的x,y坐标来计算存储和取出的位置
subscript(row : Int , col : Int) -> T {
get{
assert(row >= 0 && row < demision && col >= 0 && col < demision)
return tileArray[demision*row + col]
}
set{
assert(row >= 0 && row < demision && col >= 0 && col < demision)
tileArray[demision*row + col] = newValue
}
}
//初始化时使用
mutating func setAll(value : T){
for i in 0..<demision {
for j in 0..<demision {
self[i , j] = value
}
}
}
}
上段代码涉及到两个关键字,其中subscript
就是给结构体定义下标访问方式,mutating是结构体在修改自身属性时必须要加的。
结构体定义好了,我们知道现在要存放整个数字块状态的结构体就是一个SequenceGamebord<TileEnum>
,接下来,我们需要新建一个GameModle.swift充当我们的游戏区域的modle层,来记录当前游戏的状态以及提供一些游戏自身的操作等(这里大家可以注意下,这个项目中命名规则我都是视图层以View结尾,控制层以Controller结尾,模型层以Modle结尾,不太理解这些层意义的建议去Google下MVC,此处就不多讲了)。代码如下:
import UIKit
class GameModle : NSObject {
let dimension : Int
let threshold : Int
//存放数字块状态信息
var gamebord : SequenceGamebord<TileEnum>
unowned let delegate : GameModelProtocol
//当前分数,改变后回调用分数视图渲染分数
var score : Int = 0{
didSet{
delegate.changeScore(score)
}
}
//初始化一个都存的Empty的SequenceGamebord<TileEnum>
init(dimension : Int , threshold : Int , delegate : GameModelProtocol) {
self.dimension = dimension
self.threshold = threshold
self.delegate = delegate
gamebord = SequenceGamebord(demision: dimension , initValue: TileEnum.Empty)
super.init()
}
}
上面代码很简单,下面我们来新加方法取出游戏区中空置的块:
func getEmptyPosition() -> [(Int , Int)] {
var emptyArrys : [(Int , Int)] = []
for i in 0..<dimension {
for j in 0..<dimension {
if case .Empty = gamebord[i , j] {
emptyArrys.append((i , j))
}
}
}
return emptyArrys
}
代码很简单,就是通过遍历SequenceGamebord<TileEnum>
,将不为空的位置组成一个(Int , Int)的字典数组返回。
接下来写随机插入的方法:
func insertRandomPositoinTile(value : Int) {
let emptyArrays = getEmptyPosition()
if emptyArrays.isEmpty {
return
}
let randomPos = Int(arc4random_uniform(UInt32(emptyArrays.count - 1)))
let (x , y) = emptyArrays[randomPos]
gamebord[(x , y)] = TileEnum.Tile(value)
delegate.insertTile((x , y), value: value)
}
这个方法也很简单,就是取出当前所有的空的位置数组,在随机一个数组中的位置,之后赋值给gamebord以及调用游戏视图层渲染出新的游戏区块
接下来我们在主控制器NumbertailGameController
中加入如下代码看下效果:
var gameModle : GameModle?
init(dimension d : Int , threshold t : Int) {
//...此处省略之前代码
gameModle = GameModle(dimension: dimension , threshold: threshold , delegate: self )
}
func setupGame(){
//...此处省略之前代码
assert(gameModle != nil)
let modle = gameModle!
modle.insertRandomPositoinTile(2)
modle.insertRandomPositoinTile(2)
modle.insertRandomPositoinTile(2)
}
运行看下效果:
可以看到,在随机位置插入了三个数字为2的数字块。
今天就先介绍到这里,下期来将数字块的移动。
我的博客:blog.scarlettbai.com
欢迎关注个人微信公众号:读书健身编程