Insus.NET在实现《ASP.NET开发,从第二交汇及三交汇,至面向对象
(5)》http://www.cnblogs.com/insus/p/3880606.html 中,没有管数据编辑和创新功能同步演示,留下让网友们自由发挥,但是还是出网友想看Insus.NET用实现方式。

2048凡IOS学习的Demo中老的话题了。之前为吃小辈们提一个有关iOS+Swift的讲座,便自己付出了一个。Github上却已经来矣一个工
austinzheng/swift-2048
,但是最终之均等赖commit也一度是2015年的时候了,有些地方应当早就落伍了咔嚓。

先Insus.NET的做法,是以GridView控件中开展。如就首视频教程《GridView
Edit Update Cancel
Delete》http://www.cnblogs.com/insus/archive/2011/01/25/1944295.html

眼看首教程假而你既于Swift的为主语法只是跟Xcode的以方法发生矣一个比较清楚的体会。如果你还不了解这点的知的话,建议先失阅读以下相关文章展开入门。

或这首《网站是否来广播音乐功能》http://www.cnblogs.com/insus/archive/2013/05/09/3066347.html 
均可以看到GridView控件的翻新功能。

前言

这个科目的源代码已经身处了我之github主页上面:
Game2048,目前莫放License,不过你可以随便使用本文以及Github工程中的持有源代码。

话题回到项目自己。这个项目达,我哉是使了经典了MVC架构,即Model-View-Controller。在底下教中,我吗用着力以之顺序来介绍代码的组织及逻辑。

总结过去的更,觉得编辑或更新GridView控件的记录,用户用点击多次按钮,才能够完成。而且对准GridView控件编写的代码也大半。因此Insus.NET想直接点击GridView控件某笔记录,然后显示为长的地面中进行更新。

准备工作

于这部分,我们创建工程文件,并简要梳理一下工的结构和各个文件的来意。

OK,开始来瞧:
第一在GridView控件添加同列:
BWIN必赢亚洲56.net 1

创办项目

上面就宣称,我假而你就深谙了Xcode的操作,故这里说的简便一点。使用Xcode创建一个Single
View
Application,然后去Storyboard相关的情,我们拿会晤以代码来构建页面。然后创建一下文本:

  • Matrix.swift:
    Model部分的代码,在此处我们构建了描述打中逐条实体的概念模型以及处理打操作逻辑的算法
  • Container.swift: View部分之代码,定义了2048游乐操作的面板
  • Tile.swift:
    我们遂2048玩耍被的一个格子为一个Tile,这个文件就为Tile的View
  • ColorProvider.swift:
    我们将戏中的水彩控制部分单独了出来,使得样式的替换更加有利
  • Other.swift:其他的助代码
  • Constant.swift: 某些常量定义在这里

除此之外,我们还动用了片叔在代码库,这些库我们由此CocoaPod来装,Podfile的内容呢:

source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '10.0'
use_frameworks!

target 'Game2048' do
    pod 'SnapKit', '~> 3.0'
    pod 'Dollar'
end

运行pod install来设置这些靠。

下一场在丰富按钮后,再添加点儿单铵钮,一个是翻新,一个凡是取消:
 BWIN必赢亚洲56.net 2

Model部分 – 构建起描述打的概念模型

 

着力数据意味着 – Matrix

2048戏耍被,我们要需要处理的凡一个矩形的数据结构,为了好之囤和处理多少,我们创建一个称为吧Matrix的结构体:

struct Matrix {
      private let dimension: Int
      private var elements: [Int]

      /// 初始化函数,创建一个Matrix结构体
    ///
    /// - Parameters:
    ///   - d: 游戏中矩阵的维数,一般是4
    ///   - initialValue: 被创建的矩阵中每个元素的初始值
    init(dimension d: Int, initialValue: Int = 0) {
        dimension = d
        elements = [Int](repeating: initialValue, count: d * d)
    }

    func getDimension() -> Int {
        return dimension
    }

    func asArray() -> [Int] {
        return elements
    }
}

夫构思并无复杂,Matrix中间包裹的照样是一个一维数组。为了让这个Matrix可知像Matlab等先后中之矩阵一样可就此二处女数的章程访,我们给她助长如下的代码:

subscript(row: Int, col: Int) -> Int {
        get {
            assert(row >= 0 && row < dimension)
            assert(col >= 0 && col < dimension)
            return elements[row * dimension + col]
        }

        set {
            assert(row >= 0 && row < dimension)
            assert(col >= 0 && col < dimension)
            elements[row * dimension + col] = newValue
        }
    }

并且为传递参数的福利,我们将第二初次数定义成一个一定的类型,方便参数传递。Matrix外部加上

typealias MatrixCoordinate = (row: Int, col: Int)
// 定了一个特殊的二元数作为空坐标
let kNullMatrixCoordinate = MatrixCoordinate(row: -1, col: -1)

然后在Matrix中加上:

subscript(index: MatrixCoordinate) -> Int {
    get {
        let (row, col) = index
        return self[row, col]
    }

    set {
        let (row, col) = index
        self[row, col] = newValue
    }
}

