dndice 0.1.0

DnD Dice Rolling on the command line
use clap::Parser;

/// DnD Dice Roller on the command line!
#[derive(Parser, Debug)]
#[command(version, about, long_about = Some("DnD Dice Roller on the command line!"))]
pub struct Args {
    /// The type of sided die to use
    #[arg(short, long, value_parser = clap::value_parser!(u32).range(1..), default_value_t = 20)]
    pub die: u32,

    /// Number of dice to cast
    #[arg(short, long, value_parser = clap::value_parser!(u32).range(1..),default_value_t = 1)]
    pub number: u32,

    /// Modifier value to the output
    #[arg(short, long, default_value_t = 0)]
    pub modifier: u32,

    /// Raw value (e.g. 3d4 + 12, 2d3 etc.)
    #[arg(short, long)]
    pub raw: Option<String>,
}

fn calculate(die: u32, number: u32, modifier: u32) -> usize {
    let mut value = (0..number)
        .map(|_| rand::random_range(1..=die as usize))
        .sum::<usize>();

    value += modifier as usize;

    value
}

fn parse_raw(raw: &str) -> Result<(u32, u32, u32), ()> {
    let Some((number, rest)) = raw.split_once("d") else {
        eprintln!("Raw values expected to be in the format: '[number]d[die] + [modifier]'!");
        return Err(());
    };

    let Ok(number) = number.parse::<u32>() else {
        eprintln!("{number} is not a valid number!");
        return Err(());
    };

    let (die, modifier) = match rest.split_once("+") {
        Some((die, modifier)) => {
            let Ok(die) = die.trim().parse::<u32>() else {
                eprintln!("{die} is not a valid number!");
                return Err(());
            };
            let Ok(modifier) = modifier.trim().parse::<u32>() else {
                eprintln!("{modifier} is not a valid number!");
                return Err(());
            };

            (die, modifier)
        }
        None => {
            let Ok(die) = rest.parse::<u32>() else {
                eprintln!("{rest} is not a valid number!");
                return Err(());
            };

            (die, 0)
        }
    };

    Ok((number, die, modifier))
}

fn display(die: u32, number: u32, modifier: u32, value: usize) {
    if modifier > 0 {
        println!(
            "You rolled {}d{} + {} for a total of {value}!",
            number, die, modifier
        );
    } else {
        println!("You rolled {}d{} for a total of {value}!", number, die);
    }
}

fn main() {
    let args = Args::parse();
    match args.raw {
        Some(r) => {
            let Ok((number, die, modifier)) = parse_raw(&r) else {
                return;
            };

            let value = calculate(die, number, modifier);
            display(die, number, modifier, value);
        }
        None => {
            let value = calculate(args.die, args.number, args.modifier);
            display(args.die, args.number, args.modifier, value);
        }
    }
}