Tutorial: Snake game in Rust (Part 1/2)🐍🦀
Tags: rust
Published: 2024-04-27 18:52:50

---
title: Tutorial: Snake game in Rust (Part 1/2)🐍🦀
published: true
date: 2024-04-27 13:10:34 UTC
tags: rust
canonical_url: https://eleftheriabatsou.hashnode.dev/tutorial-snake-game-in-rust-part-12
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zavwe681nhit305bdhnf.jpeg
---

## **Introduction**

Hello, amazing people. Today I'm excited to show you how to code the "Snake Game" in Rust! I feel that Snake is the perfect program to showcase your skills. This Snake game has multiple different components.

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

### Basic information about the Components of the Game

🐍 The **Snake** can move in all directions. If our Snake eats an apple, it will grow by one "point". When we push an arrow button, the head will move up or down or left or right. The Snake cannot touch itself. For example, if it's going to the left and I push the right key, it won't be able to go backward, this would cause the game to enter a fail state because the Snake cannot actually touch itself. If the Snake touches itself, it dies and we have to restart the game. Also, if the Snake hits the wall, it dies. Those are the two main failure states for the game.

🍎 The second component is the Apple. In most Snake games, the Apple appears at random places. With this game, you'll notice that the Apple appears at the same place every time we start the game. The same with the Snake. The Snake will appear at a fixed place every time we restart it.

🧱 The third component is the walls. The walls will be represented as a rectangle that goes around the entire border of our game board. The Snake cannot pass through the walls.

🕹 Finally, we have the game board itself. This will be a 2D plane that the Snake moves along and that the Apple spawns in.

* * *

You'll need to create these 4 files:

- main.rs
- draw.rs
- snake.rs
- game.rs

## Dependencies

### Cargo.toml

In the **Cargo file** , we want to add two dependencies.

```
rand = "0.8.5"
piston_window = "0.131.0"

```

The first dependency is `rand` (for random), this is a library that will allow us to deal with the random numbers for our Apple.

The second dependency is `piston_window`. This will allow us to render our elements with a UI as well as deal with some of the game logic.

**_Tip_**: _When you write the dependencies you can use inside the quotes an asterisk for the version numbers. Then go to the terminal, type_`cargo update`_and this will update all of your dependencies in the_`cargo.lock`_file. If we go to the_`lock`_file, we can search out the two libraries, then copy the number and replace the asterisk back in the_`toml`_file._

```
rand = "*"
piston_window = "*"

```

_The reason it's important to use static versions is just in case the library actually changes. If the syntax changes, then the game will not work properly anymore because we will be behind the API. Normally, libraries try to keep a consistent API, but sometimes it does change._

### main.rs

In the `main` we'll have: `rand` and `piston_window` crate

```
extern crate piston_window;
extern crate rand;

```

## draw.rs

Add draw.rs to the main.rs -> `mod draw;`

Now let's start working on some helper functions!

The imports that we want to make inside our `draw` file are about `piston_window`.

```
use piston_window::{rectangle, Context, G2d};
use piston_window::types::Color;

```

Now, the first thing we want to do is to create a `BLOCK_SIZE` constant. Constants in Rust, like many other programming languages, require that we use uppercase letters and we need to specify the type annotation and what the value is equal to. In this case, we want it to be equal to 25. This means our blocks will scale up 25 pixels.

`const BLOCK_SIZE: f64 = 25.0;`

### Functions: `to_coord` , `draw_block` and `draw_rectangle`

Now we want to create a function `to_coord`, this will take in a game coordinate which will be an i32 and then we want to return an f64. So what we're just doing with this helper function is taking in a coordinate.

```
pub fn to_coord(game_coord: i32) -> f64 {
    (game_coord as f64) * BLOCK_SIZE
}

```

We're going to cast it to an f64 and then multiply it by our block size. Also we're using the `pub` keyword, which allows us to export this function and make it public to our entire program.

Alright so now let's look at our first major public helper function (we want to draw a block):