最后咱们还给Matrix加上有行之有效的工具函数,用于查询与插

    /// 将矩阵的所有元素置零
    mutating func clearAll() {
        for index in 0 ..< (dimension * dimension) {
            elements[index] = kZeroTileValue
        }
    }


    /// 将元素的值插入到矩阵的指定位置,注意这个函数只能给原来为空的位置赋值
    ///
    /// - Parameters:
    ///   - position: 坐标
    ///   - value: 插入的值
    mutating func insert(at position: MatrixCoordinate, with value: Int) {
        if isEmpty(at: position) {
            self[position] = value
        } else {
            assertionFailure()
        }
    }


    /// 矩阵指定位置是否为空(为空即是指此处为0)
    ///
    /// - Parameter position: 指定位置
    /// - Returns: 是否为空
    func isEmpty(at position: MatrixCoordinate) -> Bool {
          // kZeroTileValue定义在Constant.swift里面,为0
        return self[position] == kZeroTileValue
    }


    /// 获取矩阵中所有为空的位置
    ///
    /// - Returns: 列表形式的坐标集合
    func getEmptyTiles() -> [MatrixCoordinate] {
        var buffer: [MatrixCoordinate] = []
        for row in 0..<dimension {
            for col in 0..<dimension {
                let pos = MatrixCoordinate(row: row, col: col)
                if isEmpty(at: pos) {
                    buffer.append(pos)
                }
            }
        }
        return buffer
    }


    /// 矩阵中元素的最大值
    var max: Int {
        get {
            return elements.max()!
        }
    }


    /// 矩阵中所有元素的和
    var total: Int {
        return $.reduce(elements, initial: 0, combine: { $0 + $1 })
    }

迄今,我们得了对戏数量达的纸上谈兵,即将2048挨的4*4矩阵用Matrix来代表,并于这个结构体上定义了造福之造访方式跟工具函数。

即简单只铵钮,一开始它的状态是禁用的,可看它们的Enabled=“false”。也就是说网页加载时,这有限只铵钮是休可用。
连着下去,我们独家实现OnRowCreated=”GridViewEmployee_RowCreated”,OnClick=”ButtonUpdate_Click”和OnClick=”ButtonCancel_Click”事件。先实现率先独,点击GridView控件某平等画记录时,希望能够博取这笔记录数据。因此需要创造一个初的贮存过程,By主键来博取点击即记录:
BWIN必赢亚洲56.net 3

Model层对外封装结构

然后我们定义Model需要针对Controller露出的操作接口。定义一个初的GameModel类。

class GameModel {
    private var matrix: Matrix
    var dimension: Int {
        get {
            return matrix.getDimension()
        }
    }
    let winningThreshold: Int

    /// 分数
    var score: Int {
        return matrix.total
    }

    init (dimension: Int = 4, winningThreshold threshold: Int = 2048) {
        matrix = Matrix(dimension: dimension)
        winningThreshold = threshold
    }
}

设想2048底游戏规则,Model层应该本着上层提供如下这些接口:

  • 当一个即兴空位置插入一个新的格子
  • 每当指定位置插入一个指定值
  • 看清用户是否胜利
  • 判定用户是否曾破产
  • 针对用户的光景左右滑行操作做出响应
  • 重置游戏

// 在一个指定位置插入一个指定的值
func insertTile(at position: MatrixCoordinate, with value: Int) {
}
// 在一个随机空位置插入一个随机的值,按照一般的规则,随机的插入2或者4,其中2的概率要远大于4
func insertTilesAtRandonPosition(with value: Int) -> Int {
}
// 用户是否已经胜利
func userHasWon() -> Bool {
}
// 用户是否已经失败
func userHasLost() -> Bool {
}
// 响应用户操作,注意这里我们引入了新的MoveCommand和MoveAction的概念,这个我们会在后面详细解释
func perform(move command: MoveCommand) -> [MoveAction] {
}

脚我们来挨家挨户个说明各个组织的力量跟贯彻。

发上加了数量库层的囤积过程,前同篇写好之Employee.cs也要添加相应的逻辑方式恐怕函数。
BWIN必赢亚洲56.net 4

当指定位置插入指定值

夫接口实现非常简单,因为咱们曾当Matrix好像中贯彻了近乎之接口。故在此地我们只是待调用对应之函数即可。

func insertTile(at position: MatrixCoordinate, with value: Int) {
    matrix.insert(at: position, with: value)
}

本可写了:
BWIN必赢亚洲56.net 5

在一个随意空位插入一个自由的价值

处在程序设计中函数应当保持短小精悍的条件,为了促成者功效,我们增加几单器函数:

// 这个函数会返回插入的位置,返回的格式为matrix内部一维数组定义下的index
func insertTilesAtRandonPosition(with value: Int) -> Int {
        let emptyTiles = matrix.getEmptyTiles()
        if emptyTiles.isEmpty {
            return -1
        }
        let randomIdx = Int(arc4random_uniform(UInt32(emptyTiles.count - 1)))
        let result = emptyTiles[randomIdx]
        insertTile(at: emptyTiles[randomIdx], with: value)
        return coordinateToIndex(result)
}

func coordinateToIndex(_ coordincate: MatrixCoordinate) -> Int {
        let (row, col) = coordincate
        return row * dimension + col
}

// 工具函数,按照预定的概率生成2或者4
func getValueForInsert() -> Int {
        if uniformFromZeroToOne() < chanceToDisplayFour {
            return 4
        } else {
            return 2
        }
    }

    func uniformFromZeroToOne() -> Double {
        return Double(arc4random()) / Double(UINT32_MAX)
    }

上面来一个事变委托ButtonEdit_Click:
BWIN必赢亚洲56.net 6

判定用户是否胜利

