use fluffer::{async_trait, App, Client, Fluff, GemBytes};
use rand::Rng;
enum RollError {
BadRoll,
InvalidSides(i32),
InvalidRolls(i32),
}
#[async_trait]
impl GemBytes for RollError {
async fn gem_bytes(self) -> Vec<u8> {
match self {
RollError::BadRoll => "10 Bad roll. Try again.\r\n".to_string().into_bytes(),
RollError::InvalidRolls(r) => {
format!("10 Invalid number of rolls: {r}. Try again.\r\n").into_bytes()
}
RollError::InvalidSides(s) => {
format!("10 Invalid sides: {s}. Try again.\r\n").into_bytes()
}
}
}
}
struct Roll {
roll_count: i32,
sides: i32,
bonus: i32,
total: i32,
rolls: Vec<i32>,
}
impl Roll {
fn new(roll_count: i32, sides: i32, bonus: i32) -> Self {
let mut rng = rand::thread_rng();
let mut rolls: Vec<i32> = Vec::new();
let mut total: i32 = 0;
for _ in 1..=roll_count {
let r = rng.gen_range(1..=sides);
rolls.push(r);
total += r;
}
total += bonus;
Self {
roll_count,
sides,
bonus,
total,
rolls,
}
}
}
impl std::fmt::Display for Roll {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
writeln!(f, "â•─🎲 {}d{} 🎲", self.roll_count, self.sides)?;
if self.rolls.len() > 1 || self.bonus != 0 {
writeln!(f, "│Rolls: {:?}", self.rolls)?;
}
if self.bonus != 0 {
writeln!(
f,
"│{}{}",
self.bonus.is_positive().then_some("+").unwrap_or(""),
self.bonus
)?;
}
write!(f, "╰─Total: {}", self.total)?;
Ok(())
}
}
async fn roll(c: Client) -> Result<Fluff, RollError> {
let Some(input) = c.input() else {
return Ok(Fluff::Input(
r#"Example Usage:
1d20 + 1
1d8 - 1
d6"#
.to_string(),
));
};
let input = input.replace(' ', "");
let (input, bonus) = {
if let Some((input, bonus)) = input.rsplit_once('+') {
(input.to_string(), bonus.parse::<i32>().unwrap_or(0))
} else if let Some((input, bonus)) = input.rsplit_once('-') {
(input.to_string(), -bonus.parse::<i32>().unwrap_or(0))
} else {
(input, 0)
}
};
let Some((roll_count, sides)) = input.rsplit_once('d') else {
return Err(RollError::BadRoll);
};
let roll_count = roll_count.parse::<i32>().unwrap_or(1);
let sides = sides.parse::<i32>().map_err(|_| RollError::BadRoll)?;
if sides < 2 {
return Err(RollError::InvalidSides(sides));
}
if roll_count < 1 {
return Err(RollError::InvalidRolls(roll_count));
}
let roll = Roll::new(roll_count, sides, bonus);
Ok(Fluff::Text(format!(
"=> /roll Roll again\n\n```\n{roll}\n```"
)))
}
#[tokio::main]
async fn main() {
pretty_env_logger::init();
App::default()
.route("/", |_| async { "# 🎲 Dice\n\n=> /roll Roll!" })
.route("/roll", roll)
.run()
.await
.unwrap();
}