```
pub fn draw_block(color: Color, x: i32, y: i32, con: &Context, g: &mut G2d) {
    let gui_x = to_coord(x);
    let gui_y = to_coord(y);

    rectangle(
        color,
        [gui_x, gui_y, BLOCK_SIZE, BLOCK_SIZE],
        con.transform,
        g,
    );
}

```

We're passing a `color`, and an `X` and a `Y`, both are `i32`. We also need to pass in the `context` and a `G2d` . Then we call our rectangle and pass in a color and the actual parameters for the rectangle and then the width and the height. Finally, we need to pass in the `context transform` and our `g`.

Next we want to create a public function called `draw_rectangle`. This will be a slight modification on the `draw_block` function. We're still passing in a `color` and the `X` and the `Y`, but next we're also passing in the `width` and the `height` (this will allow us to draw rectangles). The only real difference is that we take the block size and we multiply it by the width, cast it as an `f64` and the height casts it as an `f64`. This way we can control the size of our rectangle. (We're going to mainly use this for the size of our board).

```
pub fn draw_rectangle(
    color: Color,
    x: i32,
    y: i32,
    width: i32,
    height: i32,
    con: &Context,
    g: &mut G2d,
) {
    let x = to_coord(x);
    let y = to_coord(y);

    rectangle(
        color,
        [
            x,
            y,
            BLOCK_SIZE * (width as f64),
            BLOCK_SIZE * (height as f64),
        ],
        con.transform,
        g,
    );
}

```

These are our helper functions in `draw.js`. We'll need one more later but for now we are OK!

## snake.rs

Let's move to the `snake.rs` file. In this file we're going to tie most of the logic that we need to actually create our snake.

**Imports:** First we're importing from the standard library collections a type called `LinkedList`. A linked list allows pushing and popping elements from either end. Next, we're bringing in our `context` and our `graphical buffer` again. We're also bringing in the `color` type.

```
use std::collections::LinkedList;
use piston_window::{Context, G2d};
use piston_window::types::Color;

```

Let's also bring the `draw_block` function from the `draw.rs` file.

`use crate::draw::draw_block;`

At this point, don't forget to add the `snake.rs` to the `main.rs` file.

`mod snake;`

Next, we want to create a constant for our snake color. It's an array of four elements. Each element corresponds with a part of the color spectrum. The first item is our red element. The second item is our green element. The third item is our blue element. And then the fourth element is our opacity. We want to have a green snake hence I'll have this as `0.80` and we want it to have 1.0 opacity.

`const SNAKE_COLOR: Color = [0.00, 0.80, 0.00, 1.0];`

The next thing we want to do is create an `enum` for the direction. The `enum` will handle the direction of the snake as well as how our keyboard inputs interact with the snake. We want the snake to be able to go up, down, left, and right on our screen.

```
pub enum Direction {
    Up,
    Down,
    Left,
    Right,
}

```

I want to do one more thing: If the snake is going up and I try to hit down, the snake shouldn't be able to go down. Let's see how to implement this:

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

```

As you can see above, we have a new public function `opposite` that takes in a reference to `&self` and outputs a `Direction`. Then we `match` with `*self`.

- If the direction is up, then pass back direction down.
- If the direction is down, pass back direction up.
- Etc...

Next, we want to create a `struct` for our block type. We want to have an `X` and a `Y`, both of `i32`.

```
struct Block {
    x: i32,
    y: i32,
}

```

And inside of Snake, we want to have the following states:

```
pub struct Snake {
    direction: Direction,
    body: LinkedList<Block>,
    tail: Option<Block>,
}

```

- The _direction_ that the snake is currently traveling in.
- The _body_ of the snake, which will be a `LinkedList` of blocks.
- The _tail_, which will be an `Option<Block>`. (This is important because we want to have our tail be an actual value when we eat an apple.)

Now we want to create an implementation block for our Snake so that we can create methods. We're going to create a function called `new` , it will take in an `X` and a `Y` value and output our `Snake`.

```
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,
        });
        body.push_back(Block {
            x: x + 1,
            y,
        });
        body.push_back(Block {
            x,
            y,
        });

        Snake {
            direction: Direction::Right,
            body,
            tail: None,
        }
    }