这里只待看清matrix惨遭的极度大值是否达标了加的阈值即可:

func userHasWon() -> Bool {
    return matrix.max >= winningThreshold
}

方程序来几乎接触说明,先是点击编辑之后,获取给点选的之笔录主键,然后因主键是取得整笔记录,然后绑定给补偿加域中错过(标记1)。标记2,需要将补偿加铵钮与绑定主键的文本框禁用。
号3凡是Active更新和收回铵钮。

看清用户是否早已失败

其一逻辑要相对复杂一点,用户失败时,即用户无论怎么操作矩阵都不见面发生变化。按照规则,用户失败当满足下面两独规范

  1. 持有的格子都早就填满
  2. 自由一个格子和夫隔壁格子都心有余而力不足统一
    马上同样历程得形成下面的代码。代码的逻辑并无十分复杂,可以由此阅读源代码进行明白。

    /// 用户是已经获胜
    func userHasWon() -> Bool {
        return matrix.max >= winningThreshold
    }


    // 用户已经失败
    func userHasLost() -> Bool {
        return !isPotentialMoveAvaialbe()
    }


    /// 用户是否还有可以移动的步骤
    func isPotentialMoveAvaialbe() -> Bool {
        var result: Bool = false
        for row in 0..<dimension {
            for col in 0..<dimension {
                result = result || isTileMovable(at: MatrixCoordinate(row: row, col: col))
                if result {
                    break
                }
            }
        }
        return result
    }


    /// 指定的格子是否还可以移动
    func isTileMovable(at tileCoordincate: MatrixCoordinate) -> Bool {
        let val = matrix[tileCoordincate]
        if val == kZeroTileValue {
            return true
        }
        let neighbors = getNeightbors(around: tileCoordincate)
        var result: Bool = false
        for index: MatrixCoordinate in neighbors {
            let fetchedVal = matrix[index]
            result = result || (fetchedVal == val) || fetchedVal == kZeroTileValue
            if result {
                break
            }
        }
        return result
    }

    /// 获取一个格子的相邻格子
    func getNeightbors(around tileCoordincate: MatrixCoordinate) -> [MatrixCoordinate] {
        let (row, col) = tileCoordincate
        var result: [MatrixCoordinate] = []
        if row - 1 > 0 {
            result.append(MatrixCoordinate(row: row - 1, col: col))
        }
        if row + 1 < dimension {
            result.append(MatrixCoordinate(row: row + 1, col: col))
        }
        if col - 1 > 0 {
            result.append(MatrixCoordinate(row: row, col: col - 1))
        }
        if col + 1 < dimension {
            result.append(MatrixCoordinate(row: row, col: col + 1))
        }
        return result
    }

下面是吊销铵钮ButtonCancel_Click事件,实际就是是初始化控件状态功能:
BWIN必赢亚洲56.net 7

重置游戏

重置游戏就待将matrix遭的数值清空即可

    func clearAll() {
        matrix.clearAll()
    }

文本框清空,该禁用的铵钮禁用,该启用的铵钮启用。最后是创新铵钮ButtonUpdate_Click事件:
BWIN必赢亚洲56.net 8

针对用户之前后左右滑行操作做出响应

观看为,取消铵钮与更新事件了后,所行之代码是平等的。因此可以把她写成一个单独的小智:
BWIN必赢亚洲56.net 9

戏逻辑分析

这片涉及到的虽是娱逻辑的主干了。通过对游戏规则的觉察2048题目时有发生如下的特点:

  • 通往某一个方向滑时,沿该方向的相继列次相互独立,故可以以同次等滑动产生的次维格子移动合并问题,转化成为多少个单身求解的平等维格子队列的移动与集合问题。
  • 不等的滑动方向,其实逻辑规则是以打转操作下是等价的。
    归纳,我们可以用游乐受对用户操作方向做出响应计算matrix矩阵的新值这样一个二维题材,分解为几只线性问题之构成。例如,若有一个操作之后matrix所表示的娱乐被格子分布为:
    2 | 2 | 0 |4
    4 | 0 | 2 | 0
    0 | 0 | 0 | 0
    0 | 0 | 0 | 0
    此时用户向左滑动,则求解新的矩阵数值分布得分解为四单支行问题:即[2,
    2, 0 4], [4, 0, 2, 0], [0, 0, 0, 0], [0, 0, 0,
    0]。而且,由于旋转等价性,我们可以以相继方向的滑行全部且讲为同一维的,向左合并之分层问题。为了还像的证实及时一点,还是参照者给起底例证。若用户向上滑动,则好说明为[2,
    4, 0, 0], [2, 0, 0, 0], [0, 2, 0, 0], [4, 0, 0,
    0]季个问题的。

成就了上述问题的空洞和简化后,我们来要分析一维的,向左合并之简化问题。这个题材之求解,可以分解变成稀种操作:一是于漏洞百出至右,移除非零数字中的零散,我们称之为condense;二凡以附近的相当数字进行统一,我们称之为collapse。一般但待condense
— collapse两步即可,少数气象下欲最后额外开展同样破condense,例如[2, 2,
2, 2],collapse完成以后获得[4, 0, 4,
0],需要再行开展同样潮condense才能够成[4, 4, 0, 0]。

以编程的下,condense是一个雅有益实现之操作。我们唯有需要拿需要处理的数组中之非零元素按照本的逐一放到新数组里面就得了。

 

用户操作的象征和促成

