关山难越,谁悲失路之人;萍水相逢,尽是他乡之客。
百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程教程 > 技术文章 > 正文

学习 Rust ??:05-你的第一个 Rust 游戏:“猜数字”

guanshanw 2023-09-26 12:22 37 浏览 0 评论

游戏说明及要求:

“猜数字”是一个简单的猜数字游戏,游戏生成一个secret number,然后用户尝试猜数字guess。

游戏应该告诉用户他/她的猜测是否是too big或too small,如果他/她猜到了秘密数字,则用户获胜并且游戏退出。

应检查用户输入是否存在无效的非数字值并警告用户。

应跟踪用户尝试的次数并在用户获胜时显示。
用户可以通过键入 退出游戏quit。
游戏应该持续运行,直到用户获胜或退出。

创建一个新项目:

就像我们之前学过的那样,输入以下命令来创建一个新guess_the_number项目并将目录 (cd) 更改到其中:

cargo new guess_the_number
cd guess_the_number

我希望您查看一下cargo.toml通过新项目为您生成的文件:

# cargo.toml
[package]
name = "guess_the_number"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]

现在,我们假设它cargo.toml是描述您的应用程序并列出其依赖项的地方(目前没有)。可以将其想象为 Python 的requirements.txt使用方式pip,pip install -r requiremts.txt但具有更多细节。

和往常一样,cargo它生成了一个“Hello, World”主函数供我们开始。

处理用户输入:

我们的首要任务是获取用户从标准输入输入的内容。输入以下内容:

use std::io;

fn main() {
    println!("Welcome to: GUESS THE NUMBER game!");
    println!("Please input your guess ...");

    let mut guess = String::new();

    io::stdin()
        .read_line(&mut guess)
        .expect("Unable to parse input!");

    println!("Your guess is {guess}")
}

这是本系列中我们第一次从应用程序“导入库”。我们用use关键字来做到这一点。这里我们io从名为 的标准库导入了该库std。

use std::io;

接下来,我们需要一些地方来保存用户的猜测。因此,我们定义了guess变量:

let mut guess = String::new();

这个mut关键词并不新鲜,但却是String::new()。因为我们不知道用户会写什么,所以guess不能是字符串文字(记住?字符串文字在代码中硬编码并在编译时已知)。相反,我们使用String类型及其关联new函数,该函数在内存中创建一个大小未知的“空”位置。

??下一篇文章将详细介绍 String 类型

之后,我们使用io导入的库并调用其stdin()函数,该函数返回 StdIn 的“句柄”,该句柄具有read_line读取 StdIn 的函数。有趣的是我们传递给read_line函数的内容,我们将“可变引用”传递给变量guess,以便函数可以更改其值(可变),但不获取它的所有权(引用)。

新 Rust 术语:“所有权”基本上是保证 Rust 内存安全的原因。我将在下一篇文章中详细讨论它,但现在知道默认情况下变量不能在 Rust 中来回切换作用域。这是“main”作用域和“read_line”函数作用域。

这段代码中的最后一个新部分是.expect("Unable to parse input!"). 这是因为它read_line不返回值,而是返回一个Result枚举(枚举),在本例中它有两个变体(将变体视为值),Ok并且Err. 默认情况下,如果Resultis Ok,则返回用户输入的值。否则,如果Result是Err,程序“恐慌”,显示定义的错误消息。

继续输入cargo run并验证您输入的内容是否已打印出来。

使用外部crate:

现在我们需要一个“随机”的秘密数字供用户猜测。这次我们将需要一个名为 的外部“板条箱” rand。

新的 Rust 术语:“ crate ”是 Python 的包,相当于 Rust。访问crates.io查看 Rust 的所有可用 crate。

要在您的应用程序中使用 crate,您可以将它们列在cargo.toml我们之前讨论的文件中。

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

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
rand = "0.8.5"

我们将在后面的文章中更详细地讨论 Rust 依赖关系。现在,请知道您用来cargo.toml列出依赖项。现在运行您的应用程序,这次您将看到正在下载并安装以下“crate”:


