Rusty Projects 03โ€” Snake Game Model๐Ÿ

Yen
Rustaceans

Follow

Published in
10 min readMay 17, 2024

--

Practice Rust with several projects

Letโ€™s create a snake game in Rust. I will use Bevy to create the game.

I use a cat ๐Ÿˆ pixel instead of a snake. The food is a dog ๐Ÿถ But the concept is the same as the original snake game.

Generally, this game is where a cat tries to find as many dogs as it can.

Catalog

  • Whatโ€™s Bevy?
  • Codes for the snake game โ€” Stage 1
  • Stage 2 โ€” one spawn snake and food
  • Stage 3

Resources

Whatโ€™s Bevy?

  • Bevy is an innovative, data-driven game engine written in Rust, perfect for passionate game developers.
  • Its ECS (Entity-Component-System) architecture offers exceptional performance and flexibility, making it easy to build and manage game worlds.
  • Bevyโ€™s modular design includes powerful features like a renderer, asset management, and a customizable UI. Its strong type safety and concurrency support leverage Rustโ€™s strengths, ensuring stability and efficiency.

Whether youโ€™re crafting intricate 2D/3D games or exploring interactive simulations, Bevyโ€™s vibrant community and extensive documentation provide an inspiring environment for creativity and learning. Dive into Bevy and unleash your game development potential!

Snake Game

main.rs

This file is the entry point of the application. It sets up the Bevy app and adds the SnakeGamePlugin.

use bevy::prelude::*;
use crate::systems::SnakeGamePlugin;

mod components;
mod systems;

fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_plugins(SnakeGamePlugin)
.run();
}

systems.rs

use bevy::prelude::*;
use crate::components::*;

// Commenting out the unused constants
// const SNAKE_HEAD_COLOR: Color = Color::rgb(0.7, 0.7, 0.7);
// const SNAKE_SEGMENT_COLOR: Color = Color::rgb(0.3, 0.3, 0.3);
// const FOOD_COLOR: Color = Color::rgb(1.0, 0.0, 0.0);

#[derive(Resource)]
pub struct SnakeTextures {
pub head: Handle<Image>,
}

pub struct SnakeGamePlugin;

impl Plugin for SnakeGamePlugin {
fn build(&self, app: &mut App) {
app.add_systems(Startup, setup)
.add_systems(Update, snake_movement)
.add_systems(Update, snake_eating)
.add_systems(Update, snake_growth)
.add_systems(Update, game_over);
}
}

fn setup(
mut commands: Commands,
asset_server: Res<AssetServer>,
) {
let snake_head_handle = asset_server.load("textures/snake_head_texture.png");
commands.insert_resource(SnakeTextures {
head: snake_head_handle.clone(),
});

commands.insert_resource(SnakeSegments::default());
commands.insert_resource(LastTailPosition(None));

// Spawn the camera
commands.spawn(Camera2dBundle::default());

// Spawn the initial snake
spawn_snake(commands, snake_head_handle);
}

fn spawn_snake(mut commands: Commands, head_texture: Handle<Image>) {
commands.spawn((
SpriteBundle {
texture: head_texture,
transform: Transform {
translation: Vec3::new(0.0, 0.0, 0.0),
..Default::default()
},
sprite: Sprite {
custom_size: Some(Vec2::new(64.0, 64.0)), // Ensure the size is set correctly
..Default::default()
},
..Default::default()
},
SnakeHead,
Position { x: 0, y: 0 },
));
}

fn snake_movement(
keyboard_input: Res<Input<KeyCode>>,
mut heads: Query<(&mut Position, &mut Transform), With<SnakeHead>>,
) {
for (mut position, mut transform) in heads.iter_mut() {
let mut direction = Vec3::ZERO;
let mut new_position = *position;

if keyboard_input.pressed(KeyCode::Left) {
direction = Vec3::new(-1.0, 0.0, 0.0);
new_position.x -= 1;
} else if keyboard_input.pressed(KeyCode::Right) {
direction = Vec3::new(1.0, 0.0, 0.0);
new_position.x += 1;
} else if keyboard_input.pressed(KeyCode::Up) {
direction = Vec3::new(0.0, 1.0, 0.0);
new_position.y += 1;
} else if keyboard_input.pressed(KeyCode::Down) {
direction = Vec3::new(0.0, -1.0, 0.0);
new_position.y -= 1;
}

transform.translation += direction * 64.0; // Move by one tile size
*position = new_position;
}
}

