From 3130841c9937ba387045a29059acdc200002d70f Mon Sep 17 00:00:00 2001 From: eRaMvn Date: Sun, 19 Dec 2021 12:15:04 -0800 Subject: [PATCH] finished cli with probability function + updated readme --- .github/workflows/ci.yml | 74 +++++ .github/workflows/release.yml | 38 +++ .gitignore | 2 +- .pre-commit-config.yaml | 12 + Cargo.toml | 5 +- README.md | 37 ++- drawing-hands-outs.gif | Bin 0 -> 27465 bytes src/calc_prob.rs | 590 ++++++++++++++++++++++++++++++++++ src/main.rs | 293 +++++------------ 9 files changed, 828 insertions(+), 223 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/release.yml create mode 100644 .pre-commit-config.yaml create mode 100644 drawing-hands-outs.gif create mode 100644 src/calc_prob.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..5439a7f --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,74 @@ +name: Continuous integration + +on: + push: + branches: + - master + +jobs: + check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/cache@v1 + with: + path: ~/.cargo/registry + key: "${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }}" + - uses: actions/cache@v1 + with: + path: ~/.cargo/git + key: "${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }}" + - uses: actions/cache@v1 + with: + path: target + key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }} + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + - uses: actions-rs/cargo@v1 + with: + command: check + + test: + name: Test Suite + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/cache@v1 + with: + path: ~/.cargo/registry + key: "${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }}" + - uses: actions/cache@v1 + with: + path: ~/.cargo/git + key: "${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }}" + - uses: actions/cache@v1 + with: + path: target + key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }} + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + - uses: actions-rs/cargo@v1 + with: + command: test + + fmt: + name: Rustfmt + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + - run: rustup component add rustfmt + - uses: actions-rs/cargo@v1 + with: + command: fmt + args: --all -- --check diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..73fefb6 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,38 @@ +name: Publish + +on: + push: + branches: + - master + +jobs: + publish: + name: Publish for ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + include: + - os: ubuntu-latest + artifact_name: poker_prob + asset_name: poker-prob-linux-amd64 + - os: windows-latest + artifact_name: poker_prob.exe + asset_name: poker-prob-windows-amd64 + - os: macos-latest + artifact_name: poker_prob + asset_name: poker-prob-macos-amd64 + + steps: + - uses: actions/checkout@v2 + - name: Build + run: cargo build --release + - name: Set outputs + id: vars + run: echo "::set-output name=sha_short::$(git rev-parse --short HEAD)" + - name: Upload binaries to release + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.RELEASE_TOKEN }} + file: target/release/${{ matrix.artifact_name }} + asset_name: ${{ matrix.asset_name }} + tag: ${{ steps.vars.outputs.sha_short }} diff --git a/.gitignore b/.gitignore index ada8be9..6985cf1 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,4 @@ Cargo.lock **/*.rs.bk # MSVC Windows builds of rustc generate these, which store debugging information -*.pdb \ No newline at end of file +*.pdb diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..03f1ab1 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,12 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.0.1 + hooks: + - id: check-yaml + - id: end-of-file-fixer + - id: trailing-whitespace + - repo: https://github.com/doublify/pre-commit-rust + rev: v1.0 + hooks: + - id: fmt + - id: cargo-check diff --git a/Cargo.toml b/Cargo.toml index 76d0b80..eb85107 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,5 +6,6 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -clap = "3.0.0-beta.5" -rs_poker = "2.0.0-alpha.1" \ No newline at end of file +clap = { version = "3.0.0-rc.7", features = ["derive"] } +rs_poker = "2.0.0-alpha.1" +colored = "2" diff --git a/README.md b/README.md index acbc810..e91a432 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,37 @@ # `poker_prob` -Program to calculate the probabilities of winning hands in rust. This is a work in progress as part of my process to learn rust +Program to calculate the probabilities of winning hands in rust based on rule of 4 and 2 -## Sample command -cargo run poker_prob -- --ch As10s -n 4 -f 2h5c6d \ No newline at end of file +## Sample commands + +### Help command + +`poker_prob.exe -h` + +Output: + +```/bin/bash + poker_prob.exe [OPTIONS] --mh --ch + +OPTIONS: + -a Set whether this is all in or not + --ch Set community cards + -h, --help Print help information + --mh Set my hand + -V, --version Print version information +``` + +### Calculate probabilities + +`poker_prob.exe --ch Ad3h --mh 4h3c5c6h -a` + +Output: + +```/bin/bash +Straight has the probability of 8% +Two Pair has the probability of 6% +One Pair has the probability of 0% +Three Of A Kind has the probability of 4% +Flush has the probability of 20% +Full House has the probability of 10% +``` diff --git a/drawing-hands-outs.gif b/drawing-hands-outs.gif new file mode 100644 index 0000000000000000000000000000000000000000..43fe2fe7e19d597ec6073277eab3c13f8aadf60f GIT binary patch literal 27465 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 Yt=!Aa+|BLW&kfztE#1@Y+(7^UJM9cYMF0Q* literal 0 HcmV?d00001 diff --git a/src/calc_prob.rs b/src/calc_prob.rs new file mode 100644 index 0000000..c77a056 --- /dev/null +++ b/src/calc_prob.rs @@ -0,0 +1,590 @@ +use rs_poker::core::{Card, Deck, Hand, Rankable, Suit, Value}; +use std::collections::HashMap; + +pub fn calc_4_and_2_probs(all_in: bool, num_community_cards: i8, outs: i8) -> i8 { + if outs < 0 { + return 0; + } + + if num_community_cards >= 4 { + if all_in { + return outs * 4; + } + } + outs * 2 +} + +// Function to remove cards in hand and community from a brand new deck +pub fn get_unknown_cards(hand: &Hand, community: &Hand) -> Deck { + // Initial deck with 52 cards + let mut deck = Deck::default(); + + // Remove cards in hand from deck + let mut temp_card: Card; + for card in hand.cards() { + temp_card = card.clone(); + deck.remove(temp_card); + } + + // Remove community cards from deck + for card in community.cards() { + temp_card = card.clone(); + deck.remove(temp_card); + } + + deck +} + +// Given a hand, count the number of card with the same suit or value +fn count_suit_and_value_on_hand( + hand: &Hand, + card_suits: &mut HashMap, + card_values: &mut HashMap, +) -> () { + for card in hand.cards() { + card_suits.entry(card.suit).or_insert(0); + card_suits.insert(card.suit, card_suits[&card.suit] + 1); + + card_values.entry(card.value).or_insert(0); + card_values.insert(card.value, card_values[&card.value] + 1); + } +} + +// Count the number of card with the same suit or value from all cards on table +fn count_suit_and_value_on_table( + hand: &Hand, + community: &Hand, +) -> (HashMap, HashMap) { + let mut card_suits = HashMap::new(); + let mut card_values = HashMap::new(); + + // Going through the cards in the hand to count number of cards based on their suits + count_suit_and_value_on_hand(hand, &mut card_suits, &mut card_values); + // Going through the community card to count number of cards based on their suits + count_suit_and_value_on_hand(community, &mut card_suits, &mut card_values); + (card_suits, card_values) +} + +fn get_one_pair_outs(hand: &Hand, community: &Hand) -> i8 { + let (_, card_values) = count_suit_and_value_on_table(&hand, &community); + + for (_, &count) in card_values.iter() { + // If count of values of cards on table == 2, that means we already a pair, return outs = 0, no calculation needed + if count == 2 { + return 0; + } + } + // There are four suits for each card, so 3 outs for each card + 3 +} + +fn get_two_pairs_outs(hand: &Hand, community: &Hand) -> i8 { + let (_, card_values) = count_suit_and_value_on_table(&hand, &community); + let mut one_pair_found = false; + let mut second_pair_found = false; + + for (_, &count) in card_values.iter() { + // If count of values of cards on table == 4 (full suits of a card value), that means we already have two pairs, return outs = 0, no calculation needed + if count == 4 { + return 0; + } + + // When there is a pair already + if count == 2 { + if one_pair_found == false { + one_pair_found = true; + continue; + } + if one_pair_found { + second_pair_found = true; + continue; + } + } + } + + if one_pair_found && second_pair_found { + return 0; + } + + // If we already have a pair, we just have to get another pair, 3 outs for each card left + if one_pair_found { + return 3; + } + // 3 outs for each card, need two cards for a pair + hand.len() as i8 * 3 +} + +fn get_three_of_a_kind_outs(hand: &Hand, community: &Hand) -> i8 { + let (_, card_values) = count_suit_and_value_on_table(&hand, &community); + + for (_, &count) in card_values.iter() { + // If count of values of cards on table == 3, that means we already have a set, return outs = 0, no calculation needed + if count >= 3 { + return 0; + } + + // If we already have a pair, we just need one more card, 2 outs + if count == 2 { + return 2; + } + } + // If we have no pair in current hand, then we need at least two more cards + 3 +} + +fn get_straight_outs(hand: &Hand, community: &Hand) -> i8 { + let (_, card_values) = count_suit_and_value_on_table(&hand, &community); + let mut value_vector = Vec::new(); + + for (&value, _) in card_values.iter() { + value_vector.push(value as i8); + } + + value_vector.sort(); + let mut num_in_sequence = 1; + + for (i, each_value) in value_vector.iter().enumerate() { + if i == 0 { + continue; + } + if each_value - 1 == value_vector[i - 1] { + num_in_sequence += 1 + } + } + + // If there are 3 community cards and the num of consecutive card is not 3, then there is no chance for straight + if community.len() as i8 == 3 && num_in_sequence < 3 { + return -1; + } + + // If there are 3 community cards and the num of consecutive card is not 3, then there is no chance for straight + if community.len() as i8 == 4 && num_in_sequence < 4 { + return -1; + } + + // We only need 5 cards to have straight, but from 4 suits + (5 - num_in_sequence) * 4 +} + +fn get_flush_outs(deck: &Deck, hand: &Hand, community: &Hand) -> i8 { + let (card_suits, _) = count_suit_and_value_on_table(&hand, &community); + + let mut num_of_highest_suit_outs: i8 = -1; + let mut outs: i8; + + for (&suit, &count) in card_suits.iter() { + // When there is more than or equal to 4 community cards, but the count of suits is less then 4, skip the suit + if community.len() >= 4 && count < 4 { + continue; + } + + // When there is less than or equal to 3 community cards, but the count of suits is less then 3, skip the suit + if community.len() <= 3 && count < 3 { + continue; + } + + outs = 0; + // For the remaining suits, get the number of cards with that suit + for card in deck.iter() { + if card.suit == suit { + outs += 1; + } + } + if outs > num_of_highest_suit_outs { + num_of_highest_suit_outs = outs + } + } + + num_of_highest_suit_outs +} + +fn get_full_house_outs(hand: &Hand, community: &Hand) -> i8 { + let (_, card_values) = count_suit_and_value_on_table(&hand, &community); + let mut one_pair_found = false; + let mut second_pair_found = false; + let mut set_found = false; + + for (_, &count) in card_values.iter() { + // If count of values of cards on table == 4 (full suits of a card value), that means we already have two pairs, only need 1 more card (2 outs) for full house + if count == 4 { + return 2 * 2; + } + + // If we already one pair + if count == 2 { + if set_found { + return 0; + } + if one_pair_found == false { + one_pair_found = true; + continue; + } + if one_pair_found { + second_pair_found = true; + continue; + } + } + + if count == 3 { + if one_pair_found { + return 0; + } + set_found = true; + } + } + + if one_pair_found && second_pair_found { + return 2 * 2; + } + + // If we already have a pair, we have to get both another pair + 1 other card of the same value with the pair we have or 3 other cards of the same value + if one_pair_found { + // Two possibilities but we go with the one with higher outs: + // 3 outs for a set + // 3 outs for a pair + 2 outs for 1 other card + return 2 + 3; + } + // If none found + 3 + 3 +} + +pub enum HandRank { + /// One Card matches another. + OnePair, + /// Two different pair of matching cards. + TwoPair, + /// Three of the same value. + ThreeOfAKind, + /// Five cards in a sequence + Straight, + /// Five cards of the same suit + Flush, + /// Three of one value and two of another value + FullHouse, +} + +impl HandRank { + pub fn calc_outs(self, deck: &Deck, hand: &Hand, community: &Hand) -> i8 { + match self { + Self::OnePair => get_one_pair_outs(hand, community), + Self::TwoPair => get_two_pairs_outs(hand, community), + Self::ThreeOfAKind => get_three_of_a_kind_outs(hand, community), + Self::Straight => get_straight_outs(hand, community), + Self::Flush => get_flush_outs(deck, hand, community), + Self::FullHouse => get_full_house_outs(hand, community), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_calc_4_and_2_probs_1() { + assert_eq!(calc_4_and_2_probs(true, 3, 2), 4); + } + + #[test] + fn test_calc_4_and_2_probs_2() { + assert_eq!(calc_4_and_2_probs(true, 4, 2), 8); + } + + #[test] + fn test_calc_4_and_2_probs_3() { + assert_eq!(calc_4_and_2_probs(false, 4, 2), 4); + } + + #[test] + fn test_calc_4_and_2_probs_4() { + assert_eq!(calc_4_and_2_probs(false, 4, -1), 0); + } + + #[test] + fn test_correct_num_of_unknown_cards() { + let hand = Hand::new_from_str("Adkh").unwrap(); + let community = Hand::new_from_str("Jd8c3d").unwrap(); + assert_eq!(get_unknown_cards(&hand, &community).len(), 47); + } + + #[test] + fn test_incorrect_num_of_unknown_cards() { + let hand = Hand::new_from_str("Adkh").unwrap(); + let community = Hand::new_from_str("Jd8c3d4s").unwrap(); + assert_ne!(get_unknown_cards(&hand, &community).len(), 47); + } + + // Testing if we have the correct number of count of suits and values on a hand + #[test] + fn test_count_suit_and_value_on_hand() { + let mut card_suits = HashMap::new(); + let mut card_values = HashMap::new(); + let hand = Hand::new_from_str("Adkh").unwrap(); + let community = Hand::new_from_str("Jd8c3d").unwrap(); + count_suit_and_value_on_hand(&hand, &mut card_suits, &mut card_values); + count_suit_and_value_on_hand(&community, &mut card_suits, &mut card_values); + assert_eq!(card_suits[&Suit::Diamond], 3); + assert_eq!(card_suits[&Suit::Heart], 1); + assert_eq!(card_suits[&Suit::Club], 1); + assert_eq!(card_values[&Value::Three], 1); + assert_eq!(card_values[&Value::Eight], 1); + assert_eq!(card_values[&Value::Ace], 1); + assert_eq!(card_values[&Value::Jack], 1); + assert_eq!(card_values[&Value::King], 1); + } + + // Testing when we have at least one pair, outs should 0, pair at the top + #[test] + fn test_existing_one_pair_1() { + let hand = Hand::new_from_str("Adkh").unwrap(); + let community = Hand::new_from_str("Ah8c3s4s").unwrap(); + assert_eq!(get_one_pair_outs(&hand, &community), 0); + } + + #[test] + fn test_existing_one_pair_2() { + let hand = Hand::new_from_str("Adkh").unwrap(); + let community = Hand::new_from_str("2h8cks4s").unwrap(); + assert_eq!(get_one_pair_outs(&hand, &community), 0); + } + + // Testing when we have at least one pair, outs should 0, pair at the middle + #[test] + fn test_one_pair_outs_2() { + let hand = Hand::new_from_str("Adkh").unwrap(); + let community = Hand::new_from_str("7h8cks4s").unwrap(); + assert_eq!(get_one_pair_outs(&hand, &community), 0); + } + + // Testing when we have at least one pair, outs should 0, pair at the bottom + #[test] + fn test_one_pair_outs_3() { + let hand = Hand::new_from_str("Adkh").unwrap(); + let community = Hand::new_from_str("7h8cks").unwrap(); + assert_eq!(get_one_pair_outs(&hand, &community), 0); + } + + // Testing when we have at least one pair, outs should 0, pair at the bottom + #[test] + fn test_one_pair_outs_4() { + let hand = Hand::new_from_str("Adkh").unwrap(); + let community = Hand::new_from_str("7h8cAs").unwrap(); + assert_eq!(get_one_pair_outs(&hand, &community), 0); + } + + // Testing when we have no pair + #[test] + fn test_one_pair_outs_5() { + let hand = Hand::new_from_str("Adkh").unwrap(); + let community = Hand::new_from_str("7h8c2s").unwrap(); + assert_eq!(get_one_pair_outs(&hand, &community), 3); + } + + // Testing when we have two pairs already + #[test] + fn test_existing_two_pairs_1() { + let hand = Hand::new_from_str("Adkh").unwrap(); + let community = Hand::new_from_str("AsKd2s").unwrap(); + assert_eq!(get_two_pairs_outs(&hand, &community), 0); + } + + #[test] + fn test_existing_two_pairs_2() { + let hand = Hand::new_from_str("Adkh").unwrap(); + let community = Hand::new_from_str("As4cKd2s").unwrap(); + assert_eq!(get_two_pairs_outs(&hand, &community), 0); + } + + // Test when we already have one pair + #[test] + fn test_one_in_two_pairs_1() { + let hand = Hand::new_from_str("Adkh").unwrap(); + let community = Hand::new_from_str("As4c5d2s").unwrap(); + assert_eq!(get_two_pairs_outs(&hand, &community), 3); + } + + // Test when we already have one pair + #[test] + fn test_one_in_two_pairs_2() { + let hand = Hand::new_from_str("Adkh").unwrap(); + let community = Hand::new_from_str("3skd4c").unwrap(); + assert_eq!(get_two_pairs_outs(&hand, &community), 3); + } + + // Test when we have no pair + #[test] + fn test_two_pairs_outs_1() { + let hand = Hand::new_from_str("Adkh").unwrap(); + let community = Hand::new_from_str("3s4cqd2s").unwrap(); + assert_eq!(get_two_pairs_outs(&hand, &community), 6); + } + + // Test when we have already have three of a kind + #[test] + fn test_existing_three_of_a_kind_1() { + let hand = Hand::new_from_str("Adkh").unwrap(); + let community = Hand::new_from_str("As4cqdAc").unwrap(); + assert_eq!(get_three_of_a_kind_outs(&hand, &community), 0); + } + + // Test when we have already have three of a kind + #[test] + fn test_existing_three_of_a_kind_2() { + let hand = Hand::new_from_str("AdAh").unwrap(); + let community = Hand::new_from_str("As4cqdAc").unwrap(); + assert_eq!(get_three_of_a_kind_outs(&hand, &community), 0); + } + + // Test when we have already have a pair + #[test] + fn test_three_of_a_kind_outs_1() { + let hand = Hand::new_from_str("AdAh").unwrap(); + let community = Hand::new_from_str("2s4cqd4h").unwrap(); + assert_eq!(get_three_of_a_kind_outs(&hand, &community), 2); + } + + // Testing when we have no chance of having straight + #[test] + fn test_impossible_straight_1() { + let hand = Hand::new_from_str("7dkh").unwrap(); + let community = Hand::new_from_str("Jd2c3s").unwrap(); + assert_eq!(get_straight_outs(&hand, &community), -1); + } + + #[test] + fn test_impossible_straight_2() { + let hand = Hand::new_from_str("7dkh").unwrap(); + let community = Hand::new_from_str("Jd2c3s4s").unwrap(); + assert_eq!(get_straight_outs(&hand, &community), -1); + } + + #[test] + fn test_straight_outs_1() { + let hand = Hand::new_from_str("7d3s").unwrap(); + let community = Hand::new_from_str("Jd2s4s5d").unwrap(); + assert_eq!(get_straight_outs(&hand, &community), 4); + } + + #[test] + fn test_straight_outs_2() { + let hand = Hand::new_from_str("7d3s").unwrap(); + let community = Hand::new_from_str("Jd2s4s").unwrap(); + assert_eq!(get_straight_outs(&hand, &community), 8); + } + + #[test] + fn test_straight_outs_3() { + let hand = Hand::new_from_str("7d3s").unwrap(); + let community = Hand::new_from_str("3d2h4s").unwrap(); + assert_eq!(get_straight_outs(&hand, &community), 8); + } + + // Testing when we have 4 community cards already, but number of cards of same suits is less than 4 + #[test] + fn test_impossible_flush_outs_1() { + let hand = Hand::new_from_str("Adkh").unwrap(); + let community = Hand::new_from_str("Jd8c3s4s").unwrap(); + let deck: Deck = get_unknown_cards(&hand, &community); + assert_eq!(get_flush_outs(&deck, &hand, &community), -1); + } + + // Testing when we have the flops (3 community cards), but number of cards of same suits is less than 3 + #[test] + fn test_impossible_flush_outs_2() { + let hand = Hand::new_from_str("Adkh").unwrap(); + let community = Hand::new_from_str("Jd8c3s").unwrap(); + let deck: Deck = get_unknown_cards(&hand, &community); + assert_eq!(get_flush_outs(&deck, &hand, &community), -1); + } + + // In this case, we have 3 diamond suited cards out of 13 diamond suited cards, the correct number should be 10 + #[test] + fn test_correct_flush_outs_1() { + let hand = Hand::new_from_str("Adkh").unwrap(); + let community = Hand::new_from_str("Jd8c3d").unwrap(); + let deck: Deck = get_unknown_cards(&hand, &community); + assert_eq!(get_flush_outs(&deck, &hand, &community), 10); + } + + // In this case, we have 4 diamond suited cards out of 13 diamond suited cards, the correct number should be 9 + #[test] + fn test_correct_flush_outs_2() { + let hand = Hand::new_from_str("Adkh").unwrap(); + let community = Hand::new_from_str("Jd8d3d").unwrap(); + let deck: Deck = get_unknown_cards(&hand, &community); + assert_eq!(get_flush_outs(&deck, &hand, &community), 9); + } + + // In this case, we have two card with the same value in hand + #[test] + fn test_correct_flush_outs_3() { + let hand = Hand::new_from_str("AdAh").unwrap(); + let community = Hand::new_from_str("Jd8d3d").unwrap(); + let deck: Deck = get_unknown_cards(&hand, &community); + assert_eq!(get_flush_outs(&deck, &hand, &community), 9); + } + + // In this case, we have two card with the same value + #[test] + fn test_correct_flush_outs_4() { + let hand = Hand::new_from_str("Ad3d").unwrap(); + let community = Hand::new_from_str("Jd8dAh").unwrap(); + let deck: Deck = get_unknown_cards(&hand, &community); + assert_eq!(get_flush_outs(&deck, &hand, &community), 9); + } + + // In this case, we have a pair and a set already + #[test] + fn test_existing_full_house_1() { + let hand = Hand::new_from_str("Ad3d").unwrap(); + let community = Hand::new_from_str("3s3hAh").unwrap(); + assert_eq!(get_full_house_outs(&hand, &community), 0); + } + + #[test] + fn test_existing_full_house_2() { + let hand = Hand::new_from_str("AdAh").unwrap(); + let community = Hand::new_from_str("3s3h4h3c").unwrap(); + assert_eq!(get_full_house_outs(&hand, &community), 0); + } + + // When there are two pairs already + #[test] + fn test_full_house_outs_1() { + let hand = Hand::new_from_str("AdAh").unwrap(); + let community = Hand::new_from_str("4h3c3h").unwrap(); + assert_eq!(get_full_house_outs(&hand, &community), 4); + } + + // When there are two pairs already + #[test] + fn test_full_house_outs_2() { + let hand = Hand::new_from_str("Ad3h").unwrap(); + let community = Hand::new_from_str("4h3c5cAh").unwrap(); + assert_eq!(get_full_house_outs(&hand, &community), 4); + } + + // When there is one pair already + #[test] + fn test_full_house_outs_3() { + let hand = Hand::new_from_str("Ad3h").unwrap(); + let community = Hand::new_from_str("4h3c5c6h").unwrap(); + assert_eq!(get_full_house_outs(&hand, &community), 5); + } + + // When there is one pair already + #[test] + fn test_full_house_outs_4() { + let hand = Hand::new_from_str("4sAd").unwrap(); + let community = Hand::new_from_str("4h3c5c").unwrap(); + assert_eq!(get_full_house_outs(&hand, &community), 5); + } + + // When there is nothing + #[test] + fn test_full_house_outs_5() { + let hand = Hand::new_from_str("4sAd").unwrap(); + let community = Hand::new_from_str("6h3c5c").unwrap(); + assert_eq!(get_full_house_outs(&hand, &community), 6); + } +} diff --git a/src/main.rs b/src/main.rs index a37b2d8..fea3b12 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,233 +1,92 @@ +use clap::{App, Arg}; +use colored::*; +use rs_poker::core::{Deck, Hand}; use std::collections::HashMap; -use clap::{Arg, App, crate_authors}; -use rs_poker::core::{Card, Hand, Deck, Suit, Value, Rankable}; +mod calc_prob; -fn get_cli_args() { +fn get_cli_args() -> (String, String, bool) { let matches = App::new("gsheet_writer") .version("0.1") - .author(crate_authors!("")) + .author("eRaMvn") .about("CLI to write to google sheet given ranges and values") - .arg(Arg::new("current-hand") - .long("ch") - .value_name("STRING") - .about("Set current hand") - .required(true) - .takes_value(true)) - .arg(Arg::new("num-of-players") - .short('n') - .long("num-of-players") - .value_name("STRING") - .about("Set number of players") - .required(true) - .takes_value(true)) - .arg(Arg::new("flop") - .short('f') - .long("flop") - .value_name("STRING") - .about("Set current flop") - .required(true) - .takes_value(true)) + .arg( + Arg::new("my-hand") + .long("mh") + .value_name("STRING") + .help("Set my hand") + .required(true) + .takes_value(true), + ) + .arg( + Arg::new("community-cards") + .long("ch") + .value_name("STRING") + .help("Set community cards") + .required(true) + .takes_value(true), + ) + .arg( + Arg::new("all-in") + .short('a') + .help("Set whether this is all in or not") + .takes_value(false), + ) .get_matches(); - let current_hand = matches.value_of("current-hand").unwrap(); - println!("Value for current_hand: {}", current_hand); - let num_of_players = matches.value_of("num-of-players").unwrap(); - println!("Value for num_of_players: {}", num_of_players); - let flop = matches.value_of("flop").unwrap(); - println!("Value for flop: {}", flop); + ( + matches.value_of("my-hand").unwrap().to_string(), + matches.value_of("community-cards").unwrap().to_string(), + matches.is_present("all-in"), + ) } -fn get_unknown_cards(hand: &Hand, community: &Hand) -> Deck{ - // Initial deck with 52 cards - let mut deck = Deck::default(); - - // Remove cards in hand from deck - let mut temp_card: Card; - for card in hand.cards() { - temp_card = card.clone(); - deck.remove(temp_card); - } - - // Remove community cards from deck - for card in community.cards() { - temp_card = card.clone(); - deck.remove(temp_card); - } - - deck -} - - -fn get_high_card_outs() -> i8{0} -fn get_one_pair_outs()-> i8{0} -fn get_two_pairs_outs()-> i8{0} -fn get_three_of_a_kind_outs()-> i8{0} -fn get_straight_outs()-> i8{0} -fn get_flush_outs(deck: &Deck, hand: &Hand, community: &Hand) -> i8{ - let mut suits = HashMap::new(); - - // Going through the cards in the hand to count number of cards based on their suits - for card in hand.cards() { - suits.entry(&card.suit).or_insert(0); - suits.insert(&card.suit, suits[&card.suit] + 1); - } +fn main() { + let (my_cards_arg, community_cards_arg, all_in) = get_cli_args(); + let my_cards = + Hand::new_from_str(my_cards_arg.as_str()).expect("Should be able to create a hand."); + let community_cards = + Hand::new_from_str(community_cards_arg.as_str()).expect("Should be able to create a hand."); + let deck: Deck = calc_prob::get_unknown_cards(&my_cards, &community_cards); + + let ranks_to_check = HashMap::from([ + ("One Pair", calc_prob::HandRank::OnePair), + ("Two Pair", calc_prob::HandRank::TwoPair), + ("Three Of A Kind", calc_prob::HandRank::ThreeOfAKind), + ("Straight", calc_prob::HandRank::Straight), + ("Flush", calc_prob::HandRank::Flush), + ("Full House", calc_prob::HandRank::FullHouse), + ]); - // Going through the community card to count number of cards based on their suits - for card in community.cards() { - suits.entry(&card.suit).or_insert(0); - suits.insert(&card.suit, suits[&card.suit] + 1); - } - println!("{:?}", suits); - let mut num_of_highest_suit_outs: i8 = 0; let mut outs: i8; - - for (&suit, &count) in suits.iter() { - // When there is more than or equal to 4 community cards, but the count of suits is less then 4, skip the suit - if community.len() >= 4 && count < 4 { - continue; - } - - // When there is less than or equal to 3 community cards, but the count of suits is less then 3, skip the suit - if community.len() <= 3 && count < 3 { - continue; - } - - outs = 0; - // For the remaining suits, get the number of cards with that suit - for card in deck.iter(){ - if card.suit == *suit { - outs += 1; - } - } - if outs > num_of_highest_suit_outs{ - num_of_highest_suit_outs = outs + let mut string_to_print: String; + let mut four_and_two_prob: i8; + let mut hand_name_colored: ColoredString; + let mut prob_string_colored: ColoredString; + + for (name, hand_rank) in ranks_to_check { + outs = hand_rank.calc_outs(&deck, &my_cards, &community_cards); + four_and_two_prob = + calc_prob::calc_4_and_2_probs(all_in, community_cards.len() as i8, outs); + + if four_and_two_prob < 10 { + hand_name_colored = name.red(); + prob_string_colored = (four_and_two_prob.to_string() + "%").red(); + } else if four_and_two_prob > 10 { + hand_name_colored = name.green(); + prob_string_colored = (four_and_two_prob.to_string() + "%").green(); + } else { + hand_name_colored = name.normal(); + prob_string_colored = (four_and_two_prob.to_string() + "%").normal(); } + string_to_print = format!( + "{} has the probability of {}", + hand_name_colored, prob_string_colored + ); + println!("{}", string_to_print); } - println!("{:?}", num_of_highest_suit_outs); - num_of_highest_suit_outs -} -fn get_full_house_outs()-> i8{0} -fn get_four_of_a_kind_outs()-> i8{0} -fn get_straight_flush_outs()-> i8{0} - -enum HandRank { - /// The lowest rank. - /// No matches - HighCard, - /// One Card matches another. - OnePair, - /// Two different pair of matching cards. - TwoPair, - /// Three of the same value. - ThreeOfAKind, - /// Five cards in a sequence - Straight, - /// Five cards of the same suit - Flush, - /// Three of one value and two of another value - FullHouse, - /// Four of the same value. - FourOfAKind, - /// Five cards in a sequence all for the same suit. - StraightFlush, -} - -impl HandRank { - pub fn calc_4_and_2_probs(){} - pub fn calc_outs(self, deck: &Deck, hand: &Hand, community: &Hand) -> i8{ - match self { - Self::HighCard => get_high_card_outs(), - Self::OnePair => get_one_pair_outs(), - Self::TwoPair => get_two_pairs_outs(), - Self::ThreeOfAKind => get_three_of_a_kind_outs(), - Self::Straight => get_straight_outs(), - Self::Flush => get_flush_outs(deck, hand, community), - Self::FullHouse => get_full_house_outs(), - Self::FourOfAKind => get_four_of_a_kind_outs(), - Self::StraightFlush => get_straight_flush_outs() - } - } -} - -fn main() { - // get_cli_args() - // let hands: Vec = ["Adkh", "8c8s"] - // .iter() - // .map(|s| Hand::new_from_str(s).expect("Should be able to create a hand.")) - // .collect(); - - // println!("{:?}", hand.cards()); - // println!("{:?}", board); - - let hand = Hand::new_from_str("Adkh").expect("Should be able to create a hand."); - let community = Hand::new_from_str("Jd8c3d").expect("Should be able to create a hand."); - - let deck: Deck = get_unknown_cards(&hand, &community); - let flush_outs = HandRank::Flush; - flush_outs.calc_outs(&deck, &hand, &community); - - println!("{:?}", deck.len()); - // println!("{:?}", deck); + // Sample usage + // let flush_outs = calc_prob::HandRank::Flush; + // println!("{:?}", flush_outs.calc_outs(&deck, &hand, &community)); // let some_card: Card = Card { value: (Value::King), suit: (Suit::Heart) }; - // println!("{:?}", deck.contains(some_card)); - - // for each_card in deck.iter(){ - // println!("{:?}",each_card) - // } } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_correct_num_of_unknown_cards() { - let hand = Hand::new_from_str("Adkh").unwrap(); - let community = Hand::new_from_str("Jd8c3d").unwrap(); - assert_eq!(get_unknown_cards(&hand, &community).len(), 47); - } - - #[test] - fn test_incorrect_num_of_unknown_cards() { - let hand = Hand::new_from_str("Adkh").unwrap(); - let community = Hand::new_from_str("Jd8c3d4s").unwrap(); - assert_ne!(get_unknown_cards(&hand, &community).len(), 47); - } - - // Testing when we have 4 community cards already, but number of cards of same suits is less than 4 - #[test] - fn test_impossible_flush_outs_1() { - let hand = Hand::new_from_str("Adkh").unwrap(); - let community = Hand::new_from_str("Jd8c3s4s").unwrap(); - let deck: Deck = get_unknown_cards(&hand, &community); - assert_eq!(get_flush_outs(&deck, &hand, &community), 0); - } - - // Testing when we have the flops (3 community cards), but number of cards of same suits is less than 3 - #[test] - fn test_impossible_flush_outs_2() { - let hand = Hand::new_from_str("Adkh").unwrap(); - let community = Hand::new_from_str("Jd8c3s").unwrap(); - let deck: Deck = get_unknown_cards(&hand, &community); - assert_eq!(get_flush_outs(&deck, &hand, &community), 0); - } - - // In this case, we have 3 diamond suited cards out of 13 diamond suited cards, the correct number should be 10 - #[test] - fn test_correct_flush_outs_1() { - let hand = Hand::new_from_str("Adkh").unwrap(); - let community = Hand::new_from_str("Jd8c3d").unwrap(); - let deck: Deck = get_unknown_cards(&hand, &community); - assert_eq!(get_flush_outs(&deck, &hand, &community), 10); - } - - // In this case, we have 4 diamond suited cards out of 13 diamond suited cards, the correct number should be 10 - #[test] - fn test_correct_flush_outs_2() { - let hand = Hand::new_from_str("Adkh").unwrap(); - let community = Hand::new_from_str("Jd8d3d").unwrap(); - let deck: Deck = get_unknown_cards(&hand, &community); - assert_eq!(get_flush_outs(&deck, &hand, &community), 9); - } -} \ No newline at end of file