当前一部分的辨析面临我们发现,不同倾向的滑动,都得讲为多少单一维题目,只是不同的滑行方向下,一维问题的分解方式,以及以顺序解出的结果还原也二维矩阵之方式不同。而一维问题的求解方法是一律的。这种特点适合吃用多态的计划方式。即我们定义一个基类MoveCommand,在中间落实一维题目求解的算法,而把一维题材之提和死灰复燃的算法放在各个滑动方向对应的子类中落实:

/// 移动指令,代表用户在屏幕上的一次滑动
class MoveCommand {
    /**
     * 我们使用了多态来处理不同的滑动指令。
     * 为了解决2048这个发生在二维空间的问题,我们需要将问题进行降维。下面以四维情况为例来说明。
     * 
     * 无论用户想那个方向滑动,格子的变化,总是沿着用户滑动的方向进行,即格子其他处于同一用户滑动方向直线上格子发生交互(合并),而与其他
     * 平行的直线上的格子无关。那么我们可以在用户滑动发生时,将矩阵按照用户滑动方向划分成多个组,然后在每组中独立的解决一维的合并问题。例如
     * 下面的矩阵情形
     *  |0  |0  |2  |2  |
     *  |0  |0  |2  |2  |
     *  |0  |0  |2  |2  |
     *  |0  |0  |2  |2  |

     * 当用户向左侧滑动是,可以将上面的矩阵拆解成|0  |0  |2  |2  |的一维问题进行求解。
     * 而且容易发现,对于用户的不同滑动方向,只是一维问题分解的方式不同,求解一维问题的方法是一致的。我们用多态来实现这种复用。
     */

    // 还原
    func getCoordinate(forIndex index: Int, withOffset offset: Int, dimension: Int) -> MatrixCoordinate {
        fatalError("Not implemented")
    }

    // 提取一维问题
    func getOneLine(forDimension dimension: Int, at index: Int) -> [MatrixCoordinate] {
        fatalError("Not implemented")
    }

    // condense
    func getMovableTiles(from line: [Int]) -> [MovableTile] {
        var buffer: [MovableTile] = []
        for (idx, val) in line.enumerated() {
            if val > 0 {
                buffer.append(MovableTile(src: idx, val: val, trg: buffer.count))
            }
        }
        return buffer
    }

    // collapse
    func collapse(_ tiles: [MovableTile]) -> [MovableTile] {
        var result: [MovableTile] = []
        var skipNext: Bool = false
        for (idx, tile) in tiles.enumerated() {
            if skipNext {
                skipNext = false
                continue
            }
            if idx == tiles.count - 1 {
                var collapsed = tile
                collapsed.trg = result.count
                result.append(collapsed)
                break
            }

            let nextTile = tiles[idx + 1]
            if nextTile.val == tile.val {
                result.append(MovableTile(src: tile.src, val: tile.val + nextTile.val, trg: result.count, src2: nextTile.src))
                skipNext = true
            } else {
                var collapsed = tile
                collapsed.trg = result.count
                result.append(collapsed)
            }
        }
        return result
    }
}

以上面的代码中,我们引入了MovableTile本条近乎。其意图是描述格子在平等差滑动操作中的变型过程。

/// 矩阵变化过程中描述每一个格子的数据结构,可以记录格子的移动,合并,消失,以及值的改变
struct MovableTile {

    /// 源位置
    var src: Int

    /// 取值
    var val: Int

    /// 目标位置
    var trg: Int = -1


    /// 如果此值非负,则意味着这个结构体描述了一个合并过程,并且这个src2代表参与合并的另一个格子,为默认值-1时,则意味着只是单纯的格子移动,没有发生合并
    var src2: Int = -1

    init (src: Int, val: Int, trg: Int = -1, src2: Int = -1) {
        self.src = src
        self.val = val
        self.trg = trg
        self.src2 = src2
    }


    /// 这个格子是否实际发生了移动。
    ///
    /// - Returns: 是否需要移动
    func needMove() -> Bool {
        return src != trg || src2 >= 0
    }
}

紧接下去,我们需要实现不同滑动方向对应之子类,其落实逻辑很直观,读者可以好知道一下:

class UpMoveCommand: MoveCommand {
    override func getOneLine(forDimension dimension: Int, at index: Int) -> [MatrixCoordinate] {
        return (0..<dimension).map({ MatrixCoordinate(row: $0, col: index) })
    }

    override func getCoordinate(forIndex index: Int, withOffset offset: Int, dimension: Int) -> MatrixCoordinate {
        return MatrixCoordinate(row: offset, col: index)
    }
}

class DownMoveCommand: UpMoveCommand {
    override func getOneLine(forDimension dimension: Int, at index: Int) -> [MatrixCoordinate] {
        return super.getOneLine(forDimension: dimension, at: index).reversed()
    }

    override func getCoordinate(forIndex index: Int, withOffset offset: Int, dimension: Int) -> MatrixCoordinate {
        return MatrixCoordinate(row: dimension - 1 - offset, col: index)
    }
}

class LeftMoveCommand: MoveCommand {
    override func getOneLine(forDimension dimension: Int, at index: Int) -> [MatrixCoordinate] {
        return (0..<dimension).map({ MatrixCoordinate(row: index, col: $0) })
    }

    override func getCoordinate(forIndex index: Int, withOffset offset: Int, dimension: Int) -> MatrixCoordinate {
        return MatrixCoordinate(row: index, col: offset)

    }
}

