Rust之基础入门项目实战:构建一个简单的猜谜游戏
Rust之基础入门项目实战:构建一个简单的猜谜游戏
引言:从理论到实践的跨越
经过前面19篇文章的学习,我们已经掌握了Rust的基础语法、所有权系统、结构体、枚举、错误处理以及模块系统。现在,是时候将这些知识综合运用到一个完整的项目中。本文将带领你构建一个经典的猜谜游戏,这个项目将综合运用我们学到的所有核心概念,为你提供一个从理论到实践的完整学习体验。
项目概述
1.1 游戏设计
我们将构建一个简单的猜数字游戏,具有以下功能:
- 生成1到100之间的随机数
 - 提示用户输入猜测的数字
 - 比较用户输入与目标数字
 - 提供反馈(太大、太小或正确)
 - 记录猜测次数
 - 处理无效输入
 - 提供重新游戏选项
 
1.2 技术栈
这个项目将使用以下Rust特性:
- 变量和数据类型:整数、字符串、布尔值
 - 控制流:循环、条件判断
 - 函数:模块化代码组织
 - 错误处理:Result类型和模式匹配
 - 标准库:随机数生成、用户输入处理
 - 模块系统:代码组织
 
项目初始化
2.1 创建项目
首先使用Cargo创建新项目:
cargo new guessing_game
cd guessing_game
2.2 项目结构
项目的基本结构如下:
guessing_game/
├── Cargo.toml
└── src/└── main.rs
Cargo.toml:
[package]
name = "guessing_game"
version = "0.1.0"
edition = "2021"[dependencies]
rand = "0.8.5"
基础版本实现
3.1 基本游戏逻辑
让我们从最简单的版本开始:
src/main.rs:
use std::io;
use rand::Rng;
use std::cmp::Ordering;fn main() {println!("=== 猜数字游戏 ===");println!("我已经想了一个1到100之间的数字,你能猜出来吗?");// 生成随机数let secret_number = rand::thread_rng().gen_range(1..=100);loop {println!("\n请输入你的猜测:");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) {Ordering::Less => println!("太小了!"),Ordering::Greater => println!("太大了!"),Ordering::Equal => {println!("恭喜你,猜对了!");break;}}}
}
3.2 代码解析
让我们详细分析这个基础版本:
- 
导入依赖:
std::io:处理用户输入rand::Rng:生成随机数std::cmp::Ordering:比较枚举
 - 
随机数生成:
let secret_number = rand::thread_rng().gen_range(1..=100);生成1到100(包含)的随机数
 - 
用户输入处理:
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;} };使用模式匹配处理可能的转换错误
 - 
比较逻辑:
match guess.cmp(&secret_number) {Ordering::Less => println!("太小了!"),Ordering::Greater => println!("太大了!"),Ordering::Equal => {println!("恭喜你,猜对了!");break;} }使用模式匹配比较数字大小
 
