use crate::dice_result::RollResult;
use dice_command_parser::{
dice_roll::Operation as CommandOperation, dice_roll::RollType as CommandRollType,
dice_roll_with_op::DiceRollWithOp,
};
use rand::Rng;
use std::cmp::{max, min};
#[derive(PartialEq, Debug)]
pub struct Dice {
pub number_of_dice_to_roll: u32,
pub sides: u32,
pub modifier: Option<i32>,
pub roll_type: RollType,
pub operation: Operation,
}
#[derive(PartialEq, Debug)]
pub enum RollType {
Advantage,
Disadvantage,
Regular,
}
#[derive(PartialEq, Debug)]
pub enum Operation {
Addition,
Subtraction,
}
impl Dice {
pub(crate) fn from_parsed_dice_roll(parsed_roll: &DiceRollWithOp) -> Self {
let roll_type = match parsed_roll.dice_roll.roll_type {
CommandRollType::Regular => RollType::Regular,
CommandRollType::WithAdvantage => RollType::Advantage,
CommandRollType::WithDisadvantage => RollType::Disadvantage,
};
let operation = match parsed_roll.operation {
CommandOperation::Addition => Operation::Addition,
CommandOperation::Subtraction => Operation::Subtraction,
};
Dice {
number_of_dice_to_roll: parsed_roll.dice_roll.number_of_dice_to_roll,
sides: parsed_roll.dice_roll.dice_sides,
modifier: parsed_roll.dice_roll.modifier,
roll_type,
operation,
}
}
#[must_use]
pub fn new(
number_of_dice: u32,
number_of_sides: u32,
modifier: Option<i32>,
roll_type: RollType,
operation: Operation,
) -> Self {
Dice {
number_of_dice_to_roll: number_of_dice,
sides: number_of_sides,
modifier,
roll_type,
operation,
}
}
#[must_use]
pub fn roll_dice(&self) -> RollResult {
let mut rng = rand::thread_rng();
self.roll_dice_from_rng(&mut rng)
}
#[allow(clippy::cast_possible_wrap)]
pub fn roll_dice_from_rng<R: Rng + Sized>(&self, mut rng: R) -> RollResult {
let current_roll_set_size = self.number_of_dice_to_roll as usize;
let mut first_roll_results: Vec<u32> = Vec::with_capacity(current_roll_set_size);
for _ in 0..self.number_of_dice_to_roll {
first_roll_results.push(rng.gen_range(1..=self.sides));
}
let second_roll_results: Option<Vec<u32>> = match self.roll_type {
RollType::Advantage | RollType::Disadvantage => {
let mut second_roll_results: Vec<u32> = Vec::with_capacity(current_roll_set_size);
for _ in 0..self.number_of_dice_to_roll {
second_roll_results.push(rng.gen_range(1..=self.sides));
}
Some(second_roll_results)
}
RollType::Regular => None,
};
let result = match self.roll_type {
RollType::Regular => {
first_roll_results.iter().sum::<u32>() as i32 + self.modifier.unwrap_or(0)
}
RollType::Advantage => {
let modifier = self.modifier.unwrap_or(0);
let first_result = first_roll_results.iter().sum::<u32>() as i32;
let second_result = second_roll_results
.as_ref()
.expect("Expect advantage roll to have second roll results")
.iter()
.sum::<u32>() as i32;
max(first_result + modifier, second_result + modifier)
}
RollType::Disadvantage => {
let modifier = self.modifier.unwrap_or(0);
let first_result = first_roll_results.iter().sum::<u32>() as i32;
let second_result = second_roll_results
.as_ref()
.expect("Expect disadvantage roll to have second roll results")
.iter()
.sum::<u32>() as i32;
min(first_result + modifier, second_result + modifier)
}
};
RollResult::new(first_roll_results, second_roll_results, result)
}
}
#[cfg(test)]
mod tests {
use super::*;
use rand::SeedableRng;
const SEED: u64 = 42;
#[test]
fn produces_predictable_results_one_d6_equals_two() {
let rng = rand_pcg::Pcg64Mcg::seed_from_u64(SEED);
let dice = Dice::new(1, 6, None, RollType::Regular, Operation::Addition);
let result = dice.roll_dice_from_rng(rng);
let expected = 2;
assert_eq!(result.result, expected);
assert_eq!(result.first_roll, vec![2]);
}
#[test]
fn modifier_added_to_predictable_result_one_d6_equals_two_plus_modifier() {
let rng = rand_pcg::Pcg64Mcg::seed_from_u64(SEED);
let modifier = Some(4);
let dice = Dice::new(1, 6, modifier, RollType::Regular, Operation::Addition);
let result = dice.roll_dice_from_rng(rng);
let expected = 2 + modifier.unwrap();
assert_eq!(result.result, expected);
assert_eq!(result.first_roll, vec![2]);
}
#[test]
fn modifier_added_to_predictable_result_one_d6_with_advantage_equals_six() {
let rng = rand_pcg::Pcg64Mcg::seed_from_u64(SEED);
let dice = Dice::new(1, 6, None, RollType::Advantage, Operation::Addition);
let result = dice.roll_dice_from_rng(rng);
let expected = 6;
assert_eq!(result.result, expected);
assert_eq!(result.first_roll, vec![2]);
assert_eq!(result.second_roll, Some(vec![6]))
}
#[test]
fn modifier_added_to_predictable_result_one_d6_with_disadvantage_equals_two() {
let rng = rand_pcg::Pcg64Mcg::seed_from_u64(SEED);
let dice = Dice::new(1, 6, None, RollType::Disadvantage, Operation::Addition);
let result = dice.roll_dice_from_rng(rng);
let expected = 2;
assert_eq!(result.result, expected);
assert_eq!(result.first_roll, vec![2]);
assert_eq!(result.second_roll, Some(vec![6]))
}
#[test]
fn produces_predictable_results_three_d6_plus_two() {
let rng = rand_pcg::Pcg64Mcg::seed_from_u64(SEED);
let dice = Dice::new(3, 6, Some(2), RollType::Regular, Operation::Addition);
let result = dice.roll_dice_from_rng(rng);
let expected = 15;
assert_eq!(result.result, expected);
assert_eq!(result.first_roll, vec![2, 6, 5]);
}
#[test]
fn roll_dice_within_range_simple() {
let dice = Dice::new(1, 20, None, RollType::Regular, Operation::Addition);
let expected_min = 1;
let expected_max = 20;
for _ in 0..100_000 {
let result = dice.roll_dice();
let result = result.result;
if result > expected_max || result < expected_min {
panic!("Value outside expected range");
}
}
}
#[test]
fn roll_dice_within_range_check_occurences() {
let dice = Dice::new(1, 20, None, RollType::Regular, Operation::Addition);
let expected_min = 1;
let expected_max = 20;
let number_of_rolls = 100_000;
let mut results: Vec<i32> = Vec::with_capacity(100_000);
for _ in 0..number_of_rolls {
let roll_result = dice.roll_dice();
if roll_result.result > expected_max || roll_result.result < expected_min {
panic!("Value outside expected range");
}
results.push(roll_result.result);
}
let mut results = results.iter();
for searching_for in expected_min..=expected_max {
if !results.any(|&item| item == searching_for) {
panic!("Could not find value expected value in all iterations of results");
}
}
}
}