class RightMoveCommand: LeftMoveCommand {
    override func getOneLine(forDimension dimension: Int, at index: Int) -> [MatrixCoordinate] {
        return super.getOneLine(forDimension: dimension, at: index).reversed()
    }

    override func getCoordinate(forIndex index: Int, withOffset offset: Int, dimension: Int) -> MatrixCoordinate {
        return MatrixCoordinate(row: index , col: dimension - 1 - offset)
    }
}

心想事成了了,看看实时操作演示效果,点击“Edit”铵钮时,它出现了一个格外:
Line: 885
Error: Sys.WebForms.PageRequestManagerServerErrorException: Object
reference not set to an instance of an object.
BWIN必赢亚洲56.net 10

实现GameModel饱受之接口

发出了上述准备,我们好入手实现GameModel惨遭的接口了。把脚的函数添加至GameModel

    /// 执行一个移动命令
    func perform(move command: MoveCommand) -> [MoveAction] {
        // 最后生成的可供UI解析的移动命令
        var actions: [MoveAction] = []
        var newMatrix = matrix
        newMatrix.clearAll()
        // 逐行或者逐列进行遍历(具体取决于滑动方向)
        (0..<matrix.getDimension()).forEach { (index) in
            // 提取出一维问题,注意这里提取的是列或者行中所有格子的坐标
            let tiles = command.getOneLine(forDimension: matrix.getDimension(), at: index)
            // 取出各个格子中的值
            let tilesVals = tiles.map({ matrix[$0] })
            // 进行condense-collapse-condense操作
            let movables = command.collapse(command.getMovableTiles(from: tilesVals))
            // 将movable tiles转化成move action
            for move in movables {
                let trg = command.getCoordinate(forIndex: index, withOffset: move.trg, dimension: matrix.getDimension())
                newMatrix[trg] = move.val
                if !move.needMove() {
                    continue
                }
                let src = command.getCoordinate(forIndex: index, withOffset: move.src, dimension: matrix.getDimension())
                if move.src != move.trg {
                    let action = MoveAction(src: src, trg: trg, val: -1)
                    actions.append(action)
                }
                if move.src2 >= 0 {
                    let src2 = command.getCoordinate(forIndex: index, withOffset: move.src2, dimension: matrix.getDimension())
                    actions.append(MoveAction(src: src2, trg: trg, val: -1))
                    actions.append(MoveAction(src: kNullMatrixCoordinate, trg: trg, val: move.val))
                }
            }
        }
        // 应用计算完之后的结果
        self.matrix = newMatrix
        newMatrix.printSelf()
        // 将需要UI执行的变化返回
        return actions
    }

这里我们又引入了一个初的近乎MoveAction,这个类似其实是对MovableTile的一个规整。在前我们关系了,当MovableTile好描述在滑行过程被切实格子的变动。诚然,单个格子的动我们可以直接动用MovableTile其间的多寡操纵UI,但是当生合并是将麻烦大多了。出于这个原因我们引入了初的MoveAction,并且保证每个MoveAction光对应UI中之一个格子的一个动。其定义如下:

struct MoveAction {
    var src: MatrixCoordinate
    var trg: MatrixCoordinate
    var val: Int

    init(src: MatrixCoordinate, trg: MatrixCoordinate, val: Int) {
        self.src = src
        self.trg = trg
        self.val = val
    }
}

瞩目这BWIN必赢亚洲56.net间和MovableTile的一个至关重要不以取消了src2本条特性。
对于由于一个MovableTile表示的鲜独格子的集合过程(即src2无呢-1),我们当地用那说明为老三独子动作,分别是简单个单纯移动和一个新的格子出现。对于只有移动如果值未发生变化的格子,我们拿其MoveActionval装成-1,对于新面世的格子,我们以那个src安成-1。当然,如果吃联合之片个格子其中有一个没移动,那么即使一味见面转变一个格子移动与一个初格子产生的MoveAction

F9设定中断点,F5debug,然后按照F11开展跟踪,跑至脚出现异常了:
BWIN必赢亚洲56.net 11

View部分

View部分相对比较简单,毕竟才发生一个页面。View部分光关乎到个别单近乎,分别是ContainerTileView(格子)。

细检查,原来真的的问题,出现在BizBaseSQLSp.cs类库中,函数public
DataSet ExecuteDataSet(),下面是改是方法:
BWIN必赢亚洲56.net 12

TileView

格子比较简单,除了背景外只待出示一个数字。我当这里运用了SnapKit这个AutoLayout库,大家好以github上阅读以下说明。也坏推荐大家以祥和的Project中使用此库房。

class TileView: UIView {
    // 显示数字
    var valLbl: UILabel!

    // 在矩阵中的位置,row * dimension + col
    var loc: Int = -1

    // 颜色配置
    var color: ColorProvider!

    // 数值
    var val: Int = 0 {
        didSet {
            valLbl.text = "\(val)"
            backgroundColor = color.colorForValue(val)
            valLbl.textColor = color.textColorForVal(val)
        }
    }

    override init(frame: CGRect) {
        super.init(frame: frame)
        configureValLbl()
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError()
    }

    func configureBackground() {
        layer.cornerRadius = 2
    }

    func configureValLbl() {
        valLbl = UILabel()
        valLbl.font = UIFont.systemFont(ofSize: 25, weight: UIFontWeightBold)
        valLbl.textColor = .black
        valLbl.textAlignment = .center

        addSubview(valLbl)

        valLbl.snp.makeConstraints { (make) in
            make.edges.equalTo(self)
        }
    }
}