fn snake_eating(
mut commands: Commands,
mut param_set: ParamSet<(
Query<(Entity, &Position), With<Food>>,
Query<(&Transform, &mut Position), With<SnakeHead>>,
)>,
mut last_tail_position: ResMut<LastTailPosition>,
) {
let mut head_position = None;
for (_, pos) in param_set.p1().iter_mut() {
head_position = Some(*pos);
break;
}

if let Some(head_pos) = head_position {
for (food_entity, food_pos) in param_set.p0().iter() {
if head_pos == *food_pos {
// Store the last tail position before growing
last_tail_position.0 = Some(head_pos);

// Despawn the food entity
commands.entity(food_entity).despawn();

// Spawn a new food entity at a random position
// This part can be implemented as needed
}
}
}
}

fn snake_growth(
mut commands: Commands,
mut segments: ResMut<SnakeSegments>,
last_tail_position: Res<LastTailPosition>,
snake_textures: Res<SnakeTextures>,
) {
if let Some(tail_position) = last_tail_position.0 {
// Add a new segment to the snake at the last tail position
segments.0.push(spawn_segment(
&mut commands,
snake_textures.head.clone(),
tail_position,
));
}
}

fn spawn_segment(commands: &mut Commands, texture: Handle<Image>, position: Position) -> Entity {
commands.spawn((
SpriteBundle {
texture,
transform: Transform {
translation: Vec3::new(position.x as f32 * 64.0, position.y as f32 * 64.0, 0.0),
..Default::default()
},
..Default::default()
},
SnakeSegment,
position,
)).id()
}

fn game_over(
mut commands: Commands,
segments: ResMut<SnakeSegments>,
head_positions: Query<&Transform, With<SnakeHead>>,
segment_positions: Query<&Transform, With<SnakeSegment>>,
) {
if let Some(head_position) = head_positions.iter().next() {
for segment_position in segment_positions.iter() {
if head_position.translation == segment_position.translation {
// Despawn all snake segments
for entity in segments.0.iter() {
commands.entity(*entity).despawn();
}

// Reset the snake segments resource
commands.insert_resource(SnakeSegments::default());

// Optionally, restart the game or end it
// This part can be implemented as needed
}
}
}
}

components.rs

use bevy::prelude::*;

#[derive(Component)]
pub struct SnakeHead;

#[derive(Component)]
pub struct SnakeSegment;

#[derive(Component)]
pub struct Food;

#[derive(Component, Clone, Copy, PartialEq, Eq, Debug)]
pub struct Position {
pub x: i32,
pub y: i32,
}

#[derive(Default, Deref, DerefMut, Resource)]
pub struct SnakeSegments(pub Vec<Entity>);

#[derive(Default, Resource)]
pub struct LastTailPosition(pub Option<Position>);

Structure


snake_game/
โ”œโ”€โ”€ Cargo.toml
โ””โ”€โ”€ src/
โ”œโ”€โ”€ components.rs
โ”œโ”€โ”€ main.rs
โ””โ”€โ”€ systems.rs
โ””โ”€โ”€assets/
โ””โ”€โ”€ textures/
โ”œโ”€โ”€ snake_head_texture.png
โ””โ”€โ”€ food_texture.png

Stage 2

  • There are food and a cat.
  • Eat the food

main.rs

mod components;
mod systems;

use bevy::prelude::*;
use systems::SnakeGamePlugin;

fn main() {
App::new()
.insert_resource(Msaa::default())
.add_plugins(DefaultPlugins.set(WindowPlugin {
primary_window: Some(Window {
title: "Snake Game".to_string(),
resolution: (800.0, 600.0).into(),
resizable: false,
..Default::default()
}),
..default()
}))
.add_plugin(SnakeGamePlugin)
.run();
}

components.rs

use bevy::prelude::*;

pub struct SnakeHead;
impl Component for SnakeHead {
type Storage = bevy::ecs::component::TableStorage;
}

pub struct SnakeSegment;
impl Component for SnakeSegment {
type Storage = bevy::ecs::component::TableStorage;
}

pub struct Food;
impl Component for Food {
type Storage = bevy::ecs::component::TableStorage;
}

#[derive(Default)]
pub struct LastTailPosition(pub Option<Position>);
impl Resource for LastTailPosition {}

pub struct SnakeMoveTimer(pub Timer);
impl Resource for SnakeMoveTimer {}

pub struct SnakeTextures {
pub head: Handle<Image>,
pub food: Handle<Image>,
}
impl Resource for SnakeTextures {}

