美文网首页swiftiOS入门开发iOS学习
iOS Apprentice中文版-从0开始学iOS开发-第六课

iOS Apprentice中文版-从0开始学iOS开发-第六课

作者: Billionfan | 来源:发表于2017-03-05 22:04 被阅读298次

    计算玩家的分数

    现在你已经实现了生成一个随机数作为目标,以及读取滑条的值,现在你应该来计算玩家的得分了。

    玩家将滑条的位置拖到离目标值越近的位置,就应该得越高的分,越偏离目标则的分应越低。

    计算每一回合的分值,你需要观察滑条的值与目标值之间的差值(也就是它们在数轴上的距离)相差多少。

    计算滑条位置和目标值的差

    一个简单的方法是对滑条的值和目标值进行相减的运算。

    不幸的是,这样可能会得到一个负值当滑条位置的值小于目标值时。

    你需要一些方法将这些可能出现的负值转换为正值,或者直接将负值加到用户的总分里去,介于后者会导致玩家秒删app,所以我们还是来完成这个转换吧。

    只是用滑条的值(currentValue)减去目标值(targetValue)的话,是解决不了这个问题的,因为targetValue总是有比currentValue大的时候。

    嗯~看来我们遇到点麻烦了

    练习:如果我要求你解决这个问题,仅仅是用语言描述不需要写代码,你会提出什么样的方案呢?不要担心电脑能不能听懂你说的话,就用口语描述吧,你现在是说给我听。

    如果是我的话,我会这样做:
    1、如果滑条的值大于目标值,这时就用滑条的值减去目标值。
    2、如果滑条的值小于目标值,这时就用目标值减去滑条的值。
    3、如果两者相等,那么差值就为0。

    这样做就不会有负数了,因为被减数总是大于等于减数。

    来算一算:

    如果滑条位置的值是60并且目标值是40,那么60-40=20.

    如果滑条位置的值是10并且目标值是30,那么30-10同样等于20.

    算法:

    刚才你想出的这种东西就叫做算法,算法就是关于一种解决一系列计算问题的机械步骤的术语。虽然我们刚才的算法极其简单,但是这是属于你在这方面踏出的第一步。

    目前世界上有许多著名的算法,比如快速排序(quicksort)用于将一些列内容进行排序,还有二进制搜索(binary search)用于将搜索这种已经排序好的列表。你可以在自己的程序中直接使用这些其他人已经发明好的算法,以便节省大量自己的脑细胞。

    然而,在我们这个游戏app中恐怕你得自己想出一些算法了。其中既有像刚才那样简单的,也有相当困难使你绝望的。不过不用担心,这正是编程的乐趣所在。

    计算机的学术领域主要就是学习算法并且简化它们。

    你可以用自己的口语描述任何算法,它们仅仅是你执行某些运算的一系列步骤。就像你用手在纸上计算什么是一样的,就像上面我们算60-40一样。但是有些复杂的算法用手永远也算不完,所以某些情况下你需要把它们写成代码,让计算机去算。

    我的建议是:如果你不知道如何让你的程序去计算,你可以先拿一张纸,在纸上写出计算的步骤,口语化的写就可以,先把电脑放在一边不要管。然后想一想这些步骤,用手算的话应该怎么去做?

    一旦你适应了这种思路,那么你在程序中为自己写一个算法就是小菜一碟了。

    你可以能会有多种方法解决我们刚才的问题,我在这里会给你展示两种,但是这次让我们用代码来表示它们:

    var difference: Int
    if currentValue > targetValue {
    difference = currentValue - targetValue
    } else if targetValue > currentValue {
    difference  = targetValue - currentValue
    } else {
    difference  = 0
    }
    

    "if"结构是一个新出现的东西。它允许你的代码做出选择,其意思和英语中的if基本相同。大体上讲,它的模式如下:

    if something is true {
    then do this
    } else if something else is true {
    then do that instead
    } else {
    do something when neither of above are true
    }
    

    if关键字后面跟的就是所谓的逻辑条件。如果这个条件被判断为真,例如currentValue大于targetValue,那么在两个花括号之间的代码就会被执行。

    然而,如果这个条件不是真的,那么电脑就会去看else if的条件是不是为真。也许这里会有多个else if,那么电脑就会从上到下的逐一的去判断每个else if的条件,直到遇到一个条件为真的为止。

    如果所有的条件都是假的,那么最后一个else括号中的代码会被执行。

    在这个简单的算法中,你首次创建了一个名为difference的局部变量用于存储计算结果。这个值只能是正整数或者0,所以它应该是Int(整数)型的:

    var difference: Int
    

    然后你对currentValue和targetValue进行减法运算。首先你决定currentValue大于targetValue的情况:

    if currentValue > targetValue {
    

    ‘>’这个左开口的三角表示大于操作符。如果currentValue变量中的值确实大于targetVlaue变量中的值,那么currentValue > targetValue这个条件就被判定为真,那么下面这行代码就会被执行:

    difference = currentValue - targetValue
    

    这里你用较大的currentValue减去较小的targetValue并且把差值存储到变量difference中。

    注意一下我是如何选择变量名称的,尽量选择那些能够清晰描述这个变量作用的词语。你经常会见到以下这种代码:

    a = b - c
    

    这看上去完全不知道它的意图,除了能看出是在做减法以外。从变量a、b、c的名称上得不到任何线索表明它们的作用。

    回到我们的Swift语句。如果currentValue小于或等于targetValue,那么上面的条件就为假了并且程序会跳过条件后面花括号内的代码,进入到下一个条件判断:

    } else if targetValue > currentValue {
    

    这里发生的事情和之前一样,除了targetValue和currentValue的位置调换了。只有当targetValue的值大于currentValue的值时,电脑才执行后面花括号内的代码:

    difference = targetValue - currentValue
    

    这一次你用targetValue减去currentValue并且将结果存储到difference变量中。

    到现在为止只剩一种情况你没有处理了,就是targetValue和currentValue相等的时候。当玩家正好将滑条拖到等于目标值当位置的时候,就会出现这种情况,完美的得分。在这种情况下difference的值为0。

    } else {
    difference = 0
    }
    

    此时,这两个数谁也不比谁大,谁也不比谁小,只给你留个一个选择,它们一定相等。

    让我们把这个算法放到动作里去。在showAlert()的顶部添加如下代码:

    @IBAction func showAlert() {
            var difference: Int
            if currentValue > targetValue {
                difference = currentValue - targetValue
            } else if targetValue > currentValue {
                difference = targetValue - currentValue
            } else {
                difference = 0
            }
            
            let message = "The value of the slider is: \(currentValue)" + "\nThe traget value is: \(targetValue)" + "\nThe difference is:\(difference)"
            . . .
        }
    

    为了观察这个算法的效果,你将difference的值显示在提醒窗口的消息里。

    运行app并且观察一下:

    提醒窗口显示difference的值

    另一种计算difference的方法

    我之前提到过,这里有其他的方法计算currentValue和targetValue的差值,并且保证其结果为正数。刚才的算法虽然工作的不错,但是它有好几行代码。我想我们应该用一些简单的方法,减少几行代码。

    新的算法思路如下:

    1、用滑条的值减去目标值(currentValue - targetValue)

    2、如果结果是负数,那么就让它乘以-1,这样把它变成正数。

    你不在费劲避免出现负数了,只是当电脑计算出负数的时候,你就将它转换为正数。

    练习:自己改造一下代码实现这个目的。线索:刚才我们的思路中包含了“如果”和“那么”这两个词,这是一个相当不错的指示,你应该在代码里使用if语句。

    你应该会得到类似下面的语句:

    var difference = currentValue - targetValue
    if difference < 0 {
    difference = difference * -1
    }
    

    这就是将新的算法简洁了当的翻译为代码的结果。

    首先你对这两个数进行相减,然后将结果存储到difference变量里。当difference小于0时,乘以-1,将它转换为正数。

    注意一下,你在一行代码里完成了创建difference变量并且将一个结算结果分配给它。完全不需要把它们写成两行,比如:

    var difference: Int
    difference = currentValue - targetValue
    

    并且在一行版本里,你也并没有告诉编译器difference是Int(整数)类型的数据。因为currentValue和targetValue都是Int型的,所以我们聪明的Swift可以自动判断currentValue-targetValue也是Int型的,那么difference当然也是Int型的。

    这个特点被称作‘类型推断’,这只是Swift众多优点中的一点。

    当你有了计算结果以后,你使用了一个if语句判断difference是否是负数(小于0)。如果是,则将这个结果乘以-1,从而得到一个正数。让我们回到difference变量。

    当你写到:

    difference = difference * -1
    

    这时电脑先将difference的值乘以-1,然后再将这个计算结果放回到difference里。所以我们看到的效果就是,通过这一运算,difference中的负值被一个正数覆盖掉了。

    这是一种常见的运算,你可以将它简写为:

    difference *= -1
    *=操作符结合了*和=两个独立的操作符,他们的计算结果是一样的
    

    其实你也可以写成下面这个样子:

    var difference = currentValue - targetValue
    if difference < 0 {
    difference = -difference
    }
    

    用负号操作符代替乘以-1,也可以保证difference绝对是一个正数,因为负负得正(如果你不相信的话,可以去问问数学专家)。

    试一试我们新的算法。将showAlert()改成下面这个样子:

    @IBAction func showAlert() {
            var difference = currentValue - targetValue
            if difference < 0 {
                difference = difference * -1
            }
          . . .
        }
    

    保存并运行一下这个新的版本,它的表现应该和之前的没有差别。电脑的计算结果并未改变,只是你的算法稍有不同。

    最后我们在推荐一个算法,使用一个函数来完成这个功能。

    你之前已经见过几次函数了:当你生成一个随机数时用到的arc4random_uniform()以及用来给滑条的值取整的lroundf()。

    为了确保计算结果为正数,你可以使用abs()这个函数。

    如果你在学校里学过数学,那么也许你依稀记得一个术语叫做‘绝对值’,就是一个不需要关心它正负号的数值。

    这正是你所需要的,并且标准函数库里给你提供了一个现成的函数,使用它你就可以将最终的解决方案缩减到一行代码里。

    let difference = abs(targetValue - currentValue)
    

    你用谁减去谁已经不在重要了。如果结果为负值,那么abs()函数会将其转换为正数。这是需要记住的一个便利的函数,你会经常用到它。

    改一下showAlert(),并且运行app试试效果:

    @IBAction func showAlert() {
            let difference = abs(targetValue - currentValue)
            
            let message = . . .
        }
    

    已经改的简单的不能再简单了。

    练习:我们还改了其他一些小地方,你注意到了吗?

    答案:你用let difference代替了var difference。

    变量(variables)和常量(constants)是有区别的,和变量不同,常量的值不可以发生变化(看名字就知道了,constants)。

    在常量这种盒子里,你只能放一次东西进去,并且不能在之后用其他东西去替换它。

    变量用关键字var定义,常量用关键字let定义。现在difference已经是一个常量了,不再是变量。

    在之前几个版本的算法里,difference的值可能会发生改变。如果为负值的话,你就要将它转换为正值。这就需要difference必须是一个变量,因为只有变量才能被分配新的值替代旧的值。

    现在你在一行代码内计算了全部所需内容,difference在得到一个值以后再也不会发生变化,所以此时最好将它定义为一个常量。(这样可以使你的意图更加清晰,并且使Swift的编译器更好的理解你的代码)

    出于同样原因,message,alert以及action都是常量(并且始终独立存在)。现在你知道为什么声明这些对象时用的都是let关键字了,因为一旦它们被赋了一个值,就再也不需要改变了。

    常量在Swift中使用非常频繁。你经常只需要暂时的保存一个值,如果在此期间这个值不需要发生变化,那么它虽好被声明为常量而不是变量。

    如何计算玩家的得分?

    现在你已经知道了滑条位置和目标值的差值,这样计算玩家的分数就简单多了。

    将showAlert()修改为下面这样:

    @IBAction func showAlert() {
            let difference = abs(targetValue - currentValue)
            let points = 100 - difference
            
            let message = "Your scored \(points) points"
            . . . 
        }
    

    你可以得到的最高分数为100,如果你正好将滑条拖到和目标值一样的位置时,此时difference为0。而里目标值越远则得分越低。

    运行app试试看你能得多少分?

    提醒窗口展示玩家当前回合的得分

    练习:因为滑条的最大值为100,最小值为1,最大差值为100 - 1 = 99。这意味着你能得到的最低分为1分。试着解释下这是为什么(这需要点数学常识)。

    累计玩家的总分

    在这个游戏里,你需要在屏幕上展示玩家得到的总分。在每一回合结束后,这个app应该添加最后一次得分到总分里并且之后更新得分的标签(score label)。

    在ViewController.swift中添加一个新的实例变量score:

    class ViewController: UIViewController {
        
        @IBOutlet weak var slider: UISlider!
        @IBOutlet weak var targetLabel: UILabel!
        
        var currentValue: Int = 50
        var targetValue: Int = 0
        var score = 0 //添加这一行
    

    这是怎么回事?和之前的两个变量不一样,你没有指定score的类型为Int。

    如果你没有指定数据类型,Swift就会使用类型推断来定位它的数据类型。因为0是一个整数,Swift就会推定score应该是Int类型的数据,并且自动将score设置为Int(整数)型。

    实际上,你也不需要指定前两个变量的数据类型:

    var currentValue = 50
    var targetValue = 0
    

    按照上图改一下这两行代码。

    感谢类型推断,你仅需要在变量没有初始值的时候指出变量的数据类型。但是大多数时候,你可以安全的让Swift自己去猜变量的类型。

    我觉得Swift的类型推断功能相当亲民!它可以明显的减少你的打字工作。

    现在来修改showAlert(),让它可以记录总分:

    @IBAction func showAlert() {
            let difference = abs(targetValue - currentValue)
            let points = 100 - difference
            score += points  //添加这一行
            
            let message = "Your scored \(points) points"
    
    

    这里没有啥新东西,你只是添加了这样一行:

    score += points
    

    其作用是将玩家每一回的的得分加到总分里去,你也可以写成下面这个样子:

    score = score + points
    

    个人而言,我喜欢+=这个版本,但是后一种版本也没问题。它们完成的工作是一样的。

    将总分显示在屏幕上

    你要做的事情和你在target label上做的一模一样:链接score label到outlet并且将score的值放到这个label的文本中。

    练习:看看在没有我的帮助下你自己能不能完成这个工作。你以前已经对target label操作过一次了,所以你应该能够在score label标签上重复一遍这个步骤。

    以下步骤都应该是你熟悉的,首先在ViewController.swift中添加这么一行:

    @IBOutlet weak var scoreLabel: UILabel!
    

    然后你打开storyboard并且将这个标签链接到这个新的scoreLabel outlet(就是写着999999的那个标签)。

    不确定如何连接到outlet?这里有好几种方法用于连接用户接口对象(user interfae objects)到view controller的outlet。

    注意:下面的1,2,3不是步骤,而是三种方法。

    1、按住ctrl点击标签(999999的那个)然后会弹出一个菜单。然后在弹出菜单上拖拽New referencing Outlet到View Controller,然后在弹出的小菜单上选择score label(我们对slider就是这样做的)。

    2、打开链接检查器标签。然后拖拽New referencing Outlet到View Controller,然后在弹出的小菜单上选择score label(我们对target label就是这样做的)。

    3、按住ctrl从View Controller(黄色图标的那个)往标签上拖(我们这次试试这种新方法);注意:直接在标签上按住ctrl拖拽到view controller是没有用的,不要弄反了。

    看到了吗,我们有多种方法用于连接outlet。

    现在scoreLabel的outlet已经有了,非常棒,你可以往这个标签的文本里写值了现在。我们应该把相关的代码写在什么地方呢?当然是updateLabels()里面了。

    回到ViewController.swift,将updateLabels()改为下面这个样子:

    func updateLabels() {
            targetLabel.text = String(targetValue)
            scoreLabel.text = String(score)
        }
    

    这里没有任何新东西,你将score,一个Int型的值转换为String型的,然后将它存储到这个标签的文本中。

    运行app并且确认无论何时你点击Hit Me时每一回合的分数都会加到总分中。

    score标签记录了玩家的总分

    关于回合:

    说到回合,当玩家开始新的一个回合的时候,你也需要将回合数进行累加。

    练习:跟踪目前回合数的值(起始为1),并且每一新回合开始时,对它进行+1,并且将这个数显示到屏幕上对应的标签里。也许我应该在这里多讲一些,再带带你,但是假如你已经理解并吸收了之前的内容的话,你已经有了足够多手段来实现这个目的,祝你好运!

    如果你已经想到这里要用一个新的实例变量,那么你已经接近成功了。你应该在源代码中添加下面这一行:

    var round = 0
    

    如果你想把数据类型的名称也加进去,也是可以的,虽然并没有必要这样做:

    var round: Int = 0
    

    再来一个outlet给这个标签:

    @IBOutlet weak var roundLabel: UILabel!
    

    和之前一样,你需要连接这个标签到Interface Builder的outlet。

    ⚠️:不要忘记这些连接
    忘记这些连接是新手常犯的一个错误,特别是对现在的你而言。
    我常常在为一个button写好outlet以及处理用户点击后的代码以后,在测试app时发现写好的代码都没有生效,然后我不得不花时间去努力的检查问题,最终发现是我忘记了连接这些buttom到outlet或者action method(动作方法)。

    最终,updateLabels()应该是这样的:

    func updateLabels() {
            roundLabel.text = String(round)
            targetLabel.text = String(targetValue)
            scoreLabel.text = String(score)
        }
    

    同时你能指出应该在哪里对round变量进行累加了吗?

    要我说的话,startNewRound()就是个理想的地方。毕竟无论何时,玩家开始新一回合的时候,你都要调用它。所以我们应该在这里对round变量进行累加。

    将startNewRound()改成下面这个样子:

    func startNewRound() {
            round += 1 \\添加这一行
            targetValue = 1 + Int(arc4random_uniform(100))
            currentValue = 50
            slider.value = Float(currentValue)
        }
    

    注意一下,你在定义round变量的时候,它的默认值为0。因此,当app启动时,它的值初始化为0。当你第一次调用startNewRound()时(在viewDidLoad()中),它被加1,这样你在屏幕上就看到第一回合的round值是1了。

    运行app试一试,无论何时你点击Hit Me按钮后,round的值都会被加1,并且显示在屏幕上。

    round标签显示当前的回合数

    你可以在04-Rounds and Score中找到源代码,如果你做出来的效果不是这样,你可以对照我的代码看看你漏掉了什么。附件请支持正版_

    希望我们的文章对你的求学之路提供了切实的帮助,由于我是个人翻译,所以进度无法太快,但是我可以保证每周至少更新一节课,同时也希望大家能心疼我一下,点击一下下方的打赏_,读者的认可,就是我最大的动力。

    相关文章

      网友评论

      • 脑子:当出现terminating with uncaught exception of type NSException
        右键点击View Controller的黄色小方块,把带黄色感叹号的链接删除重新连一次就好
      • 林水溶:感谢楼主翻译

        这节没讲什么新东西: 算法的概念.计算总分数,求绝对值....
        如果你有其他方面的编程经验.这些可以跳过了
      • 一点都不帅气哟:教程很不错,对新手很友好,慢慢跟着做。 感谢你的发布:+1:

      本文标题:iOS Apprentice中文版-从0开始学iOS开发-第六课

      本文链接:https://www.haomeiwen.com/subject/sizqwttx.html