Files
Ferrous-Solitaire/solitaire_engine/src/resources.rs
T
funman300 ffc79447d4 fix+refactor+docs: P0–P3 todo list items
P0 fixes:
- Register WinSummaryPlugin, SelectionPlugin, CardAnimationPlugin in main.rs
  (all three were exported but never wired — features silently did nothing)
- game_state::draw(): increment move_count on waste→stock recycle, not just
  on normal draws; add move_count_increments_on_recycle regression test

P1 fixes:
- solitaire_server/Cargo.toml: remove duplicate dev-dependencies
  (solitaire_sync, uuid, chrono, jsonwebtoken were in both sections)

P2 — input_plugin refactor:
- Split 198-line handle_keyboard() into three focused systems under 110 lines each:
  handle_keyboard_core (U/N/Z/D/Space), handle_keyboard_hint (H), handle_keyboard_forfeit (G)
- Introduce KeyboardConfirmState resource to share countdown timers across systems
- Add three new unit tests: all_hints_suggests_draw_*, all_hints_is_empty_when_truly_stuck,
  new_game_confirm_window_is_positive

P2 — achievement predicate tests (solitaire_core):
- Add 10 direct unit tests for speed_demon, lightning, no_undo, high_scorer,
  on_a_roll, comeback predicates (previously only covered via check_achievements())
- 141 core tests now passing

P2 — server tests:
- solitaire_server/src/sync.rs: 4 unit tests for merge logic (no DB required)
- solitaire_server/src/leaderboard.rs: 2 unit tests for entry shape and sort order

P3 — documentation:
- Add struct-level ///  to 12 Plugin structs (ChallengePlugin, CursorPlugin,
  AnimationPlugin, HelpPlugin, PausePlugin, AudioPlugin, DailyChallengePlugin,
  HudPlugin, LeaderboardPlugin, OnboardingPlugin, TimeAttackPlugin, WeeklyGoalsPlugin)
- Add field-level /// to Card, Pile, Deck, GameState, AchievementContext, AchievementDef
- Add /// to WeeklyGoalKind, WeeklyGoalDef, WeeklyGoalContext, StatsExt::update_on_win

card_animation module (new files from previous session):
- chain.rs, diagnostics.rs, tuning.rs, updated interaction.rs/animation.rs/mod.rs/lib.rs
- Remove unused HOVER_SCALE_DEFAULT / DRAG_LIFT_SCALE_DEFAULT / HOVER_LERP_SPEED_DEFAULT constants
- Add handle_touch_stock_tap so touch users can draw from the stock pile

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 22:02:52 +00:00

114 lines
3.8 KiB
Rust

//! Bevy resources owned by the engine crate.
use bevy::math::Vec2;
use bevy::prelude::Resource;
use chrono::{DateTime, Utc};
use solitaire_core::game_state::GameState;
use solitaire_core::pile::PileType;
/// Wraps the currently active `GameState`. Single source of truth for the in-progress game.
#[derive(Resource, Debug, Clone)]
pub struct GameStateResource(pub GameState);
/// Tracks an in-progress drag operation.
///
/// When `cards` is empty there is no active drag. When non-empty, the listed
/// cards are being moved by the user and should be rendered at the cursor or
/// touch position.
///
/// # Drag threshold
///
/// A drag is *pending* when `!cards.is_empty() && !committed`. The drag does
/// not become *committed* (cards do not visually move) until the pointer has
/// moved at least `AnimationTuning::drag_threshold_px` pixels from `press_pos`.
/// This prevents accidental drags on quick taps, especially on touch screens.
#[derive(Resource, Debug, Clone)]
pub struct DragState {
/// IDs of the cards being dragged (bottom-to-top stacking order).
pub cards: Vec<u32>,
/// Pile the drag originated from.
pub origin_pile: Option<PileType>,
/// World-space offset from the cursor/touch to the bottom card's centre.
pub cursor_offset: Vec2,
/// Z coordinate used for the dragged cards.
pub origin_z: f32,
/// Screen-space position (logical pixels) where the press/touch began.
///
/// Used to measure whether the drag threshold has been crossed.
pub press_pos: Vec2,
/// Whether the drag threshold has been crossed and visual drag is active.
///
/// Cards are only lifted and repositioned once `committed = true`.
pub committed: bool,
/// Touch ID driving this drag, or `None` for a mouse drag.
pub active_touch_id: Option<u64>,
}
impl Default for DragState {
fn default() -> Self {
Self {
cards: Vec::new(),
origin_pile: None,
cursor_offset: Vec2::ZERO,
origin_z: 0.0,
press_pos: Vec2::ZERO,
committed: false,
active_touch_id: None,
}
}
}
impl DragState {
/// Returns `true` when no drag (pending or committed) is in progress.
pub fn is_idle(&self) -> bool {
self.cards.is_empty()
}
/// Returns `true` when a drag has been committed (cards are visually lifted).
pub fn is_committed(&self) -> bool {
self.committed
}
/// Resets all drag state to the idle/default values.
pub fn clear(&mut self) {
self.cards.clear();
self.origin_pile = None;
self.cursor_offset = Vec2::ZERO;
self.origin_z = 0.0;
self.press_pos = Vec2::ZERO;
self.committed = false;
self.active_touch_id = None;
}
}
/// Current sync activity — shown in the settings screen.
///
/// Defined here rather than in `solitaire_data` because it is a UI/runtime
/// status value, not part of the persistence layer.
#[derive(Debug, Clone, Default)]
pub enum SyncStatus {
#[default]
Idle,
Syncing,
LastSynced(DateTime<Utc>),
Error(String),
}
/// Bevy resource wrapping the current `SyncStatus`.
#[derive(Resource, Debug, Clone, Default)]
pub struct SyncStatusResource(pub SyncStatus);
/// Tracks which hint the player is currently cycling through.
///
/// Incremented on each H press so repeated presses reveal different moves.
/// Reset to `0` whenever the game state changes (move, draw, undo, new game).
#[derive(Resource, Debug, Clone, Default)]
pub struct HintCycleIndex(pub usize);
/// Remembers the vertical scroll offset of the Settings panel between open/close cycles.
///
/// Saved when the panel is despawned and restored on next spawn so the player
/// returns to the same position in the list without re-scrolling.
#[derive(Resource, Debug, Clone, Default)]
pub struct SettingsScrollPos(pub f32);