三星电视”新”木桶理论,优势下到无限致才是王道

除开硬件,时尚的外观及人性化的计划这为是用户最关注的几只地方,一雅电视机如何能到的融入用户家庭环境?据第三正值数统计,超过70%的用户都要安装壁挂,然而壁挂之后电视墙顶滥、太丢人成为用户担心的题目,这为是电视在大厅被之短。为了解决此类用户之需求痛点,三星体光质量子点电视采用薄幕一线隐形式连接设计,用同样到底直径只有1.8毫米的晶莹线缆将有外接设备接驳到电视机上,配合360度四度无框以及全金属机身的极简设计,让视线中只有电视画面。同时为配合不同之家装风格,三星电视提供了画架式和雕塑式等不等之支架解决方案,为用户提供更多之取舍余地。三星QLED光质量子点电视轻盈纤薄的有机体、超小的边框设计、流畅精致的背板、正面简洁之连丝,令电视无论壁挂还是摆设,都能完美契合当下用户的住家环境。

看清用户是否胜利

此地仅需要判定matrix遭逢之顶大值是否上了加的阈值即可:

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

每当情节方面则未是三星电视的百折不挠,但是它们经过与里内容生产合作社合作,在本地化功能,视频点播业务等方面根据用户要求,进行了定制化研发,从而弥补了三星电视内容不足的短板。三星与多下互联网电视牌照方合作,引入了海量的视频节目资源,覆盖全家男女老少的两样视听需求。值得一提的是,三星QLED光质量子点电视还有着更视频收看特性,搭载芒果TV及银河互联网电视两要命牌子照方平台,内容选择上啊是一定丰富。通过“软硬”结合,多重并举的点子,三星QLED光质量子点电视成为看起像是绝非短板且有自然领先优势的大屏智能电视出品。

用户操作的意味与落实

于前一部分的辨析中我们发现,不同倾向的滑行,都好讲为多个一维问题,只是不同的滑动方向下,一维问题之解说方式,以及将顺序解出的结果还原也二维矩阵之主意不同。而一维题目之求解方法是一律的。这种特征适合为用多态的计划方式。即我们定义一个基类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)
    }
}

牵连回来文章开始,新木桶理论强调长板,并无是说短板就是足以不随便不顾。新木桶理论,盛水量不单纯只是在长板,恰当的通力合作,充分发挥每个木板的图,平衡发展,才会达标良好之盛水量。电视作为家庭游戏为主,对于中国人家发生至关重要的位置。据第三着数统计,国内当下备的5.5亿玉彩电中,有3.8亿台无法过渡互联网,即使在早就搭互联网的1.7亿令电视中,也来相当有存操作体验不理想、节目内容匮乏等问题。

View部分

View部分相对比较简单,毕竟只出一个页面。View部分就提到到一定量单近乎,分别是ContainerTileView(格子)。

文/赵宏民 媒媒哒联合创始人

前言

夫科目的源代码已经身处了自己之github主页上面:
Game2048,目前尚无放License,不过你可以任意使用本文以及Github工程中之有着源代码。

话题回到项目自己。这个项目落得,我也是运用了经典了MVC架构,即Model-View-Controller。在底下教中,我啊将基本以这个顺序来介绍代码的结构与逻辑。

比如说,人工智能就是就电视行业要去关注的一个触及。最近召开的社会风气互联网大会,参会的互联网大佬,众口一词大谈AI(人工智能)。从谷歌到百度,从腾讯到阿里,都老主张AI技术之前景发展前景。而语音助手即成为当时人工智能当中的一个枢纽,其中三星Bixby语音助手如今就运用到三星手机里,作为同样放缓学习型AI,三星Bixby还好透过强的深浅上效果以及交互式学习机制,不断学习用户的利用习惯,为用户带来前所未见的人为智能多模交互体验。如果前景三星电子能够将Bixby语音技术带来上三星电视,那的确是宏大提升了当下的人机交互方式。在电视机产品越来越同质化的今天
,能够由此人为智能拉开与同行间的距离,也能给三星星电视提前卡位,掌握大屏智能电视机行业之“话语权”。

实现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
    }
}