cargo了解您的依赖项cargo.toml,如果您再次运行您的应用程序,您会发现没有再次下载或安装任何内容。另一件值得注意的事情是cargo.lock驻留在项目根目录中的文件。cargo创建此文件以“锁定”依赖项版本以确保一致的构建。如果cargo找到cargo.lock,则将其用于依赖项列表而不是cargo.toml.

生成一个秘密号码:

现在我们使用rand我们列出的板条箱,cargo.toml就像我们之前对io库所做的那样。我们的代码变成这样:

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

fn main() {
    println!("Welcome to: GUESS THE NUMBER game!");

    // Generate secret number
    let secret_number = rand::thread_rng().gen_range(1..=100);
    println!("The secret number is {secret_number}");

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

    let mut guess = String::new();

    io::stdin()
        .read_line(&mut guess)
        .expect("Unable to parse input!");

    println!("Your guess is {guess}");
}

crate及其组件如何工作的细节rand现在并不重要。您需要知道的是,它用于生成 1 到 100(含)之间的随机数,如范围表达式高端之前的“=”所示1..=100。

运行代码,您每次都会看到不同的秘密数字。

猜测与密数比较:

我们有用户的guess和secret_number现在的,我们需要对它们进行比较。为了做到这一点,我们将导入另一个标准库组件,称为Orderingwithvariants enum,Less并且Greater我们Equal将使用关键字了解 Rust 的“模式匹配” match。
但在 Rust 中为了比较变量,它们必须是相同的类型。到目前为止,在我们的代码中,guessis 是String类型并且secret_number是i32隐式的(默认整数类型),因此两者之间的任何比较都会导致应用程序崩溃。为了解决这个问题,我们必须对解析变量的方式进行一些更改guess:

use rand::Rng;
use std::cmp::Ordering;
use std::io;

fn main() {
    println!("Welcome to: GUESS THE NUMBER game!");

    // Generate secret number
    let secret_number = rand::thread_rng().gen_range(1..=100);
    println!("The secret number is {secret_number}");

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

    let mut guess = String::new();

    io::stdin()
        .read_line(&mut guess)
        .expect("Unable to parse input!");

    // Shadowing
    let guess: u32 = guess
        .trim()
        .parse()
        .expect("Please input a number between 1 and 100");

    println!("Your guess is {guess}");

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

你会注意到我们再次重新定义了guess。但这一次是作为一种u32类型。这在 Rust 中被称为“遮蔽”。

新的 Rust 术语:“遮蔽”是指在 Rust 中的同一作用域内重新定义变量。您可以使用遮蔽来更改变量的类型和可变性。旧的价值观被思想摧毁了!

现在猜测是 u32,而 Secret_number 也被 Rust 编译器推断为 u32,我们可以比较它们(这在 Rust 中是允许的)。 我们使用 match 关键字进行模式匹配,因为我们将对 Secret_number 的引用(还记得 read_line 函数和所有权吗?)传递给 u32 类型上的 cmp 函数关联,然后其余部分是不言自明的 。运行代码并尝试获取 匹配块中的三个打印。

连续运行游戏:

您可能已经注意到,应用程序会在输入任何值时退出,并且如果用户给出了错误的猜测,游戏也不会继续请求用户猜测。 为了解决这个问题,我们将使用循环,如果用户猜对了,则“打破”循环。 此外,现在是跟踪用户尝试次数的好时机:

use rand::Rng;
use std::cmp::Ordering;
use std::io;

fn main() {
    println!("Welcome to: GUESS THE NUMBER game!");

    // Generate secret number
    let secret_number = rand::thread_rng().gen_range(1..=100);
    println!("The secret number is {secret_number}");

    let mut tries = 0;

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

        let mut guess = String::new();
        tries += 1;

        io::stdin()
            .read_line(&mut guess)
            .expect("Unable to parse input!");

        // Shadowing
        let guess: u32 = guess
            .trim()
            .parse()
            .expect("Please input a number between 1 and 100");

        println!("Your guess is {guess}");

        // Pattern matching
        match guess.cmp(&secret_number) {
            Ordering::Less => println!("Too small"),
            Ordering::Greater => println!("Too big"),
            Ordering::Equal => {
                println!("You win! Took you {tries} tries to guess the secret number!");
                break;
            }
        }
    }
}