此比较简单,就未多说了。

封存,再次运行:
BWIN必赢亚洲56.net 13

Container

Container采用了针锋相对比特别之宏图方法,使得我们在倒格子的上的代码操作会比较简单。总的来说,以UIStackView也主导,在方形的UIStackView容器内,放入四只横条状的UIStackView,再于亚层UIStackView外放置方格。注意,这里放入的方格并非之后用户操作移动的带来数值的方格,而是空白的,没有数字显示的”placeholder
tile”,其作用是标志方格位置。当我们需要拿一个带来数字之格子移动至某某位置时,就管该和拖欠职务的placeholder使用Autolayout对共同起来。
对上面的叙说,诸位可以参考下面的代码来喻一下。

class Container: UIViewController {

    var data: GameModel
    var color: ColorProvider

    let tileInterval: CGFloat = 5
    let horizontalMargin: CGFloat = 20
    let tileCornerRadius: CGFloat = 4
    let boardCornerRadius: CGFloat = 8

    let panDistanceUpperThreshold: CGFloat = 20
    let panDistanceLowerThreshold: CGFloat = 10

    var board: UIStackView!
    var tileMatrx: [UIView] = []
    var foreGroundTiles: [Int: TileView] = [:]
    var scoreLbl: UILabel!
    var restartBtn: UIButton!

    var needsToBeRemoved: [UIView] = []

    init(dimension: Int, winningThreshold: Int) {
        data = GameModel(dimension: dimension, winningThreshold: winningThreshold)
        color = DefaultColorProvider()
        super.init(nibName: nil, bundle: nil)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError()
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        configureBoard()
        configureTileMatrix()
        configureScoreLbl()
        configureGestureRecognizers()
        configureRestartBtn()

        restart()
    }

