Skip to content
📖 Auto 编程语言(主书)本书目录

项目:猜数字游戏 ​

这是第一阶段(Phase 1)的总结项目。在前四章中,你已经学习了变量、运算符、函数、集合和控制流。现在到了把所有这些技能综合起来的时候了——我们将构建一个完整的交互式程序:猜数字游戏。

游戏规则如下:程序在 1 到 100 之间选一个秘密数字,玩家输入猜测值,程序告诉玩家是"太大"还是"太小",直到猜对为止。我们将分四个阶段来构建这个游戏,每次增加一个新功能:

  1. 读取输入 -- 从玩家那里获取猜测值并打印出来
  2. 添加循环 -- 让玩家反复猜测并获得反馈
  3. 随机数 -- 每局游戏选一个不同的秘密数字
  4. 错误处理 -- 优雅地处理无效(非数字)输入

准备工作 ​

为本章创建一个新的 Auto 项目:

console
$ automan new guessing_game
$ cd guessing_game

在编辑器中打开 src/main.at。你将在这个文件中编写所有代码。游戏的每个版本都放在这个文件中。

读取输入 ​

第一个版本还不能玩游戏——它只是让玩家输入一个值然后回显出来。这让我们学习两个基本的内置函数:input() 和 print()。

猜数字游戏——读取用户输入

让我们逐行分析这段代码。

auto
print("Guess the number!")

print 是一个内置函数,用于向终端输出文本。它接受一个 str 参数,并在末尾自动添加换行符。我们调用了两次:一次用来宣布游戏开始,一次用来提示玩家输入。

input 函数 ​

auto
var guess = input()

input() 是一个内置函数,从玩家那里读取一行文本,返回一个 str。程序会在这里暂停,等待用户输入内容并按回车键。我们将结果存储在 guess 中,用 var 声明是因为在后面的版本中我们会重新赋值。

用 + 拼接字符串 ​

auto
print("You guessed: " + guess)

+ 运算符用于拼接两个字符串。编译器会推断结果也是 str 类型。如果你写 "a" + 42,编译器会报告类型错误——你需要先把数字转换成字符串。下一节我们会看到如何用 parse_int 做反向转换。

运行程序 ​

console
$ automan run
Guess the number!
Please input your guess.
76
You guessed: 76

程序打印提示信息,等待你输入,然后把输入回显出来。虽然还算不上什么游戏,但基础已经搭好了。

注意每种目标语言处理用户输入的方式不同:

  • Rust 使用自定义的 read_line() 函数。转译器管理返回的 String。
  • Python 直接映射到内置的 input() 函数。这是最接近的对应——Python 的 input() 和 Auto 的 input() 功能完全一样。
  • C 使用 fgets() 读入固定大小的缓冲区,然后用 strcspn 去掉末尾的换行符。需要手动管理内存。
  • TypeScript 使用 readline 模块的回调 API。回调函数接收用户输入的 string。

添加循环 ​

现在玩家只能猜一次,程序就退出了。真正的游戏需要不断询问,直到玩家猜对为止。我们还需要把字符串输入转换为数字,以便与秘密数字进行比较,并给出"太大"或"太小"的反馈。

循环直到用户猜对

这里有很多新内容。我们逐一分析。

for true 无限循环 ​

auto
for true {
    // ...
}

for true 创建一个无限循环——它会一直运行,直到循环内部有代码显式停止它。在 Auto 中,for 是唯一的循环关键字,它处理所有循环模式。for true 的意思是"永远循环"。转译器将其映射为 Rust 的 loop、Python 的 while True、C 的 while (1),以及 TypeScript 的递归函数。

parse_int 函数 ​

auto
var guess = parse_int(guess_str)

input() 返回的是 str,但我们需要一个数字才能与秘密数字比较。parse_int 将字符串转换为整数。如果字符串是 "76",它返回整数 76。编译器推断 guess 的类型为 int。

注意:在这个版本中,如果玩家输入的不是数字(比如 "hello"),parse_int 会导致程序崩溃。我们会在最终版本中修复这个问题。

比较运算符和 if/else ​

auto
if guess == secret {
    print("You win!")
    break
}
if guess < secret {
    print("Too small!")
} else {
    print("Too big!")
}

我们用 == 判断相等,用 &lt; 判断小于。if 语句在条件为 true 时执行其代码块。else 块在 if 条件为 false 时执行。

break 关键字立即退出循环。当玩家猜对时,我们打印"You win!",然后执行 break,跳转到 for 块之后的代码——由于后面没有代码了,程序结束。

运行程序 ​

console
$ automan run
Guess the number!
10
You guessed: 10
Too small!
50
You guessed: 50
Too big!
42
You guessed: 42
You win!

游戏可以正常运行了。但有一个问题:秘密数字永远是 42。如果你玩两次,你已经知道答案了。让我们修复这个问题。

随机数 ​

为了让游戏可以重复游玩,我们需要每次生成不同的秘密数字。内置函数 random_int(min, max) 在给定范围内(两端包含)生成一个随机整数。

