【swift3实战】2048 教程(三)


写到这 下一步就该插入块了.   思考:在什么样的位置才能加小块?  得判断一个位置是否为空的。所以

GameView.swift里添加判断方法:


//位置是在16个格内?

func isValid(pos:(Int,Int)) ->Bool{

    let (x,y) = pos

    return x>=0&&y>=0&&x<dimension&&y<dimension

}


//判断完空,才写插入方法。参数:插入的位置,值

func insertUnit(pos:(Int,Int),value:Int){

//其实这里断言语句都不用写,项目不大,debug不是很艰巨,assert没多大用

        assert(isValid(pos: pos))  


        let (row,col) = pos


//把行列数转化为相对于大正方形的位置的相关计算我就不解释了,自己心里算算== :

        let x0 =CGFloat(row+1)*thinPadding +unitwidth*CGFloat(row)

        let y0 =CGFloat(col+1)*thinPadding +unitwidth*CGFloat(col)

       

        let unitLabel =UnitView(pos:CGPoint(x: x0, y: y0), wid:unitwidth, value: value, delegate:provider)

        读者可以自定义UnitView的构造方法,把CGPoint参数换成一个元组或俩参数都行,不用拘泥


//添加view:

addSubview(unitLabel)

        bringSubview(toFront: unitLabel)


//别忘了再往字典里加上这个新来的 unit

        units[IndexPath(row: row, section: col)] = unitLabel


    }