#[derive(Default)]
pub struct SnakeSegments(pub Vec<Entity>);
impl Resource for SnakeSegments {}

#[derive(Component, Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct Position {
pub x: i32,
pub y: i32,
}

systems.rs

use bevy::prelude::*;
use crate::components::*;
use rand::Rng;

const FOOD_COLOR: Color = Color::rgb(1.0, 0.0, 0.0); // Color for the food
const GRID_SIZE: f32 = 32.0;
const GRID_WIDTH: i32 = 25;
const GRID_HEIGHT: i32 = 18;
const SNAKE_SIZE: f32 = 32.0;
const SNAKE_MOVEMENT_INTERVAL: f32 = 0.15;

fn setup(
mut commands: Commands,
asset_server: Res<AssetServer>,
) {
commands.spawn(Camera2dBundle::default());

// Load textures
let snake_head_handle = asset_server.load("textures/snake_head_texture.png");
let food_handle = asset_server.load("textures/food_texture.png");

commands.insert_resource(SnakeMoveTimer(Timer::from_seconds(SNAKE_MOVEMENT_INTERVAL, TimerMode::Repeating)));
commands.insert_resource(SnakeTextures { head: snake_head_handle.clone(), food: food_handle.clone() });
commands.insert_resource(LastTailPosition::default());
commands.insert_resource(SnakeSegments(vec![]));

spawn_snake(&mut commands, snake_head_handle);
spawn_food(&mut commands, food_handle);
}

fn spawn_snake(commands: &mut Commands, head_texture: Handle<Image>) {
let segment = commands.spawn(SpriteBundle {
texture: head_texture,
transform: Transform {
scale: Vec3::splat(SNAKE_SIZE / 64.0), // Adjust scale to match 32x32
..Default::default()
},
..Default::default()
})
.insert(SnakeHead)
.insert(Position { x: GRID_WIDTH / 2, y: GRID_HEIGHT / 2 })
.insert(SnakeSegment)
.id();

commands.insert_resource(SnakeSegments(vec![segment]));
}

fn spawn_food(commands: &mut Commands, texture: Handle<Image>) {
let mut rng = rand::thread_rng();
let x = rng.gen_range(0..GRID_WIDTH);
let y = rng.gen_range(0..GRID_HEIGHT);
commands.spawn(SpriteBundle {
texture,
transform: Transform {
translation: Vec3::new(x as f32 * GRID_SIZE, y as f32 * GRID_SIZE, 0.0),
scale: Vec3::splat(SNAKE_SIZE / 64.0), // Adjust scale to match 32x32
..Default::default()
},
..Default::default()
})
.insert(Food)
.insert(Position { x, y });
}

pub struct SnakeGamePlugin;

impl Plugin for SnakeGamePlugin {
fn build(&self, app: &mut App) {
app.add_systems(Startup, setup)
.add_systems(
Update,
(
snake_movement,
snake_eating,
snake_growth,
game_over,
camera_follow,
)
);
}
}

fn snake_movement(
time: Res<Time>,
keyboard_input: Res<Input<KeyCode>>,
mut timer: ResMut<SnakeMoveTimer>,
mut query: Query<(&mut Transform, &mut Position), With<SnakeHead>>,
) {
if timer.0.tick(time.delta()).just_finished() {
for (mut transform, mut position) in query.iter_mut() {
let mut direction = (0, 0);
if keyboard_input.pressed(KeyCode::Up) {
direction.1 += 1;
} else if keyboard_input.pressed(KeyCode::Down) {
direction.1 -= 1;
} else if keyboard_input.pressed(KeyCode::Left) {
direction.0 -= 1;
} else if keyboard_input.pressed(KeyCode::Right) {
direction.0 += 1;
}

position.x += direction.0;
position.y += direction.1;

// Keep the snake within the screen boundaries
position.x = position.x.clamp(0, GRID_WIDTH - 1);
position.y = position.y.clamp(0, GRID_HEIGHT - 1);

transform.translation.x = position.x as f32 * GRID_SIZE;
transform.translation.y = position.y as f32 * GRID_SIZE;
}
}
}

fn snake_eating(
mut commands: Commands,
food_positions: Query<(Entity, &Position), With<Food>>,
head_positions: Query<(&Transform, &Position), With<SnakeHead>>,
mut last_tail_position: ResMut<LastTailPosition>,
textures: Res<SnakeTextures>,
) {
for (_, head_position) in head_positions.iter() {
for (entity, food_position) in food_positions.iter() {
if head_position.x == food_position.x && head_position.y == food_position.y {
commands.entity(entity).despawn();
last_tail_position.0 = Some(*food_position);
spawn_food(&mut commands, textures.food.clone());
}
}
}
}

