一个贪吃蛇游戏的 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