这个游戏的来自Programming a Guessing Game,为了便于初学者更容易写出并尽可能多的理解,减少门槛,避免入门即放弃,对源代码做了一些修改。想看原文的也可以点击链接自行前往~
需求
在讲解如何用rust来实现一个猜数字的小游戏的时候,请允许我花几分钟时间以产品的角色来讲解下我们的需求。
猜数游戏开始的时候,首先系统会随机生成一个在一定范围内的随机整数,之后由用户在控制台输入数字,如果数字的值和之前生成的随机数相同则判定游戏成功,否则返回随机数和用户在控制台输入的数字比较结果,是大于或者小于。
预备知识
要完成这个游戏,首先我们需要具备以下几点知识:
- 如何用rust生成一个范围内的随机整数;
- 如何获取用户在控制台输入的内容,并转成整数;
- 如何向控制台输入内容;
- 如何控制代码循环,以便在用户输入错误的时候循环获取控制台输入的值,让游戏继续。
稍微在搜索引擎搜索一下,你会发现上面这些问题并不难解决。
关于生成随机数,rust中有一个专门生成随机数的库包rand可以使用,相关的api可以查阅这个文档rand::Rng,通过文档我们可以知道通过rand::thread_rng().gen_range(1..=100)就可以生成1-100以内的整数。
(这里我们需要插播两个知识点:1)范围表达式,用来快速生成一定范围内的数字,是一种特殊的类型,具体的可以参阅这个文档Range Expressions;2)rust模块引用,在rust中,我们使用第三方依赖需要先添加依赖,之后通过use引入依赖包,::在rust中是模块的路径分隔符,你可以理解成js中import路径中的/。这两个知识点我们后续在适当的时候再重点来讲解下,现在不理解可以先跳过,只需要知道刚刚那行代码可以生成范围内的随机数,然后在脑子中留下范围表达式和模块引用的概念就行了)
同样的,我们通过搜索可以知道io::stdin().read_line可以获取用户在控制台输入的内容,println可以像控制台输出内容,while可以用来控制循环(控制循环的方式不仅仅有while,这里我们挑最容易理解的来说)。
coding
有了上述的基本知识之后,我们开始做简单的coding。
首先,我们需要在项目中添加rand这个依赖。在js中我们都知道依赖是添加在package.json里面,但是rust中我们需要添加在哪呢?答案就是Cargo.toml这个文件。我们之前简单的提到过Cargo.toml就类似package.json,用来管理rust包信息,我们可以在Cargo.toml添加dependencies字段,向其加入rand依赖。
[dependencies]
rand = "0.8.5"
这里我们采用的rand版本是0.8.5。
之后我们通过use引入,并生成随机数。
use rand::Rng;
fn main() {
let secret_number = rand::thread_rng().gen_range(1..=100);
println!("secret number is {}",secret_number);
}
执行 cargo run,你将会看到以下输出:
第一步,成功。
接下来,我们加入循环。
fn main() {
let secret_number = rand::thread_rng().gen_range(1..=100);
println!("secret number is {}", secret_number);
while true {
println!("i am looping");
}
}
运行之后,控制台将会一直输出i am looping这串内容。
这样循环也搞定了,我们开始尝试获取控制台输入的内容。
use rand::Rng;
use std::io;
fn main() {
let secret_number = rand::thread_rng().gen_range(1..=100);
println!("secret number is {}", secret_number);
while true {
let mut guess = String::new();
io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");
println!("input string is {}", guess);
}
}
首先依然是通过use std::io引入io包。接着看获取输入值的部分:我们先是声明了一个变量guess,之后把guess传入read_line方法(&mut涉及到了租借的概念,我们将在后面讲解),expect则类似js中的try catch,用来做异常捕获。
运行之后,你就可以在控制台看到你输入的值了。
接下来把获取的内容转成数字,并和guess_number比较大小,就大功告成了。
use rand::Rng;
use std::io;
fn main() {
let secret_number = rand::thread_rng().gen_range(1..=100);
println!("please enter a number");
while true {
let mut guess = String::new();
io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");
let guess_number: u32 = match guess.trim().parse() {
Ok(num) => num,
Err(_) => continue,
};
println!("input number is {}", guess_number);
if guess_number == secret_number {
println!("You win!");
break;
} else if guess_number > secret_number {
println!("Too big!")
} else if guess_number < secret_number {
println!("Too small!")
}
}
}
完整的代码比我们想象的要复杂一些。数字比较的部分比较直观,基本有些开发基础的都能理解,通过==、<、>运算符来比较数字大小并输出结果,在结果相等的时候通过break来终止循环。而在字符串转数字的部分,看上去就有些费解。如果是在js中,我们可以很容易的通过parseInt做到,但是rust的代码看上去有些奇怪,前面的trim和parse都还好,分别表示去掉首尾的空格并将其处理结果通过parse方法转成数字,parse()后面跟着的内容则颇费解。这些令人费解的语法就是rust中的枚举,目前属于我们的知识盲区。
总结
虽然完成了上面的coding,但是我们却并不感到轻松,因为我们遇到了很多的知识盲区。简单来梳理下有:
- rust中包的引用;
- rust中的异常处理;
- 范围表达式;
- 枚举;
- 加上我们用的不是很熟练的循环;
接下来,我们需要继续花点时间去把这些知识点梳理清楚了。
至此,我们用rust完成了一段稍微有点意思的代码,但是过程并不轻松。我不确定你是否完全理解了代码的内容,但是没有关系,代码的过程,前期梳理知识盲区远比看懂代码本身更重要。但是我也希望你不要气馁,接着往下看,会有收获的。
rust学习的下一站,开始了。