fn snake_growth(
mut commands: Commands,
mut segments: ResMut<SnakeSegments>,
last_tail_position: Res<LastTailPosition>,
textures: Res<SnakeTextures>,
) {
if let Some(tail_position) = last_tail_position.0 {
let segment = commands.spawn(SpriteBundle {
texture: textures.head.clone(),
transform: Transform {
translation: Vec3::new(tail_position.x as f32 * GRID_SIZE, tail_position.y as f32 * GRID_SIZE, 0.0),
scale: Vec3::splat(SNAKE_SIZE / 64.0),
..Default::default()
},
..Default::default()
})
.insert(SnakeSegment)
.insert(tail_position)
.id();

segments.0.push(segment);
}
}

fn game_over(
mut commands: Commands,
mut segments: ResMut<SnakeSegments>,
head_positions: Query<&Position, With<SnakeHead>>,
segment_positions: Query<&Position, With<SnakeSegment>>,
textures: Res<SnakeTextures>,
) {
// Handle game over
if segments.0.len() > 1 {
if let Some(head_pos) = head_positions.iter().next() {
for segment_pos in segment_positions.iter() {
if head_pos.x == segment_pos.x && head_pos.y == segment_pos.y {
for entity in segments.0.iter() {
commands.entity(*entity).despawn();
}
segments.0.clear();
spawn_snake(&mut commands, textures.head.clone());
break;
}
}
}
}
}

fn camera_follow(
mut camera_query: ParamSet<(
Query<&mut Transform, With<Camera>>,
Query<&Transform, With<SnakeHead>>,
)>,
) {
if let Some(head_transform) = camera_query.p1().iter().next() {
let head_translation = head_transform.translation.clone();
for mut camera_transform in camera_query.p0().iter_mut() {
camera_transform.translation.x = head_translation.x;
camera_transform.translation.y = head_translation.y;
}
}
}

Cargo.toml

[package]
name = "snake_game"
version = "0.1.0"
edition = "2021"


[dependencies]
bevy = "0.11.0"
rand = "0.8"

File structure

snake_game/
โ”œโ”€โ”€ Cargo.toml
โ”œโ”€โ”€ src/
โ”‚ โ”œโ”€โ”€ components.rs
โ”‚ โ”œโ”€โ”€ main.rs
โ”‚ โ”œโ”€โ”€ systems.rs
โ””โ”€โ”€ assets/
โ””โ”€โ”€ textures/
โ”œโ”€โ”€ snake_head_texture.png
โ””โ”€โ”€ food_texture.png

However, there are still several issues:

  • No scoring system
  • Where is the next dog after the cat touches it?

Stage 3

  • The cat and the dog are spawn at the beginning
  • The dog will be re-spawn in a random position after being touched by the cat

main.rs

mod components;
mod systems;

use bevy::prelude::*;
use systems::SnakeGamePlugin;

fn main() {
App::new()
.insert_resource(Msaa::default())
.add_plugins(DefaultPlugins.set(WindowPlugin {
primary_window: Some(Window {
title: "Snake Game".to_string(),
resolution: (800.0, 600.0).into(),
resizable: false,
..Default::default()
}),
..default()
}))
.add_plugins(SnakeGamePlugin)
.run();
}

systems.rs

use bevy::prelude::*;
use crate::components::*;
use rand::Rng;

const GRID_SIZE: f32 = 32.0;
const GRID_WIDTH: i32 = 25;
const GRID_HEIGHT: i32 = 18;
const SNAKE_SIZE: f32 = 32.0;
const SNAKE_MOVEMENT_INTERVAL: f32 = 0.15;