    func configureRestartBtn() {
        restartBtn = UIButton()
        restartBtn.addTarget(self, action: #selector(restart), for: .touchUpInside)
        view.addSubview(restartBtn)
        restartBtn.setTitle("Restart", for: .normal)
        restartBtn.setTitleColor(.white, for: .normal)
        restartBtn.backgroundColor = color.tileBackgroundColor()
        restartBtn.layer.cornerRadius = 6
        restartBtn.snp.makeConstraints { (make) in
            make.right.equalTo(board)
            make.top.equalTo(view).offset(20)
            make.width.equalTo(70)
            make.height.equalTo(30)
        }
    }

    func configureScoreLbl() {
        scoreLbl = UILabel()
        scoreLbl.textColor = .black
        scoreLbl.font = UIFont.systemFont(ofSize: 24, weight: UIFontWeightBold)
        scoreLbl.text = "0"
        view.addSubview(scoreLbl)
        scoreLbl.snp.makeConstraints { (make) in
            make.centerX.equalTo(view)
            make.bottom.equalTo(board.snp.top).offset(-20)
        }
    }

    func configureBoard() {
        board = UIStackView()
        view.addSubview(board)
//        board.backgroundColor = color.boardBackgroundColor()
        board.alignment = .center
        board.distribution = .fillEqually
        board.axis = .vertical
        board.spacing = tileInterval

        board.snp.makeConstraints { (make) in
            make.left.equalTo(view).offset(horizontalMargin)
            make.right.equalTo(view).offset(-horizontalMargin)
            make.height.equalTo(board.snp.width)
            make.centerY.equalTo(view)
        }

        let boardBackground = UIView()
        boardBackground.backgroundColor = color.boardBackgroundColor()
        board.addSubview(boardBackground)
        boardBackground.layer.cornerRadius = boardCornerRadius
        boardBackground.snp.makeConstraints { (make) in
            make.edges.equalTo(board).inset(-tileInterval)
        }
    }

    func configureTileMatrix() {
        for _ in 0..<getDimension() {
            let stack = UIStackView()
            board.addArrangedSubview(stack)
            configureHorizontalStackViews(stack)
            for _ in 0..<getDimension() {
                let tile = createTilePlaceholder()
                stack.addArrangedSubview(tile)
                tile.snp.makeConstraints({ (make) in
                    make.height.equalTo(tile.snp.width)
                })
                tileMatrx.append(tile)
            }
        }
    }

    func configureHorizontalStackViews(_ stackView: UIStackView) {
        stackView.backgroundColor = .clear
        stackView.spacing = tileInterval
        stackView.axis = .horizontal
        stackView.alignment = .center
        stackView.snp.makeConstraints { (make) in
            make.left.equalTo(board)
            make.right.equalTo(board)
        }
    }

    func createTilePlaceholder() -> UIView {
        let tile = UIView()
        tile.backgroundColor = color.tileBackgroundColor()
        tile.layer.cornerRadius = tileCornerRadius
        return tile
    }

    func getDimension() -> Int {
        return data.dimension
    }

    func updateScore() {
        scoreLbl.text = "Score: \(data.score)"
    }

    // 创建手势识别器,用来识别用户的滑动操作
    func configureGestureRecognizers() {
        createGestureRecognizer(withDirections: [.up, .down, .right, .left]).forEach({ view.addGestureRecognizer($0) })
    }

    func createGestureRecognizer(withDirections directions: [UISwipeGestureRecognizerDirection]) -> [UIGestureRecognizer]{
        return directions.map({ (dir) -> UIGestureRecognizer in
            let swipe = UISwipeGestureRecognizer(target: self, action: #selector(swiped(_:)))
            swipe.direction = dir
            return swipe
        })
    }

    func swiped(_ swipe: UISwipeGestureRecognizer) {
        let move: MoveCommand
        switch swipe.direction {
        case UISwipeGestureRecognizerDirection.up:
            move = UpMoveCommand()
        case UISwipeGestureRecognizerDirection.down:
            move = DownMoveCommand()
        case UISwipeGestureRecognizerDirection.left:
            move = LeftMoveCommand()
        case UISwipeGestureRecognizerDirection.right:
            move = RightMoveCommand()
        default:
            fatalError()
        }
        let result = data.perform(move: move)
        print(result)
        self.move(withActions: result)
    }

    func move(withActions actions: [MoveAction]) {
        if actions.count == 0 {
            if data.userHasLost() {
                restart()
            }
            return
        }

        actions.filter({ $0.val < 0 }).forEach({ moveTile(from: data.coordinateToIndex($0.src), to: data.coordinateToIndex($0.trg)) })
        UIView.animate(withDuration: 0.1, animations: {
            self.view.layoutIfNeeded()
        })

        actions.filter({ $0.val >= 0 }).forEach({ showNewTile(at: data.coordinateToIndex($0.trg), withVal: $0.val) })


        DispatchQueue.main.asyncAfter(deadline: .now() + 0.21) {
            self.removeViewsNeededToBeRemoved()
            self.addNewRandomTile(animated: true)
            self.updateScore()
        }
    }

    func removeViewsNeededToBeRemoved() {
        for view in needsToBeRemoved {
            view.removeFromSuperview()
        }
        needsToBeRemoved.removeAll()
    }

    func moveTile(from idx1: Int, to idx2: Int) {
        guard let tileFrom = foreGroundTiles[idx1] else {
            assertionFailure()
            return
        }

        let trgTilePh = tileMatrx[idx2]
        tileFrom.snp.remakeConstraints { (make) in
            make.edges.equalTo(trgTilePh)
        }

        foreGroundTiles[idx1] = nil
        if let oldView = foreGroundTiles[idx2] {
            needsToBeRemoved.append(oldView)
        }
        foreGroundTiles[idx2] = tileFrom
    }

    func showNewTile(at idx: Int, withVal val: Int) {
        let tile = createNewTile()
        tile.val = val
        if let oldView = foreGroundTiles[idx] {
            needsToBeRemoved.append(oldView)
        }
        foreGroundTiles[idx] = tile

        let trgTilePh = tileMatrx[idx]

        view.addSubview(tile)
        tile.snp.makeConstraints { (make) in
            make.edges.equalTo(trgTilePh)
        }
        UIView.animate(withDuration: 0.1, delay: 0.05, animations: {
            tile.transform = CGAffineTransform(scaleX: 1.2, y: 1.2)
            }) { (_) in
                UIView.animate(withDuration: 0.05, animations: {
                    tile.transform = .identity
                })
        }
    }

    // MARK: - Game logic

    func restart() {
        data.clearAll()
        for (_, tile) in foreGroundTiles {
            tile.removeFromSuperview()
        }
        foreGroundTiles.removeAll()

        addNewRandomTile()
        addNewRandomTile()

        updateScore()
    }

    func addNewRandomTile(animated: Bool = false) {
        let val = data.getValueForInsert()
        let idx = data.insertTilesAtRandonPosition(with: val)
        if idx < 0 {
            return
        }
        let tile = createNewTile()
        tile.val = val
        assert(foreGroundTiles[idx] == nil)
        foreGroundTiles[idx] = tile

        let placeHolder = tileMatrx[idx]
        tile.snp.makeConstraints { (make) in
            make.edges.equalTo(placeHolder)
        }

        if animated {
            tile.transform = CGAffineTransform(scaleX: 0.2, y: 0.2)
            UIView.animate(withDuration: 0.2, animations: { 
                tile.transform = .identity
            })
        }
    }

    func createNewTile() -> TileView{
        let tile = TileView()
        tile.color = color
        view.addSubview(tile)
        tile.layer.cornerRadius = tileCornerRadius

        return tile
    }
}

于点的代码中我们还引入了片操按钮,比如更开,这有的并无困难,相信您能知晓。不过,里面关于逻辑控制的代码,可能得专门说明一下。其中最为核心的函数为move(withAction:)函数,我们将此函数以及其调用的函数单独拎出证实一下。

    // 解析一次滑动产生的`MoveAction`操作列表
    func move(withActions actions: [MoveAction]) {
        // 列表为空,那么有可能是用户已经无路可以走了
        if actions.count == 0 {
            if data.userHasLost() {
                // 这里我们是直接自动重新开始游戏了,你也可以选择弹出提示框告诉用户已经失败
                restart()
            }
            return
        }

        // `val`字段小于0的MoveAction是指纯粹的移动。将这些指令筛选出来,进行移动操作
        actions.filter({ $0.val < 0 }).forEach({ moveTile(from: data.coordinateToIndex($0.src), to: data.coordinateToIndex($0.trg)) })
        // 驱动移动动画
        UIView.animate(withDuration: 0.1, animations: {
            self.view.layoutIfNeeded()
        })

        // `val`字段非负的MoveAction是指合并后新的格子的生成。将这些指令筛选出来,并构造新的Tile
        actions.filter({ $0.val >= 0 }).forEach({ showNewTile(at: data.coordinateToIndex($0.trg), withVal: $0.val) })


        // 稍微等待一段很短的时间以后,在空格处插入一个新的格子,并且更新分数
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.21) {
            // 注意在上面的操作之后,有一些格子需要移除,主要是合并的格子,在新的格子产生以后需要将原来的两个格子去掉
            self.removeViewsNeededToBeRemoved()
            self.addNewRandomTile(animated: true)
            self.updateScore()
        }
    }

