commit 94b1f06590dbc0d41c6dcaae24dcbe1178a6b440 Author: Edward Tirado Jr. Date: Fri May 23 00:44:18 2025 -0500 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/CardGames.iml b/.idea/CardGames.iml new file mode 100644 index 0000000..cf84ae4 --- /dev/null +++ b/.idea/CardGames.iml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..91367f0 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..9776f96 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,157 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "CardGames" +version = "0.1.0" +dependencies = [ + "rand", +] + +[[package]] +name = "bitflags" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasi", +] + +[[package]] +name = "libc" +version = "0.2.172" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" + +[[package]] +name = "rand" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" +dependencies = [ + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom", +] + +[[package]] +name = "syn" +version = "2.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags", +] + +[[package]] +name = "zerocopy" +version = "0.8.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..047d9c3 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "CardGames" +version = "0.1.0" +edition = "2024" + +[dependencies] +rand = "0.9.1" diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..8755585 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,8 @@ +mod poker; + +mod models; +mod tests; + +fn main() { + poker::game() +} diff --git a/src/models/deck.rs b/src/models/deck.rs new file mode 100644 index 0000000..d68b4f4 --- /dev/null +++ b/src/models/deck.rs @@ -0,0 +1,41 @@ +use rand::seq::SliceRandom; + +const VALUES: [u8; 13] = [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]; +const RANKS: [&str; 13] = ["2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A"]; +const SUITS: [&str; 4] = ["♣", "◆", "♠", "♥"]; + +#[derive(Debug)] +pub struct Card { + pub rank: String, + pub value: u8, + pub suit: String, +} + + +#[derive(Debug)] +pub struct Deck { + pub cards: Vec, +} + +impl Deck { + pub fn new() -> Deck { + let mut cards = Vec::new(); + for suit in SUITS.iter() { + for rank in RANKS.iter() { + cards.push(Card { + rank: rank.to_string(), + suit: suit.to_string(), + value: VALUES[RANKS.iter().position(|r| r == rank).unwrap()], + }) + } + } + + Deck { + cards, + } + } + + pub fn shuffle(&mut self) { + self.cards.shuffle(&mut rand::rng()); + } +} diff --git a/src/models/hand.rs b/src/models/hand.rs new file mode 100644 index 0000000..5868849 --- /dev/null +++ b/src/models/hand.rs @@ -0,0 +1,167 @@ +use crate::models::deck::{Card, Deck}; +use std::collections::HashMap; + +#[derive(Debug)] +pub enum HandSort { + None, + Value, + Rank, +} + +#[derive(Debug)] +pub struct Hand { + pub cards: Vec, + pub card_sort: HandSort, +} + +impl Hand { + pub fn new() -> Hand { + Hand { + cards: Vec::new(), + card_sort: HandSort::None, + } + } + + pub fn draw(&mut self, count: u8, deck: &mut Deck) { + for _ in 0..count { + let card = deck.cards.pop().unwrap(); + self.cards.push(card); + } + } + + pub fn sort(&mut self, hand_sort: HandSort) { + match hand_sort { + HandSort::Value => self.sort_by_value(), + HandSort::Rank => self.sort_by_rank(), + HandSort::None => (), + } + } + + pub fn sort_by_value(&mut self) { + self.cards.sort_by(|a, b| a.value.cmp(&b.value)); + self.card_sort = HandSort::Value; + } + + pub fn sort_by_rank(&mut self) { + //self.cards.sort_by(|a, b| a.rank.cmp(&b.rank)); + self.card_sort = HandSort::Rank; + } + + fn pair_count(&self) -> u8 { + let mut pairs = 0; + + let mut value_counts: HashMap = HashMap::new(); + for card in self.cards.iter() { + value_counts.insert(card.value, value_counts.get(&card.value).unwrap_or(&0) + 1); + } + + for i in value_counts.values() { + if i > &1 { + pairs = pairs + 1; + } + } + + pairs + } + + pub fn has_single_pair(&mut self) -> bool { + if !matches!(self.card_sort, HandSort::Value) { + self.sort(HandSort::Value); + } + + if self.pair_count() == 1 { + return true; + } + + false + } + + pub fn has_two_pair(&mut self) -> bool { + if !matches!(self.card_sort, HandSort::Value) { + self.sort(HandSort::Value); + } + + if self.pair_count() == 2 { + return true; + } + + false + } + + pub fn has_three_of_a_kind(&mut self) -> bool { + if !matches!(self.card_sort, HandSort::Value) { + self.sort(HandSort::Value); + } + for i in 2..self.cards.len() { + if self.cards[i].rank == self.cards[i - 1].rank && self.cards[i].rank == self.cards[i - 2].rank { + return true; + } + } + + false + } + + pub fn has_straight(&mut self) -> bool { + if !matches!(self.card_sort, HandSort::Value) { + self.sort(HandSort::Value); + } + + let hand_size = self.cards.len(); + + // Handle Ace low straight + if self.cards[0].value == 2 && self.cards[hand_size - 1].value == 14 { + self.cards[hand_size - 1].value = 1; + self.cards.rotate_right(1) + } + + for i in 1..self.cards.len() { + if self.cards[i].value != self.cards[i - 1].value + 1 { + return false; + } + } + + true + } + + pub fn has_flush(&self) -> bool { + for i in 1..self.cards.len() { + if self.cards[i].suit != self.cards[i - 1].suit { + return false; + } + } + + true + } + + pub fn has_full_house(&mut self) -> bool { + if self.has_three_of_a_kind() && self.has_two_pair() { + return true; + } + + false + } + + pub fn has_four_of_a_kind(&mut self) -> bool { + if !matches!(self.card_sort, HandSort::Value) { + self.sort(HandSort::Value); + } + for i in 3..self.cards.len() { + if self.cards[i].rank == self.cards[i - 1].rank && + self.cards[i].rank == self.cards[i - 2].rank && + self.cards[i].rank == self.cards[i - 3].rank + { + return true; + } + } + + false + } + + pub fn has_straight_flush(&mut self) -> bool { + self.has_straight() && self.has_flush() + } + + pub fn has_royal_flush(&mut self) -> bool { + self.has_flush() && self.has_straight() && self.cards[0].value == 10 + } +} diff --git a/src/models/mod.rs b/src/models/mod.rs new file mode 100644 index 0000000..2940c03 --- /dev/null +++ b/src/models/mod.rs @@ -0,0 +1,3 @@ +pub mod deck; +pub mod hand; +pub mod player; \ No newline at end of file diff --git a/src/models/player.rs b/src/models/player.rs new file mode 100644 index 0000000..dc6246e --- /dev/null +++ b/src/models/player.rs @@ -0,0 +1,7 @@ +use crate::models::hand::Hand; + +#[derive(Debug)] +pub struct Player { + pub name: String, + pub hand: Hand, +} diff --git a/src/poker.rs b/src/poker.rs new file mode 100644 index 0000000..864a5f6 --- /dev/null +++ b/src/poker.rs @@ -0,0 +1,35 @@ +use crate::models::deck::{Card, Deck}; +use crate::models::hand::Hand; +use crate::models::player::Player; +use std::io; + +struct Game { + name: String, + deck: Vec, +} + +pub fn game() { + let mut deck = Deck::new(); + deck.shuffle(); + + let mut players: Vec = Vec::new(); + let mut player_count: String = String::new(); + + println!("Welcome to Poker! How many players? (1-4)"); + io::stdin().read_line(&mut player_count).expect("Failed to read line"); + let player_count: usize = player_count.trim().parse().expect("Failed to parse player count"); + + for index in 0..player_count { + players.push(Player { + name: String::from(format!("Player {}", index + 1)), + hand: Hand::new(), + }); + + players[index].hand.draw(5, &mut deck); + players[index].hand.sort_by_value(); + } + + for player in players.iter_mut() { + println!("{:?}\n", player.name); + } +} \ No newline at end of file diff --git a/src/tests/deck_test.rs b/src/tests/deck_test.rs new file mode 100644 index 0000000..ba26637 --- /dev/null +++ b/src/tests/deck_test.rs @@ -0,0 +1,10 @@ +#[cfg(test)] +mod tests { + use crate::models::deck::Deck; + + #[test] + fn test_new_deck_has_52_cards() { + let deck = Deck::new(); + assert_eq!(deck.cards.len(), 52); + } +} \ No newline at end of file diff --git a/src/tests/hand_test.rs b/src/tests/hand_test.rs new file mode 100644 index 0000000..9fbcbd9 --- /dev/null +++ b/src/tests/hand_test.rs @@ -0,0 +1,160 @@ +#[cfg(test)] +mod tests { + use crate::models::deck::{Card, Deck}; + use crate::models::hand::Hand; + use crate::models::player::Player; + + #[test] + fn test_hand_has_correct_amount_of_cards_after_drawing() { + let mut deck = Deck::new(); + let mut player = Player { + name: String::from("Testy McTestFace"), + hand: Hand::new(), + }; + + player.hand.draw(5, &mut deck); + assert_eq!(player.hand.cards.len(), 5); + } + + #[test] + fn test_hand_adds_cards_to_exhasting_hand() { + let mut deck = Deck::new(); + let mut player = Player { + name: String::from("Testy McTestFace"), + hand: Hand::new(), + }; + + player.hand.draw(5, &mut deck); + player.hand.draw(3, &mut deck); + assert_eq!(player.hand.cards.len(), 8); + } + + #[test] + fn test_has_single_pair() { + let mut hand = Hand::new(); + + hand.cards.push(Card { rank: "A".to_string(), value: 14, suit: "♥".to_string() }); + hand.cards.push(Card { rank: "2".to_string(), value: 2, suit: "♠".to_string() }); + hand.cards.push(Card { rank: "4".to_string(), value: 4, suit: "♥".to_string() }); + hand.cards.push(Card { rank: "A".to_string(), value: 14, suit: "◆".to_string() }); + hand.cards.push(Card { rank: "5".to_string(), value: 5, suit: "♣".to_string() }); + + assert_eq!(hand.has_single_pair(), true) + } + + #[test] + fn test_has_two_pair() { + let mut hand = Hand::new(); + + hand.cards.push(Card { rank: "A".to_string(), value: 14, suit: "♥".to_string() }); + hand.cards.push(Card { rank: "2".to_string(), value: 2, suit: "♠".to_string() }); + hand.cards.push(Card { rank: "2".to_string(), value: 2, suit: "♥".to_string() }); + hand.cards.push(Card { rank: "A".to_string(), value: 14, suit: "◆".to_string() }); + hand.cards.push(Card { rank: "5".to_string(), value: 5, suit: "♣".to_string() }); + + assert_eq!(hand.has_two_pair(), true) + } + + #[test] + fn test_has_three_of_a_kind() { + let mut hand = Hand::new(); + + hand.cards.push(Card { rank: "A".to_string(), value: 14, suit: "♥".to_string() }); + hand.cards.push(Card { rank: "2".to_string(), value: 2, suit: "♠".to_string() }); + hand.cards.push(Card { rank: "2".to_string(), value: 4, suit: "♥".to_string() }); + hand.cards.push(Card { rank: "5".to_string(), value: 14, suit: "◆".to_string() }); + hand.cards.push(Card { rank: "2".to_string(), value: 5, suit: "♣".to_string() }); + + assert_eq!(hand.has_three_of_a_kind(), true) + } + + #[test] + fn test_has_four_of_a_kind() { + let mut hand = Hand::new(); + + hand.cards.push(Card { rank: "2".to_string(), value: 14, suit: "♥".to_string() }); + hand.cards.push(Card { rank: "2".to_string(), value: 2, suit: "♠".to_string() }); + hand.cards.push(Card { rank: "2".to_string(), value: 4, suit: "◆".to_string() }); + hand.cards.push(Card { rank: "5".to_string(), value: 14, suit: "◆".to_string() }); + hand.cards.push(Card { rank: "2".to_string(), value: 5, suit: "♣".to_string() }); + + assert_eq!(hand.has_four_of_a_kind(), true) + } + + #[test] + fn test_has_straight() { + let mut hand = Hand::new(); + hand.cards.push(Card { rank: "9".to_string(), value: 9, suit: "◆".to_string() }); + hand.cards.push(Card { rank: "7".to_string(), value: 7, suit: "♥".to_string() }); + hand.cards.push(Card { rank: "J".to_string(), value: 11, suit: "♣".to_string() }); + hand.cards.push(Card { rank: "8".to_string(), value: 8, suit: "♠".to_string() }); + hand.cards.push(Card { rank: "10".to_string(), value: 10, suit: "◆".to_string() }); + + assert_eq!(hand.has_straight(), true); + } + + #[test] + fn test_has_straight_ace_low() { + let mut hand = Hand::new(); + + hand.cards.push(Card { rank: "2".to_string(), value: 2, suit: "♥".to_string() }); + hand.cards.push(Card { rank: "4".to_string(), value: 4, suit: "◆".to_string() }); + hand.cards.push(Card { rank: "A".to_string(), value: 14, suit: "♣".to_string() }); + hand.cards.push(Card { rank: "5".to_string(), value: 5, suit: "◆".to_string() }); + hand.cards.push(Card { rank: "3".to_string(), value: 3, suit: "♠".to_string() }); + + assert_eq!(hand.has_straight(), true) + } + + #[test] + fn test_has_flush() { + let mut hand = Hand::new(); + + hand.cards.push(Card { rank: "2".to_string(), value: 2, suit: "♥".to_string() }); + hand.cards.push(Card { rank: "4".to_string(), value: 4, suit: "♥".to_string() }); + hand.cards.push(Card { rank: "9".to_string(), value: 9, suit: "♥".to_string() }); + hand.cards.push(Card { rank: "5".to_string(), value: 5, suit: "♥".to_string() }); + hand.cards.push(Card { rank: "8".to_string(), value: 8, suit: "♥".to_string() }); + + assert_eq!(hand.has_flush(), true) + } + + #[test] + fn test_has_full_house() { + let mut hand = Hand::new(); + + hand.cards.push(Card { rank: "2".to_string(), value: 2, suit: "♥".to_string() }); + hand.cards.push(Card { rank: "5".to_string(), value: 5, suit: "♠".to_string() }); + hand.cards.push(Card { rank: "2".to_string(), value: 2, suit: "♣".to_string() }); + hand.cards.push(Card { rank: "5".to_string(), value: 5, suit: "◆".to_string() }); + hand.cards.push(Card { rank: "2".to_string(), value: 2, suit: "◆".to_string() }); + + assert_eq!(hand.has_full_house(), true) + } + + #[test] + fn test_has_straight_flush() { + let mut hand = Hand::new(); + + hand.cards.push(Card { rank: "2".to_string(), value: 2, suit: "♥".to_string() }); + hand.cards.push(Card { rank: "3".to_string(), value: 3, suit: "♥".to_string() }); + hand.cards.push(Card { rank: "4".to_string(), value: 4, suit: "♥".to_string() }); + hand.cards.push(Card { rank: "5".to_string(), value: 5, suit: "♥".to_string() }); + hand.cards.push(Card { rank: "6".to_string(), value: 6, suit: "♥".to_string() }); + + assert_eq!(hand.has_straight_flush(), true) + } + + #[test] + fn test_has_royal_flush() { + let mut hand = Hand::new(); + + hand.cards.push(Card { rank: "10".to_string(), value: 10, suit: "♥".to_string() }); + hand.cards.push(Card { rank: "J".to_string(), value: 11, suit: "♥".to_string() }); + hand.cards.push(Card { rank: "Q".to_string(), value: 12, suit: "♥".to_string() }); + hand.cards.push(Card { rank: "K".to_string(), value: 13, suit: "♥".to_string() }); + hand.cards.push(Card { rank: "A".to_string(), value: 14, suit: "♥".to_string() }); + + assert_eq!(hand.has_royal_flush(), true) + } +} \ No newline at end of file diff --git a/src/tests/mod.rs b/src/tests/mod.rs new file mode 100644 index 0000000..61be80f --- /dev/null +++ b/src/tests/mod.rs @@ -0,0 +1,2 @@ +mod deck_test; +mod hand_test; \ No newline at end of file