fn setup(
mut commands: Commands,
asset_server: Res<AssetServer>,
) {
commands.spawn(Camera2dBundle::default());

// Load textures
let snake_head_handle = asset_server.load("textures/snake_head_texture.png");
let food_handle = asset_server.load("textures/food_texture.png");

commands.insert_resource(SnakeMoveTimer(Timer::from_seconds(SNAKE_MOVEMENT_INTERVAL, TimerMode::Repeating)));
commands.insert_resource(SnakeTextures { head: snake_head_handle.clone(), food: food_handle.clone() });
commands.insert_resource(LastTailPosition::default());
commands.insert_resource(SnakeSegments(vec![]));
commands.insert_resource(Score::default());

spawn_snake(&mut commands, snake_head_handle);
spawn_food(&mut commands, food_handle);

// Setup UI
commands.spawn(TextBundle {
text: Text::from_section(
"Score: 0",
TextStyle {
font: asset_server.load("fonts/FiraSans-Bold.ttf"),
font_size: 40.0,
color: Color::WHITE,
}
),
style: Style {
align_self: AlignSelf::FlexEnd,
..Default::default()
},
..Default::default()
})
.insert(ScoreText);
}

fn spawn_snake(commands: &mut Commands, head_texture: Handle<Image>) {
let segment = commands.spawn(SpriteBundle {
texture: head_texture,
transform: Transform {
scale: Vec3::splat(SNAKE_SIZE / 64.0), // Adjust scale to match 32x32
..Default::default()
},
..Default::default()
})
.insert(SnakeHead)
.insert(Position { x: GRID_WIDTH / 2, y: GRID_HEIGHT / 2 })
.insert(SnakeSegment)
.id();

commands.insert_resource(SnakeSegments(vec![segment]));
}

fn spawn_food(commands: &mut Commands, texture: Handle<Image>) {
let mut rng = rand::thread_rng();
let x = rng.gen_range(0..GRID_WIDTH);
let y = rng.gen_range(0..GRID_HEIGHT);
commands.spawn(SpriteBundle {
texture,
transform: Transform {
translation: Vec3::new(x as f32 * GRID_SIZE, y as f32 * GRID_SIZE, 0.0),
scale: Vec3::splat(SNAKE_SIZE / 64.0), // Adjust scale to match 32x32
..Default::default()
},
..Default::default()
})
.insert(Food)
.insert(Position { x, y });
}

pub struct SnakeGamePlugin;

impl Plugin for SnakeGamePlugin {
fn build(&self, app: &mut App) {
app.add_systems(Startup, setup)
.add_systems(
Update,
(
snake_movement,
snake_eating.after(snake_movement),
snake_growth.after(snake_eating),
game_over,
camera_follow,
update_score,
)
);
}
}

fn snake_movement(
time: Res<Time>,
keyboard_input: Res<Input<KeyCode>>,
mut timer: ResMut<SnakeMoveTimer>,
mut head_query: Query<(&mut Transform, &mut Position), With<SnakeHead>>,
mut segment_query: Query<(&mut Transform, &mut Position), (With<SnakeSegment>, Without<SnakeHead>)>,
) {
if timer.0.tick(time.delta()).just_finished() {
if let Ok((mut head_transform, mut head_position)) = head_query.get_single_mut() {
let mut direction = (0, 0);
if keyboard_input.pressed(KeyCode::Up) {
direction.1 += 1;
} else if keyboard_input.pressed(KeyCode::Down) {
direction.1 -= 1;
} else if keyboard_input.pressed(KeyCode::Left) {
direction.0 -= 1;
} else if keyboard_input.pressed(KeyCode::Right) {
direction.0 += 1;
}

let new_head_position = Position {
x: (head_position.x + direction.0).clamp(0, GRID_WIDTH - 1),
y: (head_position.y + direction.1).clamp(0, GRID_HEIGHT - 1),
};

let mut segment_positions: Vec<Position> = segment_query.iter_mut().map(|(_, pos)| pos.clone()).collect();
if !segment_positions.is_empty() {
segment_positions.insert(0, head_position.clone());
segment_positions.pop();
for (i, (mut transform, mut position)) in segment_query.iter_mut().enumerate() {
*position = segment_positions[i].clone();
transform.translation.x = position.x as f32 * GRID_SIZE;
transform.translation.y = position.y as f32 * GRID_SIZE;
}
}

*head_position = new_head_position.clone();
head_transform.translation.x = new_head_position.x as f32 * GRID_SIZE;
head_transform.translation.y = new_head_position.y as f32 * GRID_SIZE;
}
}
}

fn snake_eating(
mut commands: Commands,
food_positions: Query<(Entity, &Position), With<Food>>,
head_positions: Query<&Position, With<SnakeHead>>,
mut last_tail_position: ResMut<LastTailPosition>,
textures: Res<SnakeTextures>,
mut score: ResMut<Score>,
) {
for head_position in head_positions.iter() {
for (entity, food_position) in food_positions.iter() {
if head_position == food_position {
commands.entity(entity).despawn();
last_tail_position.0 = Some(food_position.clone());
spawn_food(&mut commands, textures.food.clone());
score.0 += 1;
}
}
}
}

