前面的基础语法大概是过了一遍,现在能写一点小玩具出来了。
猜数字游戏
让程序生成一个随机数,然后人和机器交互,每次人猜一个数字,机器告诉人这个数字大了还是小了
生成随机数
代码如下:
package main
import (
"fmt"
"math/rand"
)
func main() {
maxNum := 100
secretNumber := rand.Intn(maxNum)
fmt.Println("The secret number is ", secretNumber)
}
生成随机数用到的函数需要 math/rand 这个包,然后调用其中的 Intn() 来生成随机的整数。
重复多次运行结果如下:
The secret number is 18
The secret number is 37
The secret number is 70
每次运行的结果是不一样的,而教程里重复多次这里得到的结果是一样的,估计是 Go 以前的版本(1.18)在使用这个函数的时候用的随机数种子是默认不变的,这样方便复现以前生成的随机数。现在在官方文档里是这么写的:
这个包的输出有可能很容易就被猜出来,即使你随机数种子的选取方法玩出花了也还是有这种可能性。为了在需要更安全的随机数的场景里请查阅
crypto/rand包。
文档(1.20.4)也更新了,现在 JS 也是随机数有两个版本,一个不推荐用到安全相关的地方,一个专门用在安全相关的地方。前者快,但是没有安全保证,后者要慢一些,能保证比前者更安全一些。。。
给随机数加种子
代码如下:
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)
}
按照惯例,用程序运行的时候当前时间的时间戳来当随机数种子,于是还需要引入 time 包获取当前时间。
运行结果不贴了,每次生成的随机数还是不一样的。不一样就可以了,之后就先不打印这个随机数了,等猜对了再打印点什么东西出来。
读取用户输入
代码如下:
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)
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")
guess, err := strconv.Atoi(input)
if err != nil {
fmt.Println("Invalid input. Please enter an integer value")
return
}
fmt.Println("You guess is", guess)
}
这一次引入了很多包。首先是 os 包,这个包用 Stdin 读取键盘输入。然后一般来说是用这个包进行文件操作的,也就是猜数字游戏的人机交互流程大概是人打开一个记事本文件写下自己猜的数字然后程序读取这个文件里的数字,对不对可以直接在命令行里打出来也可以在文件里打出来,然后人继续在刚才的文件里写下自己要猜的数字,如此循环下去。这样有点太麻烦了,于是引入 bufio 包用 bufio.NewReader(os.Stdin) 把输入变成只读的流,这样就可以跳过使用记事本文件的那个操作了。(就算不用记事本用其他编辑器依然是要在两个应用之间切换,麻烦)
下一步是读取输入的东西,用 ReadString() 来读取流里面的一行,然后现在知道用户输入的是什么了,但是输入的结尾有个换行符,这个要去掉,就要引入包 strings,用 Trim() 去掉不需要的字符,然后才是真正猜的数字的字符串。又需要引入 strconv 包用 Atoi() 转成数字,这样才是猜的数字,而且格式也是对的。这一步需要先把这个数字打印出来看看是不是和输入的时候一样。
运行结果如下:
The secret number is 55
Please input your guess
12
You guess is 12
结果一样就可以,有的人觉得字符串转数字这一步没必要,其实这一步是为了后面的比较,字符串和数字之间怎么比较?比较完以后程序才能提醒人猜的大了还是小了。
猜大猜小猜中
代码如下:
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)
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")
guess, err := strconv.Atoi(input)
if err != nil {
fmt.Println("Invalid input. Please enter an integer value")
return
}
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!")
}
}
就是一个比较,然后大了告诉人数字猜大了,小了告诉人数字猜小了,猜中了告诉人你真棒。这里有一个问题,猜数字只能猜一次,然后程序就退出了,再运行又是不同的随机数了,后面要改进,猜不对的话还能尝试。
运行结果如下:
The secret number is 77
Please input your guess
14
You guess is 14
Your guess is smaller than the secret number. Please try again
猜不对还有机会
代码如下:
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
}
}
}
可以看到在生成随机数之后的代码全部放在了一个循环里,这个循环还是个死循环(没跟任何条件)。猜不对后面跟的是 continue,猜对了才是 break。猜不对就只能强行退出了。
死循环里面的异常处理也和之前不同,因为之前是在 main() 函数里面,所以出错了就直接 return 了,现在是要跳到输入数字那里,也就是死循环的开头,所以全部换成了 continue。
如果程序后续功能运行的都没问题,就可以把之前输出生成的随机数那个调试用的输出注释掉。
运行结果如下:
Please input your guess
50
You guess is 50
Your guess is smaller than the secret number. Please try again
75
You guess is 75
Your guess is smaller than the secret number. Please try again
87
You guess is 87
Your guess is bigger than the secret number. Please try again
81
You guess is 81
Your guess is bigger than the secret number. Please try again
78
You guess is 78
Your guess is smaller than the secret number. Please try again
79
You guess is 79
Correct, you Legend!