猜数字游戏(Rust实现)

news2024/11/18 21:53:45

文章目录

  • 游戏说明
  • 游戏效果展示
  • 游戏代码
  • 游戏代码详解
    • 生成神秘数字
    • 读取用户输入
    • 解析用户输入
    • 进行猜测比较

游戏说明

游戏说明

游戏运行逻辑如下:

  1. 随机生成一个1-100的数字作为神秘数字,并提示玩家进行猜测。
  2. 如果玩家猜测的数字小于神秘数字,则提示用户“猜测的数字太小了”。
  3. 如果玩家猜测的数字大于神秘数字,则提示用户“猜测的数字太大了”。
  4. 让玩家不断进行猜测,直到最终猜出神秘数字,游戏结束。

游戏效果展示

游戏效果展示

在这里插入图片描述

游戏代码

游戏代码

游戏完整代码如下:

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

fn main() {
    println!("欢迎来到猜数游戏!");
    //1、生成神秘数字
    let secret_number = rand::thread_rng().gen_range(1, 101);
    println!("神秘数字已经生成!");

    loop {
        //2、让用户进行猜测
        println!("请猜测:>");
        let mut guess = String::new();
        io::stdin().read_line(&mut guess).expect("无法读取行");

        //3、将用户输入的数字字符串转化为整型
        let guess: u32 = match guess.trim().parse() {
            Ok(num) => num,
            Err(_) => {
                println!("请您输入一个合法的整数!");
                continue;
            }
        };

        //4、将用户猜测的数与神秘数字进行比较
        match guess.cmp(&secret_number) {
            Ordering::Less => println!("您猜测的数字太小了"),
            Ordering::Greater => println!("您猜测的数字太大了"),
            Ordering::Equal => {
                println!("恭喜您猜对了, 神秘数字就是{}!", secret_number);
                break;
            }
        }
    }
}

游戏代码详解

下面对猜数字游戏中所用到的Rust语法和包(crate)进行讲解。

生成神秘数字

rand包

  • Rust团队没有把随机数字生成功能内置到标准库中,而是选择将它作为rand包(rand crate)提供给用户。
  • Rust中的包(crate)代表了一系列源代码文件的集合,我们当前正在构建的项目是一个用于生成可执行程序的二进制包(binary crate),而我们引用的rand包则是一个用于复用功能的库包(library crate)。
  • rand包中有一个名为Rng的trait,它定义了随机数生成器需要实现的方法集合,比如Rng中的gen_range方法可以根据指定的范围来生成随机数。

在rand包中有一个名为thread_rng的方法,该方法会返回一个特定的随机数生成器,通过调用该随机数生成器的gen_range方法即可在指定范围内生成随机数。如下:

use rand::Rng;

fn main() {
    let secret_number = rand::thread_rng().gen_range(1, 101); //在[1,101)范围生成随机数
    println!("生成的神秘数字是: {}", secret_number);
}

说明一下:

  • thread_rng方法返回的随机数生成器(ThreadRng类型)位于本地线程空间,并通过操作系统获得随机种子。
  • ThreadRng类型实际并没有实现gen_range方法,我们调用的实际是Rng trait中默认实现的gen_range方法,因此需要通过use语句将rand包中的Rng trait引入到当前作用域。
  • Rust中的trait可以类比其他语言中的接口的概念,它定义了一组方法,比如可以类比Golang中的interface,但trait的职责远比interface更多。
  • 代码中生成的随机数位与1-100之间,而Rust中很多整数类型都能涵盖这个范围,比如i32、u32、i64等,除非在代码中增加更多的信息用于类型推断,否则这个类型将会被视为i32类型,代码中定义secret_number变量时就没有指定它的类型,因此其为i32类型。
  • 代码中通过fn关键字声明了一个无参无返回值的main函数,通过let关键字定义一个secret_number变量来存储生成的随机值。
  • 代码中的println!是一个宏,其功能是将字符串格式化后打印到标准输出,格式化字符串中包含占位符{},从第二个参数开始,各参数依次替换格式化字符串中的占位符。

