阅读本篇文章你需要有编程逻辑的基础,本篇文章将使用 Rust 编程语言的基本的条件控制语句和随机数生成语句来实现一个两位数的基础数学加法计算命令行小游戏程序。这可能是 The Book 原书中的 猜数字 的演进版本,Rust 程序会生成两个数的数字,用户自己则计算答案输入进去,然后由程序来判题。
准备工作
在编写程序之前你的电脑必须先安装好 Rust 的基础开发环境,并且有 Cargo 支持,如果你是其他编程语言转过来的例如 C、 Java 、Python 、JavaScript 、Swift 等,此程序将会很简单不涉及复杂的操作。首先明白什么控制台输出、什么变量、知道什么是数学中的四则运算,和编程中条件控制语句和循环语句,这是看得懂本程序的前提,这个程序很简单大概 100 行代码左右,我很劲量让小白也能看得懂,整体的逻辑图如下:
在编写程序之前我们需要使用 cargo 创建一个名字为 math_game
的项目,使用命令如下:
$: cargo new math_game
Created binary (application) `math_game` package
到此创建完成了,打开项目目录下面的 src/main.rs
源文件并且开始编写程序主体逻辑代码。
主体逻辑
首先我们要定义几个全局的基础变量,这些变量是帮助为我们来控制程序主体的逻辑的变量,具体的变量的作用已经在上方的注释中说明了。值得注意的是 Rust 中默认声明一个变量是不可变的,如果想在运行的过程中改变其值需要再变量定义的时候使用 mut
关键字,mut
关键字对应着 Mutable 的意思,如下:
fn main() {
// 当前回合数
let mut count = 0;
// 需要几轮游戏
let rounds = 10;
// 用了存储分数
let mut score = 0;
// 生成两个随机数 默认值都为 0
let mut addend: u32 = 0;
let mut adding: u32 = 0;
}
接下来我们要定义两个函数,分别来处理随机数生成和并将随机数转成数学公式展示给用户,下面定义一个函数,在 Rust 中定义一个函数要使用 fn 关键字。在 Rust 因为特殊的内存管理方式,所有权规则,默认在函数之间传递变量可能互导致变量的所有权被转移,所以这里在函数签名中采用的是可变借用的方式,具体什么是所有权规则和借用规则这不是本篇文章的重点,对应 Rust 初学者来说应该关注着怎么写一个完整的小程序然后慢慢深入,函数体如下:
fn next_math(addend: &mut u32, adding: &mut u32) -> u32 {
*addend = 0;
*adding = 0;
// 直接返回2个数的和方便结果判断
*addend + *adding
}
上面定义一个名为 next_math
的函数,参数列表分别为外部传入的是存储被加数和加数的变量,返回值则是这两个数的之和。目前具体的随机数生成逻辑还没有写,如果需要随机数生成这时我们就使用第三方的 Crates,什么是 Crate ?Crate 可以认为是第三方开发者实现的一些类库,类似于 Java 中的 jar 包,而 Crates 则是一个存放这些工具库的中心仓库,类似于 Java 中央创库 Maven。使用第三方的 rand
库只需要在项目的根目录中 cargo.toml
添加对应的依赖名称:
[dependencies]
rand = "0.8.3"
添加完成之后,需要使用 cargo update
更新一下项目,更新完成之后如果没有错误,即可去代码中完善 next_math
函数的逻辑,如下:
fn next_math(addend: &mut u32, adding: &mut u32) -> u32 {
*addend = rand::thread_rng().gen_range(1..=100);
*adding = rand::thread_rng().gen_range(1..=100);
// 直接返回2个数的和方便结果判断
*addend + *adding
}
在函数中使用 rand::thread_rng().gen_range(1..=100)
即可生成 1 至 100 之间自然数,如果需要生成更大的数字只需要调整范围即可,最后如果不写 return 表示最后一行的结果作为返回值,返回值类型为 u32 类型,这里返回值充当着两数之和并且返回给上层调用程序使用。
接下来要编写一个提问函数 question
向用户展示计算公式要求计算公式,传入刚才通过 next_math
函数生成的数字,并且传入题目的索引编号,如下:
// 向用户展示计算公式要求计算公式
fn question(addend: &mut u32, adding: &mut u32, index: &mut u32) {
println!("🤔: 第{index}题为 {addend} + {adding} = ? 请输入正确结果?", index = *index + 1, addend = addend, adding = adding);
}
这里我们使用了 println!
这是一个 Rust 内置的宏,可以将字符串格式化输出,在前段字符串中的 {} 则为后面所需要传入的变量名称占位符。接下来我们可以编写程序主体逻辑了,要让用户不断输入信息,必须使用循环控制语句来编写,在 Rust 中有多种循环语句,这里我们使用的是 while
循环语句,当条件满足时会进入循环体执行里面逻辑,当不满足时则跳出了循环体执行剩下的代码逻辑,代码逻辑如下:
use std::cmp::Ordering;
use rand::Rng;
use std::io;
fn main() {
// 当前回合数
let mut count = 0;
// 需要几轮游戏
let rounds = 10;
// 用了存储分数
let mut score = 0;
// 生成两个随机数 默认值都为 0
let mut addend: u32 = 0;
let mut adding: u32 = 0;
println!("😄: 让我们开始游戏吧! 一轮游戏为10个回合,每题10分,总分100分!");
// 这里我们使用 while 制造一个循环
while count < rounds {
// 开始生成题目并且出题,将随机数加起来计算总和数
let sum = next_math(&mut addend, &mut adding);
// 向控制输出数学问题信息
question(&mut addend, &mut adding, &mut count);
// 用来存储用户控制台输入的变量
let mut guess = String::new();
// 从控制台上读入输入字符串
io::stdin()
.read_line(&mut guess)
.expect("😠: 你能不能好好输!请输入正数!");
// 解析输入的值
let guess: u32 = match guess.trim().parse() {
Ok(num) => num,
// 如果非法设置默认值
Err(_) => 0,
};
match guess.cmp(&sum) {
Ordering::Less => {
println!("❌: 你的答案太小了!正确答案是 {sum}!", sum = sum);
}
Ordering::Greater => {
println!("❌: 你的答案太大了!正确答案是 {sum}!", sum = sum);
}
Ordering::Equal => {
score += 10;
println!("✅: 恭喜你!答案正确!加10分!");
}
};
// 添加轮数将其加一
count += 1;
}
println!("🥳: 本轮游戏结束!你的分数为 {score} !{exp}",
score = score,
exp = if score >= 60
{ "成绩合格!" }
else
{ "成绩不合格!" })
}
fn next_math(addend: &mut u32, adding: &mut u32) -> u32 {
*addend = rand::thread_rng().gen_range(1..=100);
*adding = rand::thread_rng().gen_range(1..=100);
// 直接返回2个数的和方便结果判断
*addend + *adding
}
// 向用户展示计算公式要求计算结构
fn question(addend: &mut u32, adding: &mut u32, index: &mut u32) {
println!("🤔: 第{index}题为 {addend} + {adding} = ? 请输入正确结果?", index = *index + 1, addend = addend, adding = adding);
}
这里跟要关注的是主体代码逻辑里面函数作用,要让用户输入信息那就必须使用系统调用从控制台中读入输入的数据,使用两段代码逻辑完成,如下:
// 用来存储用户控制台输入的变量
let mut guess = String::new();
// 从控制台上读入输入字符串
io::stdin()
.read_line(&mut guess)
.expect("😠: 你能不能好好输!请输入正数!");
第一段中的 guess
为用来存储临时用户输入的字符串信息,第二段逻辑则是通过标准库中的 io
包从标准输入中读入一行输入数据,而 expect
则防止用户输入的信息是非法,如果非法程序就提示该错误信息,非常容易理解。
// 解析输入的值
let guess: u32 = match guess.trim().parse() {
Ok(num) => num,
// 如果非法设置默认值
Err(_) => 0,
};
match guess.cmp(&sum) {
Ordering::Less => {
println!("❌: 你的答案太小了!正确答案是 {sum}!", sum = sum);
}
Ordering::Greater => {
println!("❌: 你的答案太大了!正确答案是 {sum}!", sum = sum);
}
Ordering::Equal => {
score += 10;
println!("✅: 恭喜你!答案正确!加10分!");
}
};
最后我们得到用户输入的字符串,这时我们需要将字符串转成数字,将字符串转成数字的方式有很多种,这里使用的具有 Rust 特性的模式匹配的方式,如果字符串转换成功将执行 Ok()
返回正常的数字,而如果字符串不能成功转换成数字则会走 Err(_)
,这里如果转换错误就使用默认值 0 作为用户输入的结果。
最后我们再次使用了模式匹配,将用户输入的结果和计算机计算的结果进行比对,在程序的头部我们增加了另一个 use 声明,从标准库引入了一个叫做 std::cmp::Ordering 的类型到作用域中。 Ordering 也是一个枚举,不过它的成员是 Less、Greater 和 Equal。这是比较两个值时可能出现的三种结果,接着,底部的五行新代码使用了 Ordering 类型,cmp 方法用来比较两个值并可以在任何可比较的值上调用。它获取一个被比较值的引用:这里是把 guess 与 sum 做比较。 然后它会返回一个刚才通过 use 引入作用域的 Ordering 枚举的成员。使用一个 match 表达式,根据对 guess 和 sum 调用 cmp 返回的 Ordering 成员来决定接下来做什么。
有了上面的解释就很容易明白这段代码的逻辑了,用户输入正确的答案就将分数加 10 分,如果不正常可以告诉时候离准确的答案的差值或者大小,这里的使用的大小,最后完成整个程序的逻辑并且将游戏轮数加 1 回合,当循环体执行完成打印分数信息并且展示到控制台中。
println!(
"🥳: 本轮游戏结束!你的分数为 {score} !{exp}",
score = score,
exp = if score >= 60 {
"成绩合格!"
} else {
"成绩不合格!"
}
)
因为 Rust 没有三目表达式,这里我采用的是 if 语句块来控制具体的分数合格信息的输出。
小 结
至此我们就完成一个 Rust 小游戏的编写工作,代码其实一点都不复杂,主要是将思路转换成具体代码实现,在线运行此小程序地址 https://github.com/auula/math_game/blob/master/src/main.rs