现在运行代码并验证如果用户猜对并显示尝试次数,应用程序是否退出。

处理无效输入并正确退出游戏:

游戏现已基本完成。我们只需要做一些用户体验增强,例如处理无效的用户输入(非数字),并在用户输入 时引入退出机制quit。

use rand::Rng;
use std::cmp::Ordering;
use std::io;

fn main() {
    println!("Welcome to: GUESS THE NUMBER game!");

    // Generate secret number
    let secret_number = rand::thread_rng().gen_range(1..=100);
    println!("The secret number is {secret_number}");

    let mut tries = 0;

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

        let mut guess = String::new();
        tries += 1;

        io::stdin()
            .read_line(&mut guess)
            .expect("Unable to parse input!");

        // If the user input "quit", the game quits.
        if guess.trim().to_lowercase() == "quit" {
            break;
        }

        // Shadowing
        let guess: u32 = match guess.trim().parse() {
            Ok(num) => num,
            Err(_) => {
                println!("Please input a number between 1 and 100");
                continue;
            }
        };

        println!("Your guess is {guess}");

        // Pattern matching
        match guess.cmp(&secret_number) {
            Ordering::Less => println!("Too small"),
            Ordering::Greater => println!("Too big"),
            Ordering::Equal => {
                println!("You win! Took you {tries} tries to guess the secret number!");
                break;
            }
        }
    }
}

对于这一部分,我们在读取标准输入行后quit使用简单的方法if进行检查。guess我们还修改了阴影部分guess以使用模式匹配。如果Result枚举返回Ok,则返回数字。否则,如果返回,Err则打印警告消息并继续循环。

完成并发布游戏:

最后,我们将删除打印秘密数字的行,因此完整的代码将是:

use rand::Rng;
use std::cmp::Ordering;
use std::io;

fn main() {
    println!("Welcome to: GUESS THE NUMBER game!");

    // Generate secret number
    let secret_number = rand::thread_rng().gen_range(1..=100);

    let mut tries = 0;

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

        let mut guess = String::new();
        tries += 1;

        io::stdin()
            .read_line(&mut guess)
            .expect("Unable to parse input!");

        // If the user input "quit", the game quits.
        if guess.trim().to_lowercase() == "quit" {
            break;
        }

        // Shadowing
        let guess: u32 = match guess.trim().parse() {
            Ok(num) => num,
            Err(_) => {
                println!("Please input a number between 1 and 100");
                continue;
            }
        };

        println!("Your guess is {guess}");

        // Pattern matching
        match guess.cmp(&secret_number) {
            Ordering::Less => println!("Too small"),
            Ordering::Greater => println!("Too big"),
            Ordering::Equal => {
                println!("You win! Took you {tries} tries to guess the secret number!");
                break;
            }
        }
    }
}

到目前为止,我们都是通过打字cargo build来构建我们的应用程序的。这会相对快速地构建应用程序,但放弃了一些可以使二进制文件运行得更快的优化。我们可以在 中看到生成的二进制文件target/debug。

对于发布版本,我们应该输入cargo build --release. 您会注意到构建阶段比平时花费的时间更长(我们讨论过的优化),现在我们将在target/release.

?? 该--release标志也适用于cargo run

继续并输入以下内容:

cargo build --release

然后直接运行游戏就不用了cargo,如下:

# Linux
./target/release/guess_the_number

并且游戏会顺利运行。尝试从第一次尝试就猜出数字!

下一篇文章,我将开始讨论 Rust 的具体功能和特性,即“所有权”。到时候见

相关推荐

七条简单命令让您玩转Git
七条简单命令让您玩转Git

凭借着出色的协作能力、快速部署效果与代码构建辅助作用,Git已经得到越来越多企业用户的青睐。除了用于开发商业及消费级应用之外,众多科学及政府机构也开始尝试使用这...

