Compare commits
No commits in common. "0eec30fe82a84bd066c90829b7d8536810e3268a" and "9dd6bee0e350a89be3eec67caefc1bb6ff089685" have entirely different histories.
0eec30fe82
...
9dd6bee0e3
3 changed files with 180 additions and 246 deletions
140
src/dice.rs
140
src/dice.rs
|
|
@ -1,10 +1,8 @@
|
||||||
use crate::limits::{MAX_DICE_COUNT_PER_ROLL, MAX_DISPLAYED_DICE_PER_MESSAGE, RESULT_CULL_CHARACTER_THRESHOLD};
|
|
||||||
use anyhow::bail;
|
use anyhow::bail;
|
||||||
use rand::{Rng, RngExt};
|
use rand::{Rng, RngExt};
|
||||||
use std::convert::Infallible;
|
use std::convert::Infallible;
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::fmt::{Write, format};
|
use std::fmt::{Write, format};
|
||||||
use std::sync::{Arc, RwLock};
|
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
pub type ExpBox = Box<Expression>;
|
pub type ExpBox = Box<Expression>;
|
||||||
|
|
@ -42,7 +40,16 @@ pub enum CompareFragment {
|
||||||
Le(ExpBox),
|
Le(ExpBox),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const DICE_POOL_LIMIT: usize = 10_000;
|
||||||
|
|
||||||
impl Expression {
|
impl Expression {
|
||||||
|
pub fn collect_evaluation(&self, mut rng: impl Rng) -> anyhow::Result<String> {
|
||||||
|
let mut witness = DiscordMdWitness::default();
|
||||||
|
let total = self.evaluate(&mut rng, &mut witness, 100)?;
|
||||||
|
let result_text = witness.buffer;
|
||||||
|
Ok(format!("**{total}** = {result_text}"))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn average(&self, rng: &mut impl Rng) -> anyhow::Result<f64> {
|
pub fn average(&self, rng: &mut impl Rng) -> anyhow::Result<f64> {
|
||||||
if let Ok(avg) = self.avg() {
|
if let Ok(avg) = self.avg() {
|
||||||
return Ok(avg);
|
return Ok(avg);
|
||||||
|
|
@ -54,7 +61,7 @@ impl Expression {
|
||||||
Ok(average)
|
Ok(average)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn evaluate<W, E>(
|
fn evaluate<W, E>(
|
||||||
&self,
|
&self,
|
||||||
rng: &mut impl Rng,
|
rng: &mut impl Rng,
|
||||||
w: &mut W,
|
w: &mut W,
|
||||||
|
|
@ -64,7 +71,6 @@ impl Expression {
|
||||||
W: Witness<Error = E>,
|
W: Witness<Error = E>,
|
||||||
E: Error + Send + Sync + 'static,
|
E: Error + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
use crate::limits::MAX_DICE_COUNT_PER_ROLL;
|
|
||||||
use Expression::*;
|
use Expression::*;
|
||||||
let precedence = self.precedence();
|
let precedence = self.precedence();
|
||||||
let needs_parens = precedence > outer_precedence;
|
let needs_parens = precedence > outer_precedence;
|
||||||
|
|
@ -90,7 +96,7 @@ impl Expression {
|
||||||
} else {
|
} else {
|
||||||
1
|
1
|
||||||
};
|
};
|
||||||
if count > MAX_DICE_COUNT_PER_ROLL {
|
if count > DICE_POOL_LIMIT {
|
||||||
bail!("Too many dice.")
|
bail!("Too many dice.")
|
||||||
};
|
};
|
||||||
write!(w, "d")?;
|
write!(w, "d")?;
|
||||||
|
|
@ -122,7 +128,7 @@ impl Expression {
|
||||||
}
|
}
|
||||||
let mut i = 0;
|
let mut i = 0;
|
||||||
while i < rolls.len() {
|
while i < rolls.len() {
|
||||||
if i > MAX_DICE_COUNT_PER_ROLL {
|
if i > DICE_POOL_LIMIT {
|
||||||
bail!("Explosion added too many dice.")
|
bail!("Explosion added too many dice.")
|
||||||
}
|
}
|
||||||
if comparers.len() == 0 {
|
if comparers.len() == 0 {
|
||||||
|
|
@ -292,7 +298,7 @@ impl Expression {
|
||||||
} else {
|
} else {
|
||||||
1
|
1
|
||||||
};
|
};
|
||||||
if count > MAX_DICE_COUNT_PER_ROLL {
|
if count > DICE_POOL_LIMIT {
|
||||||
bail!("Too many dice.")
|
bail!("Too many dice.")
|
||||||
};
|
};
|
||||||
let size = size.avg()? as i64;
|
let size = size.avg()? as i64;
|
||||||
|
|
@ -384,7 +390,7 @@ impl Expression {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) trait Witness {
|
trait Witness {
|
||||||
type Ok;
|
type Ok;
|
||||||
type Error: Error;
|
type Error: Error;
|
||||||
type WitnessSet<'a>: WitnessSet<Ok = Self::Ok, Error = Self::Error>
|
type WitnessSet<'a>: WitnessSet<Ok = Self::Ok, Error = Self::Error>
|
||||||
|
|
@ -399,10 +405,9 @@ pub(crate) trait Witness {
|
||||||
{
|
{
|
||||||
self.witness_source_text(&format(args))
|
self.witness_source_text(&format(args))
|
||||||
}
|
}
|
||||||
fn witness_total_result(&mut self, result: f64) -> Result<Self::Ok, Self::Error>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) trait WitnessSet {
|
trait WitnessSet {
|
||||||
type Ok;
|
type Ok;
|
||||||
type Error: Error;
|
type Error: Error;
|
||||||
|
|
||||||
|
|
@ -411,7 +416,7 @@ pub(crate) trait WitnessSet {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) struct DiceRoll {
|
struct DiceRoll {
|
||||||
size: usize,
|
size: usize,
|
||||||
roll: usize,
|
roll: usize,
|
||||||
is_admitted: bool,
|
is_admitted: bool,
|
||||||
|
|
@ -431,56 +436,27 @@ impl Default for DiceRoll {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
struct DiscordMdWitness {
|
||||||
pub(crate) struct DiscordMd {
|
buffer: String,
|
||||||
pub(crate) buffers: Vec<String>,
|
dice_written: usize,
|
||||||
pub(crate) dice_written: usize,
|
|
||||||
}
|
}
|
||||||
pub(crate) struct DiscordMdWitness {
|
struct DiscordMdWitnessSet<'a> {
|
||||||
parent: Arc<RwLock<DiscordMd>>,
|
parent: &'a mut DiscordMdWitness,
|
||||||
buffer_idx: usize,
|
|
||||||
result: Option<f64>,
|
|
||||||
}
|
|
||||||
pub(crate) struct DiscordMdWitnessSet<'a> {
|
|
||||||
witness: &'a mut DiscordMdWitness,
|
|
||||||
index: usize,
|
index: usize,
|
||||||
dice_elided: bool,
|
dice_elided: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub(crate) enum DiscordMdWitnessError {
|
enum DiscordMdWitnessError {
|
||||||
#[error("Formatting failed.")]
|
#[error("formatting failed")]
|
||||||
Format(#[from] std::fmt::Error),
|
Format(#[from] std::fmt::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DiscordMd {
|
impl Default for DiscordMdWitness {
|
||||||
pub(crate) fn create_witness(md: &Arc<RwLock<Self>>) -> DiscordMdWitness {
|
fn default() -> Self {
|
||||||
let mut parent = md.write().unwrap();
|
Self {
|
||||||
parent.buffers.push(String::new());
|
buffer: String::new(),
|
||||||
DiscordMdWitness {
|
dice_written: 0,
|
||||||
parent: md.clone(),
|
|
||||||
buffer_idx: parent.buffers.len() - 1,
|
|
||||||
result: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn character_count(&self) -> usize {
|
|
||||||
self.buffers.iter().map(|buf| buf.chars().count()).sum()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
impl DiscordMdWitness {
|
|
||||||
pub(crate) fn end(self) {
|
|
||||||
let mut parent = self.parent.write().unwrap();
|
|
||||||
if parent.character_count() > RESULT_CULL_CHARACTER_THRESHOLD {
|
|
||||||
parent.buffers[self.buffer_idx] = self
|
|
||||||
.result
|
|
||||||
.map(|result| format!("**{result} = …**"))
|
|
||||||
.unwrap_or_else(|| String::new());
|
|
||||||
} else if let Some(result) = self.result {
|
|
||||||
parent.buffers[self.buffer_idx]
|
|
||||||
.insert_str(0, &format!("**{result}** = "));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -491,87 +467,65 @@ impl Witness for DiscordMdWitness {
|
||||||
type WitnessSet<'a> = DiscordMdWitnessSet<'a>;
|
type WitnessSet<'a> = DiscordMdWitnessSet<'a>;
|
||||||
|
|
||||||
fn witness_source_text(&mut self, text: &str) -> Result<Self::Ok, Self::Error> {
|
fn witness_source_text(&mut self, text: &str) -> Result<Self::Ok, Self::Error> {
|
||||||
self.parent.write().unwrap().buffers[self.buffer_idx].push_str(text);
|
self.buffer.push_str(text);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn witness_set(&mut self) -> Result<Self::WitnessSet<'_>, Self::Error> {
|
fn witness_set(&mut self) -> Result<Self::WitnessSet<'_>, Self::Error> {
|
||||||
write!(self.parent.write().unwrap().buffers[self.buffer_idx], "‹ ")?;
|
write!(self.buffer, "‹ ")?;
|
||||||
Ok(DiscordMdWitnessSet {
|
Ok(DiscordMdWitnessSet {
|
||||||
witness: self,
|
parent: self,
|
||||||
index: 0,
|
index: 0,
|
||||||
dice_elided: false,
|
dice_elided: false,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn witness_total_result(&mut self, result: f64) -> Result<Self::Ok, Self::Error> {
|
|
||||||
self.result = Some(result);
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
impl<'a> WitnessSet for DiscordMdWitnessSet<'a> {
|
impl<'a> WitnessSet for DiscordMdWitnessSet<'a> {
|
||||||
type Ok = ();
|
type Ok = ();
|
||||||
type Error = DiscordMdWitnessError;
|
type Error = DiscordMdWitnessError;
|
||||||
|
|
||||||
fn witness_roll(&mut self, dice: DiceRoll) -> Result<Self::Ok, Self::Error> {
|
fn witness_roll(&mut self, dice: DiceRoll) -> Result<Self::Ok, Self::Error> {
|
||||||
let mut parent = self.witness.parent.write().unwrap();
|
if self.parent.dice_written >= 50 {
|
||||||
if parent.dice_written >= MAX_DISPLAYED_DICE_PER_MESSAGE {
|
|
||||||
if !self.dice_elided {
|
if !self.dice_elided {
|
||||||
write!(
|
write!(self.parent.buffer, "…")?;
|
||||||
parent.buffers[self.witness.buffer_idx],
|
|
||||||
"…"
|
|
||||||
)?;
|
|
||||||
self.dice_elided = true;
|
self.dice_elided = true;
|
||||||
}
|
}
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let buf = &mut parent.buffers[self.witness.buffer_idx];
|
|
||||||
let is_extreme = dice.size >= 4 && (dice.roll == 1 || dice.roll == dice.size);
|
let is_extreme = dice.size >= 4 && (dice.roll == 1 || dice.roll == dice.size);
|
||||||
// A visible separator is required despite the monospace boxing,
|
|
||||||
// due to iOS Discord being weird.
|
|
||||||
if self.index > 0 {
|
if self.index > 0 {
|
||||||
write!(buf, ",")?;
|
write!(self.parent.buffer, " ")?;
|
||||||
}
|
}
|
||||||
if !dice.is_admitted {
|
if !dice.is_admitted {
|
||||||
write!(buf, "~~")?;
|
write!(self.parent.buffer, "~~")?;
|
||||||
}
|
}
|
||||||
if dice.is_from_proliferation {
|
if dice.is_from_proliferation {
|
||||||
write!(buf, "*")?;
|
write!(self.parent.buffer, "*")?;
|
||||||
}
|
}
|
||||||
if is_extreme {
|
if is_extreme || dice.did_proliferate {
|
||||||
write!(buf, "**")?;
|
write!(self.parent.buffer, "**")?;
|
||||||
}
|
|
||||||
if dice.did_proliferate {
|
|
||||||
write!(buf, "__")?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
write!(buf, "`{}`", dice.roll)?;
|
write!(self.parent.buffer, "`{}`", dice.roll)?;
|
||||||
|
|
||||||
if dice.did_proliferate {
|
if is_extreme || dice.did_proliferate {
|
||||||
write!(buf, "__")?;
|
write!(self.parent.buffer, "**")?;
|
||||||
}
|
|
||||||
if is_extreme {
|
|
||||||
write!(buf, "**")?;
|
|
||||||
}
|
}
|
||||||
if dice.is_from_proliferation {
|
if dice.is_from_proliferation {
|
||||||
write!(buf, "*")?;
|
write!(self.parent.buffer, "*")?;
|
||||||
}
|
}
|
||||||
if !dice.is_admitted {
|
if !dice.is_admitted {
|
||||||
write!(buf, "~~")?;
|
write!(self.parent.buffer, "~~")?;
|
||||||
}
|
}
|
||||||
self.index += 1;
|
self.index += 1;
|
||||||
parent.dice_written += 1;
|
self.parent.dice_written += 1;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn end(self) -> Result<Self::Ok, Self::Error> {
|
fn end(self) -> Result<Self::Ok, Self::Error> {
|
||||||
write!(
|
write!(self.parent.buffer, " ›")?;
|
||||||
self.witness.parent.write().unwrap().buffers[self.witness.buffer_idx],
|
|
||||||
" ›"
|
|
||||||
)?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -591,10 +545,6 @@ impl Witness for std::io::Sink {
|
||||||
fn witness_set(&mut self) -> Result<Self::WitnessSet<'_>, Self::Error> {
|
fn witness_set(&mut self) -> Result<Self::WitnessSet<'_>, Self::Error> {
|
||||||
Ok(self)
|
Ok(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn witness_total_result(&mut self, _result: f64) -> Result<Self::Ok, Self::Error> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WitnessSet for &mut std::io::Sink {
|
impl WitnessSet for &mut std::io::Sink {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
pub const MAX_DICE_COUNT_PER_ROLL: usize = 20_000;
|
|
||||||
pub const RESULT_CULL_CHARACTER_THRESHOLD: usize = 3000;
|
|
||||||
pub const MAX_DISPLAYED_DICE_PER_MESSAGE: usize = 100;
|
|
||||||
pub const MAX_ROLL_REPEATS: usize = 35;
|
|
||||||
174
src/main.rs
174
src/main.rs
|
|
@ -1,18 +1,13 @@
|
||||||
mod dice;
|
mod dice;
|
||||||
mod limits;
|
|
||||||
mod parsing;
|
mod parsing;
|
||||||
|
|
||||||
use crate::dice::{DiscordMd, Witness};
|
use anyhow::bail;
|
||||||
use anyhow::{Context as AnyCtx, bail};
|
|
||||||
use dotenv::dotenv;
|
use dotenv::dotenv;
|
||||||
use limits::*;
|
|
||||||
use rand::{Rng, SeedableRng};
|
use rand::{Rng, SeedableRng};
|
||||||
use serenity::all::{
|
use serenity::all::{Command, CommandInteraction, CommandOptionType, CreateCommandOption, CreateComponent, FullEvent, Interaction, InteractionContext, MessageFlags, ResolvedOption, ResolvedValue};
|
||||||
Command, CommandInteraction, CommandOptionType, CreateCommandOption, CreateComponent,
|
|
||||||
FullEvent, Interaction, InteractionContext, MessageFlags, ResolvedOption, ResolvedValue,
|
|
||||||
};
|
|
||||||
use serenity::builder::{
|
use serenity::builder::{
|
||||||
CreateCommand, CreateContainer, CreateContainerComponent, CreateInteractionResponse, CreateInteractionResponseMessage, CreateTextDisplay,
|
CreateCommand, CreateContainer, CreateContainerComponent, CreateInteractionResponse,
|
||||||
|
CreateInteractionResponseFollowup, CreateInteractionResponseMessage, CreateTextDisplay,
|
||||||
};
|
};
|
||||||
use serenity::{async_trait, prelude::*};
|
use serenity::{async_trait, prelude::*};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
@ -115,7 +110,6 @@ impl EventHandler for Handler {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn roll(ctx: &Context, command: &CommandInteraction) -> anyhow::Result<()> {
|
pub async fn roll(ctx: &Context, command: &CommandInteraction) -> anyhow::Result<()> {
|
||||||
pub async fn inner(ctx: &Context, command: &CommandInteraction) -> anyhow::Result<()> {
|
|
||||||
let options = command.data.options();
|
let options = command.data.options();
|
||||||
let Some(ResolvedOption {
|
let Some(ResolvedOption {
|
||||||
value: ResolvedValue::String(formula),
|
value: ResolvedValue::String(formula),
|
||||||
|
|
@ -135,7 +129,7 @@ pub async fn roll(ctx: &Context, command: &CommandInteraction) -> anyhow::Result
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.unwrap_or(1)
|
.unwrap_or(1)
|
||||||
.min(MAX_ROLL_REPEATS as i64);
|
.min(20);
|
||||||
let private = options
|
let private = options
|
||||||
.iter()
|
.iter()
|
||||||
.find(|option| option.name == "private")
|
.find(|option| option.name == "private")
|
||||||
|
|
@ -169,94 +163,88 @@ pub async fn roll(ctx: &Context, command: &CommandInteraction) -> anyhow::Result
|
||||||
})
|
})
|
||||||
.and_then(|s| s.parse::<u64>().ok());
|
.and_then(|s| s.parse::<u64>().ok());
|
||||||
|
|
||||||
let expression = parsing::parse(&formula).context("Dice formula could not be parsed.")?;
|
let result = parsing::parse(&formula);
|
||||||
|
let expression = match result {
|
||||||
let seed = fixed_seed.unwrap_or_else(|| rand::rng().next_u64());
|
Ok(expression) => expression,
|
||||||
let mut rng = rand_xoshiro::Xoshiro256PlusPlus::seed_from_u64(seed);
|
Err(error) => {
|
||||||
|
|
||||||
let average = expression.average(&mut rng)?;
|
|
||||||
let ev = (average * 10.0).round() / 10.0;
|
|
||||||
|
|
||||||
let discord_md = Arc::new(std::sync::RwLock::new(DiscordMd {
|
|
||||||
buffers: vec![],
|
|
||||||
dice_written: 0,
|
|
||||||
}));
|
|
||||||
if let Some(description) = description {
|
|
||||||
discord_md
|
|
||||||
.write()
|
|
||||||
.unwrap()
|
|
||||||
.buffers
|
|
||||||
.push(format!(">>> {description}\n"));
|
|
||||||
}
|
|
||||||
for _ in 0..repeat {
|
|
||||||
let mut witness = DiscordMd::create_witness(&discord_md);
|
|
||||||
let result = expression.evaluate(&mut rng, &mut witness, i64::MAX)?;
|
|
||||||
witness.witness_total_result(result)?;
|
|
||||||
witness.end();
|
|
||||||
}
|
|
||||||
if fixed_seed.is_some() {
|
|
||||||
discord_md
|
|
||||||
.write()
|
|
||||||
.unwrap()
|
|
||||||
.buffers
|
|
||||||
.push("***⚠️ This is not a fair roll. The result was generated based on a fixed seed and is thus fully reproducible.***".to_string());
|
|
||||||
}
|
|
||||||
discord_md
|
|
||||||
.write()
|
|
||||||
.unwrap()
|
|
||||||
.buffers
|
|
||||||
.push(format!("-# Expected value: {ev} | Seed: {seed}"));
|
|
||||||
|
|
||||||
let text_components = Arc::into_inner(discord_md)
|
|
||||||
.unwrap()
|
|
||||||
.into_inner()?
|
|
||||||
.buffers
|
|
||||||
.into_iter()
|
|
||||||
.map(|text| CreateContainerComponent::TextDisplay(CreateTextDisplay::new(text)))
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
let response = CreateInteractionResponse::Message(
|
|
||||||
CreateInteractionResponseMessage::new()
|
|
||||||
.components(vec![CreateComponent::Container(CreateContainer::new(
|
|
||||||
text_components,
|
|
||||||
))])
|
|
||||||
.ephemeral(private)
|
|
||||||
.flags(
|
|
||||||
MessageFlags::IS_COMPONENTS_V2 | MessageFlags::SUPPRESS_NOTIFICATIONS,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
debug!("Returning response: {response:#?}");
|
|
||||||
|
|
||||||
command
|
|
||||||
.create_response(
|
|
||||||
&ctx.http,
|
|
||||||
response,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Ok(_) = inner(ctx, command).await {
|
|
||||||
} else if let Err(err) = inner(ctx, command).await {
|
|
||||||
warn!("Error encountered: {err:?}");
|
|
||||||
command
|
command
|
||||||
.create_response(
|
.create_response(
|
||||||
&ctx.http,
|
&ctx.http,
|
||||||
CreateInteractionResponse::Message(
|
CreateInteractionResponse::Message(
|
||||||
CreateInteractionResponseMessage::new()
|
CreateInteractionResponseMessage::default()
|
||||||
.components(vec![CreateComponent::Container(CreateContainer::new(
|
.content(format!("Dice formula could not be parsed.\n{}", error))
|
||||||
vec![CreateContainerComponent::TextDisplay(
|
.ephemeral(true),
|
||||||
CreateTextDisplay::new(err.to_string()),
|
|
||||||
)],
|
|
||||||
))])
|
|
||||||
.ephemeral(true)
|
|
||||||
.flags(
|
|
||||||
MessageFlags::IS_COMPONENTS_V2 | MessageFlags::SUPPRESS_NOTIFICATIONS,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.await?
|
.await?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
if private {
|
||||||
|
command.defer_ephemeral(&ctx.http).await?;
|
||||||
|
} else {
|
||||||
|
command.defer(&ctx.http).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let seed = fixed_seed.unwrap_or_else(|| rand::rng().next_u64());
|
||||||
|
let mut rng = rand_xoshiro::Xoshiro256PlusPlus::seed_from_u64(seed);
|
||||||
|
|
||||||
|
let average = match expression.average(&mut rng) {
|
||||||
|
Ok(res) => res,
|
||||||
|
Err(err) => {
|
||||||
|
command
|
||||||
|
.create_followup(
|
||||||
|
&ctx.http,
|
||||||
|
CreateInteractionResponseFollowup::new().content(format!("{}", err)),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
return Err(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let ev = (average * 10.0).round() / 10.0;
|
||||||
|
|
||||||
|
let mut text_components = Vec::with_capacity((repeat + 2) as usize);
|
||||||
|
if let Some(description) = description {
|
||||||
|
text_components.push(CreateContainerComponent::TextDisplay(
|
||||||
|
CreateTextDisplay::new(format!(">>> {description}\n")),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
for _ in 0..repeat {
|
||||||
|
let result = match expression.collect_evaluation(&mut rng) {
|
||||||
|
Ok(res) => res,
|
||||||
|
Err(err) => {
|
||||||
|
command
|
||||||
|
.create_followup(
|
||||||
|
&ctx.http,
|
||||||
|
CreateInteractionResponseFollowup::new().content(format!("{}", err)),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
return Err(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
text_components.push(CreateContainerComponent::TextDisplay(
|
||||||
|
CreateTextDisplay::new(format!("{}\n", result)),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
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(
|
||||||
|
CreateTextDisplay::new(format!("-# Expected value: {ev} | Seed: {seed}")),
|
||||||
|
));
|
||||||
|
|
||||||
|
command
|
||||||
|
.create_followup(
|
||||||
|
&ctx.http,
|
||||||
|
CreateInteractionResponseFollowup::new()
|
||||||
|
.components(vec![CreateComponent::Container(CreateContainer::new(
|
||||||
|
text_components,
|
||||||
|
))])
|
||||||
|
.ephemeral(private)
|
||||||
|
.flags(MessageFlags::IS_COMPONENTS_V2 | MessageFlags::SUPPRESS_NOTIFICATIONS),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue