大家好,我是考100分的小小码 ,祝大家学习进步,加薪顺利呀。今天说一说【Rust 实战】 贪吃蛇(控制台版),希望您对编程的造诣更进一步.
0x00 开篇
如果你已经看完了前面的 Rust 所有文章,那基本已经可以上手一些项目了。本篇文章也算是对前面知识的一种运用吧,我们一起来实现一个简单的贪吃蛇游戏(代码不到270行)。先看下效果图吧。
0x01简单分析贪吃蛇游戏
首先,先来简单分析下贪吃蛇游戏的组成(下图)。贪吃蛇游戏一般由 蛇
、食物
、墙
(边界)三大部分组成。其中,关键的游戏主体 蛇
又被分为 蛇身
和 蛇头
。在游戏中,蛇
可以吃 食物
,可以上下左右移动。而 食物
是随机生成。最后触发游戏结束的条件有两个,一是蛇头触及蛇身,二是蛇头触及 墙
(边界)。
0x02 将元素代码化
墙
墙
是一个四周封闭的空间,我们可以将 墙
用一个二维数组表示,它的大小就是 长 * 宽
。我们边界用 ■
表示,其余的都是空白字符表示了。
食物
蛇
和 食物
可以看做是二维数组内的任一元素。食物
和 蛇
我们可以用结构体来表示。食物
一般只有一个小方格,它的坐标只是 x
和 y
,我们用一个只包含两个元素的数组表示。另外,食物
还需要一个状态,判断是否被吃了,如果被吃了我们需要生成新的食物。食物我们用 ▣
表示。
蛇
蛇
由两部分组成,蛇头跟 食物
类似,只占一个小方格。而蛇身是一个点的集合,而且是一直在动态变化的,我们使用向量来保存。为了增加游戏趣味性,可以再增加一个速度变量。蛇头我们用 ●
表示,蛇身我们用 ▣
表示
最终的代码如下:
// 墙 -> 二维数组
const WIDTH: usize = 50;
const HEIGHT: usize = 10;
let mut map: [[&'static str; WIDTH]; HEIGHT] = [[" "; WIDTH]; HEIGHT];
/// 食物
struct Food {
position: [usize; 2],
eat: bool,
}
/// 蛇
struct Snake {
// ● 蛇头
head: [usize; 2],
// 速度
speed: u64,
// 蛇身
body: Vec<[usize; 2]>,
}
由于 墙
的定义太长了,我们使用 type
起个别名 type Map = [[&'static str; WIDTH]; HEIGHT];
。
0x03 蛇的移动
蛇
每次移动都会前进一个小方格,我们是否需要把蛇头和蛇身都前进一小格呢?我们对比移动前后的图片(下图)。
move
仔细观察不难发现,我们只需要将蛇尾的位置移动到之前蛇头的位置,并将蛇头前进一格,整个移动过程就结束了。代码如下:
// 以向右移动为例
// 移动前蛇头的位置
let before_head = snake.head;
// 蛇头的横坐标 + 1
snake.head[1] += 1;
// 以前的蛇头位置变为蛇身
map[before_head[0]][before_head[1]] = "▣";
snake.body.insert(0, before_head);
// 移除蛇尾的位置并将将原位置的元素清空
let snake_foot = snake.body.remove(snake.body.len() - 1);
map[snake_foot[0]][snake_foot[1]] = " ";
0x04 蛇吃食物
蛇
吃 食物
应该算是移动的升级版。来看下图。
吃食物
吃食物的时候,我们只需要交换蛇头和食物的位置即可。
// 移动前蛇头的位置
let before_head = snake.head;
// 蛇头的横坐标 + 1
snake.head[1] += 1;
// 以前的蛇头位置变为蛇身
map[before_head[0]][before_head[1]] = "▣";
snake.body.insert(0, before_head);
// 我们再把食物是否吃掉的标注更改下
food.eat = true;
其实可以与移动相对比下我们会发现,吃 食物
后与移动的区别就是是否移除蛇身的最后一个元素。食物
被吃掉后,就需要重新生成食物
。生成食物,只需要随机生成一个食物坐标即可。这里我们使用随机库rand
。
// 随机算法这里仅是简单实现,生成食物的时候需要避开蛇的身体
let mut rng = rand::thread_rng();
let x = rng.gen_range(1..HEIGHT - 1);
let y = rng.gen_range(1..WIDTH - 1);
map[x][y] = "▣";
food.position = [x, y];
food.eat = false;
0x05 判断游戏结束
我们开篇已经说过游戏结束的两个条件,一是蛇头触及蛇身,二是蛇头触及 墙
(边界)。这个判断很简单,代码如下。
/// 游戏是否结束
fn is_game_over(snake: &Snake) -> bool {
let head = snake.head;
// 如果蛇头触及到边界 触发游戏结束
if head[0] == 0 || head[0] == HEIGHT - 1
|| head[1] == 0 || head[1] == WIDTH - 1 {
return true;
}
let body_list = &snake.body;
if body_list.is_empty() {
return false;
}
// 蛇头触及到蛇的身体
for body in body_list.iter() {
if body[0] == head[0]
&& body[1] == head[1] {
return true;
}
}
return false;
}
0x06 其它注意事项
由于 Rust 并没有提供类似 C
语言的 getch
、kbhit
等方法。在 Rust 获取键盘按键控制的方法中,我使用的是 device_query
这个库。但是有反馈这个库占内存比较大。
另外,在 Windows 下,每次输出都需要清屏:
Command::new("cmd.exe").arg("/c").arg("cls").status().expect("clear error!");
最后再补充一句,本篇文章也只是带领大家回顾下之前学习的内容。控制台实现的贪吃蛇并不是很完美,如果你对游戏比较感兴趣,那可以使用 bevy
引擎来实现。
0x07 第三方库
device_query
: docs.rs/device_quer…
rand
: docs.rs/rand/0.8.5/…
bevy
: bevyengine.org/
0x08 源码
请于公众号内自取
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
转载请注明出处: https://daima100.com/36728.html