Compare commits

..

No commits in common. "7e7e9e534eb641c382d8b79518f27fe2540a65bc" and "125f3d0cee9ad660e025e6b71de8a0bd1cadc764" have entirely different histories.

4 changed files with 38 additions and 186 deletions

View File

@ -118,16 +118,8 @@ expressions.
!r 3d12 - 5d2 + 3 - 7d3 + 20d20 !r 3d12 - 5d2 + 3 - 7d3 + 20d20
``` ```
#### Keep/Drop Dice This system does not yet have the capability to handle things like D&D
The bot supports either keeping the highest dice in a roll, or 5e advantage or disadvantage.
dropping the highest dice in a roll. This allows the bot to handle
things like D&D 5e advantage or disadvantage.
```
!roll 2d20k1
!r 2d20dh1 + 5
!r 10d10k5 + 10d10dh5 - 2
```
### Storytelling System ### Storytelling System

View File

@ -12,29 +12,17 @@ use std::ops::{Deref, DerefMut};
pub struct Dice { pub struct Dice {
pub(crate) count: u32, pub(crate) count: u32,
pub(crate) sides: u32, pub(crate) sides: u32,
pub(crate) keep_drop: KeepOrDrop,
} }
impl fmt::Display for Dice { impl fmt::Display for Dice {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.keep_drop { write!(f, "{}d{}", self.count, self.sides)
KeepOrDrop::Keep(keep) => write!(f, "{}d{}k{}", self.count, self.sides, keep),
KeepOrDrop::Drop(drop) => write!(f, "{}d{}dh{}", self.count, self.sides, drop),
KeepOrDrop::None => write!(f, "{}d{}", self.count, self.sides),
}
} }
} }
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum KeepOrDrop {
Keep (u32),
Drop (u32),
None,
}
impl Dice { impl Dice {
pub fn new(count: u32, sides: u32, keep_drop: KeepOrDrop) -> Dice { pub fn new(count: u32, sides: u32) -> Dice {
Dice { count, sides, keep_drop } Dice { count, sides }
} }
} }

View File