添加依赖

Cargo最主要的功能就是帮助我们管理和使用第三方库,在使用rand包编写代码之前,需要在Cargo.toml文件的[dependencies]片段下将rand包声明为依赖,并指明它的版本号。如下:

[package]
name = "guessing_game"
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.3.14"

执行cargo build命令后,Cargo就会从crates.io网站下载并编译指定的包,并基于这些依赖编译我们自己的项目。如下:

在这里插入图片描述

说明一下:

  • 在Cargo.toml文件中指明的0.3.14实际上是^0.3.14的简写,它表示任何与0.3.14版本公共API相兼容的版本,因此我们下载不一定是我们指定版本号的包。
  • Cargo可以从注册表(registry)中获取所有可用rand包的最新版本信息,这些信息通常是从crates.io上复制过来的。(crates.io是用于分享各种各样开源Rust项目的网站)
  • Cargo会在更新完注册表后逐条检查[dependencies]片段下的依赖,并下载当前缺失的依赖包,由于rand包依赖于libc,因此在下载rand包的时候额外下载了一份libc数据。

Cargo.lock

Cargo提供了一套机制来保证我们构建的结果是可重现的,任何人是任何时候编译我们的代码都会生成相同的产物,因为Cargo会一直使用某个特定版本的依赖,直到我们手动指定了其他版本。

  • 当第一次使用cargo build构建项目时,在项目目录下会生成Cargo.lock文件,Cargo会依次遍历我们声明的依赖及其对应的语义化版本,找到符合要求的具体版本号,并将其写入Cargo.lock文件中。
  • 后续再次构建项目时,Cargo会先检索Cargo.lock文件,如果文件中存在已经指明具体版本的依赖库,那么它就会跳过计算版本号的过程,并直接使用文件中指明的版本,这就使得我们构建的结果是可重现的。

如果你确实需要升级某个依赖包,那么可以使用cargo update命令,该命令会强制Cargo忽略Cargo.lock文件,并重新计算出所有依赖包中符合Cargo.toml声明的最新版本。如下:

在这里插入图片描述

说明一下:

  • 如果cargo update命令运行成功,Cargo会将各个依赖包更新后的版本号写入Cargo.lock文件。
  • 在当前示例中,Cargo在自动升级时只会寻找大于0.3.0版本,并且小于0.4.0版本的最新版本,如果要将rand包的升级到0.4.x,那么就需要在Cargo.toml文件中指明对应的版本。
  • 当前示例执行cargo update命令后,Cargo.lock中的内容实际不会改变,因为之前构建项目的时候Cargo.lock中写入的就是0.3开头的最新的版本0.3.23。

读取用户输入

io模块

  • 标准库(std)中的io模块包含了许多有用的功能,通过io模块可以获取到用户输入的数据。
  • io模块中有一个名为Stdin的struct,该struct中的read_line方法可以从标准输入中读取用户的一行输入。

在io模块中有一个关联函数叫做stdin,该函数会创建一个Stdin的实例并返回,通过这个实例来调用read_line方法即可读取用户的一行输入。如下:

use std::io;

fn main() {
    let mut guess = String::new();
    io::stdin().read_line(&mut guess).expect("无法读取行");
    println!("输入的内容: {}", guess);
}

说明一下:

  • Rust会将预导入(prelude)模块自动引入每一段程序的作用域中,该模块包含了一小部分常用的类型,如果你要使用的类型不在预导入模块中,那么就需要使用use语句显示进行导入声明。
  • 调用read_line方法读取用户数据时,为了将读取到的数据传递出来,需要以引用的方式传入一个String类型的变量,因此需要定义一个String类型的guess变量。(String是标准库中的一个字符串类型,该类型的new方法会创建一个新的空白字符串。)
  • 由于在read_line函数内部会将读取到的数据写入到guess变量中,因此在定义guess变量时使用mut关键字将其定义成一个可变的String变量,并且在将guess传递给read_line函数时也需要使用mut关键字来传递这个可变的引用变量。(Rust中的变量默认是不可变的)
  • Rust有一个静态强类型系统,同时拥有自动类型推导的能力,因此当代码中定义guess变量时虽然没有指明guess的类型,Rust也会自动将其推导为String类型。
