use bevy::prelude::*; use rand::random; use std::time::Duration; // Constants const ARENA_WIDTH: u32 = 50; const ARENA_HEIGHT: u32 = 50; const SNAKE_HEAD_COLOR: Color = Color::srgb(0.7, 0.7, 0.7); const SNAKE_SEGMENT_COLOR: Color = Color::srgb(0.3, 0.3, 0.3); const FOOD_COLOR: Color = Color::srgb(1.0, 0.0, 1.0); #[derive(Component, Clone, Copy, PartialEq, Eq)] struct Position { x: i32, y: i32, } #[derive(Component)] struct Size { width: f32, height: f32, } impl Size { pub fn square(x: f32) -> Self { Self { width: x, height: x, } } } #[derive(Component)] struct SnakeHead { direction: Direction, } #[derive(Component)] struct SnakeSegment; #[derive(Component)] struct Food; #[derive(Resource, Default)] struct SnakeSegments(Vec); #[derive(Resource, Default)] struct LastTailPosition(Option); #[derive(PartialEq, Copy, Clone)] enum Direction { Left, Up, Right, Down, } impl Direction { fn opposite(self) -> Self { match self { Self::Left => Self::Right, Self::Right => Self::Left, Self::Up => Self::Down, Self::Down => Self::Up, } } } fn main() { App::new() .add_plugins(DefaultPlugins.set(WindowPlugin { primary_window: Some(Window { title: "Snake!".to_string(), resolution: bevy::window::WindowResolution::new(500, 500), ..default() }), ..default() })) .insert_resource(ClearColor(Color::srgb(0.04, 0.04, 0.04))) .insert_resource(SnakeSegments::default()) .insert_resource(LastTailPosition::default()) .add_message::() .add_message::() .add_systems(Startup, (setup_camera, spawn_snake)) .add_systems( Update, ( snake_movement_input, game_over.after(snake_movement), food_spawner, snake_movement, snake_eating, snake_growth, size_scaling, position_translation, ), ) .run(); } fn setup_camera(mut commands: Commands) { commands.spawn(Camera2d::default()); } fn spawn_snake(mut commands: Commands, mut segments: ResMut) { *segments = SnakeSegments(vec![ commands .spawn(( SnakeHead { direction: Direction::Up, }, SnakeSegment, Position { x: 3, y: 3 }, Size::square(0.8), Sprite { color: SNAKE_HEAD_COLOR, ..default() }, )) .id(), spawn_segment(&mut commands, Position { x: 3, y: 2 }), ]); } fn spawn_segment(commands: &mut Commands, position: Position) -> Entity { commands .spawn(( SnakeSegment, position, Size::square(0.65), Sprite { color: SNAKE_SEGMENT_COLOR, ..default() }, )) .id() } #[derive(Message)] struct GameOverEvent; fn snake_movement_input( keyboard_input: Res>, mut heads: Query<&mut SnakeHead>, ) { if let Some(mut head) = heads.iter_mut().next() { let dir: Direction = if keyboard_input.pressed(KeyCode::ArrowLeft) { Direction::Left } else if keyboard_input.pressed(KeyCode::ArrowDown) { Direction::Down } else if keyboard_input.pressed(KeyCode::ArrowUp) { Direction::Up } else if keyboard_input.pressed(KeyCode::ArrowRight) { Direction::Right } else { head.direction }; if dir != head.direction.opposite() { head.direction = dir; } } } fn snake_movement( mut heads: Query<(Entity, &mut SnakeHead)>, mut positions: Query<&mut Position>, segments: Res, mut last_tail_position: ResMut, mut game_over_writer: MessageWriter, time: Res