Guess Game

Guess Game

现在让我们一起用 Rust 来完成一个项目,即猜数字游戏:游戏开始后会提示用户输入数字,如果数字比生成的随机数大,那么就提示太大了,反之提示数字太小了,如果数字相同,则输出庆祝信息,然后退出游戏。 创建一个新项目 $ cargo new guesssing_game $ cd guessing_ga

现在让我们一起用 Rust 来完成一个项目,即猜数字游戏:游戏开始后会提示用户输入数字,如果数字比生成的随机数大,那么就提示太大了,反之提示数字太小了,如果数字相同,则输出庆祝信息,然后退出游戏。

创建一个新项目

$ cargo new guesssing_game
$ cd guessing_game

Cargo.toml

[package]
name = "guessing_game"
version = "0.1.0"
edition = "2021"

[dependencies]

首先打开 Cargo 项目配置文件,可以看到项目名称,项目版本和 Rust 版本信息。

main.rs

在默认的程序文件中,一如既往还是 Cargo 默认的程序:

fn main() {
	println!("Hello, world!");
}

我们接下来可以运行cargo run命令来编译运行项目。

获取用户输入

既然是让用户来猜数字的游戏,那么我们首先需要获取用户的输入:

use std::io;

fn main() {
    println!("Guess the number!");

    println!("Please input your guess.");

    let mut guess = String::new();

    io::stdin()
        .read_line(&mut guess)
        .expect("Failed to read line.");

    println!("You guessed: {}", guess);
}

程序中,包含了很多内容,我们首先从第一行看起。

use std::io;

首先,我们需要引入标准输入/输出库,来处理用户的输入/输出。

然后,创建主函数main来存放游戏的逻辑,在程序开始后分别打印:

Guess the number!

Please input your guess.

内容。

这段内容要求用户在游戏开始时输入猜想的数字。

存储用户的输入

let mut guess = String::new();

我们用这行代码来存储用户的输入。

你可能注意到,我们用let mut来定义一个新变量,这里let关键字用来声明变量,而mut关键字表示声明的变量是可修改(mutable)的。例如:

let number = 10;

这里声明的number就是一个不可变变量(immutable),可以理解为 C 语言中的 const变量。

这也是 Rust 语言在内存管理方面更严格的体现,正是这种严格的内存管理,使得 Rust 项目更少出现程序内存错误,这也是我们使用 Rust 语言的重要理由。

String::new()

回到主程序当中

let mut guess = String::new();

我们在这里调用了String::new()函数,来对guess变量赋值;这个函数返回一个字符串实例,而这个实例允许我们将字符串内容赋值给guess变量。

接收用户输入

io::stdin()
	.read_line(&mut guess)

注:

如果我们没有在程序文件开头用use引入io标准库,我们仍旧可以使用std::io::stdin来接收用户的输入。

std::io::stdin().read_line(&mut guess).expect("Failed to read line.");

如果在代码中不使用错误处理expect,程序编译时会触发警告。

生成随机数

我们在这里需要生成 0-100 的随机数。首先我们需要引入依赖:

打开Cargo.toml文件

[dependencies]
rand = "0.8.5"

依赖项目中,指定了版本号为:0.8.5的依赖项。添加保存文件后,我们重新构建项目,Cargo 会自动帮我们解决所有依赖:

cargo build                                                                       
    Updating crates.io index
     Locking 15 packages to latest compatible versions
      Adding byteorder v1.5.0
      Adding cfg-if v1.0.0
      Adding getrandom v0.2.15
      Adding libc v0.2.159
      Adding ppv-lite86 v0.2.20
      Adding proc-macro2 v1.0.86
      Adding quote v1.0.37
      Adding rand v0.8.5
      Adding rand_chacha v0.3.1
      Adding rand_core v0.6.4
      Adding syn v2.0.77
      Adding unicode-ident v1.0.13
      Adding wasi v0.11.0+wasi-snapshot-preview1 (latest: v0.13.2+wasi-0.2.1)
      Adding zerocopy v0.7.35
      Adding zerocopy-derive v0.7.35
  Downloaded libc v0.2.159
  Downloaded 1 crate (755.4 KB) in 0.69s
   Compiling proc-macro2 v1.0.86
   Compiling unicode-ident v1.0.13
   Compiling libc v0.2.159
   Compiling cfg-if v1.0.0
   Compiling byteorder v1.5.0
   Compiling getrandom v0.2.15
   Compiling rand_core v0.6.4
   Compiling quote v1.0.37
   Compiling syn v2.0.77
   Compiling zerocopy-derive v0.7.35
   Compiling zerocopy v0.7.35
   Compiling ppv-lite86 v0.2.20
   Compiling rand_chacha v0.3.1
   Compiling rand v0.8.5
   Compiling guessing_game v0.1.0 (/Users/moker/Projects/Rust-Learning/guessing_game)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 7.40s

