Skip to content

2. 猜数字游戏

2.1 代码结构

rust
// prelude,导入标准库
use std::io;

fn main() {
    println!("猜数游戏!");

    // 可变变量接收用户输入
    let mut guess = String::new();
    io::stdin().read_line(&mut guess).expect("读取行失败");
    println!("你猜测的数字是:{}", guess);
}

上述代码用于实现读取用户输入的功能,io::stdin().read_line(&mut guess) 用于读取用户输入的内容,&mut guess 表示将用户输入的内容存储到 guess 变量中。

2.2 使用 rand 库生成随机数

由于 Rust 并没有提供生成随机数的标准库,我们需要使用第三方库 rand 来生成随机数。

Cargo.toml 中添加 rand 库的依赖:

toml
[dependencies]
rand = "^0.8.5"

修改 main.rs 文件:

rust
use rand::Rng;
use std::io;

fn main() {
    println!("猜数游戏!");
    let secret_number = rand::thread_rng().gen_range(1..101);

    loop {
        let mut guess = String::new();
        io::stdin().read_line(&mut guess).expect("读取行失败");
        let guess: u32 = guess.trim().parse().expect("请输入一个数字");

        println!("你猜测的数字是:{}", guess);

        match guess.cmp(&secret_number) {
            std::cmp::Ordering::Less => println!("太小了!"),
            std::cmp::Ordering::Greater => println!("太大了!"),
            std::cmp::Ordering::Equal => {
                println!("你赢了!");
                break;
            },
        }
    }
}

我们还可以使用 match 语句对错误处理进行一些优化:

rust
use rand::Rng;
use std::io;
use std::io::Write;

fn main() {
    println!("猜数游戏!");
    let secret_number = rand::thread_rng().gen_range(1..101);

    loop {
        print!("请输入数字:");
        io::stdout().flush().unwrap();

        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;
            },
        };

        println!("你猜测的数字是:{}", guess);

        match guess.cmp(&secret_number) {
            std::cmp::Ordering::Less => println!("太小了!"),
            std::cmp::Ordering::Greater => println!("太大了!"),
            std::cmp::Ordering::Equal => {
                println!("你赢了!");
                break;
            }
        }
    }
}

我们先解释一些语句。

use 语句用于导入库,rand::Rng 是一个第三方库,用于生成随机数。

rust
use rand::Rng;
use std::io;

let 语句用于声明变量,使用 mut 关键字声明可变变量(Mutable Variable)。

1..101 是一个范围(Range),表示从 1 到 100(不包含 101)的范围。这是 Rust 的一个新语法区间表达式,如果想包含 101 可以写作 1..=100

parse().expect("请输入一个数字"); 是一个错误处理行为,如果用户输入的不是数字,程序会抛出异常。

.parse() 在此返回一个 Result<u32, ParseIntError> 类型,Result 是一个枚举类型,Ok 表示成功,Err 表示失败。

2.3 深入理解猜数字游戏

让我们深入理解上面代码中的一些关键概念:

变量遮蔽(Shadowing)

在代码中,我们使用了变量遮蔽:

rust
let mut guess = String::new();
// ...
let guess: u32 = match guess.trim().parse() {
    Ok(num) => num,
    Err(_) => continue,
};

第一个 guess 是可变的 String 类型,第二个 guess 是不可变的 u32 类型。这是 Rust 的一个特性,允许我们重用变量名,同时改变其类型和可变性。这比使用不同的变量名(如 guess_strguess_num)更简洁。

match 表达式

match 是 Rust 中强大的控制流运算符,允许我们将值与一系列模式进行比较:

rust
match guess.cmp(&secret_number) {
    std::cmp::Ordering::Less => println!("太小了!"),
    std::cmp::Ordering::Greater => println!("太大了!"),
    std::cmp::Ordering::Equal => {
        println!("你赢了!");
        break;
    }
}

cmp 方法返回 Ordering 枚举,它有三个变体:LessGreaterEqualmatch 表达式会尝试匹配每一个分支,执行匹配成功的分支。

错误处理

Rust 强制我们处理可能出现的错误。在猜数字游戏中,我们处理了两种错误:

  1. 读取输入失败:使用 .expect() 方法
  2. 解析失败:使用 match 表达式
rust
let guess: u32 = match guess.trim().parse() {
    Ok(num) => num,
    Err(_) => {
        println!("请输入一个数字!");
        continue;
    },
};

这种错误处理方式让程序更加健壮,不会因为用户输入了非数字字符而崩溃。

循环控制

游戏使用了 loop 无限循环,并通过 breakcontinue 控制流程:

  • break:当玩家猜对数字时退出循环
  • continue:当输入无效时跳过本次循环,继续下一次循环

依赖管理

Cargo.toml 中,我们使用语义化版本(Semantic Versioning)指定依赖:

toml
rand = "^0.8.5"

^0.8.5 表示任何与 0.8.5 兼容的版本,即 >= 0.8.5 且 < 0.9.0 的版本。这确保了我们能获取 bug 修复和小的功能更新,同时避免不兼容的更改。

2.4 改进猜数字游戏

我们可以对游戏进行一些改进,使其更加完善:

添加猜测次数限制

rust
use rand::Rng;
use std::io;
use std::io::Write;

fn main() {
    println!("猜数游戏!");
    let secret_number = rand::thread_rng().gen_range(1..=100);
    let max_attempts = 10;
    let mut attempts = 0;

    loop {
        if attempts >= max_attempts {
            println!("很遗憾,你已经用完了所有机会!正确答案是:{}", secret_number);
            break;
        }

        attempts += 1;
        print!("第 {} 次猜测,请输入数字(1-100):", attempts);
        io::stdout().flush().unwrap();

        let mut guess = String::new();
        io::stdin().read_line(&mut guess).expect("读取行失败");
        
        let guess: u32 = match guess.trim().parse() {
            Ok(num) => {
                if num < 1 || num > 100 {
                    println!("请输入 1 到 100 之间的数字!");
                    attempts -= 1; // 不计入无效输入
                    continue;
                }
                num
            }
            Err(_) => {
                println!("请输入一个有效的数字!");
                attempts -= 1; // 不计入无效输入
                continue;
            }
        };

        match guess.cmp(&secret_number) {
            std::cmp::Ordering::Less => {
                println!("太小了!还剩 {} 次机会。", max_attempts - attempts);
            }
            std::cmp::Ordering::Greater => {
                println!("太大了!还剩 {} 次机会。", max_attempts - attempts);
            }
            std::cmp::Ordering::Equal => {
                println!("恭喜你,猜对了!你用了 {} 次机会。", attempts);
                break;
            }
        }
    }
}

这个改进版本添加了以下功能:

  • 限制猜测次数为 10 次
  • 验证输入范围(1-100)
  • 显示剩余机会
  • 无效输入不计入猜测次数
  • 游戏结束时显示正确答案(如果未猜中)

通过这个简单的猜数字游戏,我们学习了 Rust 的许多核心概念,为深入学习 Rust 打下了基础。