1
\$\begingroup\$

I made a snake game implementation in console with rust. Would like to hear what I'm doing wrong and what can be improved.

main.rs

mod apple;
mod board;
mod game;
mod position;
mod snake;

pub const TAIL_SIGN: char = '#';
pub const HEAD_SIGN: char = 'O';
pub const APPLE_SIGN: char = '@';
pub const BOARD_SIGN: char = '.';
pub const BOARD_SIZE: usize = 10;

fn main() {
    println!("\tS N A K E");

    let mut game = game::Game::new();

    game.run();
}

game.rs

use super::apple::*;
use super::board::*;
use super::position::*;
use super::snake::*;
use std::io;
use std::process;
pub struct Game {
    board: Board,
    snake: Snake,
    apple: Apple,
}
impl Game {
    pub fn new() -> Game {
        Game {
            board: Board::new(),
            snake: Snake::new(),
            apple: Apple::new(),
        }
    }
    pub fn run(&mut self) {
        loop {
            //update board
            self.board.update(&mut self.snake, &mut self.apple);
            //draw a board
            self.board.draw();
            //ask for direction
            let mut dir_str = String::new();
            io::stdin()
                .read_line(&mut dir_str)
                .expect("Error reading the input");

            let direction = match Direction::set(&dir_str) {
                Ok(dir) => dir,
                Err(_) => panic!("UnknownDirection"),
            };

            match self.snake.next_move(&direction) {
                Ok(a) => a,
                Err(SnakeError::Direction(DirectionError::UnknownDirection)) => {
                    println!("UNKNOWN DIRECTION! (USE \'w\', \'a\', \'s\', \'d\')");
                    continue;
                }
                Err(SnakeError::Direction(DirectionError::OppositeDirection)) => {
                    println!("YOU ARE NOT ALLOWED TO MOVE IN THE OPPOSITE DIRECTION!");
                    continue;
                }
                Err(SnakeError::Position(PositionError::CollidingPositions)) => {
                    println!("GAME OVER!");
                    println!("you collided with yourself");
                    process::exit(0);
                }
            }

            if self.snake.head == self.apple.pos {
                self.apple.eaten();
                self.snake.grow();
            }

            self.apple.update_pos();

            self.apple_pos_check();
        }
    }
    fn apple_pos_check(&mut self) {
        while self.snake.head == self.apple.pos {
            self.apple.update_pos();
        }
        for i in 0..self.snake.tail.len() {
            while self.snake.tail[i] == self.apple.pos {
                self.apple.update_pos();
            }
        }
    }
}

snake.rs

use super::position::*;

pub enum SnakeError {
    Direction(DirectionError),
    Position(PositionError),
}
pub struct Snake {
    pub current_dir: Direction,
    pub head: Position,
    pub tail: Vec<Position>,
}
impl Snake {
    pub fn new() -> Snake {
        Snake {
            current_dir: Direction::Right,
            head: Position { x: 1, y: 0 },
            tail: vec![Position { x: 0, y: 0 }],
        }
    }
    pub fn next_move(&mut self, dir: &Direction) -> Result<(), SnakeError> {
        //player can't go to the opposite direction
        if *dir == self.current_dir.opposite() {
            return Err(SnakeError::Direction(DirectionError::OppositeDirection));
        }
        //increment every tail part to the next position
        for i in (0..self.tail.len()).rev() {
            if i == 0 {
                break;
            } else {
                self.tail[i] = self.tail[i - 1].clone();
            }
        }
        //set first tail part's position to head's position
        self.tail[0] = self.head.clone();
        //move a head into the direction
        self.head.move_to_dir(dir);

        if self.tail.iter().any(|tail| *tail == self.head) {
            return Err(SnakeError::Position(PositionError::CollidingPositions));
        }

        self.current_dir = *dir;

        Ok(())
    }
    pub fn grow(&mut self) {
        let last_index = self.tail.len() - 1;
        self.tail.push(self.tail[last_index].clone());
    }
}

board.rs

use super::apple::*;
use super::snake::*;
use super::*;
use std::fmt;