2023-10-07 12:14 guanshanw

基本完整的关于Git分支branch的操作
基本完整的关于Git分支branch的操作

Git使用背景项目中要用到dev或者其他分支开发完代码,需要将该分支合并到master的需求操作步骤下面以dev名称为lex为分支名为例来操作一遍客户端操作:...

2023-10-07 12:14 guanshanw

Git 进阶(合并与变基)
Git 进阶(合并与变基)

在Git中整合来自不同分支的修改主要有两种方法:合并(merge)以及变基(rebase)合并(merge)merge流程图merge的原理是找到这两个分...

2023-10-07 12:13 guanshanw

Git学习笔记 003 Git进阶功能 part5 合并(第一部分)

合并(merge)是很常用的操作。尤其是一个庞大的很多人参与开发的企业级应用。一般会设定一个主分支,和多个副分支。在副分支开发完成后,合并到主分支中。始终保持主分支是一个完整的,稳定的最新状态的分支。...

非标题党,三张图帮你理解git merge和git rebase的区别
非标题党,三张图帮你理解git merge和git rebase的区别

初始场景:基于正常的开发分支修改几个小bug,然后在合并到开发分支上。gitmergegitcheckoutfeaturegitmergeho...

2023-10-07 12:13 guanshanw

git 初次使用(01)
git 初次使用(01)

先从github上克隆代码下来:使用vscode克隆代码如下图,填写上github仓库地址:vscode有时候克隆代码速度比较慢,可以用命令行方式克隆gitc...

2023-10-07 12:12 guanshanw

Git 远程操作

4.Git远程操作命令说明gitremote远程版本库操作gitfetch从远程获取版本库gitpull下载远程代码并合并gitpush上传远程代码并合并4.1远程版本库操作gitre...

Git常用命令-总结
Git常用命令-总结

创建git用户$gitconfig--globaluser.name"YourName"$gitconfig--globaluser.em...

2023-10-07 12:12 guanshanw

git中删除从别人clone下来项目的git信息,并修改为自己的分支

如果你从别人的Git存储库中克隆了一个项目,并想要删除与该存储库相关的Git信息,并将其修改为你自己的分支,则可以执行以下步骤:使用gitclone命令克隆存储库:gitclone<u...

git系列-回滚和放弃本地修改

回滚历史提交就是reset的功能。这种情况是已经提交远程仓库,需要回滚到之前的提交。gitreset--hardcommitId//注:强制提交后,当前版本后面的提交版本将会删掉!gi...

GIT使用小技巧大全
GIT使用小技巧大全

在大型软件工程的开发过程中,版本控制是无法绕过去的;目前来说,最火的版本控制软件就是GIT了。早两年SVN比较火,不过被大神linus喷了几次后,就日落西山了,...

2023-10-07 12:11 guanshanw

git相关命令-上
git相关命令-上

这些命令都是看了文档后,个人觉得比较有用的一些,展示给大家。回到远程仓库的状态抛弃本地所有的修改,回到远程仓库的状态。gitfetch--all&...

2023-10-07 12:10 guanshanw

Git命令行接口:掌握Git的必备技能
Git命令行接口:掌握Git的必备技能

Git是一款强大的分布式版本控制工具,它支持命令行界面操作。熟练掌握Git命令行接口,是开发者使用Git的必备技能之一。在这篇文章中,我们将介绍Git命令行接口...

2023-10-07 12:10 guanshanw

Git命令详解
Git命令详解

相信各位小伙伴们应该都对git有一些了解,毕竟作为代码管理的神器,就算不是IT行业的小伙伴肯定也或多或少的听说过一些。今天就来和小伙伴们分享一下自己总结的常用命...

2023-10-07 12:10 guanshanw

工作7年收集到的git命令
工作7年收集到的git命令

概念git中的术语解释:仓库也叫版本库(repository)stage:暂存区,add后会存到暂存区,commit后提交到版本库git安装linux...

2023-10-07 12:10 guanshanw

取消回复欢迎 发表评论: