一个贪吃蛇游戏的 rust 实现,使用了 piston_window 和 rand crate。
游戏使用 上下左右 方向键进行操控,使用 R 重置游戏,使用 P 进行暂停/启动。

项目结构

·├── Cargo.lock├── Cargo.toml├── src/│ ├── main.rs│ ├──snake_game/│ │ ├── game.rs.rs│ │ └── mod.rs│ ├──snake_snake/│ │ ├── snake.rs│ │  └── mod.rs│ └──snake_window/│   ├──draw.rs│   └── mod.rs

三个mod.rs 文件

// snake_game/mod.rspub mod game;// snake_snake/mod.rspub mod snake;// snake_window/mod.rspub mod draw;

main.rs

use piston_window::types::Color;use piston_window::{clear, Button, PistonWindow, PressEvent, UpdateEvent, WindowSettings};mod snake_game;mod snake_snake;mod snake_window;use crate::snake_game::game::Game;use snake_window::draw::to_coord_u32;/// 定义背景颜色const BACK_COLOR: Color = [0.5, 0.5, 0.5, 1.0];fn main() {    // 定义窗口大小的参数    let (width, height) = (30, 30);    // 定义游戏窗口    let mut window: PistonWindow =        WindowSettings::new("Snake", [to_coord_u32(width), to_coord_u32(height)])            .exit_on_esc(true)            .build()            .unwrap();    // 创建游戏    let mut game = Game::new(width, height);    // 监听窗口输入内容    while let Some(event) = window.next() {        // 监听用户输入        if let Some(Button::Keyboard(key)) = event.press_args() {            game.key_pressed(key);        }        // 清理当前窗口内容,并重新绘制游戏内容        window.draw_2d(&event, |c, g, _| {            clear(BACK_COLOR, g);            game.draw(&c, g)        });        // 更新游戏数据        event.update(|arg| {            game.update(arg.dt);        });    }}

game.rs

