MatthewPeterKelly / poker-stats

Small library for computing poker statistics using Monte-Carlo simulation. The primary focus for the project is learning Rust.
MIT License
2 stars 1 forks source link

Add command line argument support #6

Closed MatthewPeterKelly closed 1 year ago

MatthewPeterKelly commented 1 year ago

Overview

The main program here only has the option right now to run a simple demo. Instead, it would be great to use main as an entry point to really use the library. For example, we could have things like:

Notes

We should probably do the command line argument parsing with an external crate. Step one for implementing this feature would be to figure out which crate has the best implementation for this project.

Update:

Based on some initial(brief) research (https://rust-cli-recommendations.sunshowers.io/cli-parser.html and Google), it seems like Rust CLAP is probably the best choice in terms of features and standardization, with pico-args being an alternative that has fewer dependencies and build overhead.

spoloxs commented 1 year ago

You need something like this ?

// Note: Each of the utility files must be added here as a
// module so that it can be properly included in others.
mod aggregate_score;
mod card;
mod deck;
mod hand;
mod hand_score;
mod hand_stats;

use std::env;

use poker_stats::aggregate_score::sample_aggregate_scores;
use crate::card::Card;
use crate::hand::Hand;
use crate::hand_score::HandScore;
use crate::hand_stats::HandStats;

/// Simple demo for the `poker-stats` crate. For now it
/// does not support any arguments. It will do three things:
/// (1) print out the cards in a sorted deck
/// For each of two randomly drawn hands:
/// (2) print out the hand
/// (3) compute and print the card stats
fn main() {
    let args: Vec<String> = env::args().collect(); // Take args

    if args.len() == 1{

        let mut rng = rand::thread_rng();

        println!("Sorted Deck:");
        for id in 0..52 {
            let card = Card { id };
            if card.suit().id == 3 {
                println!("  {}", card);
            } else {
                print!("  {}  ", card);
            }
        }

        println!("");
        let five_card_hand = Hand::<5>::draw(&mut rng);
        println!("{five_card_hand}");
        let hand_stats = HandStats::from(&five_card_hand);
        println!("{hand_stats}");
        let hand_score = HandScore::from(&hand_stats);
        println!("{hand_score}");

        println!("");
        let seven_card_hand = Hand::<7>::draw(&mut rng);
        println!("{seven_card_hand}");
        let hand_stats = HandStats::from(&seven_card_hand);
        println!("{hand_stats}");
        let hand_score = HandScore::from(&hand_stats);
        println!("{hand_score}");

        // Now draw N random hands and check the stats!
        println!("");
        // Note: there is probably some way to deduce the type of the RNG here...
        let scores = sample_aggregate_scores::<5, rand::rngs::ThreadRng>(&mut rng, 20_000);
        println!("{scores}")
    }

    else if args.len() == 3 && args[2].parse::<u32>().is_ok(){

        let mut rng = rand::thread_rng();

        println!("Sorted Deck:");
        for id in 0..52 {
            let card = Card { id };
            if card.suit().id == 3 {
                println!("  {}", card);
            } else {
                print!("  {}  ", card);
            }
        }

        let num_samples:u32 = args[2].parse().unwrap();
        if args[1] == "five"{
            println!("");
            let five_card_hand = Hand::<5>::draw(&mut rng);
            println!("{five_card_hand}");
            let hand_stats = HandStats::from(&five_card_hand);
            println!("{hand_stats}");
            let hand_score = HandScore::from(&hand_stats);
            println!("{hand_score}");
            let scores = sample_aggregate_scores::<5, rand::rngs::ThreadRng>(&mut rng, num_samples);
            println!("{scores}")
        }

        else if args[1] == "seven"{
            println!("");
            let seven_card_hand = Hand::<7>::draw(&mut rng);
            println!("{seven_card_hand}");
            let hand_stats = HandStats::from(&seven_card_hand);
            println!("{hand_stats}");
            let hand_score = HandScore::from(&hand_stats);
            println!("{hand_score}");
            let scores = sample_aggregate_scores::<7, rand::rngs::ThreadRng>(&mut rng, num_samples);
            println!("{scores}")
        }
    }

    else if args.len() == 3 && !args[2].parse::<u32>().is_ok(){
        // Print error and Usage
        println!("Usage: Cargo run <hand_number> <sample_number>");
        println!("where hand_number can be five or seven and sample_number can be any integer");
    }

    else{
        // Print Usage
        println!("Usage: Cargo run <hand_number> <sample_number>");
    }
}
MatthewPeterKelly commented 1 year ago

You need something like this?

Not quite, although something like that might be a step toward a solution. In your proposal, you do manual argument parsing. I want to use an external crate to do the setup for us. There are a variety of reasons to do that, but the key ones are (1) standardization and (2) this is a common problem that other people have carefully studied and solved. There are also lots of weird edge cases that are hard to catch and handle correctly.

Take a look at https://rust-cli-recommendations.sunshowers.io/cli-parser.html for some context. Based on reading that article, it seems like Rust CLAP is probably the best choice in terms of features and standardization, with pico-args being an alternative that has fewer dependencies and build overhead.

spoloxs commented 1 year ago

Oh ok thanks I will check it and repost the new code as soon as I complete it

MatthewPeterKelly commented 1 year ago

Great!

Otherwise, the general idea of your posted code looked about right: breaking up the existing functionality into a few different command line argument sets.

Feel free to open a PR if you want -- that makes it a bit easier to see the code changes and leave line-by-line comments.

spoloxs commented 1 year ago

I added the clap and some commands. Please check the PR and let me know the additional things needed and how much the codes deviates.

It shows like this currently:

Usage: poker-stats [OPTIONS] [CARDS_NUMBER] [SAMPLES_NUMBER]

Arguments: [CARDS_NUMBER] Number of cards [default: default] [SAMPLES_NUMBER] Number of samples [default: 20000]

Options: -r, --run Run program -h, --help Print help -V, --version Print version

Cards_number can take two values five and seven and to run the program one should use the -r option.

MatthewPeterKelly commented 1 year ago

Thanks! I'll take a more detailed look at the code (and try out the new feature!) sometime in the next week.

spoloxs commented 1 year ago

Did few changes. Refactored the functions in main.rs to new file output.rs. And a few more things as required per the review. image

MatthewPeterKelly commented 1 year ago

This has been (mostly) resolved by https://github.com/MatthewPeterKelly/poker-stats/pull/9. The "interactive five card draw" part has is covered by: https://github.com/MatthewPeterKelly/poker-stats/issues/8.