游戏规则
啰嗦了两节,相信你早就按捺不住上手实践的心情了,今天我们一起来看看一个猜价格的小游戏,在Rust中该怎么实现,顺便巩固一下我们之前两节学到的知识。
我们来捋一下游戏规则:小红开了一家新的服装店,服装的价格在100-500之间不等,他举行了一个活动,凡是进店的客户都可以猜一件服装的价格,每次猜,小红都会告诉猜价格的客户猜测的价格是太低还是太高了,五次内如果猜中,就可以将这件衣服免费领回家!
🔥 关注我的公众号「哈希茶馆」一起交流更多开发技巧
新建项目
老规矩利用我们第一节做的模版直接一键三连🫣新建一个项目,边学边实践!
# 直接指定 --name 可以免除后续要输入项目名称的步骤
cargo generate EditYJ/rust-template --name guess-price-game
cd guess-price-game
pre-commit install
运行看看是否正常
好了,进入 src/main.rs
文件,开始我们愉快的编码吧!
获取猜测数据
我们首先需要接收用户的输入,以便为后续我们判断价格的高低提供依据:
// src/main.rs
use std::io;
fn main() {
println!(":) 来吧!猜猜这个衣服的价格是多少?");
println!("请输入你的猜测:");
let mut guess = String::new();
io::stdin()
.read_line(&mut guess)
.expect("获取你的猜测失败!");
println!("你猜测的价格是: {}", guess.trim());
}
看到这段代码,经历前两节的学习你应该感觉又熟悉又陌生,下面我们来一行一行的过一下这些代码。
use、main与println!
use std::io;
use
关键字类似于js
中的import
、require
的作用,这里导入了标准库std中的io库,io库中提供了很多关于输入输出相关的功能,包括接收用户输入的功能。
fn main() {
println!(":) 来吧!猜猜这个衣服的价格是多少?");
println!("请输入你的猜测:");
这三行你应该很熟悉,声明了主函数main
,利用println!
宏在控制台上打印了相关提示信息,提醒用户输入他对于衣服价格的猜测。
使用变量存储用户输入的值
let mut guess = String::new();
这里我们创建了一个guess变量,准备存储后续用户输入的价格,String::new()
看起来很奇怪,我个人觉得可以这么理解:其实这句相当于Java中某个类调用类中的静态方法,new
是String
类型的一个关联函数。至于他为什么叫new而不叫其他的名称,这就是Rust中的一个约定,new名称的函数大部分都是用来实例化一个对象的。
连起来看就是String::new()
创建了一个新的String
实例,这个实例被绑定到了一个可变的变量guess
上,接下来我们就要利用这个guess去接收用户的输入。
接收用户输入
io::stdin().read_line(&mut guess)
由于我们开头声明了use std::io
,所以这里直接可以使用io
,如果我们没有声明,那我们其实还可以写成这样:
std::io::stdin().read_line(&mut guess)
当然,聪明的你一定想到了,如果我开头这样声明use std::io::stdin
是不是可以写成这样了:
stdin().read_line(&mut guess)
答案是正确的,这么一搞你是不是就彻底明白了use
的基本用法了。
后面的read_line
方法是用来读取用户输入的行数据的,当用户输入回车符后,read_line
就会将数据存储到变量guess
中,&
表示这个参数是一个 引用,这个概念我们后面会好好讲一下的,这边就先留个印象,记住是这么写就行了。
Result
类型
如果你使用的vscode
,并且按照第一节的要求安装好rust-analyzer
插件的话,你应该能看到read_line
方法的返回值是一个Result模样的东西:
Result
是一个枚举,他是一个组合类型,他是Rust中进行错误处理的一个非常重要的数据类型,他的使用频率非常高,标准库中他的模样是这样的:
pub enum Result<T, E> {
Ok(T),
Err(E),
}
Ok(T)
包含了成功的值,Err(E)
包含了操作失败的原因或方式的信息,和String
类型一样,他的实例上也拥有各种各样的方法来帮助你处理错误。
expect
方法就是Result
上面方法的其中一个,如果Result
的实例是Ok(T)
,那他就会原样返回Ok
中的值,我们这个例子中这个usize
类型的值是用户输入到标准输入中的字节数。如果Result
的实例是Err(E)
,那程序就会立即中断,并输出对应的提示“获取你的猜测失败!”
运行一下
最后一行应该不用我说了,相信聪明的你一定会明白他是什么含义,我们现在运行cargo run
,看看程序的运行结果是什么:
好了,现在我们已经获取到了用户的输入,是时候进入下一阶段了!
生成一个随机价格
安装rand库
现在我们将安装第一个rust库来帮助我们实现生成随机数的功能,运行下面的命令安装rand库:
cargo add rand
运行完成后,我们可以看到Cargo.toml中已经多了一个依赖:
对于rand
的文档,你可以直接到crates.io查看,也可以本地运行cargo doc --open
来查看rand的文档
你还可以通过Rust官方部署的文档站doc.rs查看。
尝试生成一个随机数
use std::io;
use rand::Rng;
fn main() {
let mut rng = rand::rng();
let secret_number = rng.random_range(100..=500);
println!("秘密价格:{}", secret_number);
println!("===== 欢迎来到猜价格游戏! =====");
println!(":) 来吧!猜猜这个衣服的价格是多少?");
println!("请输入你的猜测:");
let mut guess = String::new();
io::stdin()
.read_line(&mut guess)
.expect("获取你的猜测失败!");
println!("你猜测的价格是: {}", guess.trim());
}
我们来讲一讲这段新增的代码
首先我们新增了这一行
use rand::Rng;
Rng
是一个 trait
(接口),它定义了随机数生成器应实现的方法,这里因为我们要使用这其中的random_range
,要想使用random_range
,此 trait
必须在作用域中。
然后我们新增了:
let mut rng = rand::rng();
let secret_number = rng.random_range(100..=500);
第一行代码生成了一个随机数生成器rng
,它位于当前执行线程的本地环境中,并从操作系统获取随机数生成需要的种子,然后用过rng
调用random_range
方法生成100~500之前的随机整数赋值到secret_number
。
让我们多运行几次看看:
println!("秘密价格:{}", secret_number);
打印出了随机数生成的价格,这个只是测试使用的,最终我们会删除这行,将生成的价格变成一个秘密。
比较用户猜测的和随机生成的数字
fn main() {
// ...之前的代码
println!("你猜测的价格是: {}", guess.trim());
let guess: u32 = match guess.trim().parse() {
Ok(num) => num,
Err(_) => {
println!("请输入一个有效的数字!");
return;
}
};
if guess < secret_number {
println!("你猜的价格太低了!");
} else if guess > secret_number {
println!("你猜的价格太高了!");
} else {
println!("恭喜你!你猜对了!");
}
}
因为获取的 guess 类型是String类型的,我们需要将他转成整型才好进行比较大小,我们来仔细分析一下上面的代码。
parse转换类型
let guess: u32 = match guess.trim().parse() {
Ok(num) => num,
Err(_) => {
println!("请输入一个有效的数字!");
return;
}
};
细心的同学可能发现了,这个guess
和我们之前的字符串类型的guess同名了,在其他语言中,例如js中是不允许这样做的,但是rust中是允许的,相当于guess
记忆被删除,重新做人了!
上一节我们说过if
可以当做表达式,其实match
也可以当做表达式,通过指定guess
要转成的类型u32
,然后调用parse
方法,就会自动将String
类型的guess
转成u32
类型的guess
,当parse
返回的Result
为Ok
的时候,guess
就得到了转换后的值,如果是Err
的话就会报错打印一行字"请输入一个有效的数字!"。
运行一下
后面的if比较的逻辑,相信大家都看得懂,我就不多赘述了。下面我们来运行一下cargo run
看看效果:
哈哈,我承认我第二次作弊了,直接看的答案输入的!
给予用户多次机会
无限机会
一次机会的猜测猜中的几率实在是太低了,这样搞,小红的店都要倒闭了!所以还是多要给用户一些猜测的机会,我们先实现无限机会的版本,直到用户猜测正确才会罢休!
fn main() {
// ...之前的代码
println!(":) 来吧!猜猜这个衣服的价格是多少?");
loop {
println!("请输入你的猜测:");
let mut guess = String::new();
io::stdin()
.read_line(&mut guess)
.expect("获取你的猜测失败!");
let guess: u32 = match guess.trim().parse() {
Ok(num) => num,
Err(_) => {
println!("请输入一个有效的数字!");
return;
}
};
if guess < secret_number {
println!("你猜的价格太低了!");
} else if guess > secret_number {
println!("你猜的价格太高了!");
} else {
println!("恭喜你!你猜对了!");
break;
}
}
}
loop
的作用上一节我们已经说过,他会无限循环内部的逻辑,猜对的部分我添加了break
关键字,主要作用是当用户猜测正确后我们就退出这个loop
循环。
我们运行一下看看效果:
这次我可没作弊,挡住了秘密价格进行猜测的!:)
有次数的猜测
终于到了完成小红店长完整需求的时候了,让我们使用一下上一节讲的for循环对区间的遍历方法,改造一下代码:
fn main() {
// ...之前的代码
println!(":) 来吧!猜猜这个衣服的价格是多少?");
for _ in 0..5 {
println!("请输入你的猜测:");
let mut guess = String::new();
io::stdin()
.read_line(&mut guess)
.expect("获取你的猜测失败!");
let guess: u32 = match guess.trim().parse() {
Ok(num) => num,
Err(_) => {
println!("请输入一个有效的数字!");
continue;
}
};
if guess < secret_number {
println!("你猜的价格太低了!");
} else if guess > secret_number {
println!("你猜的价格太高了!");
} else {
println!("恭喜你!你猜对了!");
return;
}
}
println!("很遗憾,你已经用完了5次机会!");
}
我将loop
换成的for _ in 0..5
,稍微优化了一下流程,如果用户没有输入有效数字的时候也算一次机会,并让用户重新输入。如果答对了直接退出程序。如果用户用完了五次机会,给出提示并退出程序。现在我们运行一下看看效果:
五次机会还是有点难的!这样既有猜测的紧张感,留住客户,也不至于让小红太亏本!
完整代码
下面我们看看完整的代码,我将5这个数字变成了常量MAX_ATTEMPTS
,方便后续修改猜测的次数。还删除了秘密价格展示,毕竟开始就知道答案就不好玩了,大家可以注意一下:
use rand::Rng;
use std::io;
const MAX_ATTEMPTS: u32 = 5;
fn main() {
let mut rng = rand::rng();
let secret_number = rng.random_range(100..=500);
println!("===== 欢迎来到猜价格游戏! =====");
println!(":) 来吧!猜猜这个衣服的价格是多少?");
for _ in 0..MAX_ATTEMPTS {
println!("请输入你的猜测:");
let mut guess = String::new();
io::stdin()
.read_line(&mut guess)
.expect("获取你的猜测失败!");
let guess: u32 = match guess.trim().parse() {
Ok(num) => num,
Err(_) => {
println!("请输入一个有效的数字!");
continue;
}
};
if guess < secret_number {
println!("你猜的价格太低了!");
} else if guess > secret_number {
println!("你猜的价格太高了!");
} else {
println!("恭喜你!你猜对了!");
return;
}
}
println!("很遗憾,你已经用完了 {MAX_ATTEMPTS} 次机会!");
}
总结
本小节我们通过实现一个猜价格小游戏项目,一起快速实践了Rust基础语法与核心特性。项目以生成随机数为起点,展示了如何使用rand
库的random_range
方法,结合Rng
特性实现100-500区间的随机定价。在用户交互环节,通过std::io
模块实现输入捕获,并运用parse()
结合match
模式匹配完成类型转换与错误处理,体现了Rust严谨的错误处理机制。
核心逻辑通过for
循环控制5次猜测机会,利用if
条件判断实现价格高低提示,最终通过常量MAX_ATTEMPTS
优化代码可维护性。整个项目融合了变量绑定、类型系统、模式匹配、错误处理等Rust核心概念,既巩固了基础语法,也展现了Rust内存安全与表达力强的优势。通过40行左右的代码实践,我们可直观感受Rust在系统编程领域的独特魅力,为后续深入学习打下坚实基础。
本节代码可参照 GitHub
下节预告
下一节我将和大家将直面Rust核心机制——所有权系统与生命周期。揭秘为何所有权能实现零成本内存安全,生命周期如何成为借用检查的关键拼图。通过内存图解与实战案例,带大家一起攻克Rust最硬核的内存管理逻辑,写出真正安全高效的代码!
🔥 关注我的公众号「哈希茶馆」一起交流更多开发技巧