tenebrous-dicebot/dicebot/src/basic/roll.rs

343 lines
10 KiB
Rust
Raw Normal View History

/**
* In addition to the terms of the AGPL, this file is governed by the
* terms of the MIT license, from the original axfive-matrix-dicebot
* project.
*/
use crate::basic::dice;
2021-09-18 03:11:13 +00:00
use crate::basic::dice::KeepOrDrop;
2020-04-21 06:09:43 +00:00
use rand::prelude::*;
2020-04-20 19:44:18 +00:00
use std::fmt;
2020-04-21 06:09:43 +00:00
use std::ops::{Deref, DerefMut};
2020-04-20 17:19:50 +00:00
pub trait Roll {
type Output;
fn roll(&self) -> Self::Output;
}
2020-04-20 19:44:18 +00:00
pub trait Rolled {
fn rolled_value(&self) -> i32;
}
#[derive(Debug, PartialEq, Eq, Clone)]
2021-09-18 02:08:51 +00:00
/// array of rolls in order, how many dice to keep, and how many to drop
/// keep indicates how many of the highest dice to keep
/// drop indicates how many of the highest dice to drop
2021-09-17 02:55:11 +00:00
pub struct DiceRoll (pub Vec<u32>, usize, usize);
2020-04-20 19:44:18 +00:00
impl DiceRoll {
pub fn rolls(&self) -> &[u32] {
&self.0
}
pub fn keep(&self) -> usize {
self.1
}
2021-09-17 02:55:11 +00:00
pub fn drop(&self) -> usize {
self.2
}
// only count kept dice in total
2020-04-20 19:44:18 +00:00
pub fn total(&self) -> u32 {
2021-09-17 02:55:11 +00:00
self.0[self.2..self.1].iter().sum()
2020-04-20 19:44:18 +00:00
}
}
impl Rolled for DiceRoll {
fn rolled_value(&self) -> i32 {
self.total() as i32
}
}
impl fmt::Display for DiceRoll {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.rolled_value())?;
let rolls = self.rolls();
let keep = self.keep();
2021-09-17 02:55:11 +00:00
let drop = self.drop();
let mut iter = rolls.iter().enumerate();
2020-04-20 19:44:18 +00:00
if let Some(first) = iter.next() {
2021-09-17 02:55:11 +00:00
if drop != 0 {
write!(f, " ([{}]", first.1)?;
} else {
write!(f, " ({}", first.1)?;
}
2020-04-20 19:44:18 +00:00
for roll in iter {
2021-09-17 02:55:11 +00:00
if roll.0 >= keep || roll.0 < drop {
write!(f, " + [{}]", roll.1)?;
2021-09-17 02:55:11 +00:00
} else {
write!(f, " + {}", roll.1)?;
}
2020-04-20 19:44:18 +00:00
}
write!(f, ")")?;
}
Ok(())
}
}
2020-04-20 17:19:50 +00:00
impl Roll for dice::Dice {
2020-04-21 06:09:43 +00:00
type Output = DiceRoll;
2020-04-20 17:19:50 +00:00
2020-04-20 19:44:18 +00:00
fn roll(&self) -> DiceRoll {
2020-04-20 17:19:50 +00:00
let mut rng = rand::thread_rng();
let mut rolls: Vec<_> = (0..self.count)
.map(|_| rng.gen_range(1..=self.sides))
2020-04-21 06:09:43 +00:00
.collect();
// sort rolls in descending order
rolls.sort_by(|a, b| b.cmp(a));
2021-09-18 03:11:13 +00:00
match self.keep_drop {
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),
2021-09-18 03:11:13 +00:00
}
2020-04-20 19:44:18 +00:00
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum ElementRoll {
Dice(DiceRoll),
Bonus(u32),
}
impl Rolled for ElementRoll {
fn rolled_value(&self) -> i32 {
match self {
ElementRoll::Dice(d) => d.rolled_value(),
ElementRoll::Bonus(b) => *b as i32,
}
2020-04-20 17:19:50 +00:00
}
}
impl Roll for dice::Element {
2020-04-21 06:09:43 +00:00
type Output = ElementRoll;
2020-04-20 19:44:18 +00:00
fn roll(&self) -> ElementRoll {
match self {
dice::Element::Dice(d) => ElementRoll::Dice(d.roll()),
dice::Element::Bonus(b) => ElementRoll::Bonus(*b),
}
}
}
impl fmt::Display for ElementRoll {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ElementRoll::Dice(d) => write!(f, "{}", d),
ElementRoll::Bonus(b) => write!(f, "{}", b),
}
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum SignedElementRoll {
Positive(ElementRoll),
Negative(ElementRoll),
}
2020-04-20 17:19:50 +00:00
2020-04-20 19:44:18 +00:00
impl Rolled for SignedElementRoll {
fn rolled_value(&self) -> i32 {
2020-04-20 17:19:50 +00:00
match self {
2020-04-20 19:44:18 +00:00
SignedElementRoll::Positive(e) => e.rolled_value(),
SignedElementRoll::Negative(e) => -e.rolled_value(),
2020-04-20 17:19:50 +00:00
}
}
}
impl Roll for dice::SignedElement {
2020-04-21 06:09:43 +00:00
type Output = SignedElementRoll;
2020-04-20 17:19:50 +00:00
2020-04-20 19:44:18 +00:00
fn roll(&self) -> SignedElementRoll {
2020-04-20 17:19:50 +00:00
match self {
2020-04-20 19:44:18 +00:00
dice::SignedElement::Positive(e) => SignedElementRoll::Positive(e.roll()),
dice::SignedElement::Negative(e) => SignedElementRoll::Negative(e.roll()),
2020-04-20 17:19:50 +00:00
}
}
}
2020-04-20 19:44:18 +00:00
impl fmt::Display for SignedElementRoll {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
SignedElementRoll::Positive(e) => write!(f, "{}", e),
SignedElementRoll::Negative(e) => write!(f, "-{}", e),
}
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct ElementExpressionRoll(Vec<SignedElementRoll>);
impl Deref for ElementExpressionRoll {
type Target = Vec<SignedElementRoll>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for ElementExpressionRoll {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl Rolled for ElementExpressionRoll {
fn rolled_value(&self) -> i32 {
self.iter().map(Rolled::rolled_value).sum()
}
}
impl Roll for dice::ElementExpression {
2020-04-21 06:09:43 +00:00
type Output = ElementExpressionRoll;
2020-04-20 19:44:18 +00:00
fn roll(&self) -> ElementExpressionRoll {
ElementExpressionRoll(self.iter().map(Roll::roll).collect())
}
}
impl fmt::Display for ElementExpressionRoll {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.len() > 1 {
write!(f, "{}", self.rolled_value())?;
let mut iter = self.0.iter();
if let Some(first) = iter.next() {
write!(f, " ({}", first)?;
for roll in iter {
match roll {
SignedElementRoll::Positive(e) => write!(f, " + {}", e)?,
SignedElementRoll::Negative(e) => write!(f, " - {}", e)?,
}
}
write!(f, ")")?;
}
Ok(())
} else if self.len() > 0 {
// For a single item, just show the inner item to avoid redundancy
let first = self.0.iter().next().unwrap();
write!(f, "{}", first)
} else {
write!(f, "0")
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn dice_roll_display_test() {
2021-09-17 02:55:11 +00:00
assert_eq!(DiceRoll(vec![1, 3, 4], 3, 0).to_string(), "8 (1 + 3 + 4)");
assert_eq!(DiceRoll(vec![], 0, 0).to_string(), "0");
2020-04-21 06:09:43 +00:00
assert_eq!(
2021-09-17 02:55:11 +00:00
DiceRoll(vec![4, 7, 2, 10], 4, 0).to_string(),
2020-04-21 06:09:43 +00:00
"23 (4 + 7 + 2 + 10)"
);
assert_eq!(
2021-09-17 02:55:11 +00:00
DiceRoll(vec![20, 13, 11, 10], 3, 0).to_string(),
"44 (20 + 13 + 11 + [10])"
);
2021-09-17 02:55:11 +00:00
assert_eq!(
DiceRoll(vec![20, 13, 11, 10], 4, 1).to_string(),
"34 ([20] + 13 + 11 + 10)"
);
2020-04-20 19:44:18 +00:00
}
#[test]
fn element_roll_display_test() {
2020-04-21 06:09:43 +00:00
assert_eq!(
2021-09-17 02:55:11 +00:00
ElementRoll::Dice(DiceRoll(vec![1, 3, 4], 3, 0)).to_string(),
2020-04-21 06:09:43 +00:00
"8 (1 + 3 + 4)"
);
2020-04-20 19:44:18 +00:00
assert_eq!(ElementRoll::Bonus(7).to_string(), "7");
}
#[test]
fn signed_element_roll_display_test() {
2020-04-21 06:09:43 +00:00
assert_eq!(
2021-09-17 02:55:11 +00:00
SignedElementRoll::Positive(ElementRoll::Dice(DiceRoll(vec![1, 3, 4], 3, 0))).to_string(),
2020-04-21 06:09:43 +00:00
"8 (1 + 3 + 4)"
);
assert_eq!(
2021-09-17 02:55:11 +00:00
SignedElementRoll::Negative(ElementRoll::Dice(DiceRoll(vec![1, 3, 4], 3, 0))).to_string(),
2020-04-21 06:09:43 +00:00
"-8 (1 + 3 + 4)"
);
assert_eq!(
SignedElementRoll::Positive(ElementRoll::Bonus(7)).to_string(),
"7"
);
assert_eq!(
SignedElementRoll::Negative(ElementRoll::Bonus(7)).to_string(),
"-7"
);
2020-04-20 19:44:18 +00:00
}
#[test]
fn element_expression_roll_display_test() {
assert_eq!(
2020-04-21 06:09:43 +00:00
ElementExpressionRoll(vec![SignedElementRoll::Positive(ElementRoll::Dice(
2021-09-17 02:55:11 +00:00
DiceRoll(vec![1, 3, 4], 3, 0)
2020-04-21 06:09:43 +00:00
)),])
.to_string(),
"8 (1 + 3 + 4)"
);
2020-04-20 19:44:18 +00:00
assert_eq!(
2020-04-21 06:09:43 +00:00
ElementExpressionRoll(vec![SignedElementRoll::Negative(ElementRoll::Dice(
2021-09-17 02:55:11 +00:00
DiceRoll(vec![1, 3, 4], 3, 0)
2020-04-21 06:09:43 +00:00
)),])
.to_string(),
"-8 (1 + 3 + 4)"
);
2020-04-20 19:44:18 +00:00
assert_eq!(
2020-04-21 06:09:43 +00:00
ElementExpressionRoll(vec![SignedElementRoll::Positive(ElementRoll::Bonus(7)),])
.to_string(),
"7"
);
2020-04-20 19:44:18 +00:00
assert_eq!(
2020-04-21 06:09:43 +00:00
ElementExpressionRoll(vec![SignedElementRoll::Negative(ElementRoll::Bonus(7)),])
.to_string(),
"-7"
);
2020-04-20 19:44:18 +00:00
assert_eq!(
ElementExpressionRoll(vec![
2021-09-17 02:55:11 +00:00
SignedElementRoll::Positive(ElementRoll::Dice(DiceRoll(vec![1, 3, 4], 3, 0))),
SignedElementRoll::Negative(ElementRoll::Dice(DiceRoll(vec![1, 2], 2, 0))),
2020-04-20 19:44:18 +00:00
SignedElementRoll::Positive(ElementRoll::Bonus(4)),
SignedElementRoll::Negative(ElementRoll::Bonus(7)),
2020-04-21 06:09:43 +00:00
])
.to_string(),
"2 (8 (1 + 3 + 4) - 3 (1 + 2) + 4 - 7)"
);
2020-04-20 19:44:18 +00:00
assert_eq!(
ElementExpressionRoll(vec![
2021-09-17 02:55:11 +00:00
SignedElementRoll::Negative(ElementRoll::Dice(DiceRoll(vec![1, 3, 4], 3, 0))),
SignedElementRoll::Positive(ElementRoll::Dice(DiceRoll(vec![1, 2], 2, 0))),
2020-04-20 19:44:18 +00:00
SignedElementRoll::Negative(ElementRoll::Bonus(4)),
SignedElementRoll::Positive(ElementRoll::Bonus(7)),
2020-04-21 06:09:43 +00:00
])
.to_string(),
"-2 (-8 (1 + 3 + 4) + 3 (1 + 2) - 4 + 7)"
);
assert_eq!(
ElementExpressionRoll(vec![
2021-09-17 02:55:11 +00:00
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)"
);
2021-09-17 02:55:11 +00:00
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)"
);
2020-04-20 19:44:18 +00:00
}
}