ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Rust 实现贪吃蛇小游戏源码分享

Rust 实现贪吃蛇小游戏源码分享 Rust 实现贪吃蛇小游戏源码分享一、效果展示二、源码分享1、main.rs2、Cargo.toml三、rand 包详解1、 添加依赖2、 核心概念2.1、 随机数生成器 (RNG)2.2、 分布 (Distributions)3、 基本用法3.1、 生成随机数3.2 、随机布尔值与选择4、在贪吃蛇游戏中的应用5、 高级特性与性能5.1 、种子与可重现性5.2 、性能优化6、常见问题与陷阱7、总结一、效果展示二、源码分享1、main.rsusecrossterm::{cursor::{Hide,MoveTo,Show},event::{poll,read,Event,KeyCode,KeyEvent,KeyModifiers},execute,style::{Color,Print,ResetColor,SetBackgroundColor},terminal::{disable_raw_mode,enable_raw_mode,size,Clear,ClearType,EnterAlternateScreen,LeaveAlternateScreen,},};userand::Rng;usestd::{collections::VecDeque,io::{stdout,Result,Write},time::Duration,};/// 蛇的移动方向#[derive(Clone, Copy, PartialEq)]enumDirection{Up,Down,Left,Right,}/// 游戏状态structGame{snake:VecDeque(usize,usize),direction:Direction,next_direction:Direction,food:(usize,usize),score:u32,width:usize,height:usize,game_over:bool,}implGame{fnnew(width:usize,height:usize)-Self{// 蛇从中间开始初始长度 3letmid_xwidth/2;letmid_yheight/2;letmutsnakeVecDeque::new();snake.push_back((mid_x,mid_y));snake.push_back((mid_x-1,mid_y));snake.push_back((mid_x-2,mid_y));letmutgameGame{snake,direction:Direction::Right,next_direction:Direction::Right,food:(0,0),score:0,width,height,game_over:false,};game.spawn_food();game}/// 在空白位置随机生成食物fnspawn_food(mutself){letmutrngrand::thread_rng();loop{letxrng.gen_range(0..self.width);letyrng.gen_range(0..self.height);if!self.snake.contains((x,y)){self.food(x,y);break;}}}/// 处理输入方向禁止 180° 掉头fnset_direction(mutself,dir:Direction){match(self.direction,dir){(Direction::Up,Direction::Down)|(Direction::Down,Direction::Up)|(Direction::Left,Direction::Right)|(Direction::Right,Direction::Left){}// 忽略反向_self.next_directiondir,}}/// 更新蛇的位置fnupdate(mutself){ifself.game_over{return;}self.directionself.next_direction;let(head_x,head_y)self.snake.front().unwrap();let(nx,ny)matchself.direction{Direction::Up(*head_x,head_y.wrapping_sub(1)),Direction::Down(*head_x,head_y1),Direction::Left(head_x.wrapping_sub(1),*head_y),Direction::Right(*head_x1,*head_y),};// 撞墙检测ifnxself.width||nyself.height{self.game_overtrue;return;}// 撞自己检测ifself.snake.contains((nx,ny)){self.game_overtrue;return;}// 移动蛇头部插入新位置self.snake.push_front((nx,ny));// 吃到食物加分并生成新食物否则去掉尾部if(nx,ny)self.food{self.score10;self.spawn_food();}else{self.snake.pop_back();}}/// 渲染游戏画面fnrender(self)-Result(){letmutstdoutstdout();execute!(stdout,Clear(ClearType::All))?;// 绘制上边框forxin0..self.width{execute!(stdout,MoveTo(xasu16,0),Print(─))?;}// 绘制下边框forxin0..self.width{execute!(stdout,MoveTo(xasu16,(self.height1)asu16),Print(─))?;}// 绘制左右边框foryin0..self.height{execute!(stdout,MoveTo(0,yasu16),Print(│))?;execute!(stdout,MoveTo((self.width1)asu16,yasu16),Print(│))?;}// 绘制食物execute!(stdout,SetBackgroundColor(Color::Red),MoveTo(self.food.0asu161,self.food.1asu161),Print(●),ResetColor)?;// 绘制蛇身for(i,(x,y))inself.snake.iter().enumerate(){execute!(stdout,MoveTo(*xasu161,*yasu161),SetBackgroundColor(ifi0{Color::DarkGreen}else{Color::Green}),Print(■),ResetColor)?;}// 显示分数和游戏结束提示letscore_y(self.height3)asu16;execute!(stdout,MoveTo(0,score_y),Print(format!(Score: {},self.score)))?;ifself.game_over{execute!(stdout,MoveTo(0,score_y1),Print(Game Over! Press q to quit or r to restart))?;}execute!(stdout,MoveTo(0,score_y2))?;stdout.flush()}}/// 检查是否退出fnshould_quit(key:KeyCode)-bool{keyKeyCode::Char(q)||keyKeyCode::Esc}fnmain()-Result(){let(term_w,term_h)size()?;letwidth(term_w.saturating_sub(4)asusize).min(50);letheight(term_h.saturating_sub(8)asusize).min(25);// 初始化终端enable_raw_mode()?;execute!(stdout(),EnterAlternateScreen,Hide)?;letmutgameGame::new(width,height);// 游戏主循环120ms 一帧loop{game.render()?;ifpoll(Duration::from_millis(120))?{ifletEvent::Key(KeyEvent{code,modifiers,..})read()?{ifshould_quit(code){break;}ifgame.game_overcodeKeyCode::Char(r){gameGame::new(width,height);continue;}ifmodifiersKeyModifiers::NONE{matchcode{KeyCode::Up|KeyCode::Char(w)game.set_direction(Direction::Up),KeyCode::Down|KeyCode::Char(s)game.set_direction(Direction::Down),KeyCode::Left|KeyCode::Char(a)game.set_direction(Direction::Left),KeyCode::Right|KeyCode::Char(d)game.set_direction(Direction::Right),_{}}}}}game.update();}// 恢复终端execute!(stdout(),Show,LeaveAlternateScreen)?;disable_raw_mode()?;println!(Final score: {},game.score);Ok(())}2、Cargo.toml[package]nametttversion0.1.0edition2024[dependencies]crossterm0.28rand0.8三、rand 包详解rand是 Rust 生态中用于生成随机数的核心库。它提供了高质量的随机数生成器RNG、多种分布采样方法以及方便的实用工具。在本贪吃蛇游戏中我们使用rand来在游戏区域内随机生成食物位置。1、 添加依赖在Cargo.toml中添加rand依赖[dependencies] rand 0.8版本0.8是当前广泛使用的稳定版本提供了丰富的 API 和良好的性能。2、 核心概念2.1、 随机数生成器 (RNG)rand库的核心是随机数生成器RNG它负责产生随机比特序列。rand提供了多种 RNG 实现ThreadRng线程局部的、密码学安全的 RNG通过rand::thread_rng()获取。这是最常用的 RNG性能良好且安全。StdRng基于 ChaCha 算法的密码学安全 RNG适合需要可重现随机序列的场景。SmallRng非密码学安全的、高性能的 RNG适合模拟和游戏等对速度要求高的场景。2.2、 分布 (Distributions)分布定义了如何将 RNG 产生的原始随机比特映射到特定范围的数值或类型。rand提供了多种分布均匀分布Uniform用于生成指定范围内的整数或浮点数。正态分布Normal生成符合正态高斯分布的随机数。伯努利分布Bernoulli以给定概率生成true或false。加权选择WeightedIndex根据权重从列表中随机选择元素。3、 基本用法3.1、 生成随机数userand::Rng;fnmain(){letmutrngrand::thread_rng();// 生成一个随机整数i32 类型letn1:i32rng.gen();println!(Random i32: {},n1);// 生成一个 [0, 1) 之间的随机浮点数f64 类型letn2:f64rng.gen();println!(Random f64 in [0,1): {},n2);// 生成指定范围的随机整数letn3rng.gen_range(0..10);// 包含 0不包含 10println!(Random integer in [0, 10): {},n3);// 生成指定范围的随机浮点数letn4rng.gen_range(0.0..1.0);println!(Random float in [0.0, 1.0): {},n4);}3.2 、随机布尔值与选择userand::Rng;fnmain(){letmutrngrand::thread_rng();// 以 50% 的概率生成 trueletb:boolrng.gen_bool(0.5);println!(Random bool: {},b);// 从数组中随机选择一个元素letitems[apple,banana,cherry];letchoicerng.choose(items).unwrap();println!(Random choice: {},choice);// 打乱数组原地letmutnumsvec![1,2,3,4,5];rng.shuffle(mutnums);println!(Shuffled: {:?},nums);}4、在贪吃蛇游戏中的应用在我们的贪吃蛇游戏中rand用于在空白位置生成食物。相关代码位于Game::spawn_food方法中/// 在空白位置随机生成食物fnspawn_food(mutself){letmutrngrand::thread_rng();loop{letxrng.gen_range(0..self.width);letyrng.gen_range(0..self.height);if!self.snake.contains((x,y)){self.food(x,y);break;}}}代码解析获取 RNGlet mut rng rand::thread_rng();获取当前线程的随机数生成器。生成随机坐标rng.gen_range(0..self.width)生成一个在[0, self.width)范围内的随机整数作为 x 坐标。同理生成 y 坐标。避免重复使用loop循环确保生成的位置不在蛇身上!self.snake.contains((x, y))。如果冲突则继续生成新的随机位置直到找到空白位置为止。设置食物将找到的空白位置赋值给self.food。这种“生成-检查”循环是游戏开发中常见的随机位置生成模式确保游戏逻辑的正确性。5、 高级特性与性能5.1 、种子与可重现性如果需要可重现的随机序列例如用于测试或回放可以使用种子初始化 RNGuserand::{Rng,SeedableRng};userand_chacha::ChaCha8Rng;// 需要额外依赖 rand_chachafnmain(){letseed[0;32];// 32 字节的种子letmutrngChaCha8Rng::from_seed(seed);// 每次运行都会产生相同的随机数序列println!({},rng.gen::u32());}5.2 、性能优化重用 RNG避免在循环中反复创建thread_rng()应在循环外创建并传递。选择合适的分布对于简单的范围随机gen_range已足够高效。对于需要大量采样的情况考虑预先生成随机数数组。使用SmallRng如果不需要密码学安全可以使用SmallRng获得更好的性能。6、常见问题与陷阱范围包含性gen_range(a..b)包含a但不包含bgen_range(a..b)包含两端。线程安全thread_rng()返回的是线程局部的 RNG不同线程会得到不同的随机序列。性能瓶颈在紧密循环中频繁调用gen_range可能成为性能瓶颈可以考虑批量生成。均匀性gen_range保证均匀分布但某些自定义分布可能需要手动实现。7、总结rand库是 Rust 中处理随机性的标准工具提供了从简单随机数生成到复杂分布采样的完整功能。在贪吃蛇游戏中我们使用rand::thread_rng()和gen_range方法实现了食物的随机生成这是游戏随机性的核心来源。掌握rand的基本用法后你可以轻松将其应用于游戏开发、模拟、测试数据生成等多种场景。
返回列表