008 生成随机数
生成随机数
让我们继续完成列表中下一个待办事项:生成一个随机数,并让它显示在屏幕上。
生成随机数的目的就是为了让游戏有更多的不确定性,随机生成一个目标数,然后让玩家向着这个目标数来滑动滑块。这里会涉及到一个生成随机数的方法arc4random_uniform()。
首先在游戏开始前生成一个随机数无疑是最好的方式,所以在ViewController.swift 的viewDidLoad()中添加以下代码。
targetValue = 1 + Int(arc4random_uniform(100))
目前整个viewDidLoad() 方法看起来是这样的:
override func viewDidLoad() {
super.viewDidLoad()
currentValue = lroundf(slider.value)
targetValue = 1 + Int(arc4random_uniform(100))
}
这段代码里你都做了什么?你使用了一个新的变量targetValue
,实际上你却并没有声明这个变量,不过一会你就要去定义一个变量了。你还使用了arc4random_uniform()
方法生成了一个从 0-100 的随机数。关于 1-100 的范围还是要解释一下,你通过arc4random_uniform()
能生成的最大的随机数就是99,而我们的游戏是有 100 这个值的,所以我们在arc4random_uniform()
的基础上 +1。
然而你必须要声明 targetValue
这个变量,因为在后面需要将 targetValue
和拖动滑块所得的值相比较,而不定义变量 targetValue
的话,在后面比较的过程中 Xcode 则不会知道目标值。
所以还要在ViewController.swift 中添加以下代码,声明targetValue
变量。
var targetValue: Int = 0
在声明变量的时候,一定要有一个值,你刚刚声明变量的初始值就是 0 。而这个初始值 0 则永远不会被使用,因为这个值会在 viewDidLoad()
中被 arc4random_uniform()
方法重写过。
注意
由于 Xcode 有实时纠错,所以在你还没有完成一个语句或者一个逻辑操作的时候,Xcode 会暂时的提示你关于错误的信息,不用担心,当你完成后它就会自动消失了,如果没有消失的话,那可能意味着你真的有错误了……
下面就涉及到计算的问题了,你已经在viewDidLoad()
方法中生成了随机数,并保存到了变量中,当你点击 Hit Me 按钮的时候也同时在变量中保存了滑块值。然后将这两个值拿到 showAlert()
中进行比较并显式。
- 更改**showAlert()`中的代码如下:
@IBAction func showAlert() {
let message = "The value of the slider is: \(currentValue)" + "\nThe target value is: \(targetValue)"
let alert = . . .
}
当你看到三个点构成的省略号...
的时候,意味着在这以后的内容均相同,这可以让整个教程更加的精简。
在这一步我们只是添加了一小段话,让 targetValue
的值显示出来。
- 运行 App,你将会看到如下的效果:
注意
在前面你用了加号(+)将两个不同长度的字符串连接成一组字符串。Swift 允许用户使用加号操作不同的字符,加号所做的操作效果则取决于你涉及到的数据类型,如果是两个 Int 类型的数,Swift 做的就是数学运算,如果是两个 String(字符串)的类型,就是将两个字符串连接起来。
为游戏添加轮次功能
不知道你是否已经注意到了这个问题,当你再点几次 Hit Me 按钮,弹出来的随机数都是同一个数字。原因在于生成随机数的方法在viewDidLoad()
方法中,而viewDidLoad()
方法只在加载页面时候执行一次。
所以经过我们的实际尝试发现,to-do list 中的关于随机数的表述其实应该是:在每个轮次即每次点击 Hit Me 按钮的时候就该生成一个新的随机数。
我所谓的轮次就是期初开始分数是 0 ,轮次就是 1 ,当时预设的 slider 的值是 50 ,然后等着玩家按 Hit Me 按钮,这时候这个轮次就结束了。
然后去计算每个轮次的分数到总分数,然后依次增加轮次数,并开始下一次轮次。
想要计算轮次,我们需要一个计算轮次的方法,所以在ViewController.swift中添加以下方法:
func startNewRound() {
targetValue = 1 + Int(arc4random_uniform(100))
currentValue = 50
slider.value = Float(currentValue)
}
你可以把它放在只要在类的花括号内就可以,这个方法跟之前没啥大区别,主要是你设置了新一个轮次的参数设置,一样要生成一个随机数,然后设置好滑块的初始位置为 50。
本身这个方法的内容和 viewDidLoad()
方法中的内容就是重复的,所以我们让viewDidLoad()
方法直接调用startNewRound()
就 OK 啦,这样可以让代码更加精简,修改完的代码如下所示:
override func viewDidLoad() {
super.viewDidLoad()
startNewRound()
}
你当然也需要在点击完 Hit Me 后调用 startNewRound()
:
@IBAction func showAlert() {
. . .
startNewRound()
}
在本方法最后(present...
后)调用startNewRound()
方法。
现在这个过程就已经完成了,无论是游戏的开局调用viewDidLoad()
还是点击 Hit Me 调用的showAlert()
都会让这个游戏正常运行,整个过程也就是我们之前称之为的「事件驱动」。
可能你会看到这样的代码句:
self.startNewRound()
这跟刚才的前面没有 self
的效果一样,还是会发消息给 view controller 自己。
通常我们会这样调用一个对象的方法:
reciver.methodName(parameters)
receiver
就是你要发消息的对象,如果你想发消息给自己,那么receiver
就是 self
,但是发消息给自己实在是太平常了,一般我们就省略掉 self
这个词。
继续讲关于 showAlert()
方法,理性的来讲,这不是你第一次调用 addAction()
方法,它是 UIAlertController
中的一个方法,而present()
是view controller
中的一个方法。
当你写 Swift 程序的时候,很大一部分内容就是在调用方法。
我希望你能理解「new round」模式在自己方法中的优势,如果你没有理解透彻,我们来看 viewDidLoad()
和 showAlert()
的方法代码结构如下:
override func viewDidLoad() {
super.viewDidLoad()
targetValue = 1 + Int(arc4random_uniform(100))
currentValue = 50
slider.value = Float(currentValue)
}
@IBAction func showAlert() {
. . .
targetValue = 1 + Int(arc4random_uniform(100))
currentValue = 50
slider.value = Float(currentValue)
}
你能在这段代码中看到什么?同样的一段代码被两个地方完全的复用了,当然,虽只有三行,但是在真正的代码中你面临的情况可能要多得多。而且当你的逻辑变的时候,你就要同时修改两个地方。如果这些代码是你刚刚写过,那可能你还能记得住,但是往往我们修改的时候可能都是几个星期后了,经常会对每个方法里的重复代码修改发生遗忘的现象。所以赋值长段的代码是经常容易产生 bug 的地方。这时候我们就需要将重复的代码用一个新的方法代替。起一个相关度比较高的名字,当你一瞥的时候就知道这个方法是干什么的了。但你可能不能一瞥就知道下面代码的含义:
targetValue = 1 + Int(arc4random_uniform(100))
currentValue = 50
slider.value = Float(currentValue)
按照你的逻辑,你可能会这样想:先生成一个随机数,然后重置 slider 的位置,所以,这可能是一个下一轮的功能。如果用下面的一个方法来代替就会更加的清晰啦:
startNewRound()
所以对于善用方法的优势一目了然了吧。
- 重新运行程序,验证是否每次点击按钮都会开启下一个轮次,并且每一个轮次都会产生从 1 到 100 的随机数。
你也应该注意到,每个轮次开始的时候,slider 的位置都是被重置过的。这是因为在 startNewRound()
方法中设置了 currentVlaue
为50,设置 slider 的位置跟读取 slider 的位置正好是相反的过程。
在赋值的过程中要注意,因为 slider.value
是 Float (浮点) 类型,而currentValue
的值是 Int 类型,Int 类型是不能直接赋值给 Float 类型的,所以要在赋值的时候有个转化的过程:
slider.value = Float(currentValue)
Swift 在数据类型上要比一般的编程语言都要严格,这对初学者来说并不友好,除此之外,Xcode 的编译器对于错误的提示往往也不够明确。但是记住,一般遇到像这样的错误提示「cannot assign value of type 'something' to type 'something eles'」就说明你可能在两者的数据类型上产生了冲突,解决方法就是将一个变量的数据类型转换为另一个变量的数据类型。
赶快回顾一下我们这一节讲了什么吧。
下一节我们将讲述如何将随机数生成的目标值放在 Label 上展示给用户。