3
\$\begingroup\$

I am beginning to learn rust and I created this snake game for practice. I am looking for some feedback on my code.

main.rs

extern crate rand;
extern crate crossterm;

mod canvas;
mod snake;

use std::{thread, time};
use rand::Rng;
use crossterm::{cursor, terminal, input, ClearType, RawScreen, InputEvent, KeyEvent};
use canvas::Canvas;
use snake::*;

fn modulo(n: i16, m: i16) -> i16 {
   return ((n % m) + m) % m;
}

fn main() {
   let terminal = terminal();
   let size: (u16, u16) = terminal.terminal_size();
   let width: usize = (size.0 / 2) as usize;
   let height: usize = (size.1) as usize;
   let input = input();
   let _screen = RawScreen::into_raw_mode();
   let mut stdin = input.read_async();
   let mut canvas = Canvas::new(width, height, ' ');
   let mut player = Snake::new((width / 2) as i16, (height / 2) as i16, 'X', 'O');
   let mut food = Piece::new(
       rand::thread_rng().gen_range(0, width) as i16,
       rand::thread_rng().gen_range(0, height) as i16,
       '0'
   );
   let mut time = 0;
   terminal.clear(ClearType::All).expect("failed to clear terminal");

   loop {
       time += 1;
       time %= 7;
       let cursor = cursor();
       let mut should_update: bool = true;
       cursor.hide().expect("failed to hide cursor");
       if let Some(evt) = stdin.next() {
           match evt {
               InputEvent::Keyboard(e) => {
                   should_update = false;
                   match e {
                       KeyEvent::Up => player.set_vel(0, -1),
                       KeyEvent::Down => player.set_vel(0, 1),
                       KeyEvent::Left => player.set_vel(-1, 0),
                       KeyEvent::Right => player.set_vel(1, 0),
                       _ => (),
                   }
               },
               _ => (),
           }
       }

       canvas.fill(' ');
       if time == 0 && should_update {
           player.update();
       }
       player.body[0].x = modulo(player.body[0].x, width as i16);
       player.body[0].y = modulo(player.body[0].y, height as i16);

       canvas.write(1, 1, format!("Length: {}", player.body.len()));
       canvas.draw(food.x as usize, food.y as usize, food.ch);
       if player.body[0].collides(&food) {
           food.x = rand::thread_rng().gen_range(0, width) as i16;
           food.y = rand::thread_rng().gen_range(0, height) as i16;
           player.grow();
       }
       for (i, piece) in player.body.iter().enumerate() {
           canvas.draw(piece.x as usize, piece.y as usize, piece.ch);
           if i > 0 && piece.collides(&player.body[0]) {
               player.body.truncate(i);
               break;
           }
       }
       canvas.render(cursor);
       thread::sleep(time::Duration::from_millis(15));
   }
}

canvas.rs

use crossterm::TerminalCursor;

pub struct Canvas {
   w: usize,
   h: usize,
   display: Vec<Vec<char>>,
}

impl Canvas {
   pub fn new(w: usize, h: usize, fillch: char) -> Canvas {
       return Canvas {
           w: w,
           h: h,
           display: vec![vec![fillch; w]; h]
       };
   }

   pub fn render(&self, cursor: TerminalCursor) {
       cursor.goto(0, 0).expect("failed to move cursor");
       for row in self.display.iter() {
           for item in row {
               print!("{} ", item);
           }
           println!("");
       }
   }

   pub fn draw(&mut self, x: usize, y: usize, ch: char) {
       if x < self.w && y < self.h {
           self.display[y][x] = ch;
       }
   }

   pub fn fill(&mut self, fillch: char) {
       for y in 0..self.h {
           for x in 0..self.w {
               self.draw(x, y, fillch);
           }
       }
   }

   pub fn write(&mut self, mut x: usize, y: usize, text: String) {
       for c in text.chars() {
           self.draw(x, y, c);
           x += 1;
       }
   }
}

snake.rs

pub struct Piece {
   pub x: i16,
   pub y: i16,
   pub ch: char,
}

pub struct Snake {
   pub body: Vec<Piece>,
   bodych: char,
   vel_x: i16,
   vel_y: i16,
}

impl Piece {
   pub fn new(x: i16, y: i16, ch: char) -> Piece {
       return Piece {
           x: x,
           y: y,
           ch: ch,
       };
   }

   pub fn collides(&self, piece: &Piece) -> bool {
       return self.x == piece.x && self.y == piece.y;
   }
}

impl Snake {
   pub fn new(x: i16, y: i16, ch: char, ch2: char) -> Snake {
       return Snake {
           body: vec![Piece::new(x, y, ch), Piece::new(x, y + 1, ch2)],
           bodych: ch2,
           vel_x: 0,
           vel_y: -1,
       };
   }

   pub fn update(&mut self) {
       for i in (1..self.body.len()).rev() {
           self.body[i].x = self.body[i - 1].x;
           self.body[i].y = self.body[i - 1].y;
       }
       self.body[0].x += self.vel_x;
       self.body[0].y += self.vel_y;
   }

   pub fn set_vel(&mut self, x: i16, y: i16) {
       self.vel_x = x;
       self.vel_y = y;
       self.update();
   }

   pub fn grow(&mut self) {
       self.body.push(Piece::new(
           self.body[self.body.len() - 1].x,
           self.body[self.body.len() - 1].y,
           self.bodych
       ));
   }
}

cargo.toml

[dependencies]
crossterm = "0.9.4"
rand = "0.6.5"
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.