From 03227f8c770ce42ff94137f1beba1d0b5da9c1f0 Mon Sep 17 00:00:00 2001 From: funman300 Date: Tue, 28 Apr 2026 02:35:15 +0000 Subject: [PATCH] =?UTF-8?q?feat(engine):=20playability=20improvements=20?= =?UTF-8?q?=E2=80=94=20rounds=207=E2=80=939=20(#40=E2=80=93#64)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Round 7 — Input & feedback - H key cycles hints; F1 opens help (conflict resolved) - N key cancels active Time Attack session - Hint text distinguishes "draw from stock" vs "recycle waste" - Forfeit (G) clears AutoCompleteState so chime does not bleed into new deal - Zen mode timer clears immediately on Z press - HUD shows recycle count in both draw modes - Settings scroll position persisted across open/close Round 8 — Polish & clarity - Undo unavailable fires "Nothing to undo" toast - Streak-break toast on forfeit/abandon when streak > 1 - F11 fullscreen toggle with toast; documented in help and home screens - H-after-win, new-game countdown expiry, Tab-no-cards toasts - Win cascade duration/stagger scales with animation speed setting - Draw-Three cycle counter HUD ("Cycle: N/3") - Forfeit requires G×2 confirmation within 3 s (mirrors N key) Round 9 — Game feel & information - Escape dismisses game-over/stuck overlay (PausePlugin skips Escape when overlay visible) - Shake animation on rejected drag before snap-back - Forfeit countdown cancels when any other key is pressed (U/H/D/Z/Space) - Tab wrap-around fires "Back to first card" toast - HUD selection indicator shows active Tab-selected pile in yellow - Challenge time-limit HUD turns orange < 60s, red < 30s - Win summary shows XP breakdown (+50 base, +25 no-undo, +N speed) - Game-over overlay: "No more moves available" with clear N/Escape/G instructions Co-Authored-By: Claude Sonnet 4.6 --- ARCHITECTURE.md | 47 +- solitaire_app/src/main.rs | 8 +- solitaire_engine/src/animation_plugin.rs | 116 ++- solitaire_engine/src/audio_plugin.rs | 37 +- solitaire_engine/src/auto_complete_plugin.rs | 37 +- solitaire_engine/src/card_plugin.rs | 188 ++++- solitaire_engine/src/challenge_plugin.rs | 46 ++ solitaire_engine/src/cursor_plugin.rs | 4 - .../src/daily_challenge_plugin.rs | 5 +- solitaire_engine/src/events.rs | 9 + solitaire_engine/src/feedback_anim_plugin.rs | 110 ++- solitaire_engine/src/game_plugin.rs | 250 ++++++- solitaire_engine/src/help_plugin.rs | 39 +- solitaire_engine/src/home_plugin.rs | 236 ++++++ solitaire_engine/src/hud_plugin.rs | 282 ++++++- solitaire_engine/src/input_plugin.rs | 696 ++++++++++++++++-- solitaire_engine/src/leaderboard_plugin.rs | 35 +- solitaire_engine/src/lib.rs | 11 +- solitaire_engine/src/onboarding_plugin.rs | 34 +- solitaire_engine/src/pause_plugin.rs | 33 +- solitaire_engine/src/profile_plugin.rs | 374 ++++++++++ solitaire_engine/src/resources.rs | 14 + solitaire_engine/src/selection_plugin.rs | 234 +++++- solitaire_engine/src/settings_plugin.rs | 19 +- solitaire_engine/src/stats_plugin.rs | 149 +++- solitaire_engine/src/win_summary_plugin.rs | 529 ++++++++++++- 26 files changed, 3278 insertions(+), 264 deletions(-) create mode 100644 solitaire_engine/src/home_plugin.rs create mode 100644 solitaire_engine/src/profile_plugin.rs diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 81a4dcd..de61390 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -256,16 +256,35 @@ Done ### Bevy Plugins -| Plugin | Responsibility | -|---|---| -| `CardPlugin` | Card entity spawning, sprite management, drag-and-drop | -| `TablePlugin` | Pile markers, background, layout calculation | -| `AnimationPlugin` | Slide, flip, win cascade, toast animations | -| `AudioPlugin` | Sound effect and music playback via bevy_kira_audio | -| `UIPlugin` | All Bevy UI screens: Home, Stats, Achievements, Settings, Profile | -| `AchievementPlugin` | Listens for game events, evaluates unlock conditions, fires toasts | -| `SyncPlugin` | Manages sync lifecycle (pull on start, push on exit, status display) | -| `GamePlugin` | Core game state resource, input routing, win detection | +| Plugin | Key | Responsibility | +|---|---|---| +| `CardPlugin` | — | Card entity spawning, sprite management, drag-and-drop | +| `TablePlugin` | — | Pile markers, background, layout calculation | +| `AnimationPlugin` | — | Slide, flip, win cascade, toast animations | +| `FeedbackAnimPlugin` | — | Shake, settle, and deal-stagger animations | +| `AutoCompletePlugin` | Enter | Executes auto-complete when the HUD badge is lit | +| `AudioPlugin` | — | Sound effect and music playback via bevy_kira_audio | +| `InputPlugin` | — | Keyboard and mouse input routing | +| `CursorPlugin` | — | Custom cursor sprite during drag | +| `SelectionPlugin` | — | Keyboard-driven card selection | +| `GamePlugin` | N | Core game state resource, new-game flow, win/game-over overlays | +| `HudPlugin` | — | Score, move counter, timer, auto-complete badge | +| `StatsPlugin` | S | Stats overlay and persistence | +| `ProgressPlugin` | — | XP/level system, persistence | +| `AchievementPlugin` | A | Unlock evaluation, toast events, persistence | +| `DailyChallengePlugin` | — | Daily challenge resource and completion tracking | +| `WeeklyGoalsPlugin` | — | Weekly goal progress and completion events | +| `ChallengePlugin` | — | Challenge mode progression (seeded hard deals) | +| `TimeAttackPlugin` | — | 10-minute time-attack mode timer | +| `HomePlugin` | M | Main-menu overlay with keyboard shortcut reference | +| `ProfilePlugin` | P | Player profile overlay: level, XP, achievements, sync status | +| `SettingsPlugin` | O | Settings panel: audio, draw mode, theme, sync, cosmetics | +| `LeaderboardPlugin` | L | Leaderboard overlay | +| `HelpPlugin` | H | Help / controls overlay | +| `PausePlugin` | Esc | Pause and resume | +| `OnboardingPlugin` | — | First-run welcome screen | +| `SyncPlugin` | — | Async sync lifecycle (pull on start, push on exit, status display) | +| `WinSummaryPlugin` | — | Win cascade overlay and screen-shake effect | ### Key Bevy Resources @@ -588,6 +607,9 @@ pub enum PileType { pub enum DrawMode { DrawOne, DrawThree } +/// Active game mode. Classic is the default; others unlock at level 5. +pub enum GameMode { Classic, Zen, Challenge, TimeAttack } + pub enum MoveError { InvalidSource, InvalidDestination, @@ -600,13 +622,16 @@ pub enum MoveError { pub struct GameState { pub piles: HashMap>, pub draw_mode: DrawMode, + pub mode: GameMode, pub score: i32, pub move_count: u32, + pub undo_count: u32, // number of undos used in this game + pub recycle_count: u32, // number of stock recycles pub elapsed_seconds: u64, pub seed: u64, pub is_won: bool, pub is_auto_completable: bool, - undo_stack: Vec, // private, max 64 + undo_stack: VecDeque, // private, max 64 (VecDeque for O(1) pop_front) } ``` diff --git a/solitaire_app/src/main.rs b/solitaire_app/src/main.rs index 43e629a..32a7524 100644 --- a/solitaire_app/src/main.rs +++ b/solitaire_app/src/main.rs @@ -3,9 +3,9 @@ use solitaire_data::{load_settings_from, provider_for_backend, settings_file_pat use solitaire_engine::{ AchievementPlugin, AnimationPlugin, AudioPlugin, AutoCompletePlugin, CardPlugin, ChallengePlugin, CursorPlugin, DailyChallengePlugin, FeedbackAnimPlugin, GamePlugin, - HelpPlugin, HudPlugin, InputPlugin, LeaderboardPlugin, OnboardingPlugin, PausePlugin, - ProgressPlugin, SettingsPlugin, StatsPlugin, SyncPlugin, TablePlugin, TimeAttackPlugin, - WeeklyGoalsPlugin, + HelpPlugin, HomePlugin, HudPlugin, InputPlugin, LeaderboardPlugin, OnboardingPlugin, + PausePlugin, ProfilePlugin, ProgressPlugin, SettingsPlugin, StatsPlugin, SyncPlugin, + TablePlugin, TimeAttackPlugin, WeeklyGoalsPlugin, }; fn main() { @@ -44,6 +44,8 @@ fn main() { .add_plugins(TimeAttackPlugin) .add_plugins(HudPlugin) .add_plugins(HelpPlugin) + .add_plugins(HomePlugin) + .add_plugins(ProfilePlugin) .add_plugins(PausePlugin) .add_plugins(SettingsPlugin::default()) .add_plugins(AudioPlugin) diff --git a/solitaire_engine/src/animation_plugin.rs b/solitaire_engine/src/animation_plugin.rs index f931dc6..c2ef5ff 100644 --- a/solitaire_engine/src/animation_plugin.rs +++ b/solitaire_engine/src/animation_plugin.rs @@ -24,6 +24,7 @@ use crate::events::{InfoToastEvent, NewGameConfirmEvent, XpAwardedEvent}; use crate::events::{AchievementUnlockedEvent, GameWonEvent}; use crate::game_plugin::GameMutation; use crate::layout::LayoutResource; +use crate::pause_plugin::PausedResource; use crate::progress_plugin::LevelUpEvent; use crate::settings_plugin::{SettingsChangedEvent, SettingsResource}; use crate::time_attack_plugin::TimeAttackEndedEvent; @@ -60,8 +61,41 @@ const WEEKLY_TOAST_SECS: f32 = 3.0; const TIME_ATTACK_TOAST_SECS: f32 = 5.0; const CHALLENGE_TOAST_SECS: f32 = 3.0; const VOLUME_TOAST_SECS: f32 = 1.4; -const CASCADE_STAGGER: f32 = 0.05; -const CASCADE_DURATION: f32 = 0.5; + +/// Per-card stagger interval for the win cascade at Normal speed (seconds). +const CASCADE_STAGGER_NORMAL: f32 = 0.05; +/// Duration of each card's cascade slide at Normal speed (seconds). +const CASCADE_DURATION_NORMAL: f32 = 0.5; + +/// Returns the per-card stagger delay for the win cascade at the given `AnimSpeed`. +/// +/// | `AnimSpeed` | Returned value | +/// |-------------|----------------| +/// | `Normal` | 0.05 s | +/// | `Fast` | 0.025 s | +/// | `Instant` | 0.0 s | +pub fn cascade_step_secs(speed: AnimSpeed) -> f32 { + match speed { + AnimSpeed::Normal => CASCADE_STAGGER_NORMAL, + AnimSpeed::Fast => CASCADE_STAGGER_NORMAL / 2.0, + AnimSpeed::Instant => 0.0, + } +} + +/// Returns the slide duration for each card in the win cascade at the given `AnimSpeed`. +/// +/// | `AnimSpeed` | Returned value | +/// |-------------|----------------| +/// | `Normal` | 0.5 s | +/// | `Fast` | 0.25 s | +/// | `Instant` | 0.0 s | +pub fn cascade_duration_secs(speed: AnimSpeed) -> f32 { + match speed { + AnimSpeed::Normal => CASCADE_DURATION_NORMAL, + AnimSpeed::Fast => CASCADE_DURATION_NORMAL / 2.0, + AnimSpeed::Instant => 0.0, + } +} /// Linear-lerp slide animation. /// @@ -181,11 +215,19 @@ fn sync_slide_duration( } } +/// Advances all in-flight `CardAnim` slide animations. +/// +/// Skipped while the game is paused so cards do not move while the pause +/// overlay is open. fn advance_card_anims( mut commands: Commands, time: Res