小贴士:Rust中很多类型都有new方法,因为这是创建类型实例的惯用函数名称。

Result枚举

  • read_line函数会将读取到的内容存储到我们传入的字符串中,该函数的返回值类型是io::Result。
  • 在Rust标准库中有很多以Result命名的类型,它们通常是各个子模块中的Result泛型的特定版本。
  • Result是一个枚举类型,枚举类型由一系列固定的值组合而成,这些值被称作枚举的变体。

对于Result枚举来说,它有Ok和Err两个变体:

  • Ok变体表示当前操作执行成功,此时代码的结果值会存储在Ok变体中。
  • Err变体表示当前操作执行失败,此时引发失败的具体原因会存储在Err变体中。

Result枚举的定义如下:

pub enum Result<T, E> {
    /// Contains the success value
    Ok(T),
    /// Contains the error value
    Err(E),
}

Result类型中定义了一系列方法,其中有一个方法叫做expect,该方法接收一个字符串:

  • 如果Result枚举的值为Ok,那么expect会提取出Ok中附带的值,并将其作为结果返回给用户。
  • 如果Result枚举的值为Err,那么expect会中断当前的程序,并将传入的字符串显示出来。

expect方法的定义如下:

impl<T, E> Result<T, E> {
	//...
    pub fn expect(self, msg: &str) -> T
    where
        E: fmt::Debug,
    {
        match self {
            Ok(t) => t,
            Err(e) => unwrap_failed(msg, &e),
        }
    }
    //...
}

说明一下:

  • 如果不对read_line函数的返回值做处理,那么会产生告警,因为read_line函数的返回值可能是一个Err变体,这就意味着我们没有对潜在的错误进行处理,通过这点就可以看到Rust的安全性。

解析用户输入

解析用户输入

现在我们已经能够获得用户的输入了,但此时guess变量是String类型的,而我们生成的神秘数字是整型的,为了能够将它们进行比较,需要将guess变量转化为整型。如下:

use std::io;

fn main() {
    println!("请输入一个整数:>");
    let mut guess = String::new();
    io::stdin().read_line(&mut guess).expect("无法读取行");
    
    let guess: u32 = guess.trim().parse().expect("请您输入一个合法的整数!");
    println!("解析成功, 输入的数字是: {}", guess);
}

说明一下:

  • 字符串的trim方法的作用是去掉字符串两端的空白字符,包括空白字符、Tab、回车(\n)等,该方法会将处理后的字符串进行返回。
  • 字符串的parse方法的作用是将字符串解析成某种数值类型,比如i32、u32、i64、f64等,该方法的返回值是一个Result枚举,当字符串无法解析成数值类型时便会返回Err变体。
  • 由于parse方法的解析结果可能是多种数值类型,可以是浮点数也可以是整数,因此需要指定接收解析结果的变量的类型,定义变量时在变量名后面加一个冒号,再在冒号后面指定变量的类型即可。
  • 代码中接收解析结果的变量的变量名仍为guess,在Rust中不会报错,因为Rust允许使用同名的新变量来隐藏(shadow)原来同名的旧变量的值,这一特性通常被用在需要转换值类型的场景中。

多次处理用户输入

为了猜中神秘数字,用户可能需要进行多次猜测,因此我们也需要多次对用户的输入进行解析,在Rust中使用loop关键字即可创建一个无限循环。如下:

use std::io;

fn main() {
    loop {
        println!("请输入一个整数:>");
        let mut guess = String::new();
        io::stdin().read_line(&mut guess).expect("无法读取行");
        
        let guess: u32 = match guess.trim().parse().expect("请您输入一个合法的整数!");
        println!("解析成功, 输入的数字是: {}", guess);
    }
}

match表达式