@ -31,53 +31,12 @@ enum Sign {
Minus, Minus,
} }
/// Parse a dice expression. Does not eat whitespace // Parse a dice expression. Does not eat whitespace
fn parse_dice(input: &str) -> IResult<&str, Dice> { fn parse_dice(input: &str) -> IResult<&str, Dice> {
// parse main dice expression
let (input, (count, _, sides)) = tuple((digit1, tag("d"), digit1))(input)?; let (input, (count, _, sides)) = tuple((digit1, tag("d"), digit1))(input)?;
// check for keep expression to keep highest dice (2d20k1)
let (keep, input) = match tuple::<&str, _, (_, _), _>((tag("k"), digit1))(input) {
// if ok, keep expression is present
Ok((rest, (_, keep_amount))) => (keep_amount, rest),
// otherwise absent and keep all dice
Err(_) => ("", input)
};
// check for drop expression to drop highest dice (2d20dh1)
let (drop, input) = match tuple::<&str, _, (_, _), _>((tag("dh"), digit1))(input) {
// if ok, keep expression is present
Ok((rest, (_, drop_amount))) => (drop_amount, rest),
// otherwise absent and keep all dice
Err(_) => ("", input)
};
let count: u32 = count.parse().unwrap();
// don't allow keep greater than number of dice, and don't allow keep zero
let keep_drop = match keep.parse::<u32>() {
// Ok, there's a keep value, check and create Keep
Ok(i) => match i {
_i if _i > count || _i == 0 => KeepOrDrop::None,
i => KeepOrDrop::Keep(i),
},
// Err, check if drop works
Err(_) => {
match drop.parse::<u32>() {
// Ok, there's a drop value, check and create Drop
Ok(i) => match i {
_i if i >= count => KeepOrDrop::None,
i => KeepOrDrop::Drop(i),
},
// Err, there's neither keep nor drop
Err(_) => KeepOrDrop::None,
}
},
};
Ok(( Ok((
input, input,
Dice::new(count, sides.parse().unwrap(), keep_drop), Dice::new(count.parse().unwrap(), sides.parse().unwrap()),
)) ))
} }
@ -149,29 +108,16 @@ mod tests {
use super::*; use super::*;
#[test] #[test]
fn dice_test() { fn dice_test() {
assert_eq!(parse_dice("2d4"), Ok(("", Dice::new(2, 4, KeepOrDrop::None)))); assert_eq!(parse_dice("2d4"), Ok(("", Dice::new(2, 4))));
assert_eq!(parse_dice("20d40"), Ok(("", Dice::new(20, 40, KeepOrDrop::None)))); assert_eq!(parse_dice("20d40"), Ok(("", Dice::new(20, 40))));
assert_eq!(parse_dice("8d7"), Ok(("", Dice::new(8, 7, KeepOrDrop::None)))); assert_eq!(parse_dice("8d7"), Ok(("", Dice::new(8, 7))));
assert_eq!(parse_dice("2d20k1"), Ok(("", Dice::new(2, 20, KeepOrDrop::Keep(1)))));
assert_eq!(parse_dice("100d10k90"), Ok(("", Dice::new(100, 10, KeepOrDrop::Keep(90)))));
assert_eq!(parse_dice("11d10k10"), Ok(("", Dice::new(11, 10, KeepOrDrop::Keep(10)))));
assert_eq!(parse_dice("12d10k11"), Ok(("", Dice::new(12, 10, KeepOrDrop::Keep(11)))));
assert_eq!(parse_dice("12d10k13"), Ok(("", Dice::new(12, 10, KeepOrDrop::None))));
assert_eq!(parse_dice("12d10k0"), Ok(("", Dice::new(12, 10, KeepOrDrop::None))));
assert_eq!(parse_dice("20d40dh5"), Ok(("", Dice::new(20, 40, KeepOrDrop::Drop(5)))));
assert_eq!(parse_dice("8d7dh9"), Ok(("", Dice::new(8, 7, KeepOrDrop::None))));
assert_eq!(parse_dice("8d7dh8"), Ok(("", Dice::new(8, 7, KeepOrDrop::None))));
} }
#[test] #[test]
fn element_test() { fn element_test() {
assert_eq!( assert_eq!(
parse_element(" \t\n\r\n 8d7 \n"), parse_element(" \t\n\r\n 8d7 \n"),
Ok((" \n", Element::Dice(Dice::new(8, 7, KeepOrDrop::None)))) Ok((" \n", Element::Dice(Dice::new(8, 7))))
);
assert_eq!(
parse_element(" \t\n\r\n 3d20k2 \n"),
Ok((" \n", Element::Dice(Dice::new(3, 20, KeepOrDrop::Keep(2)))))
); );
assert_eq!( assert_eq!(
parse_element(" \t\n\r\n 8 \n"), parse_element(" \t\n\r\n 8 \n"),
@ -193,21 +139,14 @@ mod tests {
parse_signed_element(" \t\n\r\n- 8d4 \n"), parse_signed_element(" \t\n\r\n- 8d4 \n"),
Ok(( Ok((
" \n", " \n",
SignedElement::Negative(Element::Dice(Dice::new(8, 4, KeepOrDrop::None))) SignedElement::Negative(Element::Dice(Dice::new(8, 4)))
))
);
assert_eq!(
parse_signed_element(" \t\n\r\n- 8d4k4 \n"),
Ok((
" \n",
SignedElement::Negative(Element::Dice(Dice::new(8, 4, KeepOrDrop::Keep(4))))
)) ))
); );
assert_eq!( assert_eq!(
parse_signed_element(" \t\n\r\n+ 8d4 \n"), parse_signed_element(" \t\n\r\n+ 8d4 \n"),
Ok(( Ok((
" \n", " \n",
SignedElement::Positive(Element::Dice(Dice::new(8, 4, KeepOrDrop::None))) SignedElement::Positive(Element::Dice(Dice::new(8, 4)))
)) ))
); );
} }
@ -219,39 +158,29 @@ mod tests {
Ok(( Ok((
"", "",
ElementExpression(vec![SignedElement::Positive(Element::Dice(Dice::new( ElementExpression(vec![SignedElement::Positive(Element::Dice(Dice::new(
8, 4, KeepOrDrop::None 8, 4
)))]) )))])
)) ))
); );
assert_eq!(
parse_element_expression("\t2d20k1 + 5"),
Ok((
"",
ElementExpression(vec![
SignedElement::Positive(Element::Dice(Dice::new(2, 20, KeepOrDrop::Keep(1)))),
SignedElement::Positive(Element::Bonus(5)),
])
))
);
assert_eq!( assert_eq!(
parse_element_expression(" - 8d4 \n "), parse_element_expression(" - 8d4 \n "),
Ok(( Ok((
" \n ", " \n ",
ElementExpression(vec![SignedElement::Negative(Element::Dice(Dice::new( ElementExpression(vec![SignedElement::Negative(Element::Dice(Dice::new(
8, 4, KeepOrDrop::None 8, 4
)))]) )))])
)) ))
); );
assert_eq!( assert_eq!(
parse_element_expression("\t3d4k2 + 7 - 5 - 6d12dh3 + 1d1 + 53 1d5 "), parse_element_expression("\t3d4 + 7 - 5 - 6d12 + 1d1 + 53 1d5 "),
Ok(( Ok((
" 1d5 ", " 1d5 ",
ElementExpression(vec![ ElementExpression(vec![
SignedElement::Positive(Element::Dice(Dice::new(3, 4, KeepOrDrop::Keep(2)))), SignedElement::Positive(Element::Dice(Dice::new(3, 4))),
SignedElement::Positive(Element::Bonus(7)), SignedElement::Positive(Element::Bonus(7)),
SignedElement::Negative(Element::Bonus(5)), SignedElement::Negative(Element::Bonus(5)),
SignedElement::Negative(Element::Dice(Dice::new(6, 12, KeepOrDrop::Drop(3)))), SignedElement::Negative(Element::Dice(Dice::new(6, 12))),
SignedElement::Positive(Element::Dice(Dice::new(1, 1, KeepOrDrop::None))), SignedElement::Positive(Element::Dice(Dice::new(1, 1))),
SignedElement::Positive(Element::Bonus(53)), SignedElement::Positive(Element::Bonus(53)),
]) ])
)) ))

View File

@ -4,7 +4,6 @@
* project. * project.
*/ */
use crate::basic::dice; use crate::basic::dice;
use crate::basic::dice::KeepOrDrop;
use rand::prelude::*; use rand::prelude::*;
use std::fmt; use std::fmt;
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
@ -20,27 +19,15 @@ pub trait Rolled {
} }
#[derive(Debug, PartialEq, Eq, Clone)] #[derive(Debug, PartialEq, Eq, Clone)]
/// array of rolls in order, how many dice to keep, and how many to drop pub struct DiceRoll(pub Vec<u32>);
/// keep indicates how many of the highest dice to keep
/// drop indicates how many of the highest dice to drop
pub struct DiceRoll (pub Vec<u32>, usize, usize);
impl DiceRoll { impl DiceRoll {
pub fn rolls(&self) -> &[u32] { pub fn rolls(&self) -> &[u32] {
&self.0 &self.0
} }
pub fn keep(&self) -> usize {
self.1
}
pub fn drop(&self) -> usize {
self.2
}
// only count kept dice in total
pub fn total(&self) -> u32 { pub fn total(&self) -> u32 {
self.0[self.2..self.1].iter().sum() self.0.iter().sum()
} }
} }
@ -54,21 +41,11 @@ impl fmt::Display for DiceRoll {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.rolled_value())?; write!(f, "{}", self.rolled_value())?;
let rolls = self.rolls(); let rolls = self.rolls();
let keep = self.keep(); let mut iter = rolls.iter();
let drop = self.drop();
let mut iter = rolls.iter().enumerate();
if let Some(first) = iter.next() { if let Some(first) = iter.next() {
if drop != 0 { write!(f, " ({}", first)?;
write!(f, " ([{}]", first.1)?;
} else {
write!(f, " ({}", first.1)?;
}
for roll in iter { for roll in iter {
if roll.0 >= keep || roll.0 < drop { write!(f, " + {}", roll)?;
write!(f, " + [{}]", roll.1)?;
} else {
write!(f, " + {}", roll.1)?;
}
} }
write!(f, ")")?; write!(f, ")")?;
} }
@ -81,17 +58,11 @@ impl Roll for dice::Dice {
fn roll(&self) -> DiceRoll { fn roll(&self) -> DiceRoll {
let mut rng = rand::thread_rng(); let mut rng = rand::thread_rng();
let mut rolls: Vec<_> = (0..self.count) let rolls: Vec<_> = (0..self.count)
.map(|_| rng.gen_range(1..=self.sides)) .map(|_| rng.gen_range(1..=self.sides))
.collect(); .collect();
// sort rolls in descending order
rolls.sort_by(|a, b| b.cmp(a));
match self.keep_drop { DiceRoll(rolls)
KeepOrDrop::Keep(k) => DiceRoll(rolls,k as usize, 0),
KeepOrDrop::Drop(dh) => DiceRoll(rolls,self.count as usize, dh as usize),
KeepOrDrop::None => DiceRoll(rolls,self.count as usize, 0),
}
} }
} }
@ -227,26 +198,18 @@ mod tests {
use super::*; use super::*;
#[test] #[test]
fn dice_roll_display_test() { fn dice_roll_display_test() {
assert_eq!(DiceRoll(vec![1, 3, 4], 3, 0).to_string(), "8 (1 + 3 + 4)"); assert_eq!(DiceRoll(vec![1, 3, 4]).to_string(), "8 (1 + 3 + 4)");
assert_eq!(DiceRoll(vec![], 0, 0).to_string(), "0"); assert_eq!(DiceRoll(vec![]).to_string(), "0");
assert_eq!( assert_eq!(
DiceRoll(vec![4, 7, 2, 10], 4, 0).to_string(), DiceRoll(vec![4, 7, 2, 10]).to_string(),
"23 (4 + 7 + 2 + 10)" "23 (4 + 7 + 2 + 10)"
); );
assert_eq!(
DiceRoll(vec![20, 13, 11, 10], 3, 0).to_string(),
"44 (20 + 13 + 11 + [10])"
);
assert_eq!(
DiceRoll(vec![20, 13, 11, 10], 4, 1).to_string(),
"34 ([20] + 13 + 11 + 10)"
);
} }
#[test] #[test]
fn element_roll_display_test() { fn element_roll_display_test() {
assert_eq!( assert_eq!(
ElementRoll::Dice(DiceRoll(vec![1, 3, 4], 3, 0)).to_string(), ElementRoll::Dice(DiceRoll(vec![1, 3, 4])).to_string(),
"8 (1 + 3 + 4)" "8 (1 + 3 + 4)"
); );
assert_eq!(ElementRoll::Bonus(7).to_string(), "7"); assert_eq!(ElementRoll::Bonus(7).to_string(), "7");
@ -255,11 +218,11 @@ mod tests {
#[test] #[test]
fn signed_element_roll_display_test() { fn signed_element_roll_display_test() {
assert_eq!( assert_eq!(
SignedElementRoll::Positive(ElementRoll::Dice(DiceRoll(vec![1, 3, 4], 3, 0))).to_string(), SignedElementRoll::Positive(ElementRoll::Dice(DiceRoll(vec![1, 3, 4]))).to_string(),
"8 (1 + 3 + 4)" "8 (1 + 3 + 4)"
); );
assert_eq!( assert_eq!(
SignedElementRoll::Negative(ElementRoll::Dice(DiceRoll(vec![1, 3, 4], 3, 0))).to_string(), SignedElementRoll::Negative(ElementRoll::Dice(DiceRoll(vec![1, 3, 4]))).to_string(),
"-8 (1 + 3 + 4)" "-8 (1 + 3 + 4)"
); );
assert_eq!( assert_eq!(
@ -276,14 +239,14 @@ mod tests {
fn element_expression_roll_display_test() { fn element_expression_roll_display_test() {
assert_eq!( assert_eq!(
ElementExpressionRoll(vec![SignedElementRoll::Positive(ElementRoll::Dice( ElementExpressionRoll(vec![SignedElementRoll::Positive(ElementRoll::Dice(
DiceRoll(vec![1, 3, 4], 3, 0) DiceRoll(vec![1, 3, 4])
)),]) )),])
.to_string(), .to_string(),
"8 (1 + 3 + 4)" "8 (1 + 3 + 4)"
); );
assert_eq!( assert_eq!(
ElementExpressionRoll(vec![SignedElementRoll::Negative(ElementRoll::Dice( ElementExpressionRoll(vec![SignedElementRoll::Negative(ElementRoll::Dice(
DiceRoll(vec![1, 3, 4], 3, 0) DiceRoll(vec![1, 3, 4])
)),]) )),])
.to_string(), .to_string(),
"-8 (1 + 3 + 4)" "-8 (1 + 3 + 4)"
@ -300,8 +263,8 @@ mod tests {
); );
assert_eq!( assert_eq!(
ElementExpressionRoll(vec![ ElementExpressionRoll(vec![
SignedElementRoll::Positive(ElementRoll::Dice(DiceRoll(vec![1, 3, 4], 3, 0))), SignedElementRoll::Positive(ElementRoll::Dice(DiceRoll(vec![1, 3, 4]))),
SignedElementRoll::Negative(ElementRoll::Dice(DiceRoll(vec![1, 2], 2, 0))), SignedElementRoll::Negative(ElementRoll::Dice(DiceRoll(vec![1, 2]))),
SignedElementRoll::Positive(ElementRoll::Bonus(4)), SignedElementRoll::Positive(ElementRoll::Bonus(4)),
SignedElementRoll::Negative(ElementRoll::Bonus(7)), SignedElementRoll::Negative(ElementRoll::Bonus(7)),
]) ])
@ -310,33 +273,13 @@ mod tests {
); );
assert_eq!( assert_eq!(
ElementExpressionRoll(vec![ ElementExpressionRoll(vec![
SignedElementRoll::Negative(ElementRoll::Dice(DiceRoll(vec![1, 3, 4], 3, 0))), SignedElementRoll::Negative(ElementRoll::Dice(DiceRoll(vec![1, 3, 4]))),
SignedElementRoll::Positive(ElementRoll::Dice(DiceRoll(vec![1, 2], 2, 0))), SignedElementRoll::Positive(ElementRoll::Dice(DiceRoll(vec![1, 2]))),
SignedElementRoll::Negative(ElementRoll::Bonus(4)), SignedElementRoll::Negative(ElementRoll::Bonus(4)),
SignedElementRoll::Positive(ElementRoll::Bonus(7)), SignedElementRoll::Positive(ElementRoll::Bonus(7)),
]) ])
.to_string(), .to_string(),
"-2 (-8 (1 + 3 + 4) + 3 (1 + 2) - 4 + 7)" "-2 (-8 (1 + 3 + 4) + 3 (1 + 2) - 4 + 7)"
); );
assert_eq!(
ElementExpressionRoll(vec![
SignedElementRoll::Negative(ElementRoll::Dice(DiceRoll(vec![4, 3, 1], 3, 0))),
SignedElementRoll::Positive(ElementRoll::Dice(DiceRoll(vec![12, 2], 1, 0))),
SignedElementRoll::Negative(ElementRoll::Bonus(4)),
SignedElementRoll::Positive(ElementRoll::Bonus(7)),
])
.to_string(),
"7 (-8 (4 + 3 + 1) + 12 (12 + [2]) - 4 + 7)"
);
assert_eq!(
ElementExpressionRoll(vec![
SignedElementRoll::Negative(ElementRoll::Dice(DiceRoll(vec![4, 3, 1], 3, 1))),
SignedElementRoll::Positive(ElementRoll::Dice(DiceRoll(vec![12, 2], 2, 0))),
SignedElementRoll::Negative(ElementRoll::Bonus(4)),
SignedElementRoll::Positive(ElementRoll::Bonus(7)),
])
.to_string(),
"13 (-4 ([4] + 3 + 1) + 14 (12 + 2) - 4 + 7)"
);
} }
} }