a49a340a30
The Quat-flagged "≥3 tests per feature" inflation produced 43 tests
that don't earn their existence — default-value, serde-derive
round-trips on plain structs, single-field clamp tests, near-
duplicates, and trivial constant-equals-itself tests. None pin a
behaviour contract or a regression on a real bug.
Removed across `solitaire_data` and `solitaire_core`:
settings.rs −22 default-value, round-trip, legacy-format,
and per-field sanitized clamp tests. Adjust
and load-error tests retained — those exercise
real method logic.
progress.rs −1 generic round-trip on plain struct.
challenge.rs −1 challenge_count() returns CHALLENGE_SEEDS.len()
literally — testing it asserts the implementation
against itself.
game_state.rs −3 undo_count starts at 0, GameMode default is
Classic, time_attack score starts at 0 — all
default-value tests on freshly-constructed state.
card.rs −5 rank_value_ace + rank_value_king subsumed by
rank_values_are_sequential; suit_red + suit_black
consolidated into one complementarity test;
card_face_up_field_reflects_construction was
testing the struct literal.
Workspace: 1208 → 1165 passing tests (−43). clippy --workspace
--all-targets clean.
Future work: brief sub-agents for tests that pin a behaviour
contract or regression on a real bug, not a count of N. See
`feedback_test_discipline.md` in auto-memory.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
101 lines
2.5 KiB
Rust
101 lines
2.5 KiB
Rust
use serde::{Deserialize, Serialize};
|
|
|
|
/// Card suit.
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
|
pub enum Suit {
|
|
Clubs,
|
|
Diamonds,
|
|
Hearts,
|
|
Spades,
|
|
}
|
|
|
|
impl Suit {
|
|
/// Returns `true` for red suits (Diamonds, Hearts).
|
|
pub fn is_red(self) -> bool {
|
|
matches!(self, Suit::Diamonds | Suit::Hearts)
|
|
}
|
|
|
|
/// Returns `true` for black suits (Clubs, Spades).
|
|
pub fn is_black(self) -> bool {
|
|
!self.is_red()
|
|
}
|
|
}
|
|
|
|
/// Card rank, Ace through King.
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
|
pub enum Rank {
|
|
Ace,
|
|
Two,
|
|
Three,
|
|
Four,
|
|
Five,
|
|
Six,
|
|
Seven,
|
|
Eight,
|
|
Nine,
|
|
Ten,
|
|
Jack,
|
|
Queen,
|
|
King,
|
|
}
|
|
|
|
impl Rank {
|
|
/// Numeric value: Ace = 1, King = 13.
|
|
pub fn value(self) -> u8 {
|
|
match self {
|
|
Rank::Ace => 1,
|
|
Rank::Two => 2,
|
|
Rank::Three => 3,
|
|
Rank::Four => 4,
|
|
Rank::Five => 5,
|
|
Rank::Six => 6,
|
|
Rank::Seven => 7,
|
|
Rank::Eight => 8,
|
|
Rank::Nine => 9,
|
|
Rank::Ten => 10,
|
|
Rank::Jack => 11,
|
|
Rank::Queen => 12,
|
|
Rank::King => 13,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A single playing card.
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct Card {
|
|
/// Unique identifier for this card within the deal. Stable across moves and undo.
|
|
pub id: u32,
|
|
/// The card's suit (Clubs, Diamonds, Hearts, Spades).
|
|
pub suit: Suit,
|
|
/// The card's rank (Ace through King).
|
|
pub rank: Rank,
|
|
/// Whether the card is visible to the player. Face-down cards may not be moved.
|
|
pub face_up: bool,
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn rank_values_are_sequential() {
|
|
let ranks = [
|
|
Rank::Ace, Rank::Two, Rank::Three, Rank::Four, Rank::Five,
|
|
Rank::Six, Rank::Seven, Rank::Eight, Rank::Nine, Rank::Ten,
|
|
Rank::Jack, Rank::Queen, Rank::King,
|
|
];
|
|
for (i, r) in ranks.iter().enumerate() {
|
|
assert_eq!(r.value(), (i + 1) as u8);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn suit_red_and_black_are_complementary() {
|
|
for suit in [Suit::Clubs, Suit::Diamonds, Suit::Hearts, Suit::Spades] {
|
|
assert_ne!(suit.is_red(), suit.is_black(), "{suit:?} must be exactly one of red/black");
|
|
}
|
|
assert!(Suit::Diamonds.is_red() && Suit::Hearts.is_red());
|
|
assert!(Suit::Clubs.is_black() && Suit::Spades.is_black());
|
|
}
|
|
}
|