    func removeViewsNeededToBeRemoved() {
        // 需要被移除的格子被暂存在了`needsToBeRemoved`队列中
        for view in needsToBeRemoved {
            view.removeFromSuperview()
        }
        needsToBeRemoved.removeAll()
    }

    // 处理格子的移动
    func moveTile(from idx1: Int, to idx2: Int) {
        // `foreGroundTiles`是我们建立的一个由位置到格子的索引表
        guard let tileFrom = foreGroundTiles[idx1] else {
            assertionFailure()
            return
        }

        // `tileMatrix`是placeholder的索引表
        let trgTilePh = tileMatrx[idx2]

        // 移动格子
        tileFrom.snp.remakeConstraints { (make) in
            make.edges.equalTo(trgTilePh)
        }

        // 更新`foreGroundTiles`索引表
        foreGroundTiles[idx1] = nil
        // 注意,这里是为了保证在目标位置在一次操作完成后总是最多只有一个格子。
        // 设想在一次合并过程中,两个格子会一起移动到同一个目标位置,那么第二次
        // 移动执行时,会把前一个移动到这里的格子标记为需要移除
        if let oldView = foreGroundTiles[idx2] {
            needsToBeRemoved.append(oldView)
        }
        foreGroundTiles[idx2] = tileFrom
    }

    // 生成新的格子
    func showNewTile(at idx: Int, withVal val: Int) {
        let tile = createNewTile()
        tile.val = val
        // 和上面moveTile(from:to:)末尾的注释接起来。新的格子生成后,会把之前第二个移动到这里的格子标记为
        // 需要移除
        if let oldView = foreGroundTiles[idx] {
            needsToBeRemoved.append(oldView)
        }
        foreGroundTiles[idx] = tile

        let trgTilePh = tileMatrx[idx]

        view.addSubview(tile)
        // 移动格子
        tile.snp.makeConstraints { (make) in
            make.edges.equalTo(trgTilePh)
        }
        // 动画
        UIView.animate(withDuration: 0.1, delay: 0.05, animations: {
            tile.transform = CGAffineTransform(scaleX: 1.2, y: 1.2)
            }) { (_) in
                UIView.animate(withDuration: 0.05, animations: {
                    tile.transform = .identity
                })
        }
    }

从未难度,这样的话,设计网页时,可以管补偿加域与编辑同用,不必浪费在形容GridView控件内,还要定义编辑模版。

ColorProvider

立就比较简单了,直接贴代码吧,大家还能够看懂的吧。

extension UIColor {
    static func RGB(r: CGFloat, g: CGFloat, b: CGFloat, a: CGFloat) -> UIColor {
        return UIColor(red: r / 255, green: g / 255, blue: b / 255, alpha: a / 100)
    }

    static func RGB(r: CGFloat, g: CGFloat, b: CGFloat) -> UIColor {
        return UIColor.RGB(r: r, g: g, b: b, a: 100)
    }
}


protocol ColorProvider {
    func colorForValue(_ val: Int) -> UIColor
    func boardBackgroundColor() -> UIColor
    func tileBackgroundColor() -> UIColor
    func textColorForVal(_ val: Int) -> UIColor
}

class DefaultColorProvider: ColorProvider {
    private var colorMap: [Int: UIColor] = [
        2: UIColor.RGB(r: 240, g: 240, b: 240),
        4: UIColor.RGB(r: 237, g: 224, b: 200),
        8: UIColor.RGB(r: 242, g: 177, b: 121),
        16: UIColor.RGB(r: 245, g: 149, b: 99),
        32: UIColor.RGB(r: 246, g: 124, b: 95),
        64: UIColor.RGB(r: 246, g: 94, b: 59)
    ]

    func colorForValue(_ val: Int) -> UIColor {
        if let result = colorMap[val] {
            return result
        } else {
//            fatalError()
            return UIColor.red
        }
    }

    func textColorForVal(_ val: Int) -> UIColor {
        if val >= 256 {
            return UIColor.white
        } else {
            return UIColor.black
        }
    }

    func tileBackgroundColor() -> UIColor {
        return UIColor.RGB(r: 204, g: 192, b: 180)
    }

    func boardBackgroundColor() -> UIColor {
        return UIColor.RGB(r: 185, g: 171, b: 160)
    }
}

Insus.NET虽写的非是啊绝世之作,平凡中能让你从中获得对ASP.NET技术之所有提升和积累,足已。

启动APP

剩余的工作是管在AppDelegate.swift文件中添加适量的代码来启动我们的APP了:

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        self.window = UIWindow(frame: UIScreen.main.bounds)
        // Override point for customization after application launch.
        self.window!.backgroundColor = UIColor.white
        let container = Container(dimension: 4, winningThreshold: 2048)
        self.window?.rootViewController = container
        self.window!.makeKeyAndVisible()
        return true
    }

总结一下

及时首blog工程量可免聊呀,里面肯定起成百上千缺点的地方,大家遇到什么问题在评头论足里指出,我会尽快对。

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图