diff --git a/api/src/card.rs b/api/src/card.rs new file mode 100644 index 0000000..547dbab --- /dev/null +++ b/api/src/card.rs @@ -0,0 +1,192 @@ +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Card(u8); + +impl Card { + // Constants for suits + pub const SPADES: u8 = 0; + pub const HEARTS: u8 = 1; + pub const DIAMONDS: u8 = 2; + pub const CLUBS: u8 = 3; + + // Constants for ranks + pub const TWO: u8 = 2; + pub const THREE: u8 = 3; + pub const FOUR: u8 = 4; + pub const FIVE: u8 = 5; + pub const SIX: u8 = 6; + pub const SEVEN: u8 = 7; + pub const EIGHT: u8 = 8; + pub const NINE: u8 = 9; + pub const TEN: u8 = 10; + pub const JACK: u8 = 11; + pub const QUEEN: u8 = 12; + pub const KING: u8 = 13; + pub const ACE: u8 = 14; + + // Special constants + pub const BAD_CARD: u8 = 255; + pub const NUM_SUITS: u8 = 4; + pub const NUM_RANKS: u8 = 13; + pub const NUM_CARDS: u8 = 52; + + // Constructor for an empty/invalid card + pub fn new() -> Self { + Self(Self::BAD_CARD) + } + + // Constructor from rank and suit + pub fn from_rank_suit(rank: u8, suit: u8) -> Self { + assert!(rank >= Self::TWO && rank <= Self::ACE, "Invalid rank"); + assert!(suit < Self::NUM_SUITS, "Invalid suit"); + Self((suit << 4) | (rank & 0xF)) + } + + // Constructor from index (0..51) + pub fn from_index(index: u8) -> Self { + assert!(index < Self::NUM_CARDS, "Invalid index"); + let rank = (index % Self::NUM_RANKS) + Self::TWO; + let suit = index / Self::NUM_RANKS; + Self::from_rank_suit(rank, suit) + } + + // Constructor from string (e.g., "As" for Ace of Spades) + pub fn from_string(s: &str) -> Self { + assert!(s.len() == 2, "Invalid card string"); + let rank_char = s.chars().nth(0).unwrap(); + let suit_char = s.chars().nth(1).unwrap(); + let rank = Self::rank_from_char(rank_char); + let suit = Self::suit_from_char(suit_char); + Self::from_rank_suit(rank, suit) + } + + // Get the rank of the card + pub fn rank(&self) -> u8 { + self.0 & 0xF + } + + // Get the suit of the card + pub fn suit(&self) -> u8 { + (self.0 >> 4) & 0x3 + } + + // Get the index of the card (0..51) + pub fn index(&self) -> u8 { + (self.suit() * Self::NUM_RANKS) + (self.rank() - Self::TWO) + } + + // Check if the card is valid + pub fn is_valid(&self) -> bool { + self.0 != Self::BAD_CARD + } + + // Convert rank to character (e.g., 14 -> 'A') + pub fn rank_to_char(rank: u8) -> char { + match rank { + Self::TWO => '2', + Self::THREE => '3', + Self::FOUR => '4', + Self::FIVE => '5', + Self::SIX => '6', + Self::SEVEN => '7', + Self::EIGHT => '8', + Self::NINE => '9', + Self::TEN => 'T', + Self::JACK => 'J', + Self::QUEEN => 'Q', + Self::KING => 'K', + Self::ACE => 'A', + _ => panic!("Invalid rank"), + } + } + + // Convert suit to character (e.g., 0 -> 's') + pub fn suit_to_char(suit: u8) -> char { + match suit { + Self::SPADES => 's', + Self::HEARTS => 'h', + Self::DIAMONDS => 'd', + Self::CLUBS => 'c', + _ => panic!("Invalid suit"), + } + } + + // Convert rank character to rank (e.g., 'A' -> 14) + pub fn rank_from_char(rank_char: char) -> u8 { + match rank_char { + '2' => Self::TWO, + '3' => Self::THREE, + '4' => Self::FOUR, + '5' => Self::FIVE, + '6' => Self::SIX, + '7' => Self::SEVEN, + '8' => Self::EIGHT, + '9' => Self::NINE, + 'T' => Self::TEN, + 'J' => Self::JACK, + 'Q' => Self::QUEEN, + 'K' => Self::KING, + 'A' => Self::ACE, + _ => panic!("Invalid rank character"), + } + } + + // Convert suit character to suit (e.g., 's' -> 0) + pub fn suit_from_char(suit_char: char) -> u8 { + match suit_char { + 's' => Self::SPADES, + 'h' => Self::HEARTS, + 'd' => Self::DIAMONDS, + 'c' => Self::CLUBS, + _ => panic!("Invalid suit character"), + } + } +} + +impl std::fmt::Display for Card { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if !self.is_valid() { + write!(f, "Invalid Card") + } else { + let rank_char = Self::rank_to_char(self.rank()); + let suit_char = Self::suit_to_char(self.suit()); + write!(f, "{}{}", rank_char, suit_char) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_card_creation() { + let card = Card::from_rank_suit(Card::ACE, Card::SPADES); + assert_eq!(card.rank(), Card::ACE); + assert_eq!(card.suit(), Card::SPADES); + assert_eq!(card.index(), 12); // Ace of Spades is index 12 + assert!(card.is_valid()); + } + + #[test] + fn test_card_from_string() { + let card = Card::from_string("As"); + assert_eq!(card.rank(), Card::ACE); + assert_eq!(card.suit(), Card::SPADES); + assert_eq!(card.to_string(), "As"); + } + + #[test] + fn test_card_from_index() { + let card = Card::from_index(0); + assert_eq!(card.rank(), Card::TWO); + assert_eq!(card.suit(), Card::SPADES); + assert_eq!(card.to_string(), "2s"); + } + + #[test] + fn test_invalid_card() { + let card = Card::new(); + assert!(!card.is_valid()); + assert_eq!(card.to_string(), "Invalid Card"); + } +} diff --git a/api/src/lib.rs b/api/src/lib.rs new file mode 100644 index 0000000..2e8b5ec --- /dev/null +++ b/api/src/lib.rs @@ -0,0 +1 @@ +pub mod card; \ No newline at end of file diff --git a/api/tests/card_test.rs b/api/tests/card_test.rs new file mode 100644 index 0000000..b09311a --- /dev/null +++ b/api/tests/card_test.rs @@ -0,0 +1,7 @@ +use poker_api::card::Card; + +#[test] +fn test_card_integration() { + let card = Card::from_string("As"); + assert_eq!(card.to_string(), "As"); +}