diff --git a/src/game_state.rs b/src/game_state.rs index d3efe5e..9477c50 100644 --- a/src/game_state.rs +++ b/src/game_state.rs @@ -1,9 +1,13 @@ use bevy::prelude::*; use crate::components::{ - Bullet, Enemy, EnemyBullet, GameOverUI, HighScoreText, RestartMessage, StartButton, StartMenuUI, + Bullet, Enemy, EnemyBullet, Explosion, GameOverUI, HighScoreText, RestartMessage, StartButton, + StartMenuUI, +}; +use crate::constants::STARTING_LIVES; +use crate::resources::{ + CurrentStage, FormationState, HighScore, PlayerLives, RestartPressed, Score, }; -use crate::resources::{HighScore, RestartPressed, Score}; #[derive(Clone, Eq, PartialEq, Debug, Hash, Default, States)] pub enum AppState { @@ -85,7 +89,15 @@ pub fn cleanup_game_over_ui( pub fn cleanup_game_entities( mut commands: Commands, - query: Query, With, With)>>, + query: Query< + Entity, + Or<( + With, + With, + With, + With, + )>, + >, ) { for entity in &query { commands.entity(entity).despawn(); @@ -198,9 +210,170 @@ pub fn handle_restart_input( pub fn restart_game_system( mut next_state: ResMut>, mut restart: ResMut, + mut score: ResMut, + mut lives: ResMut, + mut stage: ResMut, + mut formation: ResMut, ) { if restart.pressed { restart.pressed = false; + score.value = 0; + lives.count = STARTING_LIVES; + stage.number = 1; + stage.waiting_for_clear = false; + formation.next_slot_index = 0; + formation.total_spawned_this_stage = 0; + formation.formation_complete = false; next_state.set(AppState::Playing); } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::components::Explosion; + use crate::constants::STARTING_LIVES; + use crate::resources::{CurrentStage, FormationState, PlayerLives}; + use bevy::ecs::world::World; + + #[test] + fn restart_game_resets_score_to_zero() { + let mut world = World::default(); + world.insert_resource(Score { value: 5000 }); + world.insert_resource(PlayerLives { count: 1 }); + world.insert_resource(CurrentStage { + number: 5, + waiting_for_clear: true, + }); + world.insert_resource(FormationState { + next_slot_index: 4, + total_spawned_this_stage: 10, + formation_complete: true, + }); + world.insert_resource(NextState::::Pending(AppState::Playing)); + world.insert_resource(RestartPressed { pressed: true }); + + world.register_system(restart_game_system); + world + .run_system_cached(restart_game_system) + .expect("system should run"); + + let score = world.resource::(); + assert_eq!(score.value, 0, "Score should be reset to 0 on restart"); + } + + #[test] + fn restart_game_resets_player_lives_to_starting_lives() { + let mut world = World::default(); + world.insert_resource(Score { value: 5000 }); + world.insert_resource(PlayerLives { count: 1 }); + world.insert_resource(CurrentStage { + number: 1, + waiting_for_clear: false, + }); + world.insert_resource(FormationState::default()); + world.insert_resource(NextState::::Pending(AppState::Playing)); + world.insert_resource(RestartPressed { pressed: true }); + + world.register_system(restart_game_system); + world + .run_system_cached(restart_game_system) + .expect("system should run"); + + let lives = world.resource::(); + assert_eq!( + lives.count, STARTING_LIVES, + "PlayerLives should be reset to STARTING_LIVES on restart" + ); + } + + #[test] + fn restart_game_resets_current_stage_to_defaults() { + let mut world = World::default(); + world.insert_resource(Score { value: 0 }); + world.insert_resource(PlayerLives { + count: STARTING_LIVES, + }); + world.insert_resource(CurrentStage { + number: 5, + waiting_for_clear: true, + }); + world.insert_resource(FormationState::default()); + world.insert_resource(NextState::::Pending(AppState::Playing)); + world.insert_resource(RestartPressed { pressed: true }); + + world.register_system(restart_game_system); + world + .run_system_cached(restart_game_system) + .expect("system should run"); + + let stage = world.resource::(); + assert_eq!( + stage.number, 1, + "CurrentStage.number should be reset to 1 on restart" + ); + assert_eq!( + stage.waiting_for_clear, false, + "CurrentStage.waiting_for_clear should be reset to false on restart" + ); + } + + #[test] + fn restart_game_resets_formation_state_to_defaults() { + let mut world = World::default(); + world.insert_resource(Score { value: 0 }); + world.insert_resource(PlayerLives { + count: STARTING_LIVES, + }); + world.insert_resource(CurrentStage { + number: 1, + waiting_for_clear: false, + }); + world.insert_resource(FormationState { + next_slot_index: 4, + total_spawned_this_stage: 10, + formation_complete: true, + }); + world.insert_resource(NextState::::Pending(AppState::Playing)); + world.insert_resource(RestartPressed { pressed: true }); + + world.register_system(restart_game_system); + world + .run_system_cached(restart_game_system) + .expect("system should run"); + + let formation = world.resource::(); + assert_eq!( + formation.next_slot_index, 0, + "FormationState.next_slot_index should be reset to 0" + ); + assert_eq!( + formation.total_spawned_this_stage, 0, + "FormationState.total_spawned_this_stage should be reset to 0" + ); + assert_eq!( + formation.formation_complete, false, + "FormationState.formation_complete should be reset to false" + ); + } + + #[test] + fn cleanup_game_entities_despawns_explosions() { + let mut world = World::default(); + world.spawn(Explosion { + timer: Timer::from_seconds(0.4, TimerMode::Once), + }); + + world.register_system(cleanup_game_entities); + world + .run_system_cached(cleanup_game_entities) + .expect("system should run"); + + let mut query = world.query::<&Explosion>(); + let explosion_count = query.iter(&world).count(); + assert_eq!( + explosion_count, 0, + "Explosion entities should be despawned during cleanup" + ); + } +}