实际上我们每次滑动屏幕后小块是随机插入的(或者在限制在边缘随机插入,加个条件就ok。这里先不限制了),我们需要在逻辑 GameModel.swift 里添加方法,得到哪些位置可用?然后在这些可用位置上随机插入一个块。

    //  1、返回空位置数组

    func getEmptyPos() ->[(Int,Int)]{


        var empUnits:[(Int,Int)] = []

        for i in 0..<dimension{


            for j in 0..<dimension{


                if case .empty = gameView[i,j]{

                    empUnits.append((i,j))

                }

            }

        }

        return empUnits

    }

    //    2、随机插入 

    func insertRandomUnit(value:Int){


        let emptyUnits = getEmptyPos()

        if emptyUnits.isEmpty{  return }


//在空位置数组里产生一个随机位置

        let randomPos =Int(arc4random_uniform(UInt32(emptyUnits.count-1)))

        let (x,y) = emptyUnits[randomPos]


        gameView[(x,y)] =UnitEnum.unit(value)


    思考: model知道了插入什么块,需要让协议去主面板上去加块. 现在就想,得给协议加一个方法了!但是前几节 都一下子给写出来了,当时没要求理解,现在应该理解了吧

    让delegate调用插入小块的方法,并把随机位置参数传过去


        delegate.insertUnit(at: (x,y), withValue: value)


    然后,顺理成章的去写协议方法的内容。 还记得那几个没实现的方法么 ->

   func changeScore(to score:Int) {

        scoreBoard.score = score

    }

    

    func moveOneUnit(from: (Int,Int), to: (Int,Int), value: Int) {

        //一会会写的:game.moveOneunits(from: from, to: to, value: value)

    }

    

    func moveTwoUnits(from: ((Int,Int), (Int,Int)), to: (Int,Int), value:Int) {

//一会会写的:game.moveTwounits(from: from, to: to, value: value)

    }


    //这次补充的

    func insertUnit(at location: (Int,Int), withValue value:Int) {

         game.insertUnit(pos: location, value: value)

    }


//  游戏刚打开时需要随机产生两个小块 所以在GamePanel.swift的showGridView方法里插入两个值为2的小块:

func showGridView(){


        unit = (boardwidth-thinPadding*CGFloat(dimension+1))/CGFloat(dimension)

        game =GameView(unit:unit,dime:dimension,x:x,y:y,thin:thinPadding)


        gameModle!.insertRandomUnit(value:2)

        gameModle!.insertRandomUnit(value:2)


        view.addSubview(game)

    }


当然你也可以插入2048  或者 4096 ······ 

现在已经可以 随机插入数字块了,接下来要做的,就滑动屏幕时移动及合并数字块以及插入一个新的数字块。这个游戏的难点就是移动时的 算法 ,这个算法我其实是看别人的==。 其他方法比较易写。

如果用户选择向上滑动触发一个事件,这里我们要做的是, 先取出第一列的4个格存为一个数组,对应坐标为[(0,0),(0,1),(0,2),(0,3)],其中对应的值假设分别是 0 0 2 2 , 我们首先对数组进行去除空操作,去除之后数据为:[(0,2),(0,3)],对应值为|2|2|。
之后再进行合并操作 :取数组中原来两个2的坐标以及最终坐标,然后更新存储当前数字块状态的数组以及数字块视图:将之前两个2的地方置空,并在(0,0)处插入一个4即可,之后再继续遍历每一列做同样的操作。
如果某行对应值为 4 4 4 0 ,向右滑动,则去空,合并右边两个4,4 然后右移,最终:0 0 4 8 。真正实现起来的算法非常复杂,在这之前,先定义几个enum(可以与刚才的GameModel.swift,struct,enum并列定义) 方便操作:

//四个移动方向:

enum MoveDirection{

    case UP,DOWN,LEFT,RIGHT

}


//数字几种移动状态,及重要数据:位置和新值

//还是那句话,现在不知道是干什么的不要紧,一会会明白

enum UnitAction{

    case NoAction(source:Int,value:Int)   //不动

    case Move(source:Int,value:Int)    //单纯移动

    case SingleCombine(source:Int,value:Int)    //一个不动,另一个结合上去

    case DoubleCombine(firstSource:Int,secondSource:Int,value:Int)//俩结合后一起移动到一边

   

    //source是原位置 value是块上的值


    func getValue()->Int{


        switch self {


        case let .NoAction(_,value):       return value

        case let .Move(_,value):           return value

        case let .SingleCombine(_,value):  return value

        case let .DoubleCombine(_,_,value):return value


        }

    }


    func getSource() ->Int{


        switch self {


        case let .NoAction(source,_):       return source

        case let .Move(source,_):           return source

        case let .SingleCombine(source,_):  return source

        case let .DoubleCombine(source,_,_):return source


        }

    }

}


//最终的移动数据封装,标注了所有需移动的块的原位置及新位置,以及块的最新值

enum MoveOrder{

    case SingleMoveOrder(source:Int,destination:Int,value:Int,merged:Bool)

    case DoubleMoveOrder(first:Int,second:Int,destination:Int,value:Int)

}


//最艰巨的部分   GameModel.swift中添加方法:

//一、分三个步骤:去空,合并,转成order,

  //1.去空。第一层判断,返回一个记录行为的数组,仅包含能不能动的属性

    func condense(_ group:[UnitEnum])->[UnitAction]{


        var buffer = [UnitAction]()


        for (index,unit)in group.enumerated(){


        //巧妙 : 如果buffer的大小和当前group的下标一致,则表示当前数字块不需要移动如2_2_,

//第一次时buffer大小和index都是0,不需要移动

//下一个2时,buffer大小为1,groupindex为2,则需要移动了


            switch unit {

            case let .unit(value)where buffer.count == index  :

                buffer.append(UnitAction.NoAction(source: index, value: value))


            case let .unit(value) :

                buffer.append(UnitAction.Move(source: index, value: value))


            default:break

            }

        }

        return buffer

    }


    //仍然保持不动的单元

    staticfunc quiescentUnitStillQuiescent(inputPos:Int, outputLen:Int, originalPos:Int) ->Bool {

        return (inputPos == outputLen)&& (originalPos == inputPos)

    }


    //2.合并(折叠) 第二层判断。返回记录行为的数组,更详细了,包括了单结合,还是双结合再滑动。

    //这部分算法想研究的可细究,挺折磨人的。。。我反正是复制粘贴的=====


    func collapse(_ group:[UnitAction]) -> [UnitAction]{


        var buffer = [UnitAction]()

        //如果把下一个块合并过来,则下一个数字块应该跳过

        var skipNext =false

        for(index,token)in group.enumerated(){


            if skipNext {

                skipNext = false

                continue

            }

            switch token {

           //当前块和下一个块值相同且当前块不移动,则将下一个块合并到当前块来

            case let .NoAction(source,value)

                where (index<group.count-1

                    && value == group[index+1].getValue()

                    &&GameModel.quiescentUnitStillQuiescent(inputPos: index,outputLen:buffer.count,originalPos:source)):

                let next = group[index+1]

                let newValue = value+next.getValue()

                skipNext = true


                buffer.append(UnitAction.SingleCombine(source: next.getSource(), value: newValue))

           //当前块和下一个块的值相同且两个块都要移动,则两个块移动到新的位置

            case let t

                where (index < group.count-1&& t.getValue() == group[index+1].getValue()):

                let next = group[index+1]

                let newValue = t.getValue() + group[index+1].getValue()

                skipNext = true


                buffer.append(UnitAction.DoubleCombine(firstSource: t.getSource(), secondSource: next.getSource(), value: newValue))

                

      //上一步判定不需要移动,但是之前的块有合并过,所以需要移动

            case let .NoAction(source,value) where  !GameModel.quiescentUnitStillQuiescent(inputPos:index, outputLen: buffer.count, originalPos: source):

                buffer.append(UnitAction.Move(source: source, value: value))

                

           //上一步判定不需要移动,且之前的块也没有合并,则不需要移动

            case let .NoAction(source, value):

                buffer.append(UnitAction.NoAction(source: source, value: value))

                

           //上一步判定需要移动且不符合上面的条件的,则继续保持移动

            case let .Move(source, value):

                buffer.append(UnitAction.Move(source: source, value: value))

            

    default:break       

            }

        }

        return buffer

    }


//3.最后,把移动行为数组转化成移动命令数组order


    func convert(_ group:[UnitAction]) ->[MoveOrder]{

        var buffer = [MoveOrder]()

        for (index , unitAction)in group.enumerated() {

            switch unitAction {

            case let .Move(s, v) :

                //单纯的将一个块由s位置移动到index位置,新值为v

                buffer.append(MoveOrder.SingleMoveOrder(source: s, destination: index, value: v, merged: false))

            case let .SingleCombine(s, v) :

               //将一个块由s位置移动到index位置,且index位置有数字块,俩数字块进行合并,新值为v

                buffer.append(MoveOrder.SingleMoveOrder(source: s, destination: index, value: v, merged: true))

            case let .DoubleCombine(s, d, v) :

                //sd两个数字块移动到index位置并进行合并,新值为v

                buffer.append(MoveOrder.DoubleMoveOrder(first: s, second: d, destination: index, value: v))

            default:

                break

            }

        }

        return buffer

    }

   

//草,终于到最后了:merge:合并

//给我一个UnitEnum数组,就是保存小块状态的数组,就能让它变成移动状态数组!

    func merge(group:[UnitEnum]) ->[MoveOrder]{

        return convert(collapse(condense(group)))

    }


调用merge方法

还是在原来位置添加方法 :




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值