目标实现
程序首先生成一个介于 1 与 100 之间的随机整数,玩家可以猜测这个数是多少,玩家每次猜错,程序都会提示玩家所猜的数是太大或是太小,并且让玩家再次猜测,如果玩家猜对了,则告诉玩家成功并自动退出程序。
具体过程
生成随机数
生成随机数的函数在 "math/rand" 包里,使用 rand.Intn() 函数来生成随机数,其传入的参数代表了随机数的上限(下限为 1 ),但是在以往,用该函数生成随机数仍然存在问题,即生成随机数的种子不变,则生成的随机数也不变,因此需要把随机数种子与时间挂钩,即使用 time.Now().UnixNano() 获取时间戳作为随机数种子, 传入 rand.Seed() 函数,但如今后者已弃用,只需使用 rand.Intn() 函数即可生成次次不同的随机数。
读取用户输入
这里选用的是缓冲读取的方式,涉及到多个特殊功能的使用:
os.Stdin表示标准输入流,常作为文件对象被bufio.NewReader()等读取函数读取;bufio.NewReader()表示带缓冲的读取器,用于从标准输入中读取数据,返回指针;ReadString()是bufio包中的函数,从输入流读取字符串直到指定的分隔符,并返回读取到的字符串(但并非直接输出)。
相比于直接使用 fmt.Scanf() ,缓冲读取有以下几点不同:
-
行读取:
bufio.NewReader(os.Stdin)创建了一个带有缓冲区的读取器,它可以很方便地一次性读取整行输入(包括空格和换行符),并对输入进行处理。这在需要读取整行文本的情况下非常有用。而
fmt.Scanf()主要是按照格式化字符串指定的格式逐个读取数据,它对于整行文本的处理不如bufio.NewReader(os.Stdin)方便。 -
自定义解析:
bufio.NewReader(os.Stdin)允许你根据需要自定义解析用户输入。你可以使用ReadString()方法读取整行文本,然后根据自己的逻辑解析其中的数据。fmt.Scanf()提供了格式化输入,但在处理复杂输入时可能需要多次调用并使用不同的格式化字符串,可能会变得冗长和不够灵活。 -
错误处理:
bufio.NewReader(os.Stdin)提供了更灵活的错误处理。你可以检查读取错误并采取适当的措施,例如重新读取或提供错误消息。fmt.Scanf()也会返回错误,但对于复杂的输入和错误处理,bufio.NewReader(os.Stdin)通常更强大。
读取用户输入部分代码如下所示:
fmt.Println("Please input your guess")
reader := bufio.NewReader(os.Stdin)
input, err := reader.ReadString('\n') //值得注意的是,分隔符也会被读
if err != nil {
fmt.Println("An error occured while reading input. Please try again", err)
return
}
input = strings.Trim(input, "\r\n")
其中 strings.Trim() 用于除去字符串的指定前后缀,\r\n 表示回车换行,也即电脑上的
Enter 。
然后还需用 strconv.Atoi() 函数把字符串转成整型(毕竟不是 fmt.Scanf() 那样的格式化输出)。
实现判断逻辑
只需把玩家输入的数与随机数比较,用 if else 语句判断即可。
实现游戏循环
把用户输入和判断逻辑的具体实现搬到 for 的死循环里,在判断逻辑中用 continue 和 break 控制循环即可。
代码优化
如果不使用缓冲读取,则可使用 fmt.Scanf() 来简化代码,如下所示:
package main
import (
"fmt"
"math/rand"
)
func main() {
//生成随机数
maxNum := 100
secretNumber := rand.Intn(maxNum)
//用户输入、判断与循环的逻辑实现
fmt.Println("Please input your guess")
var guess int
for {
_, err := fmt.Scanf("%d\r\n", &guess)
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
}
}
}
不过个人感觉这里面还是有一些奇怪的事情发生,这是以前写 C++ 的时候一直没有注意到的,正常来说,第 17 行代码应该直接写 "%d" 就好了(至少在 C/C++ 里这样写是绝对没有问题的),但是在这里,输入一次,fmt.Scanf() 却读取了两次,然后才可以再次输入。第一次读取是正常的,而第二次读取并没有改变 guess 的值,但是第 17 行就像跳过了一样,err 也不为空了,感觉这就是读取机制的问题了。
个人认为,在 C/C++ 中,分隔符是不会被读取的,单纯起到的就是一个分隔的作用,不会对第二次输入造成影响;然而 Golang 里分隔符似乎是会被读取的,所以如果以 "%d" 格式化输入,第一次读取的确实是赋给 guess 的值,但第二次读取的就是分隔符,分隔符自然不满足 "%d" 的格式化输入,返回不为空的错误,但由于解析失败并没有新值赋给 guess ,所以 guess 的值没变。此即为一次输入两次读取的过程。
因此,解决方法就是把 %d 改成 %d\r\n,既保证了一次输入一次读取,又能将值确实地赋给 guess。