Go基础语法实战案例——猜谜游戏 | 青训营笔记

124 阅读4分钟

这是我参与「第五届青训营」伴学笔记创作活动的第 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对于并发使用是安全的。

读取用户输入

每个程序执行的时候都会打开几个文件,stdinstdoutstderr等,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
		}
	}
}

总结

看似简单的问题实际操作起来依然有很多细节值得我们注意。