nicer looking output

This commit is contained in:
Lilith Schier 2026-08-11 11:31:37 +02:00
parent 0ebe9e3be3
commit b437cefe5c
2 changed files with 51 additions and 31 deletions

View file

@ -33,6 +33,8 @@ pub struct DiceFormula {
pub(crate) x: bool, pub(crate) x: bool,
} }
const DICE_POOL_LIMIT: usize = 100_000;
impl Expression { impl Expression {
pub fn evaluate(&self, mut rng: impl Rng) -> anyhow::Result<String> { pub fn evaluate(&self, mut rng: impl Rng) -> anyhow::Result<String> {
let mut result = String::new(); let mut result = String::new();
@ -85,7 +87,7 @@ impl Expression {
} else { } else {
1 1
}; };
if count > 1_000_000 { if count > DICE_POOL_LIMIT {
bail!("Too many dice.") bail!("Too many dice.")
}; };
output.push_str("d"); output.push_str("d");
@ -102,7 +104,7 @@ impl Expression {
if size < 2 { if size < 2 {
bail!("Infinite explosion.") bail!("Infinite explosion.")
} }
for i in 0..1_000_000 { for i in 0..DICE_POOL_LIMIT {
if i >= rolls.len() { if i >= rolls.len() {
break; break;
} }
@ -148,7 +150,7 @@ impl Expression {
let print_all_rolls = rolls.len() <= 100; let print_all_rolls = rolls.len() <= 100;
if print_all_rolls { if print_all_rolls {
output.push_str(" [ "); output.push_str(" ‹ ");
} }
for idx in 0..rolls.len() { for idx in 0..rolls.len() {
let mut skip = false; let mut skip = false;
@ -174,27 +176,25 @@ impl Expression {
skip = true; skip = true;
} }
} }
if skip { if !skip {
if print_all_rolls {
write!(output, "~~`{}`~~", roll)?;
}
continue;
}
result_set.push(roll as f64); result_set.push(roll as f64);
}
if print_all_rolls { if print_all_rolls {
let bold = size > 2 && (roll == 1 || roll == size); if skip {
if bold { write!(output, "~~`{}`~~", roll)?;
} else if size > 2 && (roll == 1 || roll == size) {
write!(output, "**`{}`**", roll)?; write!(output, "**`{}`**", roll)?;
} else { } else {
write!(output, "`{}`", roll)?; write!(output, "`{}`", roll)?;
} }
if idx != rolls.len() - 1 { if idx != rolls.len() - 1 {
output.push_str(", "); output.push_str(" ");
} }
} }
} }
if print_all_rolls { if print_all_rolls {
output.push_str(" ]"); output.push_str(" ›");
} }
result_set.iter().sum() result_set.iter().sum()
@ -253,7 +253,7 @@ impl Expression {
} else { } else {
1 1
}; };
if count > 1_000_000 { if count > DICE_POOL_LIMIT {
bail!("Too many dice.") bail!("Too many dice.")
}; };
let size = self.node_sample(rng, *size)? as i64; let size = self.node_sample(rng, *size)? as i64;
@ -268,7 +268,7 @@ impl Expression {
if size < 2 { if size < 2 {
bail!("Infinite explosion.") bail!("Infinite explosion.")
} }
for i in 0..1_000_000 { for i in 0..DICE_POOL_LIMIT {
if i >= rolls.len() { if i >= rolls.len() {
break; break;
} }

View file

@ -5,7 +5,8 @@ use anyhow::bail;
use dotenv::dotenv; use dotenv::dotenv;
use rand::{Rng, SeedableRng}; use rand::{Rng, SeedableRng};
use serenity::all::{ use serenity::all::{
Command, CommandInteraction, CommandOptionType, CreateCommandOption, CreateComponent, FullEvent, Interaction, MessageFlags, ResolvedOption, ResolvedValue, Command, CommandInteraction, CommandOptionType, CreateCommandOption, CreateComponent,
FullEvent, Interaction, MessageFlags, ResolvedOption, ResolvedValue,
}; };
use serenity::builder::{ use serenity::builder::{
CreateCommand, CreateContainer, CreateContainerComponent, CreateInteractionResponse, CreateCommand, CreateContainer, CreateContainerComponent, CreateInteractionResponse,
@ -80,7 +81,7 @@ impl EventHandler for Handler {
).required(true)) ).required(true))
.add_option(CreateCommandOption::new( .add_option(CreateCommandOption::new(
CommandOptionType::Integer, CommandOptionType::Integer,
"multi", "repeat",
"Repeat the rolled formula this many times. Useful if you're controlling too many units!", "Repeat the rolled formula this many times. Useful if you're controlling too many units!",
)) ))
.add_option(CreateCommandOption::new( .add_option(CreateCommandOption::new(
@ -92,6 +93,11 @@ impl EventHandler for Handler {
CommandOptionType::Boolean, CommandOptionType::Boolean,
"private", "private",
"Keep the roll private and hidden, only for yourself.", "Keep the roll private and hidden, only for yourself.",
))
.add_option(CreateCommandOption::new(
CommandOptionType::String,
"seed",
"Roll with a fixed seed. This bot uses xoshiro256++.",
)), )),
) )
.await; .await;
@ -114,12 +120,12 @@ pub async fn roll(ctx: &Context, command: &CommandInteraction) -> anyhow::Result
else { else {
bail!("Formula was not provided.") bail!("Formula was not provided.")
}; };
let multi = options let repeat = options
.iter() .iter()
.find(|option| option.name == "multi") .find(|option| option.name == "repeat")
.and_then(|option| { .and_then(|option| {
if let ResolvedValue::Integer(multi) = option.value { if let ResolvedValue::Integer(repeat) = option.value {
Some(multi) Some(repeat)
} else { } else {
None None
} }
@ -147,6 +153,17 @@ pub async fn roll(ctx: &Context, command: &CommandInteraction) -> anyhow::Result
None None
} }
}); });
let fixed_seed = options
.iter()
.find(|option| option.name == "seed")
.and_then(|option| {
if let ResolvedValue::String(fixed_seed) = option.value {
Some(fixed_seed)
} else {
None
}
})
.and_then(|s| s.parse::<u64>().ok());
let result = parsing::parse(&formula); let result = parsing::parse(&formula);
let expression = match result { let expression = match result {
@ -171,24 +188,27 @@ pub async fn roll(ctx: &Context, command: &CommandInteraction) -> anyhow::Result
command.defer(&ctx.http).await?; command.defer(&ctx.http).await?;
} }
let seed = rand::rng().next_u64(); let seed = fixed_seed.unwrap_or_else(|| rand::rng().next_u64());
let mut rng = rand_xoshiro::Xoshiro256PlusPlus::seed_from_u64(seed); let mut rng = rand_xoshiro::Xoshiro256PlusPlus::seed_from_u64(seed);
let ev = (expression.average(&mut rng)? * 10.0).round() / 10.0; let ev = (expression.average(&mut rng)? * 10.0).round() / 10.0;
let mut text_components = Vec::with_capacity((multi + 2) as usize); let mut text_components = Vec::with_capacity((repeat + 2) as usize);
if let Some(description) = description {
text_components.push(CreateContainerComponent::TextDisplay( text_components.push(CreateContainerComponent::TextDisplay(
CreateTextDisplay::new(if let Some(description) = description { CreateTextDisplay::new(format!(">>> {description}\n")),
format!("### {} rolled: *{description}*\n", command.user.mention())
} else {
format!("### {} rolled\n", command.user.mention())
}),
)); ));
for _ in 0..multi { }
for _ in 0..repeat {
text_components.push(CreateContainerComponent::TextDisplay( text_components.push(CreateContainerComponent::TextDisplay(
CreateTextDisplay::new(format!("{}\n", expression.evaluate(&mut rng)?)), CreateTextDisplay::new(format!("{}\n", expression.evaluate(&mut rng)?)),
)); ));
} }
if fixed_seed.is_some() {
text_components.push(CreateContainerComponent::TextDisplay(
CreateTextDisplay::new("***⚠️ This is not a fair roll. The result was generated based on a fixed seed and is thus fully reproducible.***".to_string()),
));
}
text_components.push(CreateContainerComponent::TextDisplay( text_components.push(CreateContainerComponent::TextDisplay(
CreateTextDisplay::new(format!("-# Expected value: {ev} | Seed: {seed}")), CreateTextDisplay::new(format!("-# Expected value: {ev} | Seed: {seed}")),
)); ));