当用户输入非数字字符串时,parse方法就会解析失败,这时最好让用户重新猜测,而不是终止程序,鉴于parse方法的返回值类型是Result类型,因此可以用match表达式来代替expect函数,这也是在Rust中处理错误的一个惯用手段。

  • 如果parse返回的是Ok变体,那么表示解析成功,并且解析结果存储在Ok变体中,此时将Ok变体中的值返回即可。
  • 如果parse返回的是Err变体,那么表示解析失败,并且引发失败的具体原因会存储在Err变体中,此时打印一句提示语句并让循环continue即可。

代码如下:

use std::io;

fn main() {
    loop {
        println!("请输入一个整数:>");
        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表达式由多个分支(arm)组成,这些分支必须涵盖所有可能的情况,每个分支都包含一个用于匹配的模式(pattern),以及匹配成功后要执行的相应的代码。
  • Rust会尝试用我们传入match表达式的值去依次匹配每个分支的模式,如果匹配成功,它就会执行当前分支中的代码。
  • 对于match中的每个分支来说,模式中不需要的信息可以通过_忽略,而如果匹配成功后要执行的代码有多条,可以将这些代码放到一个代码块中。

进行猜测比较

进行猜测比较

现在要做的就是将用户猜测的数字和神秘数字进行比较。

  • 如果比较成功,则通过break跳出循环,游戏结束。
  • 如果比较失败,则提示用户猜大了还是猜小了,游戏继续。

这里也可以使用match表达式来处理。如下:

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

fn main() {
    println!("欢迎来到猜数游戏!");
    //1、生成神秘数字
    let secret_number = rand::thread_rng().gen_range(1, 101);
    println!("神秘数字已经生成!");

    loop {
        //2、让用户进行猜测
        println!("请猜测:>");
        let mut guess = String::new();
        io::stdin().read_line(&mut guess).expect("无法读取行");

        //3、将用户输入的数字字符串转化为整型
        let guess: u32 = match guess.trim().parse() {
            Ok(num) => num,
            Err(_) => {
                println!("请您输入一个合法的整数!");
                continue;
            }
        };

        //4、将用户猜测的数与神秘数字进行比较
        match guess.cmp(&secret_number) {
            Ordering::Less => println!("您猜测的数字太小了"),
            Ordering::Greater => println!("您猜测的数字太大了"),
            Ordering::Equal => {
                println!("恭喜您猜对了, 神秘数字就是{}!", secret_number);
                break;
            }
        }
    }
}

说明一下:

  • 通过调用guess变量的cmp方法,可以将guess变量与同类型变量进行比较,在比较时传入另一个变量的引用即可。
  • cmp方法的返回值的类型是Ordering枚举,该枚举有Less、Greater和Equal三个变体,分别表示比较结果为小于、大于和等于。

此外,也可以使用if else语句对用户猜测的数字和神秘数字进行比较。如下:

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

fn main() {
    println!("欢迎来到猜数游戏!");
    //1、生成神秘数字
    let secret_number = rand::thread_rng().gen_range(1, 101);
    println!("神秘数字已经生成!");

    loop {
        //2、让用户进行猜测
        println!("请猜测:>");
        let mut guess = String::new();
        io::stdin().read_line(&mut guess).expect("无法读取行");

        //3、将用户输入的数字字符串转化为整型
        let guess: u32 = match guess.trim().parse() {
            Ok(num) => num,
            Err(_) => {
                println!("请您输入一个合法的整数!");
                continue;
            }
        };

        //4、将用户猜测的数与神秘数字进行比较
        if guess < secret_number {
            println!("您猜测的数字太小了");
        } else if guess > secret_number {
            println!("您猜测的数字太大了")
        } else {
            println!("恭喜您猜对了, 神秘数字就是{}!", secret_number);
            break;
        }
    }
}

说明一下:

  • 虽然之前secret_number变量被默认推导为i32类型,但由于这里将guess和secret_number进行了比较,而guess变量是u32类型的,因此Rust会将secret_number也推导为相同的u32类型。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1105903.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

智慧河湖方案:AI赋能水利水务,构建河湖智能可视化监管大数据平台

一、方案背景 我国江河湖泊众多&#xff0c;水系发达。伴随着经济社会快速发展&#xff0c;水生态水环境问题成为群众最关注的民生议题之一。一些河流开发利用已接近甚至超出水环境承载能力&#xff0c;一些地区废污水排放量居高不下&#xff0c;一些地方侵占河道、围垦湖泊等…

Apache SeaTunnel Web 功能正式发布!

Apache SeaTunnel Web 功能正式发布&#xff01; 在大数据技术的不断进步之下&#xff0c;Apache SeaTunnel 成为了众多开发者和企业关注的焦点。今天&#xff0c;我们很高兴地宣布&#xff1a;Apache SeaTunnel Web功能已正式发布&#xff0c;带来了前所未有的易用性和效率。…

手把手带你使用VSCode 搭建 STM32开发环境!

首先附上一张VS Code图一直都喜欢这种&#xff0c;黑色主题感觉高大上。 一、需要的软件和工具。 下载最新版VS Code: 安装好插件&#xff0c;具有良好的代码补全与调试功能。 “ VS Code下载地址&#xff1a;https://code.visualstudio.com/ ” 下载 LLVM&#xff1a;用于代码…

DeFi世界 MXT脱颖而出 利好不断

​​MixTrust希望成为用户在Web3世界的专用金融平台&#xff0c;注重为用户提供个性化的金融服务。而WorldCoin的愿景则是建设一个全球最大的、公平的数字身份和货币体系&#xff0c;强调构建一个涵盖全球范围的身份认证和货币交易系统。 扩展性 在扩展性方面&#xff0c;双方…

操作系统体系结构和OS

1.冯诺依曼计算机体系 关于冯诺伊曼系统&#xff0c;在这里我只是简单讲一讲&#xff0c;更加详细的内容可以看我的计算机组成系列。 常见的笔记本、台式机&#xff0c;不常见的服务器、工作站&#xff0c;大部分都遵守“冯诺依曼体系”&#xff0c;因此该计算机体系就是现代…

易点易动设备管理系统帮助生产企业提升设备巡检效率

在现代制造业中&#xff0c;设备的正常运行对于生产企业的成功至关重要。然而&#xff0c;设备巡检是确保设备安全性和可靠性的关键环节&#xff0c;但却常常耗费大量时间和资源。为了解决这个问题&#xff0c;许多企业采用了现代化的设备管理系统&#xff0c;其中易点易动设备…

简单谈谈我参加数据分析省赛的感受与体会

数据分析省赛的感受与体会 概要考试前的感受与体会考试注意事项小结 概要 大数据分析省赛指的是在省级范围内举办的大数据分析竞赛活动。该竞赛旨在鼓励和推动大数据分析领域的技术创新和人才培养&#xff0c;促进大数据技术与应用的深度融合&#xff0c;切实解决实际问题。参…

通讯协议学习之路:有线通讯协议总览

通讯协议之路主要分为两部分&#xff0c;第一部分从理论上面讲解各类协议的通讯原理以及通讯格式&#xff0c;第二部分从具体运用上讲解各类通讯协议的具体应用方法。 后续文章会同时发表在个人博客(jason1016.club)、CSDN&#xff1b;视频会发布在bilibili(UID:399951374) 一、…

【ELK使用指南 2】常用的 Logstash filter 插件详解(附应用实例)

Logstash filter 一、logstash filter过滤插件的常用模块简介二、grok 正则捕获插件2.1 grok插件的作用2.2 内置正则表达式2.3 自定义正则表达式 三、mutate 数据修改插件3.1 mutate插件的作用3.2 常用的配置选项3.3 mutate插件应用实例 四、multiline 多行合并插件4.1 multili…

哈希表(拉链法)代码模板

这里也是用数组模拟链表 //拉链法 //模拟散列表 在算法题中一般只有添加和查询不会有删除 //如果真的要删除也不是真正的删除而是打上一个标记 //mod的这个数最好取大于数据范围的第一个质数 #include<iostream> #include<cstring> using namespace std; const in…

“1688商品评论接口:打造完美电商口碑的秘密武器!“

1688商品评论接口是一种供卖家使用的接口&#xff0c;可以让卖家通过该接口维护商品的评论信息&#xff0c;包括评论内容、评分、评论时间等等。 通过使用该接口&#xff0c;卖家可以方便地管理商品的评论信息&#xff0c;包括查看、修改、删除评论等操作。同时&#xff0c;该…

屏幕截图软件Snagit 2023 mac中文特点介绍

Snagit 2023 mac是一款屏幕截图和视频录制软件&#xff0c;它可以帮助用户快速捕捉屏幕上的任何内容&#xff0c;并将其编辑、标注和共享。 Snagit 2023 软件特点 多种截图模式&#xff1a;支持全屏截图、窗口截图、区域截图、延时截图等多种截图模式&#xff0c;满足不同用户…

二蛋赠书五期:《Python数据挖掘:入门、进阶与实用案例分析》

前言 大家好&#xff01;我是二蛋&#xff0c;一个热爱技术、乐于分享的工程师。在过去的几年里&#xff0c;我一直通过各种渠道与大家分享技术知识和经验。我深知&#xff0c;每一位技术人员都对自己的技能提升和职业发展有着热切的期待。因此&#xff0c;我非常感激大家一直…

vue使用pdf-dist实现pdf预览以及水印

vue使用pdf-dist实现pdf预览以及水印 一.使用pdf-dist插件将PDF文件转换为一张张canvas图片 npm install pdf-dist二.页面引入插件 const pdfJS require("pdfjs-dist"); pdfJS.GlobalWorkerOptions.workerSrc require("pdfjs-dist/build/pdf.worker.entry&…

JavaScript中多种获取数组最后一个元素的策略。

&#x1f3ac; 江城开朗的豌豆&#xff1a;个人主页 &#x1f525; 个人专栏 :《 VUE 》 《 javaScript 》 &#x1f4dd; 个人网站 :《 江城开朗的豌豆&#x1fadb; 》 ⛺️ 生活的理想&#xff0c;就是为了理想的生活 ! 目录 ⭐ 专栏简介 &#x1f4d8; 文章引言 &…

ESD监控系统-设备接地报警器的应用领域和说明

ESD监控系统是一种用于检测和监控静电放电&#xff08;ESD&#xff09;的设备&#xff0c;其中设备接地监控报警器是其重要组成部分之一。该设备主要用于检测设备的接地情况&#xff0c;当设备没有接地或者接地不良时&#xff0c;会发出报警信号&#xff0c;提醒操作人员及时采…

idea控制台乱码

如果打包时&#xff0c;控制台出现错误乱码的解决办法 而修改此后&#xff0c;运行程序有可能报错&#xff0c;则需要添加vm参数即可&#xff1a;

每日一题 2530. 执行 K 次操作后的最大分数(中等,最大根堆)

显然每次需要取出最大的元素&#xff0c;用过后将他除以三重新加入数组中&#xff0c;所以只要维护一个最大根堆即可 class Solution:def maxKelements(self, nums: List[int], k: int) -> int:nums [-i for i in nums]heapq.heapify(nums)ans 0for i in range(k):ans -…

Himmelblau函数-优化问题的经典案例

Himmelblau函数-优化问题的经典案例 前言 Himmelblau函数是一种常见的多元函数&#xff0c;它的形式为f(x,y)(x^2y-11)^2(xy^2-7)^2。这个函数的名字来源于其发明者David Himmelblau&#xff0c;它在数学和工程领域中都有广泛的应用。 一、Himmelblau函数是什么&#xff1f; …

Fast DDS介绍

目录 架构 Fast DDS是一个DCPS(data-centric)数据中心的发布-订阅模型中间件&#xff0c;主要关注应用程序中处理和发送数据。 架构 一个DomainParticipant启动的线程 NameTypeCardinalityDescriptionEventGeneral每个DomainParticipant一个处理周期性事件和触发的时间事件Dis…