瞩目这个中和MovableTile的一个重中之重不又取消了src2以此特性。
对由于一个MovableTile表示的有数独格子的汇合过程(即src2未为-1),我们当地将该说为老三单子动作,分别是少单只移动与一个初的格子出现。对于只有移动如果值未发生变化的格子,我们将该MoveActionval安装成-1,对于新面世的格子,我们拿其src安装成-1。当然,如果为统一之星星只格子其中起一个从来不动,那么就单会变卦一个格子移动和一个新格子产生的MoveAction

(一)

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)
    }
}

当一个公司出同片足够长之长板的时候,再增长一个发出“完整的桶”的发现的首长,通过合作创新等办法补齐自己之短板与不足,就能够实现对前景所处行业至高点的超前占位。如今彩电行业竞争已经由单纯的硬件领域扩大及硬件、内容、智能、物联等大多单维度的全面化竞争,任何一方的不够,都起或致在连下去的竞争着居于不利地位。所以怎么能够在未来此起彼伏维持电视行业高居领导地位,是三星电视要去要思考的题目。

2048凡是IOS学习之Demo中久久的话题了。之前为让后辈们称一个有关iOS+Swift的讲座,便好出了一个。Github上倒是已经产生矣一个工
austinzheng/swift-2048
,但是最终之一模一样赖commit也早已是2015年之时光了,有些地方应早就落后了咔嚓。

对此此类题材,那三星电视是怎么化解之吧?首先,在操作经验方面,三星球全新升级移动端和电视端的强强联合,使用Smart
View手机APP就好在电视机及无缝观赏手机内容,并分享视频点播服务。另外三星电视还足以自动识别广电、IPTV机顶盒以及游戏机、蓝光DVD等多外接设备,并而经一个三星万克遥控器来支配,同时加重了语音搜索功能,可帮助用户迅速找到想只要察看的内容。而在长途通讯功能及,三星电视与QQ物联合作的舍及姣好功能升级至2.0版,并能够在QLED
TV联网应用,实现了长途图片、视频、音频、文字的通沟通,而这些内容都只是存储于腾讯云当中,为用户带来更加人性化体验的操作经验。

判定用户是否业已失败

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

  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
    }

当然,企业在装有一定领先优势地位的当儿,也不可避免存在一些行当风险以及竞争压力。要明白,木桶任何一个短板的断,都见面潜移默化及盛水量,所以公司只要开的不但是一直的加长长板,更使巩固长板,避免风险。对于电视行业来讲,2016年至今日,由于上游产业链元器件成本价格之腾,面板价格平均宽度约为40%,而面板是电视出品受资金高的组件,占所有股本近70%,电视厂商一般经过调价措施回成本上涨。例如,曾于称为“价格屠夫”的互联网电视品牌乐视分别被2016年下半年同2017年上半年通告部分电视价格上调,小米电视为本着一些机型进行价格调整。此外,如TCL、长虹等传统电视厂商也调了有的产品之价格。而三星电视掌控了上游产业链条,所以当电视原材料价格变动,显得尤为从容淡定。就拿QLED量子点技术来讲,三星电视是开创者,也是仅仅有的掌握并可生非含镉QLED面板的商店,同时为大力推动量子点电视市场份额的晋级,三星也开放量子点技术,除了自己外,还有海信、TCL和Vestel这样的追逐者。所以管当给显示面板等原材料价格转移,还是产品关键技术的改革,三星电视一直都是手握主动权的死去活来。

总一下

随即首blog工程量可免聊呀,里面肯定起多短的地方,大家遇到什么问题在评头论足里指出,我会尽快对。

(三)

以一个即兴空位插入一个即兴的值

高居程序设计中函数应当保障短小精悍的规格,为了贯彻此效应,我们加几个器函数:

// 这个函数会返回插入的位置,返回的格式为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)
    }

显而易见,OLED显示屏广泛用于手机等屏幕显示,如iPhone X所利用的就算是三星AMOLED面板,这是现阶段极其成熟、质量最好之OLED屏幕,但是这块OLED显示屏仍旧有烧屏寿命短的毛病。由于手机以及电视运用期限不同,一般手机使用于2-3年,而电视一般都是7-8年甚至还增长日子。所以三星球并从未将OLED应用叫死尺寸液晶面板显示,而是掌握了QLED技术,开发出QLED显示屏,不仅解决了OLED显示屏的有的短板弱项问题,而且用当电视及最终实现画质可以超越风俗习惯LCD产品。

在指定位置插入指定值

这个接口实现非常简单,因为咱们曾经在Matrix接近中落实了近似的接口。故在这里我们只有待调用对应之函数即可。

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