use crate::snake_snake::snake::{Direction, Snake};use crate::snake_window::draw::{draw_block, draw_rectangle};use piston_window::rectangle::Shape;use piston_window::types::Color;use piston_window::{Context, G2d, Key};use rand::{thread_rng, Rng};/// 食物颜色const FOOD_COLOR: Color = [255.0, 0.0, 255.0, 1.0];/// 上边框颜色const T_BORDER_COLOR: Color = [0.0000, 0.5, 0.5, 0.6];/// 下边框颜色const B_BORDER_COLOR: Color = [0.0000, 0.5, 0.5, 0.6];/// 左边框颜色const L_BORDER_COLOR: Color = [0.0000, 0.5, 0.5, 0.6];/// 右边框颜色const R_BORDER_COLOR: Color = [0.0000, 0.5, 0.5, 0.6];///游戏结束颜色const GAMEOVER_COLOR: Color = [0.90, 0.00, 0.00, 0.5];/// 移动周期,每过多长时间进行一次移动const MOVING_PERIOD: f64 = 0.3;/// 游戏主体#[derive(Debug)]pub struct Game {    /// 蛇的主体    snake: Snake,    /// 食物是否存在    food_exists: bool,    /// 食物x坐标    food_x: i32,    /// 食物y坐标    food_y: i32,    /// 游戏的宽    width: i32,    /// 游戏的高    height: i32,    /// 游戏是否结束    game_over: bool,    /// 等待时间    waiting_time: f64,    /// 是否暂停    game_pause: bool,}impl Game {    /// 初始化游戏数据    pub fn new(width: i32, height: i32) -> Game {        Game {            snake: Snake::new(2, 2),            food_exists: true,            food_x: 6,            food_y: 4,            width,            height,            game_over: false,            waiting_time: 0.0,            game_pause: false,        }    }    /// 对外暴露的控制方法    pub fn key_pressed(&mut self, key: Key) {        // 输入 R 快速重新游戏        if key == Key::R {            self.restart()        }        if self.game_over {            return;        }        let dir = match key {            Key::Up => Some(Direction::Up),            Key::Down => Some(Direction::Down),            Key::Left => Some(Direction::Left),            Key::Right => Some(Direction::Right),            Key::P => {                // 输入 P 暂停/启动游戏                self.game_pause = !self.game_pause;                None            }            _ => None,        };        if let Some(d) = dir {            // 如果输入方向为当前方向的相反方向,不做任何处理            if d == self.snake.head_direction().opposite() {                return;            }        }        // 如果为有效输入,直接刷新蛇的方向        self.update_snake(dir);    }    /// 是否吃到了果子    fn check_eating(&mut self) {        let (head_x, head_y) = self.snake.head_position();        if self.food_exists && self.food_x == head_x && self.food_y == head_y {            self.food_exists = false;            self.snake.restore_tail();        }    }    /// 对外暴露的游戏绘制    pub fn draw(&self, con: &Context, g: &mut G2d) {        self.snake.draw(con, g);        if self.food_exists {            draw_block(                FOOD_COLOR,                Shape::Round(8.0, 16),                self.food_x,                self.food_y,                con,                g,            );        }        //上边框        draw_rectangle(T_BORDER_COLOR, 0, 0, self.width, 1, con, g);        // 下边框        draw_rectangle(B_BORDER_COLOR, 0, self.height - 1, self.width, 1, con, g);        // 左边框        draw_rectangle(L_BORDER_COLOR, 0, 1, 1, self.height - 2, con, g);        // 右边框        draw_rectangle(            R_BORDER_COLOR,            self.width - 1,            1,            1,            self.height - 2,            con,            g,        );        // 如果游戏失败 绘制游戏失败画面        if self.game_over {            draw_rectangle(GAMEOVER_COLOR, 0, 0, self.width, self.height, con, g);        }    }    /// 对外暴露的游戏更新入口    pub fn update(&mut self, delta_time: f64) {        // 如果游戏暂停/结束时,不执行操作        if self.game_pause || self.game_over {            return;        }        // 增加游戏的等待时间        self.waiting_time += delta_time;        if !self.food_exists {            self.add_food()        }        if self.waiting_time > MOVING_PERIOD {            self.update_snake(None)        }    }    /// 添加果子    fn add_food(&mut self) {        let mut rng = thread_rng();        let mut new_x = rng.gen_range(1..self.width - 1);        let mut new_y = rng.gen_range(1..self.height - 1);        while self.snake.over_tail(new_x, new_y) {            new_x = rng.gen_range(1..self.width - 1);            new_y = rng.gen_range(1..self.height - 1);        }        self.food_x = new_x;        self.food_y = new_y;        self.food_exists = true;    }    /// 检查当前游戏蛇的生存状态,蛇自身碰撞检测、游戏边界碰撞检测    fn check_if_snake_alive(&self, dir: Option) -> bool {        let (next_x, next_y) = self.snake.next_head(dir);        if self.snake.over_tail(next_x, next_y) {            return false;        }        next_x > 0 && next_y > 0 && next_x < self.width - 1 && next_y < self.height - 1    }    /// 更新蛇的数据    fn update_snake(&mut self, dir: Option) {        if self.game_pause {            return;        }        if self.check_if_snake_alive(dir) {            self.snake.move_forward(dir);            self.check_eating();        } else {            self.game_over = true;        }        self.waiting_time = 0.0;    }    /// 重置游戏    fn restart(&mut self) {        self.snake = Snake::new(2, 2);        self.waiting_time = 0.0;        self.food_exists = true;        self.food_x = 6;        self.food_y = 4;        self.game_over = false;        self.game_pause = false;    }}

snake.rs

