【Rust 实战】 贪吃蛇(控制台版)

【Rust 实战】 贪吃蛇(控制台版)如果你已经看完了前面的 Rust 所有文章,那基本已经可以上手一些项目了。本篇文章也算是对前面知识的一种运用吧,我们一起来实现一个简单的贪吃蛇游戏(代码不到270行)。先看下效果图吧。

0x00 开篇

如果你已经看完了前面的 Rust 所有文章,那基本已经可以上手一些项目了。本篇文章也算是对前面知识的一种运用吧,我们一起来实现一个简单的贪吃蛇游戏(代码不到270行)。先看下效果图吧。

screenshots.gif

 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 语言的 getchkbhit 等方法。在 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

(0)
上一篇 2023-11-13
下一篇 2023-11-13

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注