Implement parsing of Cthulhu dice, only basic for now.
Does not understand anything besides single numbers at the moment.
This commit is contained in:
parent
c290393ddf
commit
08b0e58193
|
@ -11,7 +11,7 @@ impl Command for CthRoll {
|
||||||
"roll percentile pool"
|
"roll percentile pool"
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn execute(&self, ctx: &Context) -> Execution {
|
async fn execute(&self, _ctx: &Context) -> Execution {
|
||||||
//TODO this will be converted to a result when supporting variables.
|
//TODO this will be converted to a result when supporting variables.
|
||||||
let roll = self.0.roll();
|
let roll = self.0.roll();
|
||||||
let plain = format!("Roll: {}\nResult: {}", self.0, roll);
|
let plain = format!("Roll: {}\nResult: {}", self.0, roll);
|
||||||
|
@ -25,3 +25,22 @@ impl Command for CthRoll {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct CthAdvanceRoll(pub AdvancementRoll);
|
pub struct CthAdvanceRoll(pub AdvancementRoll);
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl Command for CthAdvanceRoll {
|
||||||
|
fn name(&self) -> &'static str {
|
||||||
|
"roll percentile pool"
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn execute(&self, _ctx: &Context) -> Execution {
|
||||||
|
//TODO this will be converted to a result when supporting variables.
|
||||||
|
let roll = self.0.roll();
|
||||||
|
let plain = format!("Roll: {}\nResult: {}", self.0, roll);
|
||||||
|
let html = format!(
|
||||||
|
"<p><strong>Roll:</strong> {}</p><p><strong>Result</strong>: {}</p>",
|
||||||
|
self.0, roll
|
||||||
|
);
|
||||||
|
|
||||||
|
Execution { plain, html }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -2,14 +2,14 @@ use crate::cofd::parser::{create_chance_die, parse_dice_pool};
|
||||||
use crate::commands::{
|
use crate::commands::{
|
||||||
basic_rolling::RollCommand,
|
basic_rolling::RollCommand,
|
||||||
cofd::PoolRollCommand,
|
cofd::PoolRollCommand,
|
||||||
cthulhu::CthRoll,
|
cthulhu::{CthAdvanceRoll, CthRoll},
|
||||||
misc::HelpCommand,
|
misc::HelpCommand,
|
||||||
variables::{
|
variables::{
|
||||||
DeleteVariableCommand, GetAllVariablesCommand, GetVariableCommand, SetVariableCommand,
|
DeleteVariableCommand, GetAllVariablesCommand, GetVariableCommand, SetVariableCommand,
|
||||||
},
|
},
|
||||||
Command,
|
Command,
|
||||||
};
|
};
|
||||||
use crate::cthulhu::dice::{DiceRoll, DiceRollModifier};
|
use crate::cthulhu::parser::{parse_advancement_roll, parse_regular_roll};
|
||||||
use crate::dice::parser::parse_element_expression;
|
use crate::dice::parser::parse_element_expression;
|
||||||
use crate::error::BotError;
|
use crate::error::BotError;
|
||||||
use crate::help::parse_help_topic;
|
use crate::help::parse_help_topic;
|
||||||
|
@ -57,13 +57,15 @@ fn parse_pool_roll(input: &str) -> Result<Box<dyn Command>, BotError> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_cth_roll(input: &str) -> Result<Box<dyn Command>, BotError> {
|
fn parse_cth_roll(input: &str) -> Result<Box<dyn Command>, BotError> {
|
||||||
let roll = DiceRoll {
|
let roll = parse_regular_roll(input)?;
|
||||||
target: 50,
|
|
||||||
modifier: DiceRollModifier::Normal,
|
|
||||||
};
|
|
||||||
Ok(Box::new(CthRoll(roll)))
|
Ok(Box::new(CthRoll(roll)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse_cth_advancement_roll(input: &str) -> Result<Box<dyn Command>, BotError> {
|
||||||
|
let roll = parse_advancement_roll(input)?;
|
||||||
|
Ok(Box::new(CthAdvanceRoll(roll)))
|
||||||
|
}
|
||||||
|
|
||||||
fn chance_die() -> Result<Box<dyn Command>, BotError> {
|
fn chance_die() -> Result<Box<dyn Command>, BotError> {
|
||||||
let pool = create_chance_die()?;
|
let pool = create_chance_die()?;
|
||||||
Ok(Box::new(PoolRollCommand(pool)))
|
Ok(Box::new(PoolRollCommand(pool)))
|
||||||
|
@ -121,7 +123,10 @@ pub fn parse_command(input: &str) -> Result<Option<Box<dyn Command>>, BotError>
|
||||||
"del" => parse_delete_variable_command(&cmd_input).map(|command| Some(command)),
|
"del" => parse_delete_variable_command(&cmd_input).map(|command| Some(command)),
|
||||||
"r" | "roll" => parse_roll(&cmd_input).map(|command| Some(command)),
|
"r" | "roll" => parse_roll(&cmd_input).map(|command| Some(command)),
|
||||||
"rp" | "pool" => parse_pool_roll(&cmd_input).map(|command| Some(command)),
|
"rp" | "pool" => parse_pool_roll(&cmd_input).map(|command| Some(command)),
|
||||||
"cthroll" => parse_cth_roll(&cmd_input).map(|command| Some(command)),
|
"cthroll" | "cthRoll" => parse_cth_roll(&cmd_input).map(|command| Some(command)),
|
||||||
|
"cthadv" | "cthARoll" => {
|
||||||
|
parse_cth_advancement_roll(&cmd_input).map(|command| Some(command))
|
||||||
|
}
|
||||||
"chance" => chance_die().map(|command| Some(command)),
|
"chance" => chance_die().map(|command| Some(command)),
|
||||||
"help" => help(&cmd_input).map(|command| Some(command)),
|
"help" => help(&cmd_input).map(|command| Some(command)),
|
||||||
// No recognized command, ignore this.
|
// No recognized command, ignore this.
|
||||||
|
|
|
@ -1 +1,2 @@
|
||||||
pub mod dice;
|
pub mod dice;
|
||||||
|
pub mod parser;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
/// A planned dice roll.
|
/// A planned dice roll.
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
pub struct DiceRoll {
|
pub struct DiceRoll {
|
||||||
pub target: u32,
|
pub target: u32,
|
||||||
pub modifier: DiceRollModifier,
|
pub modifier: DiceRollModifier,
|
||||||
|
@ -9,14 +9,14 @@ pub struct DiceRoll {
|
||||||
|
|
||||||
impl fmt::Display for DiceRoll {
|
impl fmt::Display for DiceRoll {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
let message = format!("target: {}, modifiers: {}", self.target, self.modifier);
|
let message = format!("target: {}, with {}", self.target, self.modifier);
|
||||||
write!(f, "{}", message)?;
|
write!(f, "{}", message)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Potential modifier on the die roll to be made.
|
/// Potential modifier on the die roll to be made.
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
pub enum DiceRollModifier {
|
pub enum DiceRollModifier {
|
||||||
/// No bonuses or penalties.
|
/// No bonuses or penalties.
|
||||||
Normal,
|
Normal,
|
||||||
|
@ -37,11 +37,11 @@ pub enum DiceRollModifier {
|
||||||
impl fmt::Display for DiceRollModifier {
|
impl fmt::Display for DiceRollModifier {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
let message = match self {
|
let message = match self {
|
||||||
Self::Normal => "none",
|
Self::Normal => "no modifiers",
|
||||||
Self::OneBonus => "one bonus",
|
Self::OneBonus => "one bonus die",
|
||||||
Self::TwoBonus => "two bonus",
|
Self::TwoBonus => "two bonus dice",
|
||||||
Self::OnePenalty => "one penalty",
|
Self::OnePenalty => "one penalty die",
|
||||||
Self::TwoPenalty => "two penalty",
|
Self::TwoPenalty => "two penalty dice",
|
||||||
};
|
};
|
||||||
|
|
||||||
write!(f, "{}", message)?;
|
write!(f, "{}", message)?;
|
||||||
|
@ -100,6 +100,7 @@ pub struct RolledDice {
|
||||||
target: u32,
|
target: u32,
|
||||||
|
|
||||||
/// Stored for informational purposes in display.
|
/// Stored for informational purposes in display.
|
||||||
|
#[allow(dead_code)]
|
||||||
modifier: DiceRollModifier,
|
modifier: DiceRollModifier,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -141,15 +142,25 @@ impl fmt::Display for RolledDice {
|
||||||
|
|
||||||
/// A planned advancement roll, where the target number is the
|
/// A planned advancement roll, where the target number is the
|
||||||
/// existing skill amount.
|
/// existing skill amount.
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
pub struct AdvancementRoll {
|
pub struct AdvancementRoll {
|
||||||
/// The amount (0 to 100) of the existing skill. We must beat this
|
/// The amount (0 to 100) of the existing skill. We must beat this
|
||||||
/// target number to advance the skill, or roll above a 95.
|
/// target number to advance the skill, or roll above a 95.
|
||||||
pub existing_skill: u32,
|
pub existing_skill: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for AdvancementRoll {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
let message = format!("advancement for skill of {}", self.existing_skill);
|
||||||
|
write!(f, "{}", message)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A completed advancement roll.
|
/// A completed advancement roll.
|
||||||
pub struct RolledAdvancement {
|
pub struct RolledAdvancement {
|
||||||
existing_skill: u32,
|
existing_skill: u32,
|
||||||
|
num_rolled: u32,
|
||||||
advancement: u32,
|
advancement: u32,
|
||||||
successful: bool,
|
successful: bool,
|
||||||
}
|
}
|
||||||
|
@ -173,6 +184,27 @@ impl RolledAdvancement {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for RolledAdvancement {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
let message = if self.successful {
|
||||||
|
format!(
|
||||||
|
"success! new skill is {} (advanced by {}).",
|
||||||
|
self.new_skill_amount(),
|
||||||
|
self.advancement
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
format!("failure! skill remains at {}", self.existing_skill)
|
||||||
|
};
|
||||||
|
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"rolled {} against {}: {}",
|
||||||
|
self.num_rolled, self.existing_skill, message
|
||||||
|
)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
trait DieRoller {
|
trait DieRoller {
|
||||||
fn roll(&mut self) -> u32;
|
fn roll(&mut self) -> u32;
|
||||||
}
|
}
|
||||||
|
@ -248,12 +280,14 @@ impl AdvancementRoll {
|
||||||
|
|
||||||
if percentile_roll < self.existing_skill || percentile_roll > 95 {
|
if percentile_roll < self.existing_skill || percentile_roll > 95 {
|
||||||
RolledAdvancement {
|
RolledAdvancement {
|
||||||
|
num_rolled: percentile_roll,
|
||||||
existing_skill: self.existing_skill,
|
existing_skill: self.existing_skill,
|
||||||
advancement: roller.roll() + 1,
|
advancement: roller.roll() + 1,
|
||||||
successful: true,
|
successful: true,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
RolledAdvancement {
|
RolledAdvancement {
|
||||||
|
num_rolled: percentile_roll,
|
||||||
existing_skill: self.existing_skill,
|
existing_skill: self.existing_skill,
|
||||||
advancement: 0,
|
advancement: 0,
|
||||||
successful: false,
|
successful: false,
|
||||||
|
|
|
@ -1,16 +1,91 @@
|
||||||
use super::dice::{AdvancementRoll, DiceRoll};
|
use super::dice::{AdvancementRoll, DiceRoll, DiceRollModifier};
|
||||||
use crate::error::BotError;
|
use crate::parser::DiceParsingError;
|
||||||
use combine::error::StringStreamError;
|
|
||||||
use combine::parser::char::{digit, letter, spaces, string};
|
|
||||||
use combine::{choice, count, many, many1, one_of, Parser};
|
|
||||||
|
|
||||||
pub fn parse_roll(input: &str) -> Result<DiceRoll, ParsingError> {
|
//TOOD convert these to use parse_amounts from the common dice code.
|
||||||
Ok(DiceRoll {
|
|
||||||
target: 50,
|
pub fn parse_regular_roll(input: &str) -> Result<DiceRoll, DiceParsingError> {
|
||||||
modifier: DiceRollModifier::Normal,
|
let input = input.trim();
|
||||||
})
|
let target: u32 = input.parse().map_err(|_| DiceParsingError::InvalidAmount)?;
|
||||||
|
|
||||||
|
if target <= 100 {
|
||||||
|
Ok(DiceRoll {
|
||||||
|
target: target,
|
||||||
|
modifier: DiceRollModifier::Normal,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Err(DiceParsingError::InvalidAmount)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_advancement_roll(input: &str) -> Result<AdvancementRoll, ParsingError> {
|
pub fn parse_advancement_roll(input: &str) -> Result<AdvancementRoll, DiceParsingError> {
|
||||||
Ok(AdvancementRoll { existing_skill: 50 })
|
let input = input.trim();
|
||||||
|
let target: u32 = input.parse().map_err(|_| DiceParsingError::InvalidAmount)?;
|
||||||
|
|
||||||
|
if target <= 100 {
|
||||||
|
Ok(AdvancementRoll {
|
||||||
|
existing_skill: target,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Err(DiceParsingError::InvalidAmount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn regular_roll_accepts_single_number() {
|
||||||
|
let result = parse_regular_roll("60");
|
||||||
|
assert!(result.is_ok());
|
||||||
|
assert_eq!(
|
||||||
|
DiceRoll {
|
||||||
|
target: 60,
|
||||||
|
modifier: DiceRollModifier::Normal
|
||||||
|
},
|
||||||
|
result.unwrap()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn regular_roll_accepts_whitespacen() {
|
||||||
|
assert!(parse_regular_roll("60 ").is_ok());
|
||||||
|
assert!(parse_regular_roll(" 60").is_ok());
|
||||||
|
assert!(parse_regular_roll(" 60 ").is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn advancement_roll_accepts_whitespacen() {
|
||||||
|
assert!(parse_advancement_roll("60 ").is_ok());
|
||||||
|
assert!(parse_advancement_roll(" 60").is_ok());
|
||||||
|
assert!(parse_advancement_roll(" 60 ").is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn advancement_roll_accepts_single_number() {
|
||||||
|
let result = parse_advancement_roll("60");
|
||||||
|
assert!(result.is_ok());
|
||||||
|
assert_eq!(AdvancementRoll { existing_skill: 60 }, result.unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn regular_roll_rejects_big_numbers() {
|
||||||
|
assert!(parse_regular_roll("3000").is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn advancement_roll_rejects_big_numbers() {
|
||||||
|
assert!(parse_advancement_roll("3000").is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn regular_roll_rejects_invalid_input() {
|
||||||
|
assert!(parse_regular_roll("abc").is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn advancement_roll_rejects_invalid_input() {
|
||||||
|
assert!(parse_advancement_roll("abc").is_err());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ use thiserror::Error;
|
||||||
//******************************
|
//******************************
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Error)]
|
#[derive(Debug, Clone, Copy, PartialEq, Error)]
|
||||||
pub enum DiceParsingError {
|
pub enum DiceParsingError {
|
||||||
#[error("invalid amount of dice")]
|
#[error("invalid amount")]
|
||||||
InvalidAmount,
|
InvalidAmount,
|
||||||
|
|
||||||
#[error("modifiers not specified properly")]
|
#[error("modifiers not specified properly")]
|
||||||
|
|
Loading…
Reference in New Issue