diff --git a/Cargo.lock b/Cargo.lock index b7bf1ec..4e21608 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -886,6 +886,7 @@ version = "0.1.0" dependencies = [ "anyhow", "rand", + "rand_core", "regex", "serenity", "thiserror", diff --git a/Cargo.toml b/Cargo.toml index b0e85eb..8cfb0a6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,3 +12,4 @@ serenity = "0.8" rand = "0.7" anyhow = "1" thiserror = "1" +rand_core = "*" \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index f0b7a4e..ca2b715 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,13 +2,14 @@ use regex::Regex; use thiserror::Error; use anyhow::Result; use rand::Rng; +use rand::seq::SliceRandom; #[cfg(test)] mod lib_test; #[derive(Debug, PartialEq, Clone)] enum DiceFilter { - DropLowest(i32), DropHighest(i32), KeepLowest(i32), KeepHighest(i32), + DropLowest(usize), DropHighest(usize), KeepLowest(usize), KeepHighest(usize), } #[derive(Debug, PartialEq, Clone)] @@ -27,8 +28,6 @@ enum Segment { #[derive(Error, Debug, PartialEq)] enum SegmentError { - #[error("Invalid segment")] - InvalidSegment, #[error("No dice size was given")] SizeIsMissing, #[error("Invalid filter operator: {op}")] @@ -37,7 +36,7 @@ enum SegmentError { IncompleteFilter, } -fn construct_dice_filter(op: &str, amount: i32) -> Result { +fn construct_dice_filter(op: &str, amount: usize) -> Result { match op.to_lowercase().as_str() { "d" | "dl" => Ok(DiceFilter::DropLowest(amount)), "dh" => Ok(DiceFilter::DropHighest(amount)), @@ -47,7 +46,7 @@ fn construct_dice_filter(op: &str, amount: i32) -> Result { } } -fn construct_dice_segment(cap: regex::Captures) -> Result { +fn construct_dice_segment(cap: regex::Captures<'_>) -> Result { let op = cap.name("op").and_then(|i| i.as_str().chars().next()).unwrap_or('+'); let modifier = cap.name("mod").map(|i| i.as_str().parse()).transpose()?; @@ -57,10 +56,12 @@ fn construct_dice_segment(cap: regex::Captures) -> Result { let count:i32 = cap.name("count").map(|i| i.as_str().parse()).transpose()?.unwrap_or(1); let size:i32 = cap.name("size").map(|i| i.as_str().parse()).transpose()?.ok_or(SegmentError::SizeIsMissing)?; - let filter_amount = cap.name("filter").map(|i| i.as_str().parse::()).transpose()?; + let filter_amount = cap.name("filter").map(|i| i.as_str().parse::()).transpose()?; let filter_op = cap.name("filter_op").map(|op| op.as_str()); + /* dbg!(filter_amount); dbg!(filter_op); + */ if filter_amount.is_some() != filter_op.is_some() { Err(SegmentError::IncompleteFilter)?; } @@ -91,7 +92,7 @@ fn parse_dice_segments(cmd: &str) -> Result> { #[derive(Debug, PartialEq, Clone)] struct RollWithModifier { - diceroll: Segment, + diceroll: Option, modifiers: Vec } @@ -103,9 +104,9 @@ fn group_modifiers_to_dicerolls(segments: &[Segment]) -> Vec { for segment in segments { if let Segment::DiceRoll {..} = segment { - if let Option::Some(current) = current_diceroll { + if current_diceroll.is_some() || current_modifiers.len() > 0 { let with_modifier = RollWithModifier { - diceroll: current.clone(), + diceroll: current_diceroll.clone(), modifiers: current_modifiers.clone(), }; @@ -113,6 +114,7 @@ fn group_modifiers_to_dicerolls(segments: &[Segment]) -> Vec { results.push(with_modifier); } + current_diceroll = Some(segment.clone()); } else if let Segment::Modifier { .. } = segment { @@ -120,9 +122,9 @@ fn group_modifiers_to_dicerolls(segments: &[Segment]) -> Vec { } } - if let Option::Some(current) = current_diceroll { + if current_diceroll.is_some() || current_modifiers.len() > 0 { let with_modifier = RollWithModifier { - diceroll: current.clone(), + diceroll: current_diceroll.clone(), modifiers: current_modifiers.clone(), }; @@ -133,3 +135,81 @@ fn group_modifiers_to_dicerolls(segments: &[Segment]) -> Vec { results } + +#[derive(Debug, PartialEq, Clone)] +struct Roll { + operator: char, + results: Vec, + total: i32, +} + +#[derive(Error, Debug, PartialEq, Clone)] +enum RollError { + #[error("Roll failed. Empty RollWithModifier")] + EmptyRollWithModifier, + #[error("Roll failed. Invalid filter operator")] + InvalidFilterOperator, +} + +fn roll_dice_segments(rwms: &[RollWithModifier], mut rng: R) -> Result, RollError> { + rwms.iter() + .map(|rwm| { + let mut results = Vec::new(); + let mut total = 0; + + // store first operator + let operator = if let Some(Segment::DiceRoll{op, ..}) = rwm.diceroll { + op + } else if let Some(Segment::Modifier{op, ..}) = rwm.modifiers.iter().next() { + *op + } else { + return Err(RollError::EmptyRollWithModifier); + }; + + if let Some(Segment::DiceRoll{count, size, filter, ..}) = &rwm.diceroll { + results = (0..*count).map(|_| { + rng.gen_range(1, size + 1) + }).collect(); + + results.sort(); + if let Some(filter) = filter { + match filter { + DiceFilter::DropLowest(n) => { + results = results.into_iter().skip(*n).collect(); + }, + DiceFilter::DropHighest(n) => { + let len = results.len(); + results = results.into_iter().take(len - n).collect(); + }, + DiceFilter::KeepLowest(n) => { + results = results.into_iter().take(*n).collect(); + }, + DiceFilter::KeepHighest(n) => { + let len = results.len(); + results = results.into_iter().skip(len - n).collect(); + }, + } + } + results.shuffle(&mut rng); + + total = results.iter().sum(); + } + + for segment in &rwm.modifiers { + if let Segment::Modifier{op,amount} = segment { + match op { + '+' => { total += amount; }, + '-' => { total -= amount; }, + '/' => { total /= amount; }, + '*' => { total *= amount; }, + _ => { + return Err(RollError::InvalidFilterOperator); + }, + } + } + } + + Ok(Roll{operator, results, total}) + }) + .collect::,_>>() +} \ No newline at end of file diff --git a/src/lib_test.rs b/src/lib_test.rs index a4f150b..307a24e 100644 --- a/src/lib_test.rs +++ b/src/lib_test.rs @@ -1,3 +1,4 @@ +use rand::{rngs::StdRng, SeedableRng}; use super::*; #[test] @@ -37,20 +38,20 @@ fn test_parse_dice_segments() { #[test] fn test_group_modifiers_to_dicerolls() { - let mut segments = parse_dice_segments("2d20 / 2 + 2 + 4d6 * 2 * 2").expect("Failed to unpack results"); + let segments = parse_dice_segments("2d20 / 2 + 2 + 4d6 * 2 * 2").expect("Failed to unpack results"); - let mut results = group_modifiers_to_dicerolls(&segments); + let results = group_modifiers_to_dicerolls(&segments); assert_eq!(results, vec![ RollWithModifier{ - diceroll: Segment::DiceRoll{ op: '+', count: 2, size: 20, filter: None }, + diceroll: Some(Segment::DiceRoll{ op: '+', count: 2, size: 20, filter: None }), modifiers: vec![ Segment::Modifier{ op: '/', amount: 2 }, Segment::Modifier{ op: '+', amount: 2 }, ] }, RollWithModifier{ - diceroll: Segment::DiceRoll{ op: '+', count: 4, size: 6, filter: None }, + diceroll: Some(Segment::DiceRoll{ op: '+', count: 4, size: 6, filter: None }), modifiers: vec![ Segment::Modifier{ op: '*', amount: 2 }, Segment::Modifier{ op: '*', amount: 2 }, @@ -58,3 +59,55 @@ fn test_group_modifiers_to_dicerolls() { }, ]); } + +#[test] +fn test_group_modifiers_to_dicerolls_no_diceroll() { + let segments = parse_dice_segments("* 2 * 2").expect("Failed to unpack results"); + + let results = group_modifiers_to_dicerolls(&segments); + + assert_eq!(results, vec![ + RollWithModifier{ + diceroll: None, + modifiers: vec![ + Segment::Modifier{ op: '*', amount: 2 }, + Segment::Modifier{ op: '*', amount: 2 }, + ] + }, + ]); +} + +#[test] +fn test_roll_dice_segments() { + let mut rng = StdRng::seed_from_u64(2); + + let segments = parse_dice_segments("4d6").expect("Failed to unpack results"); + let rolls_with_modifiers = group_modifiers_to_dicerolls(&segments); + let results = roll_dice_segments(&rolls_with_modifiers, &mut rng); + + assert_eq!(results, Ok(vec![Roll{operator: '+', results: vec![4, 5, 5, 1], total: 15}])); + + let segments = parse_dice_segments("4d6d1 + 2").expect("Failed to unpack results"); + let rolls_with_modifiers = group_modifiers_to_dicerolls(&segments); + let results = roll_dice_segments(&rolls_with_modifiers, &mut rng); + + assert_eq!(results, Ok(vec![Roll{operator: '+', results: vec![1, 1, 5], total: 9}])); + + let segments = parse_dice_segments("+ 2 + 2d12kh1 / 2").expect("Failed to unpack results"); + let rolls_with_modifiers = group_modifiers_to_dicerolls(&segments); + let results = roll_dice_segments(&rolls_with_modifiers, &mut rng); + + assert_eq!(results, Ok(vec![ + Roll{operator: '+', results: vec![], total: 2}, + Roll{operator: '+', results: vec![12], total: 6}, + ])); + + let segments = parse_dice_segments("1d6 * 1d6 + 2 / 2").expect("Failed to unpack results"); + let rolls_with_modifiers = group_modifiers_to_dicerolls(&segments); + let results = roll_dice_segments(&rolls_with_modifiers, &mut rng); + + assert_eq!(results, Ok(vec![ + Roll{operator: '+', results: vec![1], total: 1}, + Roll{operator: '*', results: vec![6], total: 4}, + ])); +}