diff --git a/src/ai.rs b/src/ai.rs index 04b011c..89674e4 100644 --- a/src/ai.rs +++ b/src/ai.rs @@ -4,10 +4,10 @@ use rand::prelude::*; use crate::apple::Apple; use crate::coordinate::Coordinate; +use crate::snake::Snake; use crate::Direction; use crate::Id; use crate::ProposeDirection; -use crate::Snake; use crate::HALF_LEN; use rand; diff --git a/src/apple.rs b/src/apple.rs index 0abee98..f350e20 100644 --- a/src/apple.rs +++ b/src/apple.rs @@ -2,8 +2,12 @@ use bevy::prelude::*; use rand::Rng; use super::{ - asset_loader::SceneAssets, coordinate::Coordinate, game_state::AppState, schedule::InGameSet, - Depth, MyColor, Snake, SnakeSegment, HALF_LEN, SIZE, + asset_loader::SceneAssets, + coordinate::Coordinate, + game_state::AppState, + schedule::InGameSet, + snake::{Depth, MyColor, Snake, SnakeSegment}, + HALF_LEN, SIZE, }; pub(crate) struct ApplePlugin; diff --git a/src/collision.rs b/src/collision.rs index 54bb49a..8100b44 100644 --- a/src/collision.rs +++ b/src/collision.rs @@ -3,7 +3,7 @@ use bevy::prelude::*; use super::coordinate::Coordinate; use super::game_state::AppState; -use super::Snake; +use super::snake::Snake; use super::blink::{BlinkPlugin, Blinking}; use super::movement::Tick; diff --git a/src/main.rs b/src/main.rs index abb7c66..2f806b2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,20 +1,16 @@ -use std::collections::VecDeque; - use bevy::{prelude::*, window::WindowMode}; use movement::ProposeDirection; mod coordinate; -use coordinate::Coordinate; mod direction; use direction::Direction; mod main_menu; use main_menu::MainMenu; -use main_menu::NumberOfPlayersSelected; mod game_state; -use game_state::{AppState, GameStatePlugin}; +use game_state::GameStatePlugin; mod ai; use ai::AIPlugin; @@ -40,11 +36,21 @@ use collision::CollisionPlugin; mod blink; mod schedule; -use schedule::InGameSet; use schedule::SchedulePlugin; +mod snake; +use snake::{Id, SnakePlugin}; + use std::env; +const SIZE: f32 = 0.8; +const HALF_LEN: i32 = 7; +const BOARD_LEN: i32 = 2 * HALF_LEN; +const PADDING: f32 = 1.0; +const BOARD_VIEWPORT_IN_WORLD_UNITS: f32 = BOARD_LEN as f32 + 2.0 * PADDING; + +const MAX_NUMBER_OF_PLAYERS: usize = 4; + fn main() { let mut app = App::new(); @@ -84,207 +90,3 @@ fn main() { app.run(); } - -struct SnakePlugin; - -const SIZE: f32 = 0.8; -const HALF_LEN: i32 = 7; -const BOARD_LEN: i32 = 2 * HALF_LEN; -const PADDING: f32 = 1.0; -const BOARD_VIEWPORT_IN_WORLD_UNITS: f32 = BOARD_LEN as f32 + 2.0 * PADDING; - -const MAX_NUMBER_OF_PLAYERS: usize = 4; - -impl Plugin for SnakePlugin { - fn build(&self, app: &mut App) { - app.add_systems(Startup, setup) - .add_systems( - Update, - ( - toroid_coordinates, - add_sprite_bundles, - // This is needed in order to render the sprites correctly, we need to flush the sprites into the world and then update their transforms - apply_deferred, - update_local_coordinates_to_world_transforms, - ) - .chain() - .in_set(InGameSet::Last) - .run_if(in_state(AppState::InGame)), - ) - .add_systems( - OnEnter(AppState::InGame), - (despawn_snakes, spawn_snakes).chain(), - ); - } -} - -fn setup(mut commands: Commands) { - let mut grid = vec![]; - - for x in -HALF_LEN..=HALF_LEN { - for y in -HALF_LEN..=HALF_LEN { - grid.push(( - SpriteBundle { - sprite: Sprite { - custom_size: Some(Vec2 { x: SIZE, y: SIZE }), - color: Color::DARK_GRAY, - ..Default::default() - }, - ..Default::default() - }, - Coordinate(Vec2::new(x as f32, y as f32)), - Depth(-1.0), - )); - } - } - commands.spawn_batch(grid); - - commands.spawn(Camera2dBundle { - projection: OrthographicProjection { - far: 1000., - near: -1000., - scaling_mode: bevy::render::camera::ScalingMode::AutoMin { - min_width: BOARD_VIEWPORT_IN_WORLD_UNITS, - min_height: BOARD_VIEWPORT_IN_WORLD_UNITS, - }, - ..Default::default() - }, - ..default() - }); -} - -fn spawn_snakes(mut commands: Commands, number_of_players: Res) { - let mut spawn_snake = - |id, spawn_coord: Coordinate, direction: Direction, color: MyColor, name: String| { - let head_a = commands - .spawn((color, SnakeSegment, spawn_coord.clone())) - .id(); - - commands.spawn(( - Snake { - segments: VecDeque::from([head_a]), - player_number: id, - direction: direction.clone(), - trail: Coordinate( - spawn_coord.0 - >::into(direction), - ), - input_blocked: false, - inmortal_ticks: 0, - name, - }, - color, - )); - }; - - let snakes = [ - ( - Id(1), - Coordinate::from((-3.0, -3.0)), - Direction::Right, - MyColor(Color::LIME_GREEN), - "Ninja".to_string(), - ), - ( - Id(2), - Coordinate::from((3.0, 3.0)), - Direction::Left, - MyColor(Color::PINK), - "Panther".to_string(), - ), - ( - Id(3), - Coordinate::from((-3.0, 3.0)), - Direction::Down, - MyColor(Color::SALMON), - "Sushi".to_string(), - ), - ( - Id(4), - Coordinate::from((3.0, -3.0)), - Direction::Up, - MyColor(Color::TURQUOISE), - "Sonic".to_string(), - ), - ]; - - snakes - .into_iter() - .take(number_of_players.0) - .map(|(id, coord, direction, color, name)| spawn_snake(id, coord, direction, color, name)) - .count(); -} - -fn despawn_snakes(mut commands: Commands, snakes: Query<(Entity, &Snake)>) { - snakes.iter().for_each(|(entity, snake)| { - snake - .segments - .iter() - .for_each(|&entity| commands.entity(entity).despawn_recursive()); - commands.entity(entity).despawn_recursive(); - }); -} - -#[derive(Component)] -pub(crate) struct Snake { - name: String, - segments: VecDeque, - direction: Direction, - player_number: Id, // TODO: move into its own component - trail: Coordinate, - input_blocked: bool, - inmortal_ticks: u8, -} - -#[derive(Component, Debug, PartialEq, Clone)] -struct Id(u8); - -#[derive(Component)] -struct SnakeSegment; - -#[derive(Component, Clone, Copy)] -struct MyColor(Color); - -fn update_local_coordinates_to_world_transforms( - mut query: Query< - (&Coordinate, &mut Transform, Option<&Depth>), - Or<(Changed, Changed)>, - >, -) { - for (coordinate, mut transform, depth) in query.iter_mut() { - transform.translation = coordinate.0.extend(depth.map_or(0.0, |x| x.0)) - } -} - -#[derive(Component)] -struct Depth(f32); - -// TODO: we assume that Transform == SpriteBundle -fn add_sprite_bundles( - mut query: Query<(Entity, &MyColor), (Changed, Without)>, - mut commands: Commands, -) { - for (entity, color) in query.iter_mut() { - commands.entity(entity).insert(SpriteBundle { - sprite: Sprite { - custom_size: Some(Vec2 { x: SIZE, y: SIZE }), - color: color.0, - ..Default::default() - }, - - ..Default::default() - }); - } -} - -fn toroid_coordinates( - mut query: Query<&mut Coordinate, (With, Changed)>, -) { - for mut coordinate in query.iter_mut() { - if coordinate.0.x.abs() > HALF_LEN as f32 { - coordinate.0.x = -coordinate.0.x.signum() * HALF_LEN as f32; - } - if coordinate.0.y.abs() > HALF_LEN as f32 { - coordinate.0.y = -coordinate.0.y.signum() * HALF_LEN as f32; - } - } -} diff --git a/src/movement.rs b/src/movement.rs index 2d7966d..2ba40a9 100644 --- a/src/movement.rs +++ b/src/movement.rs @@ -1,7 +1,7 @@ use bevy::prelude::*; use leafwing_input_manager::prelude::*; -use crate::{coordinate::Coordinate, game_state, Direction, Id, Snake}; +use crate::{coordinate::Coordinate, game_state, snake::Snake, Direction, Id}; const SNAKE_TICK_SECONDS: f32 = 0.1; diff --git a/src/score.rs b/src/score.rs index ebf7d59..73a51f0 100644 --- a/src/score.rs +++ b/src/score.rs @@ -1,7 +1,7 @@ use bevy::prelude::*; use std::iter; -use crate::{game_state, main_menu::NumberOfPlayersSelected, MyColor, Snake}; +use crate::{game_state, main_menu::NumberOfPlayersSelected, snake::MyColor, snake::Snake}; pub(crate) struct ScorePlugin; diff --git a/src/snake.rs b/src/snake.rs new file mode 100644 index 0000000..15d3b60 --- /dev/null +++ b/src/snake.rs @@ -0,0 +1,203 @@ +use std::collections::VecDeque; + +use bevy::prelude::*; + +use crate::{ + coordinate::Coordinate, direction::Direction, game_state::AppState, + main_menu::NumberOfPlayersSelected, schedule::InGameSet, BOARD_VIEWPORT_IN_WORLD_UNITS, + HALF_LEN, SIZE, +}; + +pub(crate) struct SnakePlugin; + +impl Plugin for SnakePlugin { + fn build(&self, app: &mut App) { + app.add_systems(Startup, setup) + .add_systems( + Update, + ( + toroid_coordinates, + add_sprite_bundles, + // This is needed in order to render the sprites correctly, we need to flush the sprites into the world and then update their transforms + apply_deferred, + update_local_coordinates_to_world_transforms, + ) + .chain() + .in_set(InGameSet::Last) + .run_if(in_state(AppState::InGame)), + ) + .add_systems( + OnEnter(AppState::InGame), + (despawn_snakes, spawn_snakes).chain(), + ); + } +} + +fn setup(mut commands: Commands) { + let mut grid = vec![]; + + for x in -HALF_LEN..=HALF_LEN { + for y in -HALF_LEN..=HALF_LEN { + grid.push(( + SpriteBundle { + sprite: Sprite { + custom_size: Some(Vec2 { x: SIZE, y: SIZE }), + color: Color::DARK_GRAY, + ..Default::default() + }, + ..Default::default() + }, + Coordinate(Vec2::new(x as f32, y as f32)), + Depth(-1.0), + )); + } + } + commands.spawn_batch(grid); + + commands.spawn(Camera2dBundle { + projection: OrthographicProjection { + far: 1000., + near: -1000., + scaling_mode: bevy::render::camera::ScalingMode::AutoMin { + min_width: BOARD_VIEWPORT_IN_WORLD_UNITS, + min_height: BOARD_VIEWPORT_IN_WORLD_UNITS, + }, + ..Default::default() + }, + ..default() + }); +} + +fn spawn_snakes(mut commands: Commands, number_of_players: Res) { + let mut spawn_snake = + |id, spawn_coord: Coordinate, direction: Direction, color: MyColor, name: String| { + let head_a = commands + .spawn((color, SnakeSegment, spawn_coord.clone())) + .id(); + + commands.spawn(( + Snake { + segments: VecDeque::from([head_a]), + player_number: id, + direction: direction.clone(), + trail: Coordinate(spawn_coord.0 - >::into(direction)), + input_blocked: false, + inmortal_ticks: 0, + name, + }, + color, + )); + }; + + let snakes = [ + ( + Id(1), + Coordinate::from((-3.0, -3.0)), + Direction::Right, + MyColor(Color::LIME_GREEN), + "Ninja".to_string(), + ), + ( + Id(2), + Coordinate::from((3.0, 3.0)), + Direction::Left, + MyColor(Color::PINK), + "Panther".to_string(), + ), + ( + Id(3), + Coordinate::from((-3.0, 3.0)), + Direction::Down, + MyColor(Color::SALMON), + "Sushi".to_string(), + ), + ( + Id(4), + Coordinate::from((3.0, -3.0)), + Direction::Up, + MyColor(Color::TURQUOISE), + "Sonic".to_string(), + ), + ]; + + snakes + .into_iter() + .take(number_of_players.0) + .map(|(id, coord, direction, color, name)| spawn_snake(id, coord, direction, color, name)) + .count(); +} + +fn despawn_snakes(mut commands: Commands, snakes: Query<(Entity, &Snake)>) { + snakes.iter().for_each(|(entity, snake)| { + snake + .segments + .iter() + .for_each(|&entity| commands.entity(entity).despawn_recursive()); + commands.entity(entity).despawn_recursive(); + }); +} + +#[derive(Component)] +pub(crate) struct Snake { + pub(crate) name: String, + pub(crate) segments: VecDeque, + pub(crate) direction: Direction, + pub(crate) player_number: Id, // TODO: move into its own component + pub(crate) trail: Coordinate, + pub(crate) input_blocked: bool, + pub(crate) inmortal_ticks: u8, +} + +#[derive(Component, Debug, PartialEq, Clone)] +pub(crate) struct Id(pub(crate) u8); + +#[derive(Component)] +pub(crate) struct SnakeSegment; + +#[derive(Component, Clone, Copy)] +pub(crate) struct MyColor(pub(crate) Color); + +fn update_local_coordinates_to_world_transforms( + mut query: Query< + (&Coordinate, &mut Transform, Option<&Depth>), + Or<(Changed, Changed)>, + >, +) { + for (coordinate, mut transform, depth) in query.iter_mut() { + transform.translation = coordinate.0.extend(depth.map_or(0.0, |x| x.0)) + } +} + +#[derive(Component)] +pub(crate) struct Depth(pub(crate) f32); + +// TODO: we assume that Transform == SpriteBundle +fn add_sprite_bundles( + mut query: Query<(Entity, &MyColor), (Changed, Without)>, + mut commands: Commands, +) { + for (entity, color) in query.iter_mut() { + commands.entity(entity).insert(SpriteBundle { + sprite: Sprite { + custom_size: Some(Vec2 { x: SIZE, y: SIZE }), + color: color.0, + ..Default::default() + }, + + ..Default::default() + }); + } +} + +fn toroid_coordinates( + mut query: Query<&mut Coordinate, (With, Changed)>, +) { + for mut coordinate in query.iter_mut() { + if coordinate.0.x.abs() > HALF_LEN as f32 { + coordinate.0.x = -coordinate.0.x.signum() * HALF_LEN as f32; + } + if coordinate.0.y.abs() > HALF_LEN as f32 { + coordinate.0.y = -coordinate.0.y.signum() * HALF_LEN as f32; + } + } +} diff --git a/src/win.rs b/src/win.rs index a571448..831e792 100644 --- a/src/win.rs +++ b/src/win.rs @@ -1,7 +1,7 @@ use bevy::prelude::*; use crate::game_state::AppState; -use crate::{MyColor, Snake}; +use crate::snake::{MyColor, Snake}; const LENGTH_TO_WIN: usize = 10; const HOLD_TIME_TO_WIN: f32 = 10.0;