生成随机秘密数字

与代码清单 5-2 相比,唯一的变化是 main 内的第一行:

auto
var secret = random_int(1, 100)

不再硬编码 42,我们调用 random_int(1, 100) 获取 1 到 100 之间的随机整数。secret 的类型被推断为 int。我们还更新了开头的提示信息,告诉玩家数字的范围。

现在每次运行程序都会得到不同的秘密数字。试试看:

console
$ automan run
Guess the number between 1 and 100!
50
You guessed: 50
Too big!
25
You guessed: 25
Too small!
37
You guessed: 37
You win!

好多了。但还有一个问题需要解决。

错误处理 ​

如果玩家输入的不是数字会怎样?目前 parse_int("hello") 会导致程序崩溃。这可不是好的用户体验。我们应该检测到无效输入,打印一条消息,然后让玩家重新输入。

在 Auto 中,parse_int 返回一个可选整数:?int。可选值要么是一个真正的整数,要么是 none(表示"没有值")。我们可以检查 none 来处理错误情况。

带输入验证的完整猜数字游戏

这是游戏的最终版本。让我们看看新增了什么。

可选类型和 none ​

auto
var guess = parse_int(guess_str)
if guess == none {
    print("Please enter a valid number!")
    continue
}

parse_int 现在返回 ?int(读作"可选 int")而不是普通的 int。可选类型意味着值要么是一个整数,要么是 none。当玩家输入 "hello" 时,parse_int 返回 none——没有有效的整数可以提取。当玩家输入 "42" 时,它返回 42。

我们用 guess == none 检测错误情况。如果输入无效,我们打印一条消息,然后用 continue 跳过循环体的剩余部分,开始下一次迭代,再次要求玩家输入。

在第二阶段(Phase 2)中,你将学习 Auto 的完整错误处理机制——!T 类型和 解包运算符。现在,用 == none 检查是处理可选值的基本方法。

continue 关键字 ​

continue 跳过当前循环迭代的剩余部分,回到条件检查。结合 for true,它实际上就是"从循环顶部重新开始"。这非常适合跳过无效输入,而不把它算作一次猜测。

计数尝试次数 ​

auto
var attempts = 0
// ...
attempts = attempts + 1

我们添加了一个 attempts 计数器,从 0 开始,每次玩家输入有效猜测时加 1。当玩家获胜时,我们在胜利消息中包含尝试次数。注意我们只在 none 检查之后才递增 attempts,这样无效输入不会被计入。

运行最终版本 ​

console
$ automan run
Guess the number between 1 and 100!
Please input your guess.
hello
Please enter a valid number!
Please input your guess.
50
You guessed: 50
Too big!
Please input your guess.
25
You guessed: 25
Too small!
Please input your guess.
37
You guessed: 37
You win! You got it in 3 tries!

游戏处理了所有边界情况:非数字输入、过大猜测、过小猜测和正确猜测。它会计数尝试次数,给出反馈,并在玩家获胜时干净地退出。

看看每种目标语言如何处理"无效输入"检查:

  • Rust 使用 Option&lt;i64&gt;,通过 .ok()(将 Result 转换为 Option), .is_none() 进行检查,.unwrap() 提取值。
  • Python 使用 try/except ValueError 块。如果 int() 对无效输入失败, 会抛出 ValueError,except 分支来处理。
  • C 使用 strtoll(),它会设置 endptr 指向第一个非数字字符。如果 *endptr != '\0',说明输入包含非数字字符。
  • TypeScript 在 parseInt() 之后检查 isNaN(guess)。如果输入不是有效数字, parseInt 返回 NaN(Not a Number)。

总结 ​

恭喜你——你已经在 Auto 中构建了一个完整的交互式游戏!这个项目综合运用了你在第一阶段学到的所有知识:

  • 变量(var)—— secret、attempts、guess、guess_str 都使用 var 来存储游戏中会变化的值
  • 函数(fn)—— 整个游戏都在 main() 中,我们还调用了内置函数 print()、input()、parse_int() 和 random_int()
  • 运算符 —— ==、&lt;、+ 用于比较和拼接
  • 控制流 —— if/else 用于分支判断,for true 用于循环,break 退出循环,continue 跳过迭代
  • 类型 —— int、str、?int(可选整数),编译器从上下文推断类型
  • 集合 —— 虽然这个游戏没有直接使用,但你在第四章已经学习了数组、对象、映射和集合

这是第一阶段的结束。你现在已经打下了 Auto 核心语法和语义的坚实基础。在第二阶段,我们将更深入:

  • 第二阶段 引入 let 不可变绑定、is 关键字进行模式匹配、type 自定义类型、 ext 扩展方法,以及 !T 完整错误处理。
  • 第三阶段 涵盖泛型、接口、并发等更高级的语言特性。

花点时间回顾一下你学到的内容。试试自己修改猜数字游戏:改变数字范围、添加最大尝试次数、或者根据玩家猜对的快慢打印不同的消息。你已经掌握了所有需要的工具。

基于 MIT 许可发布。