这是我参与「第五届青训营 」伴学笔记创作活动的第4天
猜谜游戏
(1). 随机数生成
前面我们已经学习了Golang的基础语法,现在通过归纳之前的知识,已经可以开始完成一些具有一定功能的小程序了,这节课首先完成的是猜谜游戏实战案例。
首先我们可以实现一个简单的基础随机程序,文件命名为 main.go :
package main
import (
"fmt"
"math/rand"
)
上面导入了fmt输入输出包,以及math数学函数包的rand,用于生成伪随机数。
下面设置了最大数为100,类似于Java的 random.nextInt(100); ,它的作用是用于函数返回 0 ~ n 之间的随机数,n是 rand.Intn() 中的参数。这里用它生成一个整形数据secretNumber,然后用fmt包输出内容。
func main() {
maxNum := 100
secretNumber := rand.Intn(maxNum)
fmt.Println("The secret number is ", secretNumber)
}
我们可以通过多次运行程序观察结果发现,这个程序通过 rand.Intn(maxNum) 生成的整型数字每一次都相同,这是因为rand的种子默认值是1,且这个值没有被改变,其结果是按照这个Seed值来改变的,不能保证正确的随机,因此添加一个变化的种子才能保证随机。这一点和C++的伪随机库函数类似。
默认值为1的种子资源,会在程序每次运行时都产生确定的序列。而如果需要每次运行产生不同的序列,应使用Seed函数进行初始化,这里引入包 "time",在 rand.Seed() 函数的参数中填入 time.Now().UnixNano() 值,这个值是纳秒级别的时间戳,利用每时刻时间的唯一性来生成不同的种子。
package main
import (
"fmt"
"math/rand"
"time"
)
func main() {
maxNum := 100
rand.Seed(time.Now().UnixNano())
secretNumber := rand.Intn(maxNum)
fmt.Println("The secret number is ", secretNumber)
}
(2). 输入与输出
这是一个功能为猜数字的小程序,因此需要实现“猜”的环节,即为输入,并用输出来反应猜后的结果。 导入一些需要的包:
import (
"bufio"
"fmt"
"math/rand"
"os"
"strconv"
"strings"
"time"
)
在上一个随机数程序的基础上,加入输入的内容:
fmt.Println("请输入你要猜测的数字:")
reader := bufio.NewReader(os.Stdin)
input, err := reader.ReadString('\n')
if err != nil {
fmt.Println("读入输入数据时发生了错误,请重新运行程序", err)
return
}
input = strings.Trim(input, "\r\n")
这里还可以使用fmt.Scan()函数读入数据,但如果数据规模很大的情况下,使用该函数会造成性能问题,时间变长;使用 bufio.NewReader(os.Stdin) 可以实现快读,优化输入输出数据的速度。
bufio包的NewReader在实现时,首先进行打开文件的操作,然后使用打开的文件返回的文件句柄作为函数参数 传入NewReader,这里文件句柄采用的是操作系统的标准输入 Stdin 。
接下来使用 NewReader 返回的 reader 对象调用 ReadString() 来读取输入的字符串。
文件读取结束的标志是返回值为0,如果需要读取整个文件内容,那么应使用for循环轮询读取文件,直到n等于0为止,这里就不需要了,如果读取发生错误,则产生一个非空的err。如果发生错误,则结束提前程序运行,并输出错误提示。
在完成输入后,对输入的内容还需要进行一个规范化的操作,调用strings包中的 Trim() 函数。这个函数有两个参数,第一个参数是待处理字符串,第二个参数是要去除的字符串,它的功能是对待处理的字符串,删除其首尾连续的包含在第二个参数中的字符,若删除了字符,则返回一个新的值。这一点和Java的语法不同,Java该函数用于去除首尾算起连续的小于十进制32的所有字符(若有修改则返回新对象而不是原对象),这些是包括空格在内的ASCII字符,夹在中间的空格无法去除。
接下来进行输出操作:
guess, err := strconv.Atoi(input)
if err != nil {
fmt.Println("非法的输入,请输入整形数据")
return
}
fmt.Println("你猜的数字是:", guess)
由于上面的输入采用快读字符串的方式,但我们要的是一个数字,因此需要判断读入的字符串是否是一个规范的整形数据,不是则输出错误并结束。调用strconv包的 Atoi() 函数,用于将字符串类型转换为int类型。除了这个函数,还可以使用该包下的ParseInt函数,将字符串转为数字,这个函数有三个参数:
func ParseInt(s string, base int, bitSize int) (i int64, err error)
/*
s:源字符串,数字的字符串形式
base:s的进制,比如二进制、八进制、十进制、十六进制,填入数字0/2/8/10/16,如果这个值为0,基数由符号后面的字符串前缀是否存在来解释,2 - "0b",8 - "0"/"0o",16 - "0x",否则默认为 10。
bitSize:返回结果的bit大小,可以有0、8、16、32、64,说明是int、int8、int16、int32、int64
*/
(3). 猜数字过程
上面已经完成了随机数的生成、即为待猜测的数字;完成了输入和输出,即为猜测的数字;接下来需要告诉程序的使用者,是否猜中数字,进行条件语句判断比较:
if guess > secretNumber {
fmt.Println("你猜测的数字有点大,请再试一次")
} else if guess < secretNumber {
fmt.Println("你猜测的数字有点小,请再试一次)
} else {
fmt.Println("恭喜你,猜对了!")
}
完善程序,设置当使用者猜中数字结束游戏,若猜不中将会一直执行,直到猜中为止,添加循环和break语句、continue语句,修改语句结构,完整程序如下:
package main
import (
"bufio"
"fmt"
"math/rand"
"os"
"strconv"
"strings"
"time"
)
func main(){
maxNum := 100
rand.Seed(time.Now().UnixNano())
secretNumber := rand.Intn(maxNum)
reader := bufio.NewReader(os.Stdin)
for {
fmt.Print("请输入你要猜测的数字: ")
input, err := reader.ReadString('\n')
if err != nil {
fmt.Println("读入输入数据时发生了错误,请重新运行程序", err)
continue
}
input = strings.Trim(input, "\r\n")
guess, err := strconv.Atoi(input)
if err != nil {
fmt.Println("非法的输入,请输入整形数据")
continue
}
fmt.Println("你猜的数字是:", guess)
if guess > secretNumber {
fmt.Println("你猜测的数字有点大,请再试一次")
} else if guess < secretNumber {
fmt.Println("你猜测的数字有点小,请再试一次)
} else {
fmt.Println("恭喜你,猜对了!")
break
}
}
}