refactored dice output
This commit is contained in:
parent
7b6eecf40e
commit
8e2f75d624
2 changed files with 259 additions and 78 deletions
313
src/dice.rs
313
src/dice.rs
|
|
@ -1,6 +1,9 @@
|
|||
use anyhow::bail;
|
||||
use rand::{Rng, RngExt};
|
||||
use std::io::Write;
|
||||
use std::convert::Infallible;
|
||||
use std::error::Error;
|
||||
use std::fmt::{Write, format};
|
||||
use thiserror::Error;
|
||||
|
||||
pub type ExpBox = Box<Expression>;
|
||||
|
||||
|
|
@ -35,13 +38,13 @@ pub enum CompareFragment {
|
|||
Le(ExpBox),
|
||||
}
|
||||
|
||||
const DICE_POOL_LIMIT: usize = 100_000;
|
||||
const DICE_POOL_LIMIT: usize = 10_000;
|
||||
|
||||
impl Expression {
|
||||
pub fn collect_evaluation(&self, mut rng: impl Rng) -> anyhow::Result<String> {
|
||||
let mut buffer = Vec::new();
|
||||
let total = self.evaluate(&mut rng, &mut buffer, 100)?;
|
||||
let result_text = String::from_utf8(buffer)?;
|
||||
let mut witness = DiscordMdWitness::default();
|
||||
let total = self.evaluate(&mut rng, &mut witness, 100)?;
|
||||
let result_text = witness.buffer;
|
||||
Ok(format!("**{total}** = {result_text}"))
|
||||
}
|
||||
|
||||
|
|
@ -56,21 +59,25 @@ impl Expression {
|
|||
Ok(average)
|
||||
}
|
||||
|
||||
fn evaluate(
|
||||
fn evaluate<W, E>(
|
||||
&self,
|
||||
rng: &mut impl Rng,
|
||||
writer: &mut impl Write,
|
||||
w: &mut W,
|
||||
outer_precedence: i64,
|
||||
) -> anyhow::Result<f64> {
|
||||
) -> anyhow::Result<f64>
|
||||
where
|
||||
W: Witness<Error = E>,
|
||||
E: Error + Send + Sync + 'static,
|
||||
{
|
||||
use Expression::*;
|
||||
let precedence = self.precedence();
|
||||
let needs_parens = precedence > outer_precedence;
|
||||
if needs_parens {
|
||||
write!(writer, "(")?;
|
||||
write!(w, "(")?;
|
||||
}
|
||||
let result = match self {
|
||||
Const(x) => {
|
||||
write!(writer, "{}", x)?;
|
||||
write!(w, "{}", x)?;
|
||||
*x
|
||||
}
|
||||
Dice(DiceFormula {
|
||||
|
|
@ -83,15 +90,15 @@ impl Expression {
|
|||
x,
|
||||
}) => {
|
||||
let count = if let Some(count_node) = count_node {
|
||||
count_node.evaluate(rng, writer, precedence)? as usize
|
||||
count_node.evaluate(rng, w, precedence)? as usize
|
||||
} else {
|
||||
1
|
||||
};
|
||||
if count > DICE_POOL_LIMIT {
|
||||
bail!("Too many dice.")
|
||||
};
|
||||
write!(writer, "d")?;
|
||||
let size = size_node.evaluate(rng, writer, precedence)? as i64;
|
||||
write!(w, "d")?;
|
||||
let size = size_node.evaluate(rng, w, precedence)? as i64;
|
||||
if size < 1 {
|
||||
bail!("Invalid die size.")
|
||||
}
|
||||
|
|
@ -102,13 +109,13 @@ impl Expression {
|
|||
|
||||
// Exploding dice are early, they add rolls
|
||||
if let Some(fragments) = x {
|
||||
write!(writer, "x")?;
|
||||
write!(w, "x")?;
|
||||
if size < 2 {
|
||||
bail!("Infinite explosion.")
|
||||
}
|
||||
let mut comparers = Vec::with_capacity(fragments.len());
|
||||
for fragment in fragments {
|
||||
comparers.push(fragment.comparer(rng, writer)?);
|
||||
comparers.push(fragment.comparer(rng, w)?);
|
||||
}
|
||||
for i in 0..DICE_POOL_LIMIT {
|
||||
if i >= rolls.len() {
|
||||
|
|
@ -133,26 +140,26 @@ impl Expression {
|
|||
|
||||
// Advantage/disadvantage roll filters
|
||||
let kh = if let Some(child) = kh {
|
||||
write!(writer, "kh")?;
|
||||
Some(child.evaluate(rng, writer, precedence)? as usize)
|
||||
write!(w, "kh")?;
|
||||
Some(child.evaluate(rng, w, precedence)? as usize)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let kl = if let Some(child) = kl {
|
||||
write!(writer, "kl")?;
|
||||
Some(child.evaluate(rng, writer, precedence)? as usize)
|
||||
write!(w, "kl")?;
|
||||
Some(child.evaluate(rng, w, precedence)? as usize)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let dh = if let Some(child) = dh {
|
||||
write!(writer, "dh")?;
|
||||
Some(child.evaluate(rng, writer, precedence)? as usize)
|
||||
write!(w, "dh")?;
|
||||
Some(child.evaluate(rng, w, precedence)? as usize)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let dl = if let Some(child) = dl {
|
||||
write!(writer, "dl")?;
|
||||
Some(child.evaluate(rng, writer, precedence)? as usize)
|
||||
write!(w, "dl")?;
|
||||
Some(child.evaluate(rng, w, precedence)? as usize)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
|
@ -168,11 +175,9 @@ impl Expression {
|
|||
}
|
||||
let mut result_set = Vec::with_capacity(rolls.len());
|
||||
|
||||
let print_all_rolls = rolls.len() <= 100;
|
||||
write!(w, " ")?;
|
||||
let mut w_set = w.witness_set()?;
|
||||
|
||||
if print_all_rolls {
|
||||
write!(writer, " ‹ ")?;
|
||||
}
|
||||
for idx in 0..rolls.len() {
|
||||
let mut skip = false;
|
||||
let roll = rolls[idx];
|
||||
|
|
@ -200,58 +205,50 @@ impl Expression {
|
|||
if !skip {
|
||||
result_set.push(roll as f64);
|
||||
}
|
||||
if print_all_rolls {
|
||||
if skip {
|
||||
write!(writer, "~~`{}`~~", roll)?;
|
||||
} else if size > 2 && (roll == 1 || roll == size) {
|
||||
write!(writer, "**`{}`**", roll)?;
|
||||
} else {
|
||||
write!(writer, "`{}`", roll)?;
|
||||
}
|
||||
|
||||
if idx != rolls.len() - 1 {
|
||||
write!(writer, " ")?;
|
||||
}
|
||||
}
|
||||
}
|
||||
if print_all_rolls {
|
||||
write!(writer, " ›")?;
|
||||
w_set.witness_roll(DiceRoll {
|
||||
size: size as usize,
|
||||
roll: roll as usize,
|
||||
is_admitted: !skip,
|
||||
did_proliferate: false,
|
||||
is_from_proliferation: idx >= count,
|
||||
})?;
|
||||
}
|
||||
w_set.end()?;
|
||||
|
||||
result_set.iter().sum()
|
||||
}
|
||||
Neg(inner_node) => {
|
||||
write!(writer, "-")?;
|
||||
let inner = inner_node.evaluate(rng, writer, precedence)?;
|
||||
write!(w, "-")?;
|
||||
let inner = inner_node.evaluate(rng, w, precedence)?;
|
||||
-inner
|
||||
}
|
||||
Add(lhs_node, rhs_node) => {
|
||||
let lhs = lhs_node.evaluate(rng, writer, precedence)?;
|
||||
write!(writer, " + ")?;
|
||||
let rhs = rhs_node.evaluate(rng, writer, precedence)?;
|
||||
let lhs = lhs_node.evaluate(rng, w, precedence)?;
|
||||
write!(w, " + ")?;
|
||||
let rhs = rhs_node.evaluate(rng, w, precedence)?;
|
||||
lhs + rhs
|
||||
}
|
||||
Sub(lhs_node, rhs_node) => {
|
||||
let lhs = lhs_node.evaluate(rng, writer, precedence)?;
|
||||
write!(writer, " - ")?;
|
||||
let rhs = rhs_node.evaluate(rng, writer, precedence)?;
|
||||
let lhs = lhs_node.evaluate(rng, w, precedence)?;
|
||||
write!(w, " - ")?;
|
||||
let rhs = rhs_node.evaluate(rng, w, precedence)?;
|
||||
lhs - rhs
|
||||
}
|
||||
Mul(lhs_node, rhs_node) => {
|
||||
let lhs = lhs_node.evaluate(rng, writer, precedence)?;
|
||||
write!(writer, " × ")?;
|
||||
let rhs = rhs_node.evaluate(rng, writer, precedence)?;
|
||||
let lhs = lhs_node.evaluate(rng, w, precedence)?;
|
||||
write!(w, " × ")?;
|
||||
let rhs = rhs_node.evaluate(rng, w, precedence)?;
|
||||
lhs * rhs
|
||||
}
|
||||
Div(lhs_node, rhs_node) => {
|
||||
let lhs = lhs_node.evaluate(rng, writer, precedence)?;
|
||||
write!(writer, " ÷ ")?;
|
||||
let rhs = rhs_node.evaluate(rng, writer, precedence)?;
|
||||
let lhs = lhs_node.evaluate(rng, w, precedence)?;
|
||||
write!(w, " ÷ ")?;
|
||||
let rhs = rhs_node.evaluate(rng, w, precedence)?;
|
||||
lhs / rhs
|
||||
}
|
||||
};
|
||||
if needs_parens {
|
||||
write!(writer, ")")?;
|
||||
write!(w, ")")?;
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
|
|
@ -315,35 +312,39 @@ impl Expression {
|
|||
}
|
||||
|
||||
impl CompareFragment {
|
||||
fn comparer(
|
||||
fn comparer<W, E>(
|
||||
&self,
|
||||
rng: &mut impl Rng,
|
||||
writer: &mut impl Write,
|
||||
) -> anyhow::Result<Box<dyn Fn(f64) -> bool>> {
|
||||
witness: &mut W,
|
||||
) -> anyhow::Result<Box<dyn Fn(f64) -> bool>>
|
||||
where
|
||||
W: Witness<Error = E>,
|
||||
E: Error + Send + Sync + 'static,
|
||||
{
|
||||
Ok(match self {
|
||||
CompareFragment::Eq(inner) => {
|
||||
write!(writer, "=")?;
|
||||
let comp = inner.evaluate(rng, writer, 2)?;
|
||||
write!(witness, "=")?;
|
||||
let comp = inner.evaluate(rng, witness, 2)?;
|
||||
Box::new(move |value| value == comp)
|
||||
}
|
||||
CompareFragment::Gt(inner) => {
|
||||
write!(writer, ">")?;
|
||||
let comp = inner.evaluate(rng, writer, 2)?;
|
||||
write!(witness, ">")?;
|
||||
let comp = inner.evaluate(rng, witness, 2)?;
|
||||
Box::new(move |value| value > comp)
|
||||
}
|
||||
CompareFragment::Ge(inner) => {
|
||||
write!(writer, ">=")?;
|
||||
let comp = inner.evaluate(rng, writer, 2)?;
|
||||
write!(witness, ">=")?;
|
||||
let comp = inner.evaluate(rng, witness, 2)?;
|
||||
Box::new(move |value| value >= comp)
|
||||
}
|
||||
CompareFragment::Lt(inner) => {
|
||||
write!(writer, "<")?;
|
||||
let comp = inner.evaluate(rng, writer, 2)?;
|
||||
write!(witness, "<")?;
|
||||
let comp = inner.evaluate(rng, witness, 2)?;
|
||||
Box::new(move |value| value < comp)
|
||||
}
|
||||
CompareFragment::Le(inner) => {
|
||||
write!(writer, "<=")?;
|
||||
let comp = inner.evaluate(rng, writer, 2)?;
|
||||
write!(witness, "<=")?;
|
||||
let comp = inner.evaluate(rng, witness, 2)?;
|
||||
Box::new(move |value| value <= comp)
|
||||
}
|
||||
})
|
||||
|
|
@ -362,3 +363,173 @@ impl Expression {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
trait Witness {
|
||||
type Ok;
|
||||
type Error: Error;
|
||||
type WitnessSet<'a>: WitnessSet<Ok = Self::Ok, Error = Self::Error>
|
||||
where
|
||||
Self: 'a;
|
||||
|
||||
fn witness_source_text(&mut self, text: &str) -> Result<Self::Ok, Self::Error>;
|
||||
fn witness_set(&mut self) -> Result<Self::WitnessSet<'_>, Self::Error>;
|
||||
fn write_fmt(&mut self, args: std::fmt::Arguments<'_>) -> Result<Self::Ok, Self::Error>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.witness_source_text(&format(args))
|
||||
}
|
||||
}
|
||||
|
||||
trait WitnessSet {
|
||||
type Ok;
|
||||
type Error: Error;
|
||||
|
||||
fn witness_roll(&mut self, dice: DiceRoll) -> Result<Self::Ok, Self::Error>;
|
||||
fn end(self) -> Result<Self::Ok, Self::Error>;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct DiceRoll {
|
||||
size: usize,
|
||||
roll: usize,
|
||||
is_admitted: bool,
|
||||
did_proliferate: bool,
|
||||
is_from_proliferation: bool,
|
||||
}
|
||||
|
||||
impl Default for DiceRoll {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
size: 1,
|
||||
roll: 1,
|
||||
is_admitted: true,
|
||||
did_proliferate: false,
|
||||
is_from_proliferation: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct DiscordMdWitness {
|
||||
buffer: String,
|
||||
dice_written: usize,
|
||||
}
|
||||
struct DiscordMdWitnessSet<'a> {
|
||||
parent: &'a mut DiscordMdWitness,
|
||||
index: usize,
|
||||
dice_elided: bool,
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
enum DiscordMdWitnessError {
|
||||
#[error("formatting failed")]
|
||||
Format(#[from] std::fmt::Error),
|
||||
}
|
||||
|
||||
impl Default for DiscordMdWitness {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
buffer: String::new(),
|
||||
dice_written: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Witness for DiscordMdWitness {
|
||||
type Ok = ();
|
||||
type Error = DiscordMdWitnessError;
|
||||
type WitnessSet<'a> = DiscordMdWitnessSet<'a>;
|
||||
|
||||
fn witness_source_text(&mut self, text: &str) -> Result<Self::Ok, Self::Error> {
|
||||
self.buffer.push_str(text);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn witness_set(&mut self) -> Result<Self::WitnessSet<'_>, Self::Error> {
|
||||
write!(self.buffer, "‹ ")?;
|
||||
Ok(DiscordMdWitnessSet {
|
||||
parent: self,
|
||||
index: 0,
|
||||
dice_elided: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> WitnessSet for DiscordMdWitnessSet<'a> {
|
||||
type Ok = ();
|
||||
type Error = DiscordMdWitnessError;
|
||||
|
||||
fn witness_roll(&mut self, dice: DiceRoll) -> Result<Self::Ok, Self::Error> {
|
||||
if self.parent.dice_written >= 100 {
|
||||
if !self.dice_elided {
|
||||
write!(self.parent.buffer, "…")?;
|
||||
self.dice_elided = true;
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let is_extreme = dice.size >= 4 && (dice.roll == 1 || dice.roll == dice.size);
|
||||
if self.index > 0 {
|
||||
write!(self.parent.buffer, " ")?;
|
||||
}
|
||||
if !dice.is_admitted {
|
||||
write!(self.parent.buffer, "~~")?;
|
||||
}
|
||||
if dice.is_from_proliferation {
|
||||
write!(self.parent.buffer, "*")?;
|
||||
}
|
||||
if is_extreme || dice.did_proliferate {
|
||||
write!(self.parent.buffer, "**")?;
|
||||
}
|
||||
|
||||
write!(self.parent.buffer, "`{}`", dice.roll)?;
|
||||
|
||||
if is_extreme || dice.did_proliferate {
|
||||
write!(self.parent.buffer, "**")?;
|
||||
}
|
||||
if dice.is_from_proliferation {
|
||||
write!(self.parent.buffer, "*")?;
|
||||
}
|
||||
if !dice.is_admitted {
|
||||
write!(self.parent.buffer, "~~")?;
|
||||
}
|
||||
self.index += 1;
|
||||
self.parent.dice_written += 1;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn end(self) -> Result<Self::Ok, Self::Error> {
|
||||
write!(self.parent.buffer, " ›")?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Witness for std::io::Sink {
|
||||
type Ok = ();
|
||||
type Error = Infallible;
|
||||
type WitnessSet<'a>
|
||||
= &'a mut Self
|
||||
where
|
||||
Self: 'a;
|
||||
|
||||
fn witness_source_text(&mut self, _text: &str) -> Result<Self::Ok, Self::Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn witness_set(&mut self) -> Result<Self::WitnessSet<'_>, Self::Error> {
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl WitnessSet for &mut std::io::Sink {
|
||||
type Ok = ();
|
||||
type Error = Infallible;
|
||||
|
||||
fn witness_roll(&mut self, _dice: DiceRoll) -> Result<Self::Ok, Self::Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn end(self) -> Result<Self::Ok, Self::Error> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue