Creating a Snake game in Rust

Byte Blog
4 min readMay 3, 2023

--

Snake is a classic video game that has been enjoyed by generations of players. It’s a simple game in which the player controls a snake that grows longer as it eats food and tries to avoid colliding with the walls or its own body. In this post, we will explore how to create a Snake game in Rust using the ncurses library for terminal graphics.

What is ncurses?

ncurses is a library for creating text-based user interfaces (TUIs) in a terminal. It provides a set of functions for drawing characters and color to the screen, reading keyboard input, and managing the terminal display. ncurses is available on most Unix-like systems, including Linux and macOS.

Setting up the project

Before we begin, make sure that Rust and ncurses are installed on your system. You can install Rust by following the instructions on the official Rust website (https://www.rust-lang.org/tools/install), and you can install ncurses using your system’s package manager.

Once you have Rust and ncurses installed, create a new Rust project by running the following command in your terminal:

cargo new snake --bin

This will create a new Rust project with a binary executable named snake. Change into the project directory by running cd snake.

Next, add the ncurses crate to your Cargo.toml file by adding the following line under the [dependencies] section:

ncurses = "5.100.0"

Now, run cargo build to download the ncurses library and build your project.

Drawing the Snake

To draw the snake on the screen, we will create a struct that represents the snake's body. We'll use a LinkedList to store the coordinates of each segment of the snake's body.

use std::collections::LinkedList;

struct Snake {
body: LinkedList<(i32, i32)>,
}

We’ll also create a function to draw the snake on the screen. This function will iterate over each segment of the snake’s body and draw a character at the corresponding position.

use ncurses::*;

fn draw_snake(snake: &Snake) {
for &(x, y) in &snake.body {
mvaddch(x, y, '@' as u32);
}
}

Here, mvaddch is a ncurses function that moves the cursor to the specified position and adds the specified character to the screen. The @ character is used to represent the snake's body.

Moving the Snake

To move the snake, we will add a direction field to the Snake struct that represents the direction in which the snake is moving. We'll also add a move_forward method that updates the coordinates of each segment of the snake's body based on the current direction.

#[derive(Clone, Copy, PartialEq)]
enum Direction {
Left,
Right,
Up,
Down,
}

struct Snake {
body: LinkedList<(i32, i32)>,
direction: Direction,
}
impl Snake {
fn move_forward(&mut self) {
let (mut x, mut y) = self.body.front().unwrap().clone();
match self.direction {
Direction::Left => y -= 1,
Direction::Right => y += 1,
Direction::Up => x -= 1,
Direction::Down => x += 1,
}
self.body.push_front((x, y));
self.body.pop_back();
}
}

Here, self.body.front().unwrap() gets the coordinates of the front segment of the snake's body. We then update the coordinates based on the current direction, and add the new coordinates to the front of the body and remove the coordinates of the back segment of the body.

Next, we’ll add a function to handle keyboard input. This function will update the direction of the snake based on the arrow keys.

fn handle_input(snake: &mut Snake) {
let input = getch();

match input {
KEY_LEFT => snake.direction = Direction::Left,
KEY_RIGHT => snake.direction = Direction::Right,
KEY_UP => snake.direction = Direction::Up,
KEY_DOWN => snake.direction = Direction::Down,
_ => (),
}
}

Here, getch is a ncurses function that waits for keyboard input and returns the corresponding key code. The KEY_* constants represent the arrow keys.

Creating the Game Loop

Finally, we’ll create the game loop that updates the game state and redraws the screen.

fn main() {
initscr();
noecho();
cbreak();
curs_set(CURSOR_VISIBILITY::CURSOR_INVISIBLE);

let mut snake = Snake {
body: LinkedList::from_iter(vec![(10, 10), (10, 9), (10, 8)]),
direction: Direction::Right,
};
loop {
clear();
handle_input(&mut snake);
snake.move_forward();
draw_snake(&snake);
refresh();
napms(100);
}
endwin();
}

Here, initscr initializes the ncurses library, noecho disables key echoing, cbreak sets the terminal to cbreak mode, and curs_set sets the cursor visibility.

In the game loop, we first clear the screen using clear, then handle input, move the snake, and draw the snake using the functions we defined earlier. refresh updates the screen with the changes, and napms waits for a specified number of milliseconds before continuing.

Finally, we clean up the ncurses library using endwin.

Conclusion

In this technical blog, we have explored how to create a simple Snake game in Rust using the ncurses library for terminal graphics. We covered the basics of drawing on the screen, handling input, and creating a game loop. By following this tutorial, you should now have a solid understanding of how to create simple TUI games in Rust using ncurses.

--

--

Byte Blog
Byte Blog

Written by Byte Blog

Technology enthusiast with a passion for transforming complex concepts into bite sized chunks

No responses yet