Rust编写的贪吃蛇小游戏源代码解读
学习语言就要多读多写多调试代码
1、源代码获取以及运行
下载附件资源,进入目录后使用
cargo run
可以执行,获得如下运行界面,使用方向键可以控制蛇的走向:
2、代码解析
2.1 snake的创建与运动
在 snake.rs
文件中定义了 Snake
结构体,它代表了游戏中的蛇,以及与之相关的一系列行为。下面我们将通过源代码来解析蛇是如何活动的。
Snake
结构体定义
pub struct Snake {
moving_direction: Direction,
body: LinkedList<Block>,
last_removed_block: Option<Block>
}
moving_direction
: 当前移动方向。body
: 蛇的身体,由Block
(块)组成,每个块代表蛇身体的一个部分。last_removed_block
: 最后移除的块,用于实现蛇吃食物后身体的增长。
Direction
枚举
#[derive(Clone, Copy, PartialEq)]
pub enum Direction {
Up, Down, Left, Right
}
定义了蛇可能的移动方向。
Block
结构体
struct Block {
x: i32,
y: i32
}
代表蛇身体的一个部分,包含 x
和 y
坐标。
Snake
实现的方法
new
方法
pub fn new(init_x: i32, init_y: i32) -> Snake {
let mut body: LinkedList<Block> = LinkedList::new();
body.push_back(Block {
x: init_x + 2,
y: init_y
});
body.push_back(Block {
x: init_x + 1,
y: init_y
});
body.push_back(Block {
x: init_x,
y: init_y
});
Snake {
moving_direction: Direction::Right,
body: body,
last_removed_block: None
}
}
创建一个新的蛇实例,初始位置为 (init_x, init_y)
,蛇的身体由三部分组成,分别对应蛇头和两段身体。
draw
方法
pub fn draw(&self, con: &Context, g: &mut G2d) {
for block in &self.body {
draw_block(SNAKE_COLOR, block.x, block.y, con, g);
}
}
绘制蛇的每一部分。遍历蛇身体的每个 Block
,并使用 draw_block
函数绘制每个部分。
move_forward
方法
pub fn move_forward(&mut self, dir: Option<Direction>) {
match dir {
Some(d) => self.moving_direction = d,
None => {}
}
let (last_x, last_y): (i32, i32) = self.head_position();
let new_block = match self.moving_direction {
Direction::Up => Block {
x: last_x,
y: last_y - 1
},
Direction::Down => Block {
x: last_x,
y: last_y + 1
},
Direction::Left => Block {
x: last_x - 1,
y: last_y
},
Direction::Right => Block {
x: last_x + 1,
y: last_y
}
};
self.body.push_front(new_block);
let removed_blk = self.body.pop_back().unwrap();
self.last_removed_block = Some(removed_blk);
}
根据当前方向移动蛇。首先,根据传入的 dir
参数更新蛇的移动方向(如果有的话)。然后,计算蛇头的新位置,并在蛇的前面添加一个新的 Block
。最后,移除蛇尾的最后一个 Block
,并将其保存在 last_removed_block
中。
head_position
方法
pub fn head_position(&self) -> (i32, i32) {
let head_block = self.body.front().unwrap();
(head_block.x, head_block.y)
}
返回蛇头的坐标。
restore_last_removed
方法
pub fn restore_last_removed(&mut self) {
let blk = self.last_removed_block.clone().unwrap();
self.body.push_back(blk);
}
恢复最后移除的 Block
,这通常发生在蛇吃到食物后,需要增长身体。
总结
蛇的活动主要包括初始化、绘制、移动和增长。在游戏循环中,蛇会根据玩家的输入和游戏逻辑不断移动,每次移动都会在前面添加一个新的 Block
并移除尾部的 Block
。当蛇吃到食物时,会调用 restore_last_removed
方法来增长身体。通过这种方式,蛇在游戏中不断活动。
2.2 snake移动,判定撞墙,以及吃到食代码逻辑
蛇的自动移动、撞墙判定和吃到食物的逻辑主要在 game.rs
和 snake.rs
文件中实现。下面将详细解析这些功能是如何工作的。
蛇的自动移动
蛇的自动移动是由 game.rs
中的 update
方法触发的。这个方法在每次游戏循环中被调用,负责更新游戏状态,包括蛇的移动。
pub fn update(&mut self, delta_time: f64) {
self.waiting_time += delta_time;
// If the game is over
if self.is_game_over {
if self.waiting_time > RESTART_TIME {
self.restart();
}
return;
}
// Check if the food still exists
if !self.food_exist {
self.add_food();
}
// Move the snake
if self.waiting_time > MOVING_PERIOD {
self.update_snake(None);
}
}
waiting_time
用于控制蛇的移动频率。只有当waiting_time
大于MOVING_PERIOD
时,蛇才会移动。update_snake(None)
被调用,将蛇向前移动一格。
在 Snake
结构体的 move_forward
方法中,蛇的实际移动逻辑被执行:
pub fn move_forward(&mut self, dir: Option<Direction>) {
// Change moving direction
match dir {
Some(d) => self.moving_direction = d,
None => {}
}
// Retrieve the position of the head block
let (last_x, last_y): (i32, i32) = self.head_position();
// The snake moves
let new_block = match self.moving_direction {
Direction::Up => Block {
x: last_x,
y: last_y - 1
},
Direction::Down => Block {
x: last_x,
y: last_y + 1
},
Direction::Left => Block {
x: last_x - 1,
y: last_y
},
Direction::Right => Block {
x: last_x + 1,
y: last_y
}
};
self.body.push_front(new_block);
let removed_blk = self.body.pop_back().unwrap();
self.last_removed_block = Some(removed_blk);
}
撞墙判定
撞墙判定是在 update_snake
方法中执行的:
fn check_if_the_snake_alive(&self, dir: Option<Direction>) -> bool {
let (next_x, next_y) = self.snake.next_head_position(dir);
// Check if the snake hits itself
if self.snake.is_overlap_except_tail(next_x, next_y) {
return false;
}
// Check if the snake overlaps with the border
next_x > 0 && next_y > 0 && next_x < self.width - 1 && next_y < self.height - 1
}
next_head_position
方法计算蛇头的下一个位置。is_overlap_except_tail
方法检查蛇是否撞到自己的身体(不包括尾巴)。- 检查蛇头的下一个位置是否在游戏边界内。
如果蛇撞墙或撞到自己,update_snake
方法将游戏状态设置为结束:
fn update_snake(&mut self, dir: Option<Direction>) {
if self.check_if_the_snake_alive(dir) {
self.snake.move_forward(dir);
self.check_eating();
} else {
self.is_game_over = true;
}
self.waiting_time = 0.0;
}
吃到食物
吃到食物的逻辑主要在 check_eating
和 update_snake
方法中实现:
fn check_eating(&mut self) {
let (head_x, head_y): (i32, i32) = self.snake.head_position();
if self.food_exist && self.food_x == head_x && self.food_y == head_y {
self.food_exist = false;
self.snake.restore_last_removed();
}
}
head_position
获取蛇头的当前位置。- 如果蛇头的位置与食物的位置相同,且食物存在,则食物被吃掉,蛇的身体增长。
在 update_snake
方法中,每次蛇移动后都会调用 check_eating
方法来检查是否吃到食物。如果蛇吃到食物,restore_last_removed
方法会被调用,将之前移除的块(尾巴)重新添加到蛇的身体中,实现蛇的生长。
通过这些方法的协同工作,蛇在游戏中自动移动,自动判定撞墙,以及吃到食物的逻辑得以实现。