[swift实战入门]手把手教你编写2048(二)

上篇地址: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

欢迎关注个人微信公众号:读书健身编程

时间: 2024-12-07 13:48:01

[swift实战入门]手把手教你编写2048(二)的相关文章

[swift实战入门]手把手教你编写2048(三)

上篇地址:swift实战入门之手把手教你编写2048(二) github地址:https://github.com/scarlettbai/2048.git. 今天给大家带来2048最后一篇,之前已经实现了向游戏区域中随机插入数字块,接下来要做的,就是当我们滑动屏幕时移动及合并数字块以及插入一个新的数字块.本篇的难点就是移动时的算法问题,首先来给大家讲一下算法. 2048的算法实现其实很简单,假如我们当前数字格的格式如下: | |4| | | | | |4| | |2| |2|2| |2| |

[swift实战入门]手把手教你编写2048(一)

苹果设备越来越普及,拿着个手机就想捣鼓点啥,于是乎就有了这个系列,会一步一步教大家学习swift编程,学会自己做一个自己的app,github地址:https://github.com/scarlettbai/2048.git. 这篇文章需要大家了解一些swift基本语法,这里注重实践,就不讲太多基本语法了,不懂的大家可以Google一下,swift开发环境也很简单,直接在mac上安装一个XCode即可,首先我们来看下最终我们要实现的效果: 当然你也可以将其中的数字换成文字给你女票安手机上,还可

CSS3实战开发: 手把手教大家实战开发鼠标划过图片动画特效

各位网友大家好,我是陌上花会开,当然大家也可以叫我陌陌.今天这篇文章,我将手把手带领大家实战开发一个鼠标划过图片时的动画特效.在这里我不会给大家提供案例的源码下载,但是本人可以保证,只要大家跟着我的思路,一步步将代码复制到本地,一定会得到同样的运行效果.希望大家明白我的用心.好了不废话,直接进入今天的主题吧. 在今天这个案例里,我准备3个素材,一张背景图,两张风景图片.这个大家可以到网上自行下载. 在写代码之前,我先给大家展示一下,动画特效的效果图. 鼠标花过前: 当鼠标划过图片时,图片将逐渐变

CSS3实战开发:手把手教你鼠标滑动特效开发

各位网友,如果你已经看过我的CSS3实战开发系列教程,我相信你对CSS3已经有了非常全面深刻的了解.有些人可能CSS3语法掌握了不少,但是真正实际用起来还有点生疏,甚至无从下手.请别担心,我会不断更新一系列实战开发案例,我会为大家分步骤剖析特效开发过程. 今天我将手把手带领大家开发一个鼠标滑动的特效案例,废话不多说,直接上效果动画: 你有没有觉得上面的这个特效很棒呢! 好,现在咱们就开始分步骤实战开发这个动画特效吧: 首先,我们先准备好html页面代码: <!DOCTYPE html> <

CSS3实战开发:手把手教你照片墙实战开发

在<CSS3 2D转换技术之translate实战开发>文章中,我给大家列出了CSS3中的2D转换方法: 1.    translate() 2.    rotate() 3.    scale() 4.    skew() 5.    matrix() 同时对第一个方法 translate()做了非常详尽的介绍,并带领大家实战开发了一个导航条.如果你对translate不了解或不是太熟悉,请阅读我的博文 <CSS3 2D转换技术之translate实战开发> . 在讲解知识点之前

CSS3实战开发:手把手教大家折角效果实战开发

各位网友,大家好,我是陌上花会开,今天这篇文章,我将手把手教大家如何开发一套纯CSS的折角效果.一如往常,我不提供代码下载,但是我可以保证,只要将教程中的代码复制到本地,绝对百分百获得与我演示的效果一样,希望各位明白我的用意. 好了,直接开始今天的教程吧.首先,我先给大家演示一下今天实战案例的效果: 有人会说这是什么?这就是我们今天的实战开发,我将带领大家开发上图中的右上角折叠效果. 在我讲解完之前,有些人可能觉得很难,不可思议.我想跟你们说:真的so easy.下面就请跟着我的分解步骤一步步学

手把手教你编写一个简单的PHP模块形态的后门

看到Freebuf 小编发表的用这个隐藏于PHP模块中的rootkit,就能持久接管服务器文章,很感兴趣,苦无作者没留下PoC,自己研究一番,有了此文 0×00. 引言 PHP是一个非常流行的web server端的script语言.目前很多web应用程序都基于php语言实现.由于php是个开源软件并易于扩展,所以我们可以通过编写一个PHP模块(module 或者叫扩展 extension)来实现一个Backdoor. 本文就简单介下如何一步步编写一个简单的php 动态扩展后门. 0×01. p

Hexo+NexT(六):手把手教你编写一个Hexo过滤器插件

Hexo+NexT介绍到这里,我认为已经可以很好地完成任务了.它所提供的一些基础功能及配置,都已经进行了讲解.你已经可以随心所欲地配置一个自己的博客环境,然后享受码字的乐趣. 把博客托管到Github上,是个很好的想法,没有自己空间的博主肯定很欢迎.其实文章编译之后,他就是一个非常简单的静态网站.部署的目的就是简单的把静态网站文件夹拷贝到Github的一个仓库里,然后把这个仓库当作一个网站文件夹,仅此而已,非常简单.所以,没有讲的价值. 但是,作为一个Coder,研究了Hexo,总得来点真本事,

Java入门 手把手教你配置环境变量

很多人觉得配置Java开发的环境变量很麻烦,很容易忘记,时常被它搞得晕头转向.如果出现这样的情况,那么原因只有一个,你不了解为毛需要配置环境变量,不配置环境变量就不能开发了吗? 答案是:NO!,那么下面就带大家一起来分析一下原因所在. 先确定一个概念.编译一个Java源程序需要使用到javac 命令:运行启动一个Java程序需要使用到java命令.以我本机为例,这两个命令工具存在于 : D:\java\jdk\jdk1.7.0_06\bin路径下 在命令提示符下有这样一个特点,那就是只能够访问到