Tutorial: Snake game in Rust (Part 2/2)🐍🦀
Tags: rust
Published: 2024-05-04 14:30:04

---
title: Tutorial: Snake game in Rust (Part 2/2)🐍🦀
published: true
date: 2024-05-03 12:32:51 UTC
tags: rust
canonical_url: https://eleftheriabatsou.hashnode.dev/tutorial-snake-game-in-rust-part-2
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s6mi01faa5svy19kykpk.jpeg
---

## **Introduction**

Hello, amazing people, and welcome to the 2nd part of this tutorial, building the Snake game in Rust. If you missed the 1st part you can find it [here](https://dev.to/eleftheriabatsou/tutorial-snake-game-in-rust-part-12-23gc).

{% embed https://dev.to/eleftheriabatsou/tutorial-snake-game-in-rust-part-12-23gc %}

In this article, we'll finish the `snake.rs` file, and also continue with the rest of the files (main.rs, draw.rs, game.rs).

{% embed https://twitter.com/BatsouElef/status/1784178606752645130 %}

## snake.rs

As a reminder from the [1st part], we had finished working with the functions `draw`, `head_position` and `move_forward` in the snake.rs file.

### Functions: `head_direction`, `next_head`, `restore_tail` and `overlap_tail`

Time to create a new function that will allow us to take in our snake or a reference to our snake and then get a direction.

```
pub fn head_direction(&self) -> Direction { 
      self.direction
}
```

Alright, so we want another method, I'm going to call it `next_head`. This will take in a reference to `&self` and an `Option<Direction>`, and then it will output a tuple of `i32`. So we'll say `let (head_x, head_y): (i32, i 32)` and then we'll get the `head_position` using our `head_position` method.

```
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),
        }
    }
```

We'll get the snake direction with the mutable moving direction `let mut moving_dir = self.direction;` and then we're going to `match` on the direction that we're passing into the method.

Then we're going to `match` again on this new `moving_dir`, this will help with accuracy.

Finally, we have two more methods we want to create. Create another public function called `restore_tail`. It will take in a reference to our mutable Snake. We'll also create a block which will be based on our tail. Then we're going to `push_back` our cloned tail into the back of our body.

_Basically_, _as you know the tail doesn't get rendered unless we eat an apple. So if we eat an apple this method will be run and the tail will be pushed into our linked list body. This is how our snake is growing in size._

```
   pub fn restore_tail(&mut self) {
        let blk = self.tail.clone().unwrap();
        self.body.push_back(blk);
    }

```

Last but not least, we have our last method for this file. Let's call this method `overlap_tail`. It will take in our Snake an `x` and a `y`, then we will pass back a `boolean`.Let's also create a mutable value and set it to equal to zero. We'll iterate through our snake body and we'll check to see if `x` equals `block.x` and if `y` equals `block.x`. So in other words:

- If our snake is overlapping with any other part of its actual body then we'll `return true`.

- Otherwise, we're going to increment `ch`.

Then we're going to check if `ch` equals `== self.body.len() - 1`, what we're doing with this part of our method is checking to see if our snake is actually overpassing the tail. If the tail and the head overlap in the same block there is actually a moment where the head will be in that block and so will the tail and we don't want this to cause a failure state so we `break`.

```
     pub fn overlap_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;
            }
        }
        return false;
    }

```

That's it for our snake file! Woohoo! Take a moment to reflect on the code we wrote so far, cause quite honestly we have a few more functions to write in the other files! 😊

## game.rs

Let's go to the `game.rs` file. Same as with our other files we want to come into our `main` file and type `mod game;` to link it up with our project.

Then, back on the `game.rs` files, we want to import all of the `piston_window` (that's why we'll use the asterisk).

We also want the `random` library and we want to get out `thread_rng` as it allows us to create a thread local random number generator (this way we're using our operating system to create a random number). We're also bringing in the `Rng` .

```
use piston_window::types::Color;
use piston_window::*;

use rand::{thread_rng, Rng};

```

Then we also want to bring in our Snake direction and then the Snake itself.

`use crate::snake::{Direction, Snake};`

And we also want to bring in our Draw block and our Draw rectangle functions.

`use crate::draw::{draw_block, draw_rectangle};`

We want to create 3 constants:

- `FOOD_COLOR`: This will be red, so 0.8 and it will have an opacity of 1.

- `BORDER_COLOR`: This will be completely black.

- `GAMEOVER_COLOR`: This will be 0.9 so it will be red again, but it will have an opacity of 0.5.

```
const FOOD_COLOR: Color = [0.80, 0.00, 0.00, 1.0];
const BORDER_COLOR: Color = [0.00, 0.00, 0.00, 1.0];
const GAMEOVER_COLOR: Color = [0.90, 0.00, 0.00, 0.5];

```

Then we also want to create 2 other constants.

- `MOVING_PERIOD`: This is essentially the frames per second that our snake will move at.

- `RESTART_TIME`: The restart time is 1 second. When we hit a failure state with our snake this will pause the game for one second before resetting it. If you find this to be too fast you can fiddle around with it.

```
const MOVING_PERIOD: f64 = 0.1;
const RESTART_TIME: f64 = 1.0;
```

Alright, now we're going to create a new `struct` called `Game`. This will have a snake in it but also the food which will be a `boolean`. If `food_exists` on the board then we don't need to spawn more. We'll have the `food_x` and `food_y` coordinates, and then we'll have the `width` and the `height` of the actual game board. Finally, we'll have the game state (`game_over`) as a `boolean` and the `waiting_time` which is the restart time up.

```
pub struct Game {
    snake: Snake,

    food_exists: bool,
    food_x: i32,
    food_y: i32,

    width: i32,
    height: i32,

    game_over: bool,
    waiting_time: f64,
}

```

#### Implementation block `Game`

We want to make an implementation block for our game so we can create some methods. We're going to create a `new` method so that we can instantiate a new game. This will take in the `width` and the `height` of the actual game board itself and then we'll output a `Game` which will then run the `Snake::new(2,2)` function (2,2 is 2 units out and 2 units down). Then our `waiting_time` will be `0` so the snake will automatically start moving. `food_exists` will be `true` so the food will spawn and it will spawn at this `food_x` and `food_y`. Then we have our `width` and `height`, these are the size of the board and then our `game_over` will be `false`. When the game is running this will be `false` and then once we hit a wall or we hit ourselves it will turn to `true`.

```
impl Game {  
    pub fn new(width: i32, height: i32) -> Game {
        Game {
            snake: Snake::new(2, 2),
            waiting_time: 0.0,
            food_exists: true,
            food_x: 6,
            food_y: 4,
            width,
            height,
            game_over: false,
        }
    }

```

Now we want to create another method called `key_pressed`, this will allow us to figure out whether or not the user has pressed the `key` and then react accordingly. So `key_pressed` takes in a mutable game `self` and then it takes in a `key` type. If `game_over` then we want to just quit but if it's not then we want to `match` on `key` and:

- If `Key::Up => Some(Direction::Up)` then we're going to go up.

- If `Key::Down => Some(Direction::Down)` then we're going to go down.

- Etc...

Then we're going to check `dir`, if `dir == self.snake.head_direction().opposite()` then we're going to quit out of this function. So for example, if the snake is moving up and we try to hit down then nothing will happen.

```
    pub fn key_pressed(&mut self, key: Key) {
        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),
            _ => Some(self.snake.head_direction()),
        };

        if let Some(dir) = dir {
            if dir == self.snake.head_direction().opposite() {
                return;
            }
        }

        self.update_snake(dir);
    }

```

Alright, as you can see above, in the last line, I have the `self.update_snake(dir);`, but we haven't written it yet. We'll do that pretty soon... Keep reading and coding with me.

Let's create a public `draw` function. It will take in a reference to our game board, the context and our graphics buffer. First, we're going to call `self.snake.draw` and what this will do is to iterate through our linked list and then `draw_block` based on those linked lists. Then we're going to check and see if `food_exists`. If this comes back as `true` then we're going to `draw_block` with the `FOOD_COLOR` , `self.food.x` and `self.food.y`.

```
pub fn draw(&self, con: &Context, g: &mut G2d) {
        self.snake.draw(con, g);

        if self.food_exists {
            draw_block(FOOD_COLOR, self.food_x, self.food_y, con, g);
        }

        draw_rectangle(BORDER_COLOR, 0, 0, self.width, 1, con, g);
        draw_rectangle(BORDER_COLOR, 0, self.height - 1, self.width, 1, con, g);
        draw_rectangle(BORDER_COLOR, 0, 0, 1, self.height, con, g);
        draw_rectangle(BORDER_COLOR, self.width - 1, 0, 1, self.height, con, g);

        if self.game_over {
            draw_rectangle(GAMEOVER_COLOR, 0, 0, self.width, self.height, con, g);
        }
    }

```

Then we're going to draw the borders and finally, we will run another check: `if self.game_over` then we want to draw the entire screen.

All right, now we're going to make an `update` function. We'll pass our game state as a mutable and then a time (`delta_time: f64`). Then we're going to iterate our `waiting_time` and if the game is over and if `self.waiting_time > RESTART_TIME` then restart the game. We'll use this function `restart` , we haven't written it yet, but keep it up and you'll write it soon with me! Otherwise, we're just going to `return` .

If the food does not exist then we're going to call the `add_food` method (we'll write it soon). Then we're going to update the snake (`update_snake`~ see the function below).

```
    pub fn update(&mut self, delta_time: f64) {
        self.waiting_time += delta_time;

        if self.game_over {
            if self.waiting_time > RESTART_TIME {
                self.restart();
            }
            return;
        }

        if !self.food_exists {
            self.add_food();
        }

        if self.waiting_time > MOVING_PERIOD {
            self.update_snake(None);
        }
    }

```

Now let's check and see if the snake has eaten. We have a new function `check_eating` which takes the mutable game state. We're going to find the `head_x` and `head_y` of the head using our `head_position` method. Then we're going to check if the `food_exists` and if `self.food_x == head_x && self.food_y == head_y`. If the head overlaps with our food then we're going to say that food doesn't exist anymore (`false`) and call our `restore_tail` function. In other words our snake is going to grow one block!

```
fn check_eating(&mut self) {
        let (head_x, head_y): (i32, i32) = 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();
        }
    }

```

Now we want to check if the snake is alive! We have a new function `check_if_snake_alive` and we pass in our reference to `self` and then an `Option` of `Direction` , we're also going to pass back a `boolean`. We're going to check if the snake head overlaps with the tail `self.snake.overlap_tail(next_x, next_y)` , in this case, we'll `return false`. If we go out of bounds of the window then the game will end and it will restart after a second.

```
    fn check_if_snake_alive(&self, dir: Option<Direction>) -> bool {
        let (next_x, next_y) = self.snake.next_head(dir);

        if self.snake.overlap_tail(next_x, next_y) {
            return false;
        }

        next_x > 0 && next_y > 0 && next_x < self.width - 1 && next_y < self.height - 1
    }

```

Now let's actually add the food! The `add_food` is the method that we were calling in the `update` function. It takes a mutable game state and then we create an `rng` element and call our `thread_rng` . We'll check if the snake is overlapping with the tail (we don't want the snake to overall with the apple), and then we'll set the `food_x` and `food_y` and also the `food_exists` to `true`.

```
    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.overlap_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;
    }

```

Perfect, we're getting closer! We just need a few more functions.

Let's create the `update_snake` function which was mentioned above, in the `update` and `key_pressed` functions. We pass in our reference to `self` and then an `Option` of `Direction.` We'll check if the snake is alive, and if it is then we'll `move_forward` and check for eating, if it's not the the `game_over` becomes `true` and we set the `waiting_time` to `0.0`.

```
  fn update_snake(&mut self, dir: Option<Direction>) {
        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;
    }

```

Let's also write the `restart` method that we saw in the `restart` function. We pass in our reference to `self` and then we create a new Snake game, and set all the other parameters as well (like `wating_time`, `food_exists`, etc). This is very similar to the `new` function. The reason we don't call it it's because we don't want to render a new window everytime the game resets!

## main.rs

Alright! Time to move on to `main.rs`.

Make sure you have imported the `piston_window` and the crates `game` and `draw`. We also want a `CONST` for `BACK_COLOR` (the color looks like gray):

```
use piston_window::*;
use piston_window::types::Color;

use crate::game::Game;
use crate::draw::to_coord_u32;

const BACK_COLOR: Color = [0.5, 0.5, 0.5, 1.0];

```

Note the `to_coord_u32` function. This is very similar to `to_coord` from `draw.rs` except here we don't want to return an `f64` but a `u32`.

In the `fn main()` we'll get the `width` and the `height` and set it to `(20, 20)` (you can obviously set it to whatever you prefer), then we're going to create a mutable window which will be a `PistonWindow` and we'll create: a Snake game, a game window (`[to_coord_u32(width), to_coord_u32(height)]` ), we want to `build` the actual window and finally we have the `unwrap` to deal with any errors.

```
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();

    .
    .
    .
}

```

Then we'll create a new Game with `width` and `height`. If the player presses a button, we're going to call the `press_args` and then pass a `key` in `key_pressed` , otherwise, we're going to `draw_2d` and pass in the event, `clear` the window and then `draw` the game.

Lastly, we're going to `update` the game with `arg.dt`.

```
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);
        });
    }

```

That's it, our game is finished! 👏👏

## Run the Game

You can run in your terminal `cargo check` to check if there are any errors and then `cargo run` to play the game! Enjoy and congrats on building it.

Thank you for staying with me in this long, 2-parts, tutorial.

I hope you liked it and learned something new. If you have any comments or need more details don't hesitate to type your thoughts.

Find the code [here](https://github.com/EleftheriaBatsou/snake-game-rust/tree/main).

Happy Rust Coding! 🤞🦀

* * *

👋 Hello, I'm Eleftheria, **Community Manager,** developer, public speaker, and content creator.

🥰 If you liked this article, consider sharing it.

🔗 [**All links**](https://limey.io/batsouelef) | [**X**](https://twitter.com/BatsouElef) | [**LinkedIn**](https://www.linkedin.com/in/eleftheriabatsou/)