Aprendendo a ferrugem: como eu fiz um jogo de cobra

imagem

Recentemente, comecei a estudar a linguagem de programação Rust e, desde que, quando eu aprendo uma nova linguagem, faço "Snake", decidi fazê-la.

Para gráficos 3D, foi usada a biblioteca Three.rs, que é uma porta da biblioteca Three.js.

Código
Baixar e jogar

Captura de tela do jogo
imagem

Código do jogo
/*  .    Cargo.toml [dependencies] rand="*" three="*" serde="*" bincode="*" serde_derive="*"  */ extern crate rand; extern crate three; extern crate bincode; extern crate serde; #[macro_use] extern crate serde_derive; //        . use rand::Rng; use three::*; use std::error::Error; //Entities ------------------------------------------------------------------ /*  .       .    : Debug -          Clone -        . .      clone() Eq  PartialEq    Point    == */ #[derive(Debug, Clone, Eq, PartialEq, Default)] //    .      struct Point { x: u8, y: u8, } //   impl Point { //      ==  ,         pub fn intersects(&self, point: &Point) -> bool { self.x == point.x && self.y == point.y } } #[derive(Debug, Clone, Eq, PartialEq, Default)] //               struct Frame { min_x: u8, min_y: u8, max_x: u8, max_y: u8, } impl Frame { pub fn intersects(&self, point: &Point) -> bool { point.x == self.min_x || point.y == self.min_y || point.x == self.max_x || point.y == self.max_y } } #[derive(Debug, Clone, Eq, PartialEq)] //   4  //            enum Direction { Left, Right, Top, Bottom, } //  (      ) //   . //        . impl Default for Direction { fn default() -> Direction { return Direction::Right; } } #[derive(Debug, Clone, Eq, PartialEq, Default)] //   struct Snake { direction: Direction, points: std::collections::VecDeque<Point>, start_x: u8, start_y: u8, } impl Snake { //         pub fn new(x: u8, y: u8) -> Snake { let mut points = std::collections::VecDeque::new(); for i in 0..3 { points.push_front(Point { x: x + i, y: i + y }); } Snake { direction: Direction::default(), points, start_x: x, start_y: y } } //       pub fn grow(mut self) -> Snake { if let Some(tail) = self.points.pop_back() { self.points.push_back(Point { x: tail.x, y: tail.y }); self.points.push_back(tail); } self } //      pub fn reset(self) -> Snake { Snake::new(self.start_x, self.start_y) } //       pub fn turn(mut self, direction: Direction) -> Snake { self.direction = direction; self } //                     pub fn try_eat(mut self, point: &Point) -> (Snake, bool) { let head = self.head(); if head.intersects(point) { return (self.grow(), true); } (self, false) } //            pub fn try_intersect_frame(mut self, frame: &Frame) -> Snake { let head = self.head(); if frame.intersects(&head) { return self.reset(); } self } //            . pub fn try_intersect_tail(mut self) -> Snake { let head = self.head(); let p = self.points.clone(); let points = p.into_iter().filter(|p| head.intersects(p)); if points.count() > 1 { return self.reset(); } self } //   pub fn head(&self) -> Point { self.points.front().unwrap().clone() } //               pub fn move_snake(mut self) -> Snake { if let Some(mut tail) = self.points.pop_back() { let head = self.head(); match self.direction { Direction::Right => { tail.x = head.x + 1; tail.y = head.y; } Direction::Left => { tail.x = head.x - 1; tail.y = head.y; } Direction::Top => { tail.x = head.x; tail.y = head.y - 1; } Direction::Bottom => { tail.x = head.x; tail.y = head.y + 1; } } self.points.push_front(tail); } self } } //Data Access Layer ---------------------------------------------------------------- #[derive(Debug, Clone, Eq, PartialEq, Default)] //       struct FoodGenerator { frame: Frame } impl FoodGenerator { //         pub fn generate(&self) -> Point { let x = rand::thread_rng().gen_range(self.frame.min_x + 1, self.frame.max_x); let y = rand::thread_rng().gen_range(self.frame.min_y + 1, self.frame.max_y); Point { x, y } } } #[derive(Serialize, Deserialize)] //      struct ScoreRepository { score: usize } impl ScoreRepository { //        // Result             fn save(value: usize) -> Result<(), Box<Error>> { use std::fs::File; use std::io::Write; let score = ScoreRepository { score: value }; //        bincode //  ?     . .          let bytes: Vec<u8> = bincode::serialize(&score)?; //          . let mut file = File::create(".\\score.data")?; match file.write_all(&bytes) { Ok(t) => Ok(t), //Error             //      Box         //                  //     Err(e) => Err(Box::new(e)) } } //     fn load() -> Result<usize, Box<Error>> { use std::fs::File; let mut file = File::open("./score.data")?; let data: ScoreRepository = bincode::deserialize_from(file)?; Ok(data.score) } } //Business Logic Layer------------------------------------------------------------ #[derive(Debug, Clone, Default)] //     struct Game { snake: Snake, frame: Frame, food: Point, food_generator: FoodGenerator, score: usize, max_score: usize, total_time: f32, } impl Game { //          fn new(height: u8, width: u8) -> Game { let frame = Frame { min_x: 0, min_y: 0, max_x: width, max_y: height }; let generator = FoodGenerator { frame: frame.clone() }; let food = generator.generate(); let snake = Snake::new(width / 2, height / 2); Game { snake, frame, food, food_generator: generator, score: 0, max_score: match ScoreRepository::load() { Ok(v) => v, Err(_) => 0 }, total_time: 0f32, } } // ,            //         //          //     fn update(mut self, time_delta_in_seconds: f32) -> Game { let (game, is_moving) = self.is_time_to_move(time_delta_in_seconds); self = game; if is_moving { self.snake = self.snake.clone() .move_snake() .try_intersect_tail() .try_intersect_frame(&self.frame); self.try_eat() } else { self } } //,        . fn is_time_to_move(mut self, time_delta_in_seconds: f32) -> (Game, bool) { let time_to_move: f32 = 0.030; self.total_time += time_delta_in_seconds; if self.total_time > time_to_move { self.total_time -= time_to_move; (self, true) } else { (self, false) } } //,         //    ,    //      fn try_eat(mut self) -> Game { let initial_snake_len = 3; if self.snake.points.len() == initial_snake_len { self.score = 0 } let (snake, eaten) = self.snake.clone().try_eat(&self.food); self.snake = snake; if eaten { self.food = self.food_generator.generate(); self.score += 1; if self.max_score < self.score { self.max_score = self.score; ScoreRepository::save(self.max_score); } }; self } //      fn handle_input(mut self, input: Direction) -> Game { let snake = self.snake.turn(input); self.snake = snake; self } } //Application Layer-------------------------------------------------------------- // --- Model ---- #[derive(Debug, Clone, Eq, PartialEq)] enum PointDtoType { Head, Tail, Food, Frame, } impl Default for PointDtoType { fn default() -> PointDtoType { PointDtoType::Frame } } #[derive(Debug, Clone, Eq, PartialEq, Default)] //       . struct PointDto { x: u8, y: u8, state_type: PointDtoType, } //------------------------------Controller ----------------------------- #[derive(Debug, Clone, Default)] //           struct GameController { game: Game, } impl GameController { fn new() -> GameController { GameController { game: Game::new(30, 30) } } //          fn get_state(&self) -> Vec<PointDto> { let mut vec: Vec<PointDto> = Vec::new(); vec.push(PointDto { x: self.game.food.x, y: self.game.food.y, state_type: PointDtoType::Food }); let head = self.game.snake.head(); vec.push(PointDto { x: head.x, y: head.y, state_type: PointDtoType::Head }); //      for p in self.game.snake.points.iter().filter(|p| **p != head) { vec.push(PointDto { x: px, y: py, state_type: PointDtoType::Tail }); } //   for x in self.game.frame.min_x..=self.game.frame.max_x { vec.push(PointDto { x: x, y: self.game.frame.max_y, state_type: PointDtoType::Frame }); vec.push(PointDto { x: x, y: self.game.frame.min_y, state_type: PointDtoType::Frame }); } //   for y in self.game.frame.min_y..=self.game.frame.max_y { vec.push(PointDto { x: self.game.frame.max_x, y: y, state_type: PointDtoType::Frame }); vec.push(PointDto { x: self.game.frame.min_x, y: y, state_type: PointDtoType::Frame }); } vec } //   fn update(mut self, time_delta: f32, direction: Option<Direction>) -> GameController { let game = self.game.clone(); self.game = match direction { None => game, Some(d) => game.handle_input(d) } .update(time_delta); self } pub fn get_max_score(&self) -> usize { self.game.max_score.clone() } pub fn get_score(&self) -> usize { self.game.score.clone() } } //------------------------View --------------- //           struct GameView { controller: GameController, window: three::Window, camera: three::camera::Camera, ambient: three::light::Ambient, directional: three::light::Directional, font: Font, current_score: Text, max_score: Text, } impl GameView { fn new() -> GameView { let controller = GameController::new(); //        let mut window = three::Window::new("3D Snake Game By Victorem"); //         let camera = window.factory.perspective_camera(60.0, 10.0..40.0); //   [x, y, z] camera.set_position([15.0, 15.0, 30.0]); //    let ambient_light = window.factory.ambient_light(0xFFFFFF, 0.5); window.scene.add(&ambient_light); //   let mut dir_light = window.factory.directional_light(0xffffff, 0.5); dir_light.look_at([350.0, 350.0, 550.0], [0.0, 0.0, 0.0], None); window.scene.add(&dir_light); //        let font = window.factory.load_font(".\\DejaVuSans.ttf"); //           let current_score = window.factory.ui_text(&font, "0"); let mut max_score = window.factory.ui_text(&font, "0"); max_score.set_pos([0.0, 40.0]); window.scene.add(&current_score); window.scene.add(&max_score); GameView { controller, window, camera, ambient: ambient_light, directional: dir_light, font, current_score, max_score } } //             fn get_input(&self) -> Option<Direction> { match self.window.input.keys_hit().last() { None => None, Some(k) => match *k { three::Key::Left => Some(Direction::Left), three::Key::Right => Some(Direction::Right), three::Key::Down => Some(Direction::Top), three::Key::Up => Some(Direction::Bottom), _ => None, } } } //           fn get_meshes(mut self) -> (Vec<Mesh>, GameView) { //  let sphere = &three::Geometry::uv_sphere(0.5, 24, 24); //           let green = &three::material::Phong { color: three::color::GREEN, glossiness: 30.0, }; let blue = &three::material::Phong { color: three::color::BLUE, glossiness: 30.0, }; let red = &three::material::Phong { color: three::color::RED, glossiness: 30.0, }; let yellow = &three::material::Phong { color: three::color::RED | three::color::GREEN, glossiness: 30.0, }; //       let meshes = self.controller.clone().get_state().iter().map(|s| { let state = s.clone(); match state.state_type { PointDtoType::Frame => { let m = self.window.factory.mesh(sphere.clone(), blue.clone()); m.set_position([state.x as f32, state.y as f32, 0.0]); m } PointDtoType::Tail => { let m = self.window.factory.mesh(sphere.clone(), yellow.clone()); m.set_position([state.x as f32, state.y as f32, 0.0]); m } PointDtoType::Head => { let m = self.window.factory.mesh(sphere.clone(), red.clone()); m.set_position([state.x as f32, state.y as f32, 0.0]); m } PointDtoType::Food => { let m = self.window.factory.mesh(sphere.clone(), green.clone()); m.set_position([state.x as f32, state.y as f32, 0.0]); m } } }).collect(); (meshes, self) } //   fn update(mut self) -> GameView { //       let elapsed_time = self.window.input.delta_time(); let input = self.get_input(); let controller = self.controller.update(elapsed_time, input); self.controller = controller; self } //    fn draw(mut self) -> GameView { let (meshes, view) = self.get_meshes(); self = view; //   . for m in &meshes { self.window.scene.add(m); } //    self.window.render(&self.camera); //  for m in meshes { self.window.scene.remove(m); } //    self.max_score.set_text(format!("MAX SCORE: {}", self.controller.get_max_score())); self.current_score.set_text(format!("CURRENT SCORE: {}", self.controller.get_score())); self } //         pub fn run(mut self) { //           while self.window.update() && !self.window.input.hit(three::KEY_ESCAPE) { self = self.update().draw(); } } } fn main() { let mut view = GameView::new(); view.run(); } 


Além do Three.rs, também considerei o Piston , um conjunto de bibliotecas para criação de jogos, e o Ametist , um mecanismo de jogos. Escolhi o Three.rs porque me pareceu o mais simples e mais adequado para a prototipagem.
Infelizmente, dentro da estrutura deste jogo, não foi possível sentir os fluxos e trabalhar com a rede. Vou tentar no próximo projeto. Até agora, eu gosto da linguagem e trabalhar com ela é um prazer. Ficaria grato por bons conselhos e críticas construtivas.

Source: https://habr.com/ru/post/pt428830/


All Articles