如果正是因三星电视所有QLED量子点技术,所以三星电视在同类其它电视品牌产品受兼有部分无比的预发优势地位。目前来拘禁,三星QLED光质量子点电视Q8C产品拥有“亮、久、广”的表征。具体来讲,在“亮”方面,三星QLED
TV能见高臻1500尼姑特-2000尼姑特的峰值亮度,这个亮度是寻常电视机的6加倍,是WOLED的3倍。由此带动的高对比度特性,能够被画面层次显示更丰富,明暗细节还清晰,有效提升用户之观感体验。在“久”方面,QLED换用无机材料,与WOLED中采取的有机材料不一,不见面出现就时间之缓发生降解烧屏的题目,经久耐用,显著延伸了电视机的使用寿命。而以“广”方面,三星QLED光质量子点电视是世界首台能够针对100%显色体积进行还原的电视机,具备更富又广的可视角度。而再次宽的色域,更强的亮度可以带动了更为纯粹的视觉感受。

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)
        }
    }
}

其一比较简单,就未多说了。

简言之来说,三星电子通过多年底技能沉淀,凭借在处理器芯片、储存芯片、液晶屏等上游领域的不停不断创新,借助强大的翻新研发实力同到的产业链融合优势,继续巩固了三星体电视于大屏、曲面、智能等高端电视出品之商海地位。此外,深入观察未来科技发展趋势,重视用户体验创新,落实活本地化服务,也培养了千古三星电视可以连接9年居全球电视销量居首的明地位,而以未来,我们也发出理由相信,三星电视还能够为咱带来惊叹不已的精良产品。

备工作

每当是有,我们创建工程文件,并简短梳理一下工程的组织及各个文件之来意。

(二)

启动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
    }

自从行业高达来讲,近10年来,我国电视产量一直维系平静增长态势。但是打今年以来,彩电产业加快不断下跌。据中国电子视像行业协会的数码展示,今年上半年,电视零售量2181万贵,同比下降7.3%,预计全年零售量4792万尊,同比降低5.8%。伴随着花升级,电视行业也面临着技术革新产品升级的题材。

玩耍逻辑分析

本条片段关联到的即是戏逻辑的中心了。通过对游戏规则的发现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是一个生便于实现的操作。我们只有待将需处理的数组中的非零元素按照原来的依次放到新数组里面就是可了。

而是准统计,上半年,国内55英寸以上大屏电视、智能电视机、4K超高清电视、HDR电视、量子点电视、OLED电视的市场渗透率分别上40%、85%、57%、29%、1.7%与0.2%,并呈不断增强态势,大屏液晶电视将化未来电视企业新的净收入增长点。其中量子点电视的商海渗透率增速显著,而作QLED量子点技术的创建者三星电视,更是成量子点电视的重要性推动力,在显示屏方面三星电视有自发之所有优势,这吗是三星电视的“长板”。

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] {
}

下面我们来挨家挨户个讲各个组织的法力和实现。

学的当儿,我们都任罢老师吃说过一个辩护,叫木桶理论。一个木桶最短的板子决定了木桶的盛水量。学校、老师教育我们若德、智、体、美、劳全面腾飞,在民用的就学与成长过程中,这是只很重要之辩护。但是从人口及组织又到合作社,从工业时代到本底动互联网时代,这个理论从某重程度达看,显然已稍站不住脚了。今天企业提高都于短板原理,变成长板原理–当你把桶倾斜,你晤面发现能够装最多的回决定于公的长板(核心竞争力),而当您闹了一样片长板,围绕这块长板展开布局,同时持有系统化的思维,你虽可用创新、合作、购买等方法,补足而别的短板,从而实现企业完全的飞发展。今天我们而说之三星电视就将自我的“长板”发挥到了极端。

基本数据代表 – 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来代表,并于是结构体上定义了便宜的拜会方式同工具函数。

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

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
                })
        }
    }
本着用户的左右左右滑动操作做出响应
重置游戏

重置游戏只需要拿matrix遭之数值清空即可

    func clearAll() {
        matrix.clearAll()
    }
创造项目

面就宣称,我借而你就深谙了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来设置这些靠。

当即篇教程假要你都对此Swift的为主语法只是暨Xcode的动办法有了一个比较清楚的体会。如果您还非了解这点的学问的话,建议先失读以下相关文章展开入门。