从前端到rust:如何用rust来实现一个猜数字的小游戏

1,314 阅读5分钟

这个游戏的来自Programming a Guessing Game,为了便于初学者更容易写出并尽可能多的理解,减少门槛,避免入门即放弃,对源代码做了一些修改。想看原文的也可以点击链接自行前往~

需求

在讲解如何用rust来实现一个猜数字的小游戏的时候,请允许我花几分钟时间以产品的角色来讲解下我们的需求。

猜数游戏开始的时候,首先系统会随机生成一个在一定范围内的随机整数,之后由用户在控制台输入数字,如果数字的值和之前生成的随机数相同则判定游戏成功,否则返回随机数和用户在控制台输入的数字比较结果,是大于或者小于。

预备知识

要完成这个游戏,首先我们需要具备以下几点知识:

  1. 如何用rust生成一个范围内的随机整数;
  2. 如何获取用户在控制台输入的内容,并转成整数;
  3. 如何向控制台输入内容;
  4. 如何控制代码循环,以便在用户输入错误的时候循环获取控制台输入的值,让游戏继续。

稍微在搜索引擎搜索一下,你会发现上面这些问题并不难解决。

关于生成随机数,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,你将会看到以下输出:

image.png

第一步,成功。

接下来,我们加入循环。

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,但是我们却并不感到轻松,因为我们遇到了很多的知识盲区。简单来梳理下有:

  1. rust中包的引用;
  2. rust中的异常处理;
  3. 范围表达式;
  4. 枚举;
  5. 加上我们用的不是很熟练的循环;

接下来,我们需要继续花点时间去把这些知识点梳理清楚了。


至此,我们用rust完成了一段稍微有点意思的代码,但是过程并不轻松。我不确定你是否完全理解了代码的内容,但是没有关系,代码的过程,前期梳理知识盲区远比看懂代码本身更重要。但是我也希望你不要气馁,接着往下看,会有收获的。

rust学习的下一站,开始了。