增强版本:添加更多功能
4.1 添加猜测次数统计
让我们增强游戏功能,添加猜测次数统计:
src/main.rs(增强版本):
use std::io;
use rand::Rng;
use std::cmp::Ordering;fn main() {println!("=== 猜数字游戏 ===");println!("我已经想了一个1到100之间的数字,你能猜出来吗?");let secret_number = rand::thread_rng().gen_range(1..=100);let mut attempts = 0;loop {attempts += 1;println!("\n请输入你的猜测(第{}次尝试):", attempts);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) {Ordering::Less => println!("太小了!"),Ordering::Greater => println!("太大了!"),Ordering::Equal => {println!("恭喜你,猜对了!");println!("你总共尝试了{}次", attempts);// 根据尝试次数给出评价match attempts {1 => println!("太厉害了!一次就猜中!"),2..=5 => println!("很棒!你是个猜数字高手!"),6..=10 => println!("不错!继续努力!"),_ => println!("再接再厉!"),}break;}}}
}
4.2 添加难度选择
让我们进一步扩展,添加难度选择功能:
src/main.rs(带难度选择):
use std::io;
use rand::Rng;
use std::cmp::Ordering;fn main() {println!("=== 猜数字游戏 ===");// 选择难度let (max_number, max_attempts) = select_difficulty();println!("我已经想了一个1到{}之间的数字,你有{}次机会,你能猜出来吗?",max_number, max_attempts);let secret_number = rand::thread_rng().gen_range(1..=max_number);let mut attempts = 0;loop {attempts += 1;// 检查是否超过最大尝试次数if attempts > max_attempts {println!("\n游戏结束!你已经用完了所有{}次机会。", max_attempts);println!("正确答案是: {}", secret_number);break;}println!("\n请输入你的猜测(第{}次尝试,还剩{}次):",attempts, max_attempts - attempts + 1);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;}};// 检查数字范围if guess < 1 || guess > max_number {println!("请输入1到{}之间的数字!", max_number);continue;}println!("你猜的是: {}", guess);match guess.cmp(&secret_number) {Ordering::Less => println!("太小了!"),Ordering::Greater => println!("太大了!"),Ordering::Equal => {println!("恭喜你,猜对了!");println!("你总共尝试了{}次", attempts);// 根据尝试次数给出评价evaluate_performance(attempts, max_attempts);break;}}}
}fn select_difficulty() -> (u32, u32) {loop {println!("\n请选择难度:");println!("1. 简单 (1-50, 10次机会)");println!("2. 中等 (1-100, 8次机会)");println!("3. 困难 (1-200, 6次机会)");println!("4. 专家 (1-500, 5次机会)");let mut choice = String::new();io::stdin().read_line(&mut choice).expect("读取输入失败");match choice.trim() {"1" => return (50, 10),"2" => return (100, 8),"3" => return (200, 6),"4" => return (500, 5),_ => {println!("无效选择,请输入1-4之间的数字!");continue;}}}
}fn evaluate_performance(attempts: u32, max_attempts: u32) {let percentage = (attempts as f32 / max_attempts as f32) * 100.0;if percentage <= 30.0 {println!("太厉害了!你是猜数字大师!");} else if percentage <= 60.0 {println!("很棒!你是个猜数字高手!");} else if percentage <= 90.0 {println!("不错!继续努力!");} else {println!("再接再厉!");}
}
模块化重构
5.1 创建模块化结构
现在让我们将代码重构为模块化结构,提高可维护性:
项目结构:
guessing_game/
├── Cargo.toml
└── src/├── main.rs├── game.rs├── config.rs└── utils.rs
src/main.rs:
mod game;
mod config;
mod utils;use game::Game;
use config::GameConfig;fn main() {println!("=== 猜数字游戏 ===");// 创建游戏配置let config = GameConfig::new();// 创建游戏实例let mut game = Game::new(config);// 运行游戏game.run();
}
src/config.rs:
use std::io;/// 游戏配置
#[derive(Debug, Clone)]
pub struct GameConfig {pub max_number: u32,pub max_attempts: u32,
}impl GameConfig {/// 创建新的游戏配置pub fn new() -> Self {Self::select_difficulty()}/// 选择难度fn select_difficulty() -> Self {loop {println!("\n请选择难度:");println!("1. 简单 (1-50, 10次机会)");println!("2. 中等 (1-100, 8次机会)");println!("3. 困难 (1-200, 6次机会)");println!("4. 专家 (1-500, 5次机会)");println!("5. 自定义");let mut choice = String::new();io::stdin().read_line(&mut choice).expect("读取输入失败");match choice.trim() {"1" => return GameConfig { max_number: 50, max_attempts: 10 },"2" => return GameConfig { max_number: 100, max_attempts: 8 },"3" => return GameConfig { max_number: 200, max_attempts: 6 },"4" => return GameConfig { max_number: 500, max_attempts: 5 },"5" => return Self::custom_difficulty(),_ => {println!("无效选择,请输入1-5之间的数字!");continue;}}}}/// 自定义难度fn custom_difficulty() -> Self {loop {println!("\n请输入最大数字范围:");let max_number = match Self::read_number() {Ok(num) if num > 1 => num,_ => {println!("请输入大于1的数字!");continue;}};println!("请输入最大尝试次数:");let max_attempts = match Self::read_number() {Ok(num) if num > 0 => num,_ => {println!("请输入大于0的数字!");continue;}};return GameConfig {max_number,max_attempts,};}}/// 读取数字输入fn read_number() -> Result<u32, ()> {let mut input = String::new();io::stdin().read_line(&mut input).map_err(|_| ())?;input.trim().parse().map_err(|_| ())}
}
src/game.rs:
use rand::Rng;
use std::cmp::Ordering;
use crate::config::GameConfig;
use crate::utils;/// 游戏主逻辑
pub struct Game {config: GameConfig,secret_number: u32,attempts: u32,
}impl Game {/// 创建新游戏pub fn new(config: GameConfig) -> Self {let secret_number = rand::thread_rng().gen_range(1..=config.max_number);Game {config,secret_number,attempts: 0,}}/// 运行游戏pub fn run(&mut self) {println!("我已经想了一个1到{}之间的数字,你有{}次机会,你能猜出来吗?",self.config.max_number, self.config.max_attempts);loop {self.attempts += 1;// 检查是否超过最大尝试次数if self.attempts > self.config.max_attempts {println!("\n游戏结束!你已经用完了所有{}次机会。", self.config.max_attempts);println!("正确答案是: {}", self.secret_number);break;}println!("\n请输入你的猜测(第{}次尝试,还剩{}次):",self.attempts, self.config.max_attempts - self.attempts + 1);// 获取用户输入let guess = match utils::read_guess() {Ok(num) => num,Err(_) => {println!("请输入有效的数字!");continue;}};// 检查数字范围if guess < 1 || guess > self.config.max_number {println!("请输入1到{}之间的数字!", self.config.max_number);continue;}println!("你猜的是: {}", guess);// 处理猜测match self.process_guess(guess) {GameResult::Continue => continue,GameResult::Win => break,}}// 询问是否重新开始if utils::ask_play_again() {self.restart();} else {println!("谢谢游玩!再见!");}}/// 处理用户猜测fn process_guess(&self, guess: u32) -> GameResult {match guess.cmp(&self.secret_number) {Ordering::Less => {println!("太小了!");GameResult::Continue}Ordering::Greater => {println!("太大了!");GameResult::Continue}Ordering::Equal => {println!("恭喜你,猜对了!");println!("你总共尝试了{}次", self.attempts);// 评价表现utils::evaluate_performance(self.attempts, self.config.max_attempts);GameResult::Win}}}/// 重新开始游戏fn restart(&mut self) {self.secret_number = rand::thread_rng().gen_range(1..=self.config.max_number);self.attempts = 0;self.run();}
}/// 游戏结果枚举
#[derive(Debug)]
enum GameResult {Continue,Win,
}
src/utils.rs:
use std::io;/// 读取用户猜测
pub fn read_guess() -> Result<u32, ()> {let mut input = String::new();io::stdin().read_line(&mut input).map_err(|_| ())?;input.trim().parse().map_err(|_| ())
}/// 评价玩家表现
pub fn evaluate_performance(attempts: u32, max_attempts: u32) {let percentage = (attempts as f32 / max_attempts as f32) * 100.0;if percentage <= 30.0 {println!("太厉害了!你是猜数字大师!");} else if percentage <= 60.0 {println!("很棒!你是个猜数字高手!");} else if percentage <= 90.0 {println!("不错!继续努力!");} else {println!("再接再厉!");}
}/// 询问是否重新开始
pub fn ask_play_again() -> bool {loop {println!("\n是否再玩一次?(y/n):");let mut input = String::new();io::stdin().read_line(&mut input).expect("读取输入失败");match input.trim().to_lowercase().as_str() {"y" | "yes" | "是" => return true,"n" | "no" | "否" => return false,_ => {println!("请输入 y(是) 或 n(否)");continue;}}}
}
添加测试
6.1 单元测试
让我们为关键功能添加测试:
src/utils.rs(添加测试):
#[cfg(test)]
mod tests {use super::*;#[test]fn test_evaluate_performance_excellent() {// 测试优秀表现评价// 这个测试主要验证函数不会panicevaluate_performance(3, 10); // 30%}#[test]fn test_evaluate_performance_good() {// 测试良好表现评价evaluate_performance(6, 10); // 60%}#[test]fn test_evaluate_performance_average() {// 测试平均表现评价evaluate_performance(9, 10); // 90%}#[test]fn test_evaluate_performance_poor() {// 测试较差表现评价evaluate_performance(10, 10); // 100%}
}
src/game.rs(添加测试):
#[cfg(test)]
mod tests {use super::*;#[test]fn test_game_creation() {let config = GameConfig {max_number: 100,max_attempts: 10,};let game = Game::new(config);// 验证游戏正确创建assert_eq!(game.attempts, 0);assert!(game.secret_number >= 1 && game.secret_number <= 100);}#[test]fn test_process_guess_correct() {let config = GameConfig {max_number: 100,max_attempts: 10,};let mut game = Game::new(config);game.secret_number = 42; // 设置固定值便于测试// 测试正确猜测let result = game.process_guess(42);assert!(matches!(result, GameResult::Win));}#[test]fn test_process_guess_too_low() {let config = GameConfig {max_number: 100,max_attempts: 10,};let mut game = Game::new(config);game.secret_number = 42;// 测试太小猜测let result = game.process_guess(10);assert!(matches!(result, GameResult::Continue));}#[test]fn test_process_guess_too_high() {let config = GameConfig {max_number: 100,max_attempts: 10,};let mut game = Game::new(config);game.secret_number = 42;// 测试太大猜测let result = game.process_guess(80);assert!(matches!(result, GameResult::Continue));}
}
最终优化和功能完善
7.1 添加颜色和更好的用户体验
让我们使用colored crate来增强用户体验:
更新Cargo.toml:
[package]
name = "guessing_game"
version = "0.1.0"
edition = "2021"[dependencies]
rand = "0.8.5"
colored = "2.0.0"
在utils.rs中添加颜色支持:
use colored::*;// 更新评价函数,使用颜色
pub fn evaluate_performance(attempts: u32, max_attempts: u32) {let percentage = (attempts as f32 / max_attempts as f32) * 100.0;if percentage <= 30.0 {println!("{}", "太厉害了!你是猜数字大师!".green().bold());} else if percentage <= 60.0 {println!("{}", "很棒!你是个猜数字高手!".blue().bold());} else if percentage <= 90.0 {println!("{}", "不错!继续努力!".yellow().bold());} else {println!("{}", "再接再厉!".red().bold());}
}
7.2 添加游戏统计
让我们添加游戏统计功能:
在game.rs中添加统计:
use std::time::Instant;/// 游戏统计
#[derive(Debug)]
struct GameStats {total_games: u32,total_wins: u32,best_score: Option<u32>,total_time: std::time::Duration,
}impl GameStats {fn new() -> Self {GameStats {total_games: 0,total_wins: 0,best_score: None,total_time: std::time::Duration::new(0, 0),}}fn record_win(&mut self, attempts: u32, duration: std::time::Duration) {self.total_games += 1;self.total_wins += 1;self.total_time += duration;// 更新最佳成绩if self.best_score.map_or(true, |best| attempts < best) {self.best_score = Some(attempts);}}fn record_loss(&mut self, duration: std::time::Duration) {self.total_games += 1;self.total_time += duration;}fn display(&self) {println!("\n=== 游戏统计 ===");println!("总游戏次数: {}", self.total_games);println!("胜利次数: {}", self.total_wins);let win_rate = if self.total_games > 0 {(self.total_wins as f32 / self.total_games as f32) * 100.0} else {0.0};println!("胜率: {:.1}%", win_rate);if let Some(best) = self.best_score {println!("最佳成绩: {}次尝试", best);}let avg_time = if self.total_games > 0 {self.total_time / self.total_games} else {std::time::Duration::new(0, 0)};println!("平均游戏时间: {:.1}秒", avg_time.as_secs_f32());}
}
结论
通过构建这个猜数字游戏,我们综合运用了Rust入门阶段的所有核心概念:
- 基础语法:变量、数据类型、控制流
 - 函数编程:模块化代码组织
 - 错误处理:Result类型和模式匹配
 - 标准库使用:随机数、用户输入、比较
 - 模块系统:代码组织和可见性控制
 - 测试开发:单元测试和集成测试
 - 用户体验:交互设计和错误恢复
 - 项目结构:Cargo项目管理和依赖配置
 
这个项目不仅巩固了理论知识,更重要的是提供了从零开始构建完整Rust应用程序的实践经验。在接下来的文章中,我们将进入Rust核心概念的学习,探索集合类型、泛型、Trait系统等更高级的特性。
掌握这个基础项目后,你已经具备了使用Rust构建简单应用程序的能力,为学习更复杂的Rust概念奠定了坚实的基础。