use crate::snake_window::draw::draw_block;use piston_window::rectangle::Shape;use piston_window::types::Color;use piston_window::{Context, G2d};use std::collections::LinkedList;/// 蛇身体的颜色const SNAKE_BODY_COLOR: Color = [0.5, 0.0, 0.0, 1.0];/// 蛇头的颜色const SNAKE_HEAD_COLOR: Color = [1.0, 0.00, 0.00, 1.0];/// 输入方向限定为 上下左右#[derive(Debug, Clone, Copy, PartialEq, Eq)]pub enum Direction {    Up,    Down,    Left,    Right,}impl Direction {    /// 方向输入合法性验证,不能直接转向相反方向    pub fn opposite(&self) -> Direction {        match *self {            Direction::Up => Direction::Down,            Direction::Down => Direction::Up,            Direction::Left => Direction::Right,            Direction::Right => Direction::Left,        }    }}/// 块,蛇的身体的最小单元#[derive(Debug, Clone)]struct Block {    x: i32,    y: i32,}/// 定义蛇的数据#[derive(Debug)]pub struct Snake {    /// 当前朝向    direction: Direction,    /// 蛇的身体    body: LinkedList,    /// 蛇的尾巴    tail: Option,}impl Snake {    /// 蛇的初始化    pub fn new(x: i32, y: i32) -> Snake {        let mut body: LinkedList = LinkedList::new();        body.push_back(Block { x: x + 2, y: y });        body.push_back(Block { x: x + 1, y: y });        body.push_back(Block { x: x, y: y });        Snake {            direction: Direction::Right,            body,            tail: None,        }    }    /// 蛇的绘制    pub fn draw(&self, con: &Context, g: &mut G2d) {        let mut is_head = true;        for block in &self.body {            if is_head {                is_head = false;                draw_block(                    SNAKE_HEAD_COLOR,                    Shape::Round(10.0, 16),                    block.x,                    block.y,                    con,                    g,                );            } else {                draw_block(                    SNAKE_BODY_COLOR,                    Shape::Round(12.5, 16),                    block.x,                    block.y,                    con,                    g,                );            }        }    }    /// 蛇头的当前坐标    pub fn head_position(&self) -> (i32, i32) {        let head = self.body.front().unwrap();        (head.x, head.y)    }    /// 蛇头的当前方向    pub fn head_direction(&self) -> Direction {        self.direction    }    /// 蛇头的下一个位置的坐标    pub fn next_head(&self, dir: Option) -> (i32, i32) {        let (head_x, head_y): (i32, i32) = self.head_position();        let mut moving_dir = self.direction;        match dir {            Some(d) => moving_dir = d,            None => {}        }        match moving_dir {            Direction::Up => (head_x, head_y - 1),            Direction::Down => (head_x, head_y + 1),            Direction::Left => (head_x - 1, head_y),            Direction::Right => (head_x + 1, head_y),        }    }    /// 向前移动    pub fn move_forward(&mut self, dir: Option) {        match dir {            Some(d) => self.direction = d,            None => (),        }        let (x, y) = self.next_head(dir);        self.body.push_front(Block { x, y });        let remove_block = self.body.pop_back().unwrap();        self.tail = Some(remove_block);    }    /// 增加蛇的长度    pub fn restore_tail(&mut self) {        let blk = self.tail.clone().unwrap();        self.body.push_back(blk);    }    /// 自身碰撞检测    pub fn over_tail(&self, x: i32, y: i32) -> bool {        let mut ch = 0;        for block in &self.body {            if x == block.x && y == block.y {                return true;            }            ch += 1;            if ch == self.body.len() - 1 {                break;            }        }        false    }}

draw.rs

use piston_window::rectangle::Shape;use piston_window::types::Color;use piston_window::{rectangle, Context, DrawState, G2d, Rectangle};/// 定义块的大小const BLOCK_SIZE: f64 = 20.0;/// 将 i32 转为 f64pub fn to_coord(game_coord: i32) -> f64 {    (game_coord as f64) * BLOCK_SIZE}/// 将 i32 转为 u32pub fn to_coord_u32(game_coord: i32) -> u32 {    to_coord(game_coord) as u32}/// 块图形绘制/// * shape : piston_window::rectangle::Shapepub fn draw_block(color: Color, shape: Shape, x: i32, y: i32, con: &Context, g: &mut G2d) {    let rec = Rectangle::new(color).color(color).shape(shape);    let gui_x = to_coord(x);    let gui_y = to_coord(y);    let rectangle = [gui_x, gui_y, BLOCK_SIZE, BLOCK_SIZE];    rec.draw(rectangle, &DrawState::default(), con.transform, g)}/// 长方形区域绘制pub fn draw_rectangle(    color: Color,    x: i32,    y: i32,    width: i32,    height: i32,    con: &Context,    g: &mut G2d,) {    let gui_x = to_coord(x);    let gui_y = to_coord(y);    let width = to_coord(width);    let height = to_coord(height);    rectangle(color, [gui_x, gui_y, width, height], con.transform, g);}

Rust官网
Rust 中文社区

本文来自博客园,作者:贤云曳贺,转载请注明原文链接:https://www.cnblogs.com/SantiagoZhang/p/17286058.html