首页 > 其他分享 >一个简单的rust项目贪吃蛇

一个简单的rust项目贪吃蛇

时间:2023-04-04 13:11:52浏览次数:50  
标签:head i32 self game 贪吃蛇 let snake 简单 rust

一个贪吃蛇游戏的 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.rs
pub mod game;

// snake_snake/mod.rs
pub mod snake;

// snake_window/mod.rs
pub 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<Direction>) -> 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<Direction>) {
        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<Block>,
    /// 蛇的尾巴
    tail: Option<Block>,
}

impl Snake {
    /// 蛇的初始化
    pub fn new(x: i32, y: i32) -> Snake {
        let mut body: LinkedList<Block> = 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<Direction>) -> (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<Direction>) {
        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 转为 f64
pub fn to_coord(game_coord: i32) -> f64 {
    (game_coord as f64) * BLOCK_SIZE
}

/// 将 i32 转为 u32
pub fn to_coord_u32(game_coord: i32) -> u32 {
    to_coord(game_coord) as u32
}

/// 块图形绘制
/// * shape : piston_window::rectangle::Shape
pub 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 中文社区

标签:head,i32,self,game,贪吃蛇,let,snake,简单,rust
From: https://www.cnblogs.com/SantiagoZhang/p/17286058.html

相关文章

  • vue第三课:简单点击器应用
    简单需求:1,最小为0,小于0则不能再点击减少,并显示提示2,最大值为10,小于10则可以点击增加,超过10则不能再点击,并显示提示<!DOCTYPEhtml><html><head><metacharset="UTF-8"><title>v-html测试</title><scriptsrc="vue.js"></script>......
  • 一个.Net简单、易用的配置文件操作库
    在我们日常项目开发中,操作INI/CFG配置文件,往往会通过调用WinAPI来实现,WinAPI接口参数只支持字符串,而我们项目中,往往数据类型是多种多样的,在保存和获取配置值,我们就要进行类型的转换。今天给大家推荐一个操作库,这个库就可以解决我们的问题。项目简介这是一个基于.Net开发的简单......
  • 一个非常简单用.NET操作RabbitMQ的方法
    一个非常简单用.NET操作RabbitMQ的方法 RabbitMQ作为一款主流的消息队列工具早已广受欢迎。相比于其它的MQ工具,RabbitMQ支持的语言更多、功能更完善。 本文提供一种市面上最/极简单的使用RabbitMQ的方式(支持.NET/.NETFramework/.NETCore),只需要会调用以下三个方法,你就几......
  • 高效简单的.Net数据库“访问+操作”技术
    高效简单的.Net数据库“访问+操作”技术 本文技术源自外企,并已在多个世界500强大型项目开发中运用。本文适合有初步C#、Linq、Sql知识的同学阅读。 相关技术在IDataAccess接口中提供。IDataAccess所在的命名空间是:DeveloperSharp.Framework.QueryEngine。(需事先从nuget......
  • vector 简单应用
                            Vector简单应用定义:vector是C++标准模板库中的部分内容,中文偶尔译作"容器",但并不准确。它是一个多功能的,能够操作多种数据结构和算法的模板类和函数库。vector之所以被认为是一个容器,是因为它能够像容器......
  • 制作图标,设置简单元素
    #1、批量制作数据透视表'''file_path='商品销售表'file_list=os.listdir(file_path)forjinfile_list:ifos.path.splitext(j)[1]=='.xslx':workbook=app.books.open('销售表.xlsx')worksheet=workbook.s......
  • Flask学习笔记(2)--最简单的小应用
    Flask学习笔记(2)--最简单的小应用 新建一个flask项目,第一个小程序,我们来看一下fromflaskimportFlaskapp=Flask(__name__)@app.route('/')defhello_world():return'HelloWorld!'if__name__=='__main__':app.run() 1、引入flask类2、将Flask对象......
  • Prism 中区域、模块化、导航功能、对话服务、发布订阅的简单使用
    本文演示了Prism框架在WPF编程中的几个基本功能环境:VisualStudio2022内容介绍Prism几个基本功能,包括区域、模块化、导航功能、对话服务、发布订阅区域可以在XMAL中定义某ContentControl为Prism的区域,并在App中注册模块://这个区域的名字为ContentRegion......
  • C#简单实现回调函数
    C#简单实现回调函数//C#简单实现回调函数Test.Main();publicclassTest//用户层,执行输入等操作{publicstaticvoidMain(){Calculatecc=newCalculate();Functionfc=newFunction();intresult1=cc.CalculateTest(2,3,f......
  • 为什么 Python、Go 和 Rust 都不支持三元运算符?
    在编程时,我们经常要作条件判断,并根据条件的结果选择执行不同的语句块。在许多编程语言中,最常见的写法是三元运算符,但是,Python并不支持三元运算符,无独有偶,两个最热门的新兴语言Go和Rust也不支持!为什么Python不支持三元运算符呢?本文将主要分析Python在设计条件选择语法时......