这是我参与「第五届青训营」伴学笔记创作活动的第 4 天
本文是对Go基础语法实战案例的整理与补充。
Go基础语法实战案例——猜谜游戏
在本案例中,程序首先会生成一个介于1到100之间的随机整数,然后提示玩家进行猜测,玩家每次输入一个数字,程序会告诉玩家这个猜测的值是高于还是低于那个秘密的随机数,并且让玩家再次猜测。如果猜对了,就告诉玩家胜利并且退出程序。
生成随机数
- 用到的包
math/rand - 用到的函数
func rand.Intn(n int) int,该函数功能为返回一个半开区间[0,n)中的非负数伪随机数。如果n<=0,则报错 - 需要注意的地方,如果我们仅仅是用该函数返回一个值,会发现结果都是一样的,因为它需要一个随机数种子来生成不同的随机数序列。一般惯例用法是在程序启动的时候,用启动的时间戳来初始化随机数种子。
- 设置时间戳
用到的包
time,设置随机数rand.Seed(time.Now().UnixNano())关于这里的几个函数:
-
func (time.Time).UnixNano() int64
UnixNano返回t作为Unix时间,即从1970年1月1日UTC开始经过的纳秒数。如果Unix时间的纳秒数不能用int64表示(1678年之前或2262年之后的日期),则结果未定。注意,这意味着在零时间上调用UnixNano的结果是未定义的。其结果不取决于与t相关的位置。
-
func time.Now() time.Time
现在返回当前本地时间。
-
func rand.Seed(seed int64)
Seed使用所提供的种子值将默认的Source初始化为一个确定的状态。如果不调用Seed,生成器的行为就像用Seed(1)做种子一样。在除以2³¹-1时有相同余数的种子值会产生相同的伪随机序列。与Rand.Seed方法不同,Seed对于并发使用是安全的。
-
- 设置时间戳
用到的包
读取用户输入
每个程序执行的时候都会打开几个文件,stdin,stdout,stderr等,stdin文件可以用os.Stdin得到。然而直接操作这个文件十分不方便,我们会用bufio.NewReader把一个文件转换成一个reader变量。reader变量上会有很多用来操作一个流的操作,我们可以用ReadString方法来读取一行。如果失败,则打印错误并退出。ReadString返回的结果包含结尾的换行符,我们把换行符\n去掉,再转换成数字。
实现判断逻辑
很显然,只需要将前面读取到的用户输入与随机生成的数进行比较,如果相等就返回成功的信息,反之将大小比较的结果返回即可。
实现游戏循环
为了让游戏正常玩下去,需要加一个循环,最简单的就是放到for里,然后将return改为continue,结果正确的话break。
代码实现
package main
import (
"bufio"
"fmt"
"math/rand"
"os"
"strconv"
"strings"
"time"
)
func main() {
maxNum := 100
rand.Seed(time.Now().UnixNano())
secretNumber := rand.Intn(maxNum)
// fmt.Println("The secret number is ", secretNumber)
fmt.Println("Please input your guess")
reader := bufio.NewReader(os.Stdin)
for {
input, err := reader.ReadString('\n')
if err != nil {
fmt.Println("An error occured while reading input. Please try again", err)
continue
}
input = strings.Trim(input, "\r\n")
guess, err := strconv.Atoi(input)
if err != nil {
fmt.Println("Invalid input. Please enter an integer value")
continue
}
fmt.Println("You guess is", guess)
if guess > secretNumber {
fmt.Println("Your guess is bigger than the secret number. Please try again")
} else if guess < secretNumber {
fmt.Println("Your guess is smaller than the secret number. Please try again")
} else {
fmt.Println("Correct, you Legend!")
break
}
}
}
课后作业
修改猜谜游戏里面的最终代码,使用fmt.Scanf来简化代码`
-
先认识Go的
fmt.Scanf -
函数定义
func fmt.Scanf(format string, a ...interface{}) (n int, err error)Scanf 扫描从标准输入读取的文本,将连续的以空格分隔的值存储到由格式决定的连续参数中。它返回成功扫描的项目的数量。如果少于参数的数量,Err将报告原因。输入中的换行符必须与格式中的换行符一致。有一个例外:动词%c总是扫描输入中的下一个符文,即使它是一个空格(或制表符等)或新行。
-
遇到的问题:如果我们单纯的像C语言那样仅仅使用scanf读取数字,你会得到以下输出(以输入一次5为例)
You guess is 5
Your guess is smaller than the secret number. Please try again
You guess is 5
Your guess is smaller than the secret number. Please try again
You guess is 5
很奇怪啊,为什么我明明是和之前一样的
for循环,输入一个5后却有两遍输出呢?在查阅相关资料后发现,Go的scanf是不会无视回车的,也就是在我输入5并按回车后,它实际上是读取了5和回车两次,而第二次因为空格和%d不匹配,又因为我这里没有做错误处理,第二次读取是失败的,但是仍然进行了一次循环,所以会出现一次输入两次输出的结果。解决办法就是通过添加fmt.Scanln()去读取多余的回车符。
我的代码
package main
import (
"fmt"
"math/rand"
"time"
)
func main() {
maxNum := 100
rand.Seed(time.Now().UnixNano())
secretNumber := rand.Intn(maxNum)
var guess int
fmt.Println("Please input your guess")
for {
fmt.Scanf("%d", &guess)
fmt.Scanln()
fmt.Println("You guess is", guess)
if guess < 0 || guess >= 100 {
fmt.Println("Your guess is not within the specified range. Please try again")
} else if guess > secretNumber {
fmt.Println("Your guess is bigger than the secret number. Please try again")
} else if guess < secretNumber {
fmt.Println("Your guess is smaller than the secret number. Please try again")
} else {
fmt.Println("Correct, you Legend!")
break
}
}
}
总结
看似简单的问题实际操作起来依然有很多细节值得我们注意。