88 lines
2.3 KiB
Rust
88 lines
2.3 KiB
Rust
use std::time::{SystemTime, UNIX_EPOCH};
|
|
|
|
use crate::bot::module::{Action, Command, CommandSpec, Module};
|
|
|
|
const COMMANDS: &[CommandSpec] = &[CommandSpec {
|
|
name: "roll",
|
|
usage: "roll [NdM]",
|
|
about: "roll N dice of M sides (default 1d6), e.g. roll 2d20",
|
|
}];
|
|
|
|
const MAX_DICE: u64 = 100;
|
|
const MAX_SIDES: u64 = 1000;
|
|
|
|
pub struct Dice {
|
|
rng: u64,
|
|
}
|
|
|
|
impl Dice {
|
|
pub fn new() -> Dice {
|
|
let seed = SystemTime::now()
|
|
.duration_since(UNIX_EPOCH)
|
|
.map(|d| d.as_nanos() as u64)
|
|
.unwrap_or(0x9E37_79B9_7F4A_7C15);
|
|
Dice { rng: seed | 1 }
|
|
}
|
|
|
|
fn next_u64(&mut self) -> u64 {
|
|
let mut x = self.rng;
|
|
x ^= x << 13;
|
|
x ^= x >> 7;
|
|
x ^= x << 17;
|
|
self.rng = x;
|
|
x
|
|
}
|
|
|
|
fn die(&mut self, sides: u64) -> u64 {
|
|
self.next_u64() % sides + 1
|
|
}
|
|
}
|
|
|
|
impl Default for Dice {
|
|
fn default() -> Dice {
|
|
Dice::new()
|
|
}
|
|
}
|
|
|
|
impl Module for Dice {
|
|
fn name(&self) -> &'static str {
|
|
"dice"
|
|
}
|
|
|
|
fn commands(&self) -> &'static [CommandSpec] {
|
|
COMMANDS
|
|
}
|
|
|
|
fn on_command(&mut self, cmd: &Command) -> Vec<Action> {
|
|
let spec = cmd.args.first().copied().unwrap_or("1d6");
|
|
let reply = match parse_spec(spec) {
|
|
Some((count, sides)) => {
|
|
let rolls: Vec<u64> = (0..count).map(|_| self.die(sides)).collect();
|
|
let total: u64 = rolls.iter().sum();
|
|
if count == 1 {
|
|
format!("{count}d{sides}: {total}")
|
|
} else {
|
|
let parts: Vec<String> = rolls.iter().map(u64::to_string).collect();
|
|
format!("{count}d{sides}: {} = {total}", parts.join(" + "))
|
|
}
|
|
}
|
|
None => format!("usage: {p}roll [NdM], e.g. {p}roll 2d6", p = cmd.prefix),
|
|
};
|
|
vec![Action::Reply(reply)]
|
|
}
|
|
}
|
|
|
|
fn parse_spec(s: &str) -> Option<(u64, u64)> {
|
|
let (count, sides) = match s.split_once('d') {
|
|
Some((c, m)) => {
|
|
let count = if c.is_empty() { 1 } else { c.parse().ok()? };
|
|
(count, m.parse().ok()?)
|
|
}
|
|
None => (1, s.parse().ok()?),
|
|
};
|
|
if (1..=MAX_DICE).contains(&count) && (2..=MAX_SIDES).contains(&sides) {
|
|
Some((count, sides))
|
|
} else {
|
|
None
|
|
}
|
|
}
|