配置文件添加依赖之后,我们需要在代码中引入:

use rand::Rng;

生成随机数:

let serect_number = rand::thread_rng().gen_range(1..=100); // 1..=100 代表从 1 到 100 的范围

将随机数和用户输入的数字进行比较

use std::cmp::Ordering;
// ...
    match guess.cmp(&secret_number) {
        Ordering::Less => println!("Too small.");
        Ordering::Greater => println!("Too big.");
        Ordering::Equal => println!("You win!");
    }

我们添加了 5 行代码,用来比较随机数和用户输入。

首先我们从标准库中引入了:cmp::Ordering,Ordering 是一种枚举类型,其提供了 Less ,Greater,Equal,用来表示cmp方法的结果。

match 是 Rust 提供的一种表达式,表达式由分支(arm)构成,一个分支包含一个用于匹配的模式(pattern)。match 的值与分支模式相匹配时,Rust 会执行对应分支的代码,在这里,如果用户输入与随机数相同,match 会执行匹配模式中,代表相同的代码,而不会执行其他。模式和分支是 Rust 中强大的功能,它体现了代码可能遇到的多种情形,并确保你没有遗漏任何处理。详细内容将在后续介绍。

我们现在检查代码,会发现报错:
Guess Game-20240926180443876.webp

这里,报错是因为 guess 变量是字符串类型,而 secret_number 变量是数字(i32,32位有符号整数)类型,类型不同,不能比较。所以,我们需要将 guess 变量转换为数字类型。

i32, a 32-bit number; u32, an unsigned 32-bit number; i64, a 64-bit number;

将字符串类型转换为数字类型:

let guess: u32 = guess.trim().parse().expect("Please type a number!");

你可能注意到,我们已经在之前的代码中声明了一个名为 guess 的变量,为什么又重新定义了一个相同名字的变量?这是因为 Rust 允许使用重新定义变量的方式,来遮蔽(shadow)之前的值,让我们可以复用变量,而不是被迫重新创建变量。在这里,我们只需要知道,这是一种在 Rust 中转换变量类型很常用的方式。

guess 为原始变量,trim 表示去除首尾的空白字符,例如'\0‘,‘\n',只留下用户输入的内容。字符串的 parse 方法将字符串解析成数字,如果报错,则输出:"Please type a number!"。因为 parse 方法只能在逻辑上将字符串转换为数字,而并非将字符转换为 ASCII 码,所以需要进行错误处理。

补全游戏逻辑

太棒了,现在游戏已经能玩了,但是如果运行,你会注意到,游戏只会运行一次,所以我们将游戏逻辑包含在循环中。

添加循环

    loop {
        let mut guess = String::new();

        println!("Please input your guess.");

        io::stdin()
            .read_line(&mut guess)
            .expect("Failed to read line.");

        let guess: u32 = guess.trim().parse().expect("Please type a number!");

        println!("You guessed: {}", guess);

        match guess.cmp(&secret_number) {
            Ordering::Less => println!("Too small."),
            Ordering::Greater => println!("Too big."),
            Ordering::Equal => println!("You win!"),
        }
    }

虽然用户随时能通过 Ctrl-c 来终止程序,但这不是一种优雅的方法,因为我们在代码中添加游戏结束逻辑。

Ordering::Equal => {
	println!("You win!");
	break;
}

处理无效输入

尽管我们的游戏已经很完善,但是如果我们尝试输入字符,程序会报错并退出,然而这并不是我们希望看到的,所以,我们现在处理无效输入,并告知用户必须输入数字。

        let guess: u32 = match guess.trim().parse() {
            Ok(num) => num,
            Err(_) => {
                println!("Please type a number!");
                continue;
            },
        };

我们将原来的 expect 调用换成 match 语句,因为 parse 方法会返回一个 Result 类型,而 Result 类型是一个包含 Ok 或 Err 成员的枚举。这里,match 模式中包含有两种分支,即 Ok 和 Err,match 将执行与 parse 方法返回值相同的分支。

Ok(num) => num 代表 match 将返回 guess 对应的数字作为结果赋值给 guess,

Err(_) => continue 代表 match 将返回 continue 作为结果,并触发程序跳过,其中,_是一个通配符,不论 Err 中包含有任何信息,都将执行第二个分支的动作,continue 意味着跳过此次循环,由此就避免了 parse 报错。

现在,我们完成了游戏的所有内容!

LICENSED UNDER CC BY-NC-SA 4.0
Comment