feat: implement dice rolling
This commit is contained in:
parent
9c6249ca41
commit
34a25641d6
39
Cargo.lock
generated
39
Cargo.lock
generated
@ -1178,8 +1178,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"rand_chacha",
|
"rand_chacha 0.3.1",
|
||||||
"rand_core",
|
"rand_core 0.6.4",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand"
|
||||||
|
version = "0.9.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
|
||||||
|
dependencies = [
|
||||||
|
"rand_chacha 0.9.0",
|
||||||
|
"rand_core 0.9.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1189,7 +1199,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ppv-lite86",
|
"ppv-lite86",
|
||||||
"rand_core",
|
"rand_core 0.6.4",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_chacha"
|
||||||
|
version = "0.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
|
||||||
|
dependencies = [
|
||||||
|
"ppv-lite86",
|
||||||
|
"rand_core 0.9.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1201,6 +1221,15 @@ dependencies = [
|
|||||||
"getrandom 0.2.16",
|
"getrandom 0.2.16",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_core"
|
||||||
|
version = "0.9.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom 0.3.3",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "redox_syscall"
|
name = "redox_syscall"
|
||||||
version = "0.5.17"
|
version = "0.5.17"
|
||||||
@ -1301,9 +1330,11 @@ dependencies = [
|
|||||||
name = "roll-one-ring"
|
name = "roll-one-ring"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"bytecount",
|
||||||
"color-eyre",
|
"color-eyre",
|
||||||
"dotenvy",
|
"dotenvy",
|
||||||
"poise",
|
"poise",
|
||||||
|
"rand 0.9.2",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
@ -1951,7 +1982,7 @@ dependencies = [
|
|||||||
"http 1.3.1",
|
"http 1.3.1",
|
||||||
"httparse",
|
"httparse",
|
||||||
"log",
|
"log",
|
||||||
"rand",
|
"rand 0.8.5",
|
||||||
"rustls 0.22.4",
|
"rustls 0.22.4",
|
||||||
"rustls-pki-types",
|
"rustls-pki-types",
|
||||||
"sha1",
|
"sha1",
|
||||||
|
@ -4,9 +4,11 @@ version = "0.1.0"
|
|||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
bytecount = "0.6.9"
|
||||||
color-eyre = "0.6.5"
|
color-eyre = "0.6.5"
|
||||||
dotenvy = "0.15.7"
|
dotenvy = "0.15.7"
|
||||||
poise = "0.6.1"
|
poise = "0.6.1"
|
||||||
|
rand = "0.9.2"
|
||||||
tokio = { version = "1.47.1", features = ["macros", "rt-multi-thread"] }
|
tokio = { version = "1.47.1", features = ["macros", "rt-multi-thread"] }
|
||||||
tracing = "0.1.41"
|
tracing = "0.1.41"
|
||||||
tracing-subscriber = "0.3.20"
|
tracing-subscriber = "0.3.20"
|
||||||
|
@ -2,7 +2,8 @@ use color_eyre::eyre::{Error, Result};
|
|||||||
use poise::serenity_prelude::{self as serenity, FullEvent};
|
use poise::serenity_prelude::{self as serenity, FullEvent};
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
mod roll;
|
mod roll_dice;
|
||||||
|
mod source;
|
||||||
|
|
||||||
type Context<'a> = poise::Context<'a, (), Error>;
|
type Context<'a> = poise::Context<'a, (), Error>;
|
||||||
|
|
||||||
@ -17,7 +18,7 @@ pub async fn make_bot() -> Result<serenity::Client> {
|
|||||||
let intents = serenity::GatewayIntents::non_privileged();
|
let intents = serenity::GatewayIntents::non_privileged();
|
||||||
let framework = poise::Framework::<(), Error>::builder()
|
let framework = poise::Framework::<(), Error>::builder()
|
||||||
.options(poise::FrameworkOptions {
|
.options(poise::FrameworkOptions {
|
||||||
commands: vec![roll::roll()],
|
commands: vec![roll_dice::roll(), source::source()],
|
||||||
event_handler: |ctx, event, _framework: poise::FrameworkContext<'_, (), _>, _data| {
|
event_handler: |ctx, event, _framework: poise::FrameworkContext<'_, (), _>, _data| {
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
event_handler(ctx.clone(), event);
|
event_handler(ctx.clone(), event);
|
||||||
|
@ -1,70 +0,0 @@
|
|||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
use super::Context;
|
|
||||||
use color_eyre::eyre::{Error, Result};
|
|
||||||
use tracing::info;
|
|
||||||
|
|
||||||
enum Advantage {
|
|
||||||
None,
|
|
||||||
Beni,
|
|
||||||
Maudit,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for Advantage {
|
|
||||||
type Err = color_eyre::eyre::Report;
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
|
|
||||||
match s {
|
|
||||||
"aucun" => Ok(Self::None),
|
|
||||||
"béni" => Ok(Self::Beni),
|
|
||||||
"maudit" => Ok(Self::Maudit),
|
|
||||||
other => Err(Error::msg(format!("Could not parse {other} into an Advantage enum"))),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<Option<String>> for Advantage {
|
|
||||||
type Error = color_eyre::eyre::Report;
|
|
||||||
|
|
||||||
fn try_from(value: Option<String>) -> std::result::Result<Self, Self::Error> {
|
|
||||||
match value {
|
|
||||||
Some(str) => Advantage::from_str(&str),
|
|
||||||
None => Ok(Advantage::None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
impl std::fmt::Display for Advantage {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
||||||
let str = match self {
|
|
||||||
Self::None => "aucun".to_string(),
|
|
||||||
Self::Beni => "béni".to_string(),
|
|
||||||
Self::Maudit => "maudit".to_string(),
|
|
||||||
};
|
|
||||||
write!(f, "{str}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn advantage_autocomplete(_ctx: Context<'_>, _: &str) -> impl Iterator<Item = String> {
|
|
||||||
[Advantage::None, Advantage::Beni, Advantage::Maudit]
|
|
||||||
.iter()
|
|
||||||
.map(std::string::ToString::to_string)
|
|
||||||
.collect::<Vec<String>>()
|
|
||||||
.into_iter()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[poise::command(slash_command)]
|
|
||||||
pub async fn roll(
|
|
||||||
ctx: Context<'_>,
|
|
||||||
#[description = "Seuil de réussite"] sr: u32,
|
|
||||||
#[description = "Maîtrise"] mastery: u32,
|
|
||||||
#[description = "Avantage"]
|
|
||||||
#[autocomplete = "advantage_autocomplete"]
|
|
||||||
advantage: Option<String>,
|
|
||||||
) -> Result<()> {
|
|
||||||
info!("Called /roll with following context: {ctx:?}");
|
|
||||||
let advantage = Advantage::try_from(advantage)?;
|
|
||||||
info!("Rolling against SR {sr} with mastery {mastery} and advantage {advantage}");
|
|
||||||
Ok(())
|
|
||||||
}
|
|
41
src/discord/roll_dice/advantage.rs
Normal file
41
src/discord/roll_dice/advantage.rs
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
use color_eyre::eyre::Error;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
pub enum Advantage {
|
||||||
|
None,
|
||||||
|
Beni,
|
||||||
|
Maudit,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Advantage {
|
||||||
|
type Err = color_eyre::eyre::Report;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
|
||||||
|
match s {
|
||||||
|
"aucun" => Ok(Self::None),
|
||||||
|
"béni" => Ok(Self::Beni),
|
||||||
|
"maudit" => Ok(Self::Maudit),
|
||||||
|
other => Err(Error::msg(format!("Could not parse {other} into an Advantage enum"))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<Option<String>> for Advantage {
|
||||||
|
type Error = color_eyre::eyre::Report;
|
||||||
|
|
||||||
|
fn try_from(value: Option<String>) -> std::result::Result<Self, Self::Error> {
|
||||||
|
value.map_or_else(|| Ok(Self::None), |str| Self::from_str(&str))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for Advantage {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
let str = match self {
|
||||||
|
Self::None => "aucun".to_string(),
|
||||||
|
Self::Beni => "béni".to_string(),
|
||||||
|
Self::Maudit => "maudit".to_string(),
|
||||||
|
};
|
||||||
|
write!(f, "{str}")
|
||||||
|
}
|
||||||
|
}
|
45
src/discord/roll_dice/mod.rs
Normal file
45
src/discord/roll_dice/mod.rs
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
mod advantage;
|
||||||
|
mod roll;
|
||||||
|
mod roll_result;
|
||||||
|
mod success;
|
||||||
|
|
||||||
|
use advantage::Advantage;
|
||||||
|
use roll::Roll;
|
||||||
|
use roll_result::RollResult;
|
||||||
|
use success::Success;
|
||||||
|
|
||||||
|
use super::Context;
|
||||||
|
use color_eyre::eyre::Result;
|
||||||
|
use tracing::info;
|
||||||
|
|
||||||
|
async fn advantage_autocomplete(_ctx: Context<'_>, _: &str) -> impl Iterator<Item = String> {
|
||||||
|
[Advantage::None, Advantage::Beni, Advantage::Maudit]
|
||||||
|
.iter()
|
||||||
|
.map(std::string::ToString::to_string)
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.into_iter()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[poise::command(slash_command)]
|
||||||
|
pub async fn roll(
|
||||||
|
ctx: Context<'_>,
|
||||||
|
#[description = "Seuil de réussite"]
|
||||||
|
#[min = 0]
|
||||||
|
#[max = 100]
|
||||||
|
sr: u8,
|
||||||
|
#[description = "Maîtrise"]
|
||||||
|
#[min = 0]
|
||||||
|
#[max = 6]
|
||||||
|
mastery: u8,
|
||||||
|
#[description = "Avantage"]
|
||||||
|
#[autocomplete = "advantage_autocomplete"]
|
||||||
|
advantage: Option<String>,
|
||||||
|
) -> Result<()> {
|
||||||
|
let advantage = Advantage::try_from(advantage)?;
|
||||||
|
let roll = Roll::new(sr, mastery, advantage);
|
||||||
|
info!("Received roll {roll:?}");
|
||||||
|
let result: RollResult = roll.into();
|
||||||
|
info!("Result: {result:?}");
|
||||||
|
ctx.say(result.to_string()).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
14
src/discord/roll_dice/roll.rs
Normal file
14
src/discord/roll_dice/roll.rs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
use super::Advantage;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Roll {
|
||||||
|
pub sr: u8,
|
||||||
|
pub mastery: u8,
|
||||||
|
pub advantage: Advantage,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Roll {
|
||||||
|
pub const fn new(sr: u8, mastery: u8, advantage: Advantage) -> Self {
|
||||||
|
Self { sr, mastery, advantage }
|
||||||
|
}
|
||||||
|
}
|
73
src/discord/roll_dice/roll_result.rs
Normal file
73
src/discord/roll_dice/roll_result.rs
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
use super::{Advantage, Roll, Success};
|
||||||
|
|
||||||
|
fn roll_main_dice() -> u8 {
|
||||||
|
rand::random_range(0..=11)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn roll_mastery_dice() -> u8 {
|
||||||
|
rand::random_range(1..=6)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct RollResult {
|
||||||
|
sr: u8,
|
||||||
|
main_dice: u8,
|
||||||
|
all_main_dice: Vec<u8>,
|
||||||
|
mastery: Vec<u8>,
|
||||||
|
result: u8,
|
||||||
|
success: Success,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RollResult {
|
||||||
|
pub fn main_dice_result(main_dice: &[u8], advantage: &Advantage) -> u8 {
|
||||||
|
match advantage {
|
||||||
|
Advantage::None => *main_dice.first().unwrap(),
|
||||||
|
Advantage::Beni => *main_dice.iter().max().unwrap_or(&0),
|
||||||
|
Advantage::Maudit => *main_dice.iter().min().unwrap_or(&0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compute_result(main_dice: &[u8], mastery: &[u8], advantage: &Advantage) -> u8 {
|
||||||
|
let main_dice = Self::main_dice_result(main_dice, advantage);
|
||||||
|
main_dice + mastery.iter().sum::<u8>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Roll> for RollResult {
|
||||||
|
fn from(value: Roll) -> Self {
|
||||||
|
let all_main_dice: Vec<u8> = if value.advantage == Advantage::None {
|
||||||
|
vec![roll_main_dice()]
|
||||||
|
} else {
|
||||||
|
(0..2).map(|_| roll_mastery_dice()).collect()
|
||||||
|
};
|
||||||
|
let mastery: Vec<u8> = std::iter::repeat_with(roll_mastery_dice)
|
||||||
|
.take(value.mastery.into())
|
||||||
|
.collect();
|
||||||
|
let sr = value.sr;
|
||||||
|
let result = Self::compute_result(&all_main_dice, &mastery, &value.advantage);
|
||||||
|
let main_dice = Self::main_dice_result(&all_main_dice, &value.advantage);
|
||||||
|
let success = Success::new(main_dice, result, sr, &mastery);
|
||||||
|
Self {
|
||||||
|
sr,
|
||||||
|
main_dice,
|
||||||
|
all_main_dice,
|
||||||
|
mastery,
|
||||||
|
result,
|
||||||
|
success,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for RollResult {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
let result = format!(
|
||||||
|
"Seuil de réussite : {}
|
||||||
|
Dé(s) du destin: {} ({:?})
|
||||||
|
Dé(s) de maîtrise: {:?}
|
||||||
|
Résultat : {}
|
||||||
|
Réussite : {}",
|
||||||
|
self.sr, self.main_dice, self.all_main_dice, self.mastery, self.result, self.success
|
||||||
|
);
|
||||||
|
write!(f, "{result}")
|
||||||
|
}
|
||||||
|
}
|
59
src/discord/roll_dice/success.rs
Normal file
59
src/discord/roll_dice/success.rs
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Success {
|
||||||
|
Failure,
|
||||||
|
Success(SuccessLevel),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Success {
|
||||||
|
pub fn new(main_dice: u8, result: u8, sr: u8, mastery: &[u8]) -> Self {
|
||||||
|
let success_level = SuccessLevel::from(mastery);
|
||||||
|
match main_dice {
|
||||||
|
11 => Self::Success(success_level),
|
||||||
|
_ => {
|
||||||
|
if result >= sr {
|
||||||
|
Self::Success(success_level)
|
||||||
|
} else {
|
||||||
|
Self::Failure
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for Success {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
let str = match self {
|
||||||
|
Self::Failure => "échec".to_owned(),
|
||||||
|
Self::Success(success_level) => success_level.to_string(),
|
||||||
|
};
|
||||||
|
write!(f, "{str}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum SuccessLevel {
|
||||||
|
Normal,
|
||||||
|
Great,
|
||||||
|
Incredible,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&[u8]> for SuccessLevel {
|
||||||
|
fn from(value: &[u8]) -> Self {
|
||||||
|
match bytecount::count(value, 6) {
|
||||||
|
0 => Self::Normal,
|
||||||
|
1 => Self::Great,
|
||||||
|
_ => Self::Incredible,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for SuccessLevel {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
let level = match self {
|
||||||
|
Self::Normal => "réussite normale",
|
||||||
|
Self::Great => "grande réussite",
|
||||||
|
Self::Incredible => "très grande réussite",
|
||||||
|
};
|
||||||
|
write!(f, "{level}")
|
||||||
|
}
|
||||||
|
}
|
10
src/discord/source.rs
Normal file
10
src/discord/source.rs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
use color_eyre::eyre::Result;
|
||||||
|
|
||||||
|
use super::Context;
|
||||||
|
|
||||||
|
#[poise::command(slash_command)]
|
||||||
|
pub async fn source(ctx: Context<'_>) -> Result<()> {
|
||||||
|
let message = "Mon code source se situe [là](https://labs.phundrak.com/phundrak/roll-one-ring)";
|
||||||
|
ctx.say(message).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
@ -1,14 +1,17 @@
|
|||||||
#![deny(clippy::all, clippy::pedantic, clippy::nursery)]
|
#![deny(clippy::all, clippy::pedantic, clippy::nursery)]
|
||||||
#![warn(missing_docs)]
|
#![warn(missing_docs)]
|
||||||
#![allow(clippy::module_name_repetitions, clippy::redundant_pub_crate)]
|
#![allow(
|
||||||
|
clippy::module_name_repetitions,
|
||||||
|
clippy::redundant_pub_crate,
|
||||||
|
clippy::enum_variant_names
|
||||||
|
)]
|
||||||
#![allow(clippy::unused_async)]
|
#![allow(clippy::unused_async)]
|
||||||
|
|
||||||
mod discord;
|
mod discord;
|
||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
use poise::serenity_prelude::{self as serenity};
|
|
||||||
use tracing::info;
|
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
|
use tracing::info;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<()> {
|
async fn main() -> Result<()> {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user