.
.
}

```

We will create a mutable `body`, which will be a linked list of blocks. Then we'll use the `push_back` method (it appends an element to the back of a list). Essentially, what we're doing here is we're setting up the default Snake.

- Our first block is `X` and `Y`.
- Our second block is an ``x+1` `.
- Our third block is `Y` and then `X+2`.

So our Snake will be horizontal with the `X` and `Y` coordinates. It will also start out moving in the direction of right and the tail will be none. It will be exactly three blocks long.

Functions: `draw` , `head_position` , `move_forward`

We want to create a function called `draw`. It will take in a reference to `&self`, the `context`, and our `graphical buffer`. Then we will iterate through our list.

```
pub fn draw(&self, con: &Context, g: &mut G2d) {
   for block in &self.body {
        draw_block(SNAKE_COLOR, block.x, block.y, con, g);
   }
}
```

We'll call our `draw_block` function on each of the blocks of the Snake with our `SNAKE_COLOR` inside of it. (This will render out a green snake.)

Now we want to create a `head_position` function. It will take a mutable `&self` variable and then it will output a `tuple` of `i32`. We'll find the head of our Snake by using the `self.body.front()` method. Our return will be `head_block.x` and `head_block.y`.

```
pub fn head_position(&self) -> (i32, i32) {
        let head_block = self.body.front().unwrap();
        (head_block.x, head_block.y)
    }
```

Then, we're going to create a `move_forward` function. It will take in a mutable Snake reference and a `dir` which will be an `Option` with a `Direction` inside of it. First, we'll `match` on `dir` to get the option away from it.

```
pub fn move_forward(&mut self, dir: Option<Direction>) {
   match dir {
       Some(d) => self.direction = d,
       None => (),
   }

    let (last_x, last_y): (i32, i32) = self.head_position();
.
.
}
```

Now let's work on the direction.

If we're going in `Direction::Up` then we're going to create a new `Block` (this is going to end up on the head of our snake).

```
let new_block = match self.direction {
            Direction::Up => Block {
                x: last_x,
                y: last_y - 1,
            },
            Direction::Down => Block {
                x: last_x,
                y: last_y + 1,
            },
            Direction::Left => Block {
                x: last_x - 1,
                y: last_y,
            },
            Direction::Right => Block {
                x: last_x + 1,
                y: last_y,
            },
};
```

_Note: 🎶Hello Math my old friend...!_

_As we go down this is actually the positive Y axis. For actually moving downwards we're moving up the Y axis. Now left and right are as you would actually imagine them. For left we're subtracting 1 and then for right we're adding 1._

To recap, we're removing the last block and adding a new one in front.

Let's push this into the front of our list:

```
self.body.push_front(new_block);
let removed_block = self.body.pop_back().unwrap();
self.tail = Some(removed_block);
```

We call `self.body.pop_back()`, this will pop off the back part of our linked list. And then we use that `unwrap`method again. Finally, we set `self.tail` equal to `Some(removed_block)`.

Read the 2nd part [here](https://dev.to/eleftheriabatsou/tutorial-snake-game-in-rust-part-22-1khf).

* * *

Alright folks, since this article is getting too long and a bit hard to manage, and quite frankly we have some more steps and concepts to cover, I'll create a **part 2** where we'll finish the `snake.rs` file, and also continue with the rest of the files we created at the beginning of this tutorial.

In this tutorial, we explored how to create a Snake game in Rust, focusing on setting up the game environment and initializing key components such as the snake, apple, walls, and game board. We discussed adding necessary dependencies via Cargo.toml, and started coding the main.rs, draw.rs, and snake.rs, where we defined functions and structures essential for the games functionality. We also introduced drawing functions and snake movement logic, setting the stage for further development in a subsequent part of the tutorial.

You can already find all the code [here](https://github.com/EleftheriaBatsou/snake-game-rust).

{% embed https://github.com/EleftheriaBatsou/snake-game-rust %}

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/)