#[derive(Clone)]
enum Cell {
    Empty,
    Tail,
    Head,
    Apple,
}
impl fmt::Display for Cell {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Cell::Empty => write!(f, "{}", BOARD_SIGN),
            Cell::Tail => write!(f, "{}", TAIL_SIGN),
            Cell::Head => write!(f, "{}", HEAD_SIGN),
            Cell::Apple => write!(f, "{}", APPLE_SIGN),
        }
    }
}

pub struct Board {
    board: Vec<Vec<Cell>>,
}
impl Board {
    pub fn new() -> Board {
        Board {
            board: vec![vec![Cell::Empty; BOARD_SIZE]; BOARD_SIZE],
        }
    }
    pub fn update(&mut self, snake: &mut Snake, apple: &mut Apple) {
        //reset the board to empty
        self.board.fill(vec![Cell::Empty; BOARD_SIZE]);
        //take a position of snake's head and set cell for Head in the corresponding position in the board
        self.board[snake.head.y][snake.head.x] = Cell::Head;
        //iterate over snake's tail and set cell with a corresponding position to Tail
        for tail_part in snake.tail.iter() {
            self.board[tail_part.y][tail_part.x] = Cell::Tail;
        }
        //set Cell to Apple to corresponding apple position
        self.board[apple.pos.y][apple.pos.x] = Cell::Apple;
    }
    pub fn draw(&mut self) {
        print!("{}[2J", 27 as char); //clears the terminal
        for row in self.board.iter() {
            for cell in row.iter() {
                print!("{}", cell);
            }
            println!();
        }
    }
}

apple.rs

use super::position::*;
use super::*;
use rand::prelude::*;

pub struct Apple {
    pub eaten: bool,
    pub pos: Position,
}

impl Apple {
    pub fn new() -> Apple {
        Apple {
            eaten: false,
            pos: Position {
                x: thread_rng().gen_range(0..BOARD_SIZE),
                y: thread_rng().gen_range(0..BOARD_SIZE),
            },
        }
    }
    pub fn update_pos(&mut self) {
        if self.eaten {
            self.eaten = false;
            self.pos.x = thread_rng().gen_range(0..BOARD_SIZE);
            self.pos.y = thread_rng().gen_range(0..BOARD_SIZE);
        }
    }
    pub fn eaten(&mut self) {
        self.eaten = true;
    }
}

position.rs

use super::*;

pub enum PositionError {
    CollidingPositions,
}
#[derive(Clone, PartialEq, Debug)]
pub struct Position {
    pub x: usize,
    pub y: usize,
}
impl Position {
    pub fn move_to_dir(&mut self, dir: &Direction) {
        match dir {
            Direction::Up => {
                if self.y == 0 {
                    self.y = BOARD_SIZE - 1;
                } else {
                    self.y -= 1;
                }
            }
            Direction::Right => {
                if self.x == BOARD_SIZE - 1 {
                    self.x = 0;
                } else {
                    self.x += 1;
                }
            }
            Direction::Left => {
                if self.x == 0 {
                    self.x = BOARD_SIZE - 1;
                } else {
                    self.x -= 1;
                }
            }
            Direction::Down => {
                if self.y == BOARD_SIZE - 1 {
                    self.y = 0;
                } else {
                    self.y += 1;
                }
            }
        }
    }
}

pub enum DirectionError {
    UnknownDirection,
    OppositeDirection,
}
#[derive(PartialEq, Copy, Clone)]
pub enum Direction {
    Up,
    Right,
    Left,
    Down,
}
impl Direction {
    pub fn set(dir: &str) -> Result<Direction, DirectionError> {
        match dir.trim() {
            "w" => Ok(Direction::Up),
            "d" => Ok(Direction::Right),
            "a" => Ok(Direction::Left),
            "s" => Ok(Direction::Down),
            _ => Err(DirectionError::UnknownDirection),
        }
    }
    pub fn opposite(&self) -> Direction {
        match self {
            Direction::Up => Direction::Down,
            Direction::Right => Direction::Left,
            Direction::Left => Direction::Right,
            Direction::Down => Direction::Up,
        }
    }
}
\$\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.