//! Time Attack mode runtime: 10-minute countdown wrapped around back-to-back //! `GameMode::TimeAttack` games. Pressing **T** starts a session (gated by //! level ≥ `CHALLENGE_UNLOCK_LEVEL`); each win during the session bumps the //! counter and auto-deals a fresh game. When the timer expires the session //! ends and `TimeAttackEndedEvent` fires. use bevy::prelude::*; use solitaire_core::game_state::GameMode; use crate::challenge_plugin::CHALLENGE_UNLOCK_LEVEL; use crate::events::{GameWonEvent, NewGameRequestEvent}; use crate::game_plugin::GameMutation; use crate::progress_plugin::ProgressResource; use crate::resources::GameStateResource; /// Length of a Time Attack session in real-world seconds (10 minutes). pub const TIME_ATTACK_DURATION_SECS: f32 = 600.0; /// Session state for an in-progress Time Attack run. Not persisted. #[derive(Resource, Debug, Clone, Default)] pub struct TimeAttackResource { pub active: bool, pub remaining_secs: f32, pub wins: u32, } /// Fired when the Time Attack timer expires. The summary toast in /// `AnimationPlugin` consumes this; UI/stats consumers can also subscribe. #[derive(Event, Debug, Clone, Copy)] pub struct TimeAttackEndedEvent { pub wins: u32, } pub struct TimeAttackPlugin; impl Plugin for TimeAttackPlugin { fn build(&self, app: &mut App) { app.init_resource::() .add_event::() .add_event::() .add_event::() .add_systems( Update, handle_start_time_attack_request.before(GameMutation), ) .add_systems(Update, advance_time_attack) .add_systems(Update, auto_deal_on_time_attack_win.after(GameMutation)); } } fn handle_start_time_attack_request( keys: Res>, progress: Res, mut session: ResMut, mut new_game: EventWriter, ) { if !keys.just_pressed(KeyCode::KeyT) { return; } if progress.0.level < CHALLENGE_UNLOCK_LEVEL { info!( "Time Attack locked — reach level {} (currently {}).", CHALLENGE_UNLOCK_LEVEL, progress.0.level ); return; } *session = TimeAttackResource { active: true, remaining_secs: TIME_ATTACK_DURATION_SECS, wins: 0, }; new_game.send(NewGameRequestEvent { seed: None, mode: Some(GameMode::TimeAttack), }); } fn advance_time_attack( time: Res