use crate::pile::PileType; /// Score delta for moving cards from `from` to `to`. /// /// Windows XP Standard scoring: /// - +10 for any card reaching a foundation pile /// - +5 for a waste → tableau move /// - 0 for all other moves pub fn score_move(from: &PileType, to: &PileType) -> i32 { match to { PileType::Foundation(_) => 10, PileType::Tableau(_) => match from { PileType::Waste => 5, PileType::Foundation(_) => -15, _ => 0, }, _ => 0, } } /// Score penalty applied when the player uses undo: -15. pub fn score_undo() -> i32 { -15 } /// Time bonus added to the score on a win: `700_000 / elapsed_seconds`. /// Returns 0 when `elapsed_seconds` is 0 to avoid division by zero. pub fn compute_time_bonus(elapsed_seconds: u64) -> i32 { if elapsed_seconds == 0 { return 0; } (700_000u64 / elapsed_seconds).min(i32::MAX as u64) as i32 } #[cfg(test)] mod tests { use super::*; #[test] fn move_to_foundation_scores_ten() { assert_eq!(score_move(&PileType::Waste, &PileType::Foundation(2)), 10); assert_eq!(score_move(&PileType::Tableau(0), &PileType::Foundation(0)), 10); } #[test] fn waste_to_tableau_scores_five() { assert_eq!(score_move(&PileType::Waste, &PileType::Tableau(3)), 5); } #[test] fn tableau_to_tableau_scores_zero() { assert_eq!(score_move(&PileType::Tableau(0), &PileType::Tableau(1)), 0); } #[test] fn undo_penalty_is_negative_fifteen() { assert_eq!(score_undo(), -15); } #[test] fn time_bonus_at_100_seconds() { assert_eq!(compute_time_bonus(100), 7000); } #[test] fn time_bonus_at_zero_is_zero() { assert_eq!(compute_time_bonus(0), 0); } #[test] fn time_bonus_at_one_second() { assert_eq!(compute_time_bonus(1), 700_000); } #[test] fn foundation_to_tableau_penalises_fifteen() { // Moving a card back off a foundation (take_from_foundation rule) costs -15. assert_eq!(score_move(&PileType::Foundation(0), &PileType::Tableau(0)), -15); } #[test] fn move_to_stock_or_waste_scores_zero() { // These destinations are illegal moves in practice, but the function // must not panic and should return 0. assert_eq!(score_move(&PileType::Waste, &PileType::Stock), 0); assert_eq!(score_move(&PileType::Waste, &PileType::Waste), 0); } #[test] fn time_bonus_is_capped_at_i32_max_for_huge_values() { // Very short elapsed time would overflow without the .min() guard. let bonus = compute_time_bonus(1); assert!(bonus >= 0, "time bonus must be non-negative after u64→i32 cast"); } }