“猜数字”是一个简单的猜数字游戏,游戏生成一个secret number,然后用户尝试猜数字GUESS。
游戏应该告诉用户他/她的猜测是否是too big或too small,如果他/她猜到了秘密数字,则用户获胜并且游戏退出。
应检查用户输入是否存在无效的非数字值并警告用户。
应跟踪用户尝试的次数并在用户获胜时显示。
用户可以通过键入 退出游戏quit。
游戏应该持续运行,直到用户获胜或退出。
就像我们之前学过的那样,输入以下命令来创建一个新guess_the_number项目并将目录 (cd) 更改到其中:
cargo new guess_the_number
cd guess_the_number
我希望您查看一下cargo.toml通过新项目为您生成的文件:
# cargo.toml
[package]
name = "guess_the_number"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
现在,我们假设它cargo.toml是描述您的应用程序并列出其依赖项的地方(目前没有)。可以将其想象为 Python 的requirements.txt使用方式pip,pip install -r requiremts.txt但具有更多细节。
和往常一样,cargo它生成了一个“Hello, World”主函数供我们开始。
我们的首要任务是获取用户从标准输入输入的内容。输入以下内容:
use std::io;
fn main() {
println!("Welcome to: GUESS THE NUMBER game!");
println!("Please input your guess ...");
let mut guess = String::new();
io::stdin()
.read_line(&mut guess)
.expect("Unable to parse input!");
println!("Your guess is {guess}")
}
这是本系列中我们第一次从应用程序“导入库”。我们用use关键字来做到这一点。这里我们io从名为 的标准库导入了该库std。
use std::io;
接下来,我们需要一些地方来保存用户的猜测。因此,我们定义了guess变量:
let mut guess = String::new();
这个mut关键词并不新鲜,但却是String::new()。因为我们不知道用户会写什么,所以guess不能是字符串文字(记住?字符串文字在代码中硬编码并在编译时已知)。相反,我们使用String类型及其关联new函数,该函数在内存中创建一个大小未知的“空”位置。
⚠️下一篇文章将详细介绍 String 类型
之后,我们使用io导入的库并调用其stdin()函数,该函数返回 StdIn 的“句柄”,该句柄具有read_line读取 StdIn 的函数。有趣的是我们传递给read_line函数的内容,我们将“可变引用”传递给变量guess,以便函数可以更改其值(可变),但不获取它的所有权(引用)。
新 Rust 术语:“所有权”基本上是保证 Rust 内存安全的原因。我将在下一篇文章中详细讨论它,但现在知道默认情况下变量不能在 Rust 中来回切换作用域。这是“main”作用域和“read_line”函数作用域。
这段代码中的最后一个新部分是.expect("Unable to parse input!"). 这是因为它read_line不返回值,而是返回一个Result枚举(枚举),在本例中它有两个变体(将变体视为值),Ok并且Err. 默认情况下,如果Resultis Ok,则返回用户输入的值。否则,如果Result是Err,程序“恐慌”,显示定义的错误消息。
继续输入cargo run并验证您输入的内容是否已打印出来。
现在我们需要一个“随机”的秘密数字供用户猜测。这次我们将需要一个名为 的外部“板条箱” rand。
新的 Rust 术语:“ crate ”是 Python 的包,相当于 Rust。访问crates.io查看 Rust 的所有可用 crate。
要在您的应用程序中使用 crate,您可以将它们列在cargo.toml我们之前讨论的文件中。
[package]
name = "guess_the_number"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
rand = "0.8.5"
我们将在后面的文章中更详细地讨论 Rust 依赖关系。现在,请知道您用来cargo.toml列出依赖项。现在运行您的应用程序,这次您将看到正在下载并安装以下“crate”:
cargo了解您的依赖项cargo.toml,如果您再次运行您的应用程序,您会发现没有再次下载或安装任何内容。另一件值得注意的事情是cargo.lock驻留在项目根目录中的文件。cargo创建此文件以“锁定”依赖项版本以确保一致的构建。如果cargo找到cargo.lock,则将其用于依赖项列表而不是cargo.toml.
现在我们使用rand我们列出的板条箱,cargo.toml就像我们之前对io库所做的那样。我们的代码变成这样:
use rand::Rng;
use std::io;
fn main() {
println!("Welcome to: GUESS THE NUMBER game!");
// Generate secret number
let secret_number = rand::thread_rng().gen_range(1..=100);
println!("The secret number is {secret_number}");
println!("Please input your guess ...");
let mut guess = String::new();
io::stdin()
.read_line(&mut guess)
.expect("Unable to parse input!");
println!("Your guess is {guess}");
}
crate及其组件如何工作的细节rand现在并不重要。您需要知道的是,它用于生成 1 到 100(含)之间的随机数,如范围表达式高端之前的“=”所示1..=100。
运行代码,您每次都会看到不同的秘密数字。
我们有用户的guess和secret_number现在的,我们需要对它们进行比较。为了做到这一点,我们将导入另一个标准库组件,称为Orderingwithvariants enum,Less并且Greater我们Equal将使用关键字了解 Rust 的“模式匹配” match。
但在 Rust 中为了比较变量,它们必须是相同的类型。到目前为止,在我们的代码中,guessis 是String类型并且secret_number是i32隐式的(默认整数类型),因此两者之间的任何比较都会导致应用程序崩溃。为了解决这个问题,我们必须对解析变量的方式进行一些更改guess:
use rand::Rng;
use std::cmp::Ordering;
use std::io;
fn main() {
println!("Welcome to: GUESS THE NUMBER game!");
// Generate secret number
let secret_number = rand::thread_rng().gen_range(1..=100);
println!("The secret number is {secret_number}");
println!("Please input your guess ...");
let mut guess = String::new();
io::stdin()
.read_line(&mut guess)
.expect("Unable to parse input!");
// Shadowing
let guess: u32 = guess
.trim()
.parse()
.expect("Please input a number between 1 and 100");
println!("Your guess is {guess}");
// Pattern matching
match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small"),
Ordering::Greater => println!("Too big"),
Ordering::Equal => println!("You win!"),
}
}
你会注意到我们再次重新定义了guess。但这一次是作为一种u32类型。这在 Rust 中被称为“遮蔽”。
新的 Rust 术语:“遮蔽”是指在 Rust 中的同一作用域内重新定义变量。您可以使用遮蔽来更改变量的类型和可变性。旧的价值观被思想摧毁了!
现在猜测是 u32,而 Secret_number 也被 Rust 编译器推断为 u32,我们可以比较它们(这在 Rust 中是允许的)。 我们使用 match 关键字进行模式匹配,因为我们将对 Secret_number 的引用(还记得 read_line 函数和所有权吗?)传递给 u32 类型上的 cmp 函数关联,然后其余部分是不言自明的 。运行代码并尝试获取 匹配块中的三个打印。
连续运行游戏:您可能已经注意到,应用程序会在输入任何值时退出,并且如果用户给出了错误的猜测,游戏也不会继续请求用户猜测。 为了解决这个问题,我们将使用循环,如果用户猜对了,则“打破”循环。 此外,现在是跟踪用户尝试次数的好时机:
use rand::Rng;
use std::cmp::Ordering;
use std::io;
fn main() {
println!("Welcome to: GUESS THE NUMBER game!");
// Generate secret number
let secret_number = rand::thread_rng().gen_range(1..=100);
println!("The secret number is {secret_number}");
let mut tries = 0;
loop {
println!("Please input your guess ...");
let mut guess = String::new();
tries = 1;
io::stdin()
.read_line(&mut guess)
.expect("Unable to parse input!");
// Shadowing
let guess: u32 = guess
.trim()
.parse()
.expect("Please input a number between 1 and 100");
println!("Your guess is {guess}");
// Pattern matching
match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small"),
Ordering::Greater => println!("Too big"),
Ordering::Equal => {
println!("You win! Took you {tries} tries to guess the secret number!");
break;
}
}
}
}
现在运行代码并验证如果用户猜对并显示尝试次数,应用程序是否退出。
处理无效输入并正确退出游戏:游戏现已基本完成。我们只需要做一些用户体验增强,例如处理无效的用户输入(非数字),并在用户输入 时引入退出机制quit。
use rand::Rng;
use std::cmp::Ordering;
use std::io;
fn main() {
println!("Welcome to: GUESS THE NUMBER game!");
// Generate secret number
let secret_number = rand::thread_rng().gen_range(1..=100);
println!("The secret number is {secret_number}");
let mut tries = 0;
loop {
println!("Please input your guess ...");
let mut guess = String::new();
tries = 1;
io::stdin()
.read_line(&mut guess)
.expect("Unable to parse input!");
// If the user input "quit", the game quits.
if guess.trim().to_lowercase() == "quit" {
break;
}
// Shadowing
let guess: u32 = match guess.trim().parse() {
Ok(num) => num,
Err(_) => {
println!("Please input a number between 1 and 100");
continue;
}
};
println!("Your guess is {guess}");
// Pattern matching
match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small"),
Ordering::Greater => println!("Too big"),
Ordering::Equal => {
println!("You win! Took you {tries} tries to guess the secret number!");
break;
}
}
}
}
对于这一部分,我们在读取标准输入行后quit使用简单的方法if进行检查。guess我们还修改了阴影部分guess以使用模式匹配。如果Result枚举返回Ok,则返回数字。否则,如果返回,Err则打印警告消息并继续循环。
完成并发布游戏:最后,我们将删除打印秘密数字的行,因此完整的代码将是:
use rand::Rng;
use std::cmp::Ordering;
use std::io;
fn main() {
println!("Welcome to: GUESS THE NUMBER game!");
// Generate secret number
let secret_number = rand::thread_rng().gen_range(1..=100);
let mut tries = 0;
loop {
println!("Please input your guess ...");
let mut guess = String::new();
tries = 1;
io::stdin()
.read_line(&mut guess)
.expect("Unable to parse input!");
// If the user input "quit", the game quits.
if guess.trim().to_lowercase() == "quit" {
break;
}
// Shadowing
let guess: u32 = match guess.trim().parse() {
Ok(num) => num,
Err(_) => {
println!("Please input a number between 1 and 100");
continue;
}
};
println!("Your guess is {guess}");
// Pattern matching
match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small"),
Ordering::Greater => println!("Too big"),
Ordering::Equal => {
println!("You win! Took you {tries} tries to guess the secret number!");
break;
}
}
}
}
到目前为止,我们都是通过打字cargo build来构建我们的应用程序的。这会相对快速地构建应用程序,但放弃了一些可以使二进制文件运行得更快的优化。我们可以在 中看到生成的二进制文件target/debug。
对于发布版本,我们应该输入cargo build --release. 您会注意到构建阶段比平时花费的时间更长(我们讨论过的优化),现在我们将在target/release.
⚠️ 该--release标志也适用于cargo run
继续并输入以下内容:
cargo build --release
然后直接运行游戏就不用了cargo,如下:
# Linux
./target/release/guess_the_number
并且游戏会顺利运行。尝试从第一次尝试就猜出数字!
下一篇文章,我将开始讨论 Rust 的具体功能和特性,即“所有权”。到时候见
Copyright © 2024 妖气游戏网 www.17u1u.com All Rights Reserved