fn snake_growth(
mut commands: Commands,
mut segments: ResMut<SnakeSegments>,
mut last_tail_position: ResMut<LastTailPosition>,
textures: Res<SnakeTextures>,
) {
if let Some(tail_position) = &last_tail_position.0 {
let segment = commands.spawn(SpriteBundle {
texture: textures.head.clone(),
transform: Transform {
translation: Vec3::new(tail_position.x as f32 * GRID_SIZE, tail_position.y as f32 * GRID_SIZE, 0.0),
scale: Vec3::splat(SNAKE_SIZE / 64.0),
..Default::default()
},
..Default::default()
})
.insert(SnakeSegment)
.insert(tail_position.clone())
.id();

segments.0.push(segment);
last_tail_position.0 = None; // Correctly set the last tail position to None
}
}

fn game_over(
mut commands: Commands,
mut segments: ResMut<SnakeSegments>,
head_positions: Query<&Position, With<SnakeHead>>,
segment_positions: Query<&Position, With<SnakeSegment>>,
textures: Res<SnakeTextures>,
) {
if segments.0.len() > 1 {
if let Some(head_pos) = head_positions.iter().next() {
for segment_pos in segment_positions.iter() {
if head_pos == segment_pos {
for entity in segments.0.iter() {
commands.entity(*entity).despawn();
}
segments.0.clear();
spawn_snake(&mut commands, textures.head.clone());
break;
}
}
}
}
}

fn camera_follow(
mut camera_query: ParamSet<(
Query<&mut Transform, With<Camera>>,
Query<&Transform, With<SnakeHead>>,
)>,
) {
if let Some(head_transform) = camera_query.p1().iter().next() {
let head_transform = *head_transform; // Clone the head transform to avoid borrowing issues
for mut camera_transform in camera_query.p0().iter_mut() {
camera_transform.translation.x = head_transform.translation.x;
camera_transform.translation.y = head_transform.translation.y;
}
}
}

fn update_score(
score: Res<Score>,
mut query: Query<&mut Text, With<ScoreText>>,
) {
for mut text in query.iter_mut() {
text.sections[0].value = format!("Score: {}", score.0);
}
}

components.rs

use bevy::prelude::*;

#[derive(Component)]
pub struct SnakeHead;

#[derive(Component)]
pub struct SnakeSegment;

#[derive(Component)]
pub struct Food;

#[derive(Component, Clone, PartialEq)]
pub struct Position {
pub x: i32,
pub y: i32,
}

#[derive(Default, Resource)]
pub struct SnakeMoveTimer(pub Timer);

#[derive(Default, Resource)]
pub struct LastTailPosition(pub Option<Position>);

#[derive(Resource)]
pub struct SnakeTextures {
pub head: Handle<Image>,
pub food: Handle<Image>,
}

#[derive(Resource, Default)]
pub struct SnakeSegments(pub Vec<Entity>);

#[derive(Resource, Default)]
pub struct Score(pub i32);

#[derive(Component)]
pub struct ScoreText;

Cargo.toml

[package]
name = "snake_game"
version = "0.1.0"
edition = "2021"


[dependencies]
bevy = "0.11.0"
rand = "0.8"

File structure

snake_game/
โ”œโ”€โ”€ Cargo.toml
โ”œโ”€โ”€ src/
โ”‚ โ”œโ”€โ”€ components.rs
โ”‚ โ”œโ”€โ”€ main.rs
โ”‚ โ”œโ”€โ”€ systems.rs
โ””โ”€โ”€ assets/
โ””โ”€โ”€ textures/
โ”œโ”€โ”€ snake_head_texture.png
โ””โ”€โ”€ food_texture.png

Hey Rustaceans!

Thanks for being an awesome part of the community! Before you head off, here are a few ways to stay connected and show your love:

  • Give us a clap! Your appreciation helps us keep creating valuable content.
  • Become a contributor! โœ๏ธ Weโ€™d love to hear your voice. Learn how to write for us.
  • Stay in the loop! Subscribe to the Rust Bytes Newsletter for the latest news and insights.
  • Support our work! โ˜• Buy us a coffee.
  • Connect with us: X

--

--

Yen
Rustaceans
Writer for

Programming is a mindset. Cybersecurity is the process. Focus on Python & Rust currently. More about me | https://msha.ke/monles