自学内容网 自学内容网

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
}

代表蛇身体的一个部分,包含 xy 坐标。

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.rssnake.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_eatingupdate_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 方法会被调用,将之前移除的块(尾巴)重新添加到蛇的身体中,实现蛇的生长。

通过这些方法的协同工作,蛇在游戏中自动移动,自动判定撞墙,以及吃到食物的逻辑得以实现。


原文地址:https://blog.csdn.net/gzjimzhou/article/details/143753225

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!