猜谜游戏
在深入学习 rust 之前,先通过一个简单的小游戏练练手。
实现一个经典的初学者编程问题:猜谜游戏。
它是这样工作的:程序将生成一个介于 1 和 100 之间的随机整数。然后它会提示玩家输入猜测。输入猜测值后,程序将指示猜测值是过低还是过高。如果猜对了,游戏将打印祝贺信息并退出。
新建项目
进入目录并使用 Cargo 创建一个新项目
cd ~/projects cargo new guessing_game cd guessing_game
目录文件结构和内容和上一章节介绍的一样只是项目名变了而已
编译运行它,依旧是一个 helloword 。
$ cargo run Compiling guessing_game v0.1.0 (/home/mshing/projects/guessing_game) Finished dev [unoptimized + debuginfo] target(s) in 4.07s Running `target/debug/guessing_game` Hello, world!
在后续的练习中,直接使用 run 命令来进行测试版本迭代,无需每次都编译。
处理猜想
猜谜游戏程序的第一部分将要求用户输入,处理该输入,并检查输入是否符合预期形式。首先,我们将允许玩家输入猜测。
文件名:src/main.rs
use std::io; fn main() { println!("猜猜数字!"); println!("请输入你猜到的数字:"); let mut guess = String::new(); io::stdin() .read_line(&mut guess) .expect("Failed to read line"); println!("你猜对了: {guess}"); }
这段代码不长,但是信息挺多的。
首先看第一行,因为要获取用户的输入然后将结果打印出来,就需要将 io
输入、输出导入代码中。
该io
库来自标准库,称为std
:
use std::io;
可以在标准库文档中看到其中的所有内容。
下面是main
函数是程序的入口:
fn main() {
fn
语法声明了一个新函数,括号 ,表示()
没有参数,大括号{
, 开始函数体。
println!
是一个将字符串打印到屏幕
println!("Guess the number!"); println!("Please input your guess.");
此代码正在打印一个提示,说明游戏是什么并请求用户输入。
再下一行是创建一个用来存储用户输入信息的变量:
let mut guess = String::new();
存储用户输入的信息
从模块 io
中调用 stdin
函数,这允许我们处理用户输入:
io::stdin() .read_line(&mut guess)
如果我们没有在程序开头导入 io
库 use std::io
,我们仍然可以通过将此函数调用编写为 std::io::stdin
。
Result使用类型处理潜在故障
.expect("Failed to read line");
我们可以把这段代码写成:
io::stdin().read_line(&mut guess).expect("Failed to read line");
但是,一长行很难阅读,因此最好将其分开。
更多的相关知识后面章节介绍。
println!使用占位符打印值
println!("You guessed: {guess}");
运行测试
$ cargo run Compiling guessing_game v0.1.0 (/home/mshing/projects/guessing_game) Finished dev [unoptimized + debuginfo] target(s) in 0.48s Running `target/debug/guessing_game` 猜猜数字! 请输入你猜到的数字: 12 你猜对了: 12
到这里完成第一部分:从键盘输入,然后打印出来。
生成秘密数字
接下来,将生成一个用户猜测的秘密数字,它是1到100之间的随机数。
Rust 的标准库中还没有包含随机数功能,但是有一个类似功能的 rand
crate 函数可以使用。
crate 是 Rust 源代码文件的集合,在构建的项目是一个二进制 crate,它是一个可执行文件。rand
crate 是一个库 crate,其中包含旨在用于其他程序且不能单独执行的代码。
需要修改Cargo.toml文件以包含rand
crate 作为依赖项
文件名:Cargo.toml
[dependencies] rand = "0.8.5"
编译项目
$ cargo build Downloaded ppv-lite86 v0.2.16 (registry `tuna`) Downloaded cfg-if v1.0.0 (registry `tuna`) Downloaded getrandom v0.2.7 (registry `tuna`) Downloaded rand v0.8.5 (registry `tuna`) Downloaded rand_core v0.6.3 (registry `tuna`) Downloaded rand_chacha v0.3.1 (registry `tuna`) Downloaded libc v0.2.126 (registry `tuna`) Downloaded 7 crates (773.8 KB) in 4.92s Compiling libc v0.2.126 Compiling cfg-if v1.0.0 Compiling ppv-lite86 v0.2.16 Compiling getrandom v0.2.7 Compiling rand_core v0.6.3 Compiling rand_chacha v0.3.1 Compiling rand v0.8.5 Compiling guessing_game v0.1.0 (/home/mshing/projects/guessing_game) Finished dev [unoptimized + debuginfo] target(s) in 10.16s
当我们包含一个外部依赖项时,Cargo 会从注册表中获取依赖项所需的所有内容的最新版本,该注册表是来自Crates.io的数据副本。Crates.io 是 Rust 生态系统中的人们发布他们的开源 Rust 项目供其他人使用的地方。
官方源速度太慢,文中的源已经换成国内的清华大学源了。
生成随机数
使用刚才引入的 rand 依赖创建随机数
文件名:src/main.rs
use std::io; use rand::Rng; fn main() { println!("猜猜数字!"); let secret_number = rand::thread_rng().gen_range(1..=100); println!("谜底数字是:{secret_number}"); println!("请输入你猜到的数字(1-100):"); let mut guess = String::new(); io::stdin() .read_line(&mut guess) .expect("Failed to read line"); println!("你猜对了: {guess}"); }
添加行use rand::Rng
。该Rng
特征定义了随机数生成器实现的方法,我们使用他定义生成(1-100)之间的随机整数。
对于打印出最后的谜底数字是好随时调试程序,最终是需要删除的,如果一开始就知道答案,那肯定就没法玩了。
多运行几次程序,可以看到随机数据:
$ cargo run Compiling guessing_game v0.1.0 (/home/mshing/projects/guessing_game) Finished dev [unoptimized + debuginfo] target(s) in 2.57s Running `target/debug/guessing_game` 猜猜数字! 谜底数字是:78 请输入你猜到的数字(1-100): 7 你猜对了: 7 $ cargo run Finished dev [unoptimized + debuginfo] target(s) in 0.01s Running `target/debug/guessing_game` 猜猜数字! 谜底数字是:92 请输入你猜到的数字(1-100): 4 你猜对了: 4
循环对比输入数字和谜底数字
首先,我们添加另一个use
语句,将标准库中调用的类型 std::cmp::Ordering
引入范围。该Ordering
类型是一个枚举,具有 Less
、Greater
和Equal
。这是比较两个值时可能出现的三种结果。
最终程序
我们新增循环输入直到答案正确为止、判断输入的是不是整数。
文件名:src/main.rs
use std::io; use rand::Rng; use std::cmp::Ordering; fn main() { println!("猜猜数字!"); let secret_number = rand::thread_rng().gen_range(1..=100); loop { println!("请输入你猜到的数字(1-100):"); let mut guess = String::new(); io::stdin() .read_line(&mut guess) .expect("Failed to read line"); println!("你猜的数字是: {guess}"); let guess: u32 = match guess.trim().parse() { Ok(num) => num, Err(_) => { println!("请确保输入的是数字,且范围是1到100的整数!"); continue; } }; match guess.cmp(&secret_number) { Ordering::Less => println!("太小了!"), Ordering::Greater => println!("太大了!"), Ordering::Equal => { println!("你赢了!") ; break; } } } }
运行结果:
$ cargo run Finished dev [unoptimized + debuginfo] target(s) in 0.01s Running `target/debug/guessing_game` 猜猜数字! 请输入你猜到的数字(1-100): 100 你猜的数字是: 100 太大了! 请输入你猜到的数字(1-100): 60 你猜的数字是: 60 太小了! 请输入你猜到的数字(1-100): asd 你猜的数字是: asd 请确保输入的是数字,且范围是1到100的整数! 请输入你猜到的数字(1-100): 80 你猜的数字是: 80 你赢了!
总结
到这里,已经成功构建了一个猜数字的游戏。
这个项目介绍了很多 Rust 新概念:let 、match、 外部crate的使用等等。接下来的学习将会详细的了解到这些概念。