feat(engine): auto-complete — cards auto-deal to foundations

When is_auto_completable flips true (stock/waste empty, all cards
face-up), AutoCompletePlugin fires MoveRequestEvent every 120 ms,
driving cards to the foundation one at a time without player input.
An "Auto-completing…" toast announces the sequence.

- solitaire_core: add next_auto_complete_move() to GameState with
  3 new unit tests
- solitaire_engine: new AutoCompletePlugin with detect + drive systems
  and 4 unit tests; animation_plugin shows one-shot toast on activation
- solitaire_app: register AutoCompletePlugin

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
root
2026-04-27 00:24:21 +00:00
parent 00f0383867
commit f7850c0075
5 changed files with 274 additions and 4 deletions
+21
View File
@@ -6,6 +6,7 @@
use bevy::prelude::*;
use crate::achievement_plugin::display_name_for;
use crate::auto_complete_plugin::AutoCompleteState;
use crate::card_plugin::CardEntity;
use crate::challenge_plugin::ChallengeAdvancedEvent;
use crate::daily_challenge_plugin::DailyChallengeCompletedEvent;
@@ -80,6 +81,7 @@ impl Plugin for AnimationPlugin {
handle_time_attack_toast,
handle_challenge_toast,
handle_settings_toast,
handle_auto_complete_toast,
tick_toasts,
)
.after(GameMutation),
@@ -233,6 +235,25 @@ fn handle_settings_toast(
}
}
/// Shows a one-time "Auto-completing..." toast when auto-complete activates.
fn handle_auto_complete_toast(
mut commands: Commands,
state: Option<Res<AutoCompleteState>>,
mut shown: Local<bool>,
) {
let Some(s) = state else { return };
if s.is_changed() {
if s.active {
if !*shown {
*shown = true;
spawn_toast(&mut commands, "Auto-completing…".to_string(), 2.0);
}
} else {
*shown = false;
}
}
}
fn tick_toasts(
mut commands: Commands,
time: Res<Time>,