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"
|
||||
}
|
||||
|
||||
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.
|
||||
let roll = self.0.roll();
|
||||
let plain = format!("Roll: {}\nResult: {}", self.0, roll);
|
||||
|
@ -25,3 +25,22 @@ impl Command for CthRoll {
|
|||
}
|
||||
|
||||
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::{
|
||||
basic_rolling::RollCommand,
|
||||
cofd::PoolRollCommand,
|
||||
cthulhu::CthRoll,
|
||||
cthulhu::{CthAdvanceRoll, CthRoll},
|
||||
misc::HelpCommand,
|
||||
variables::{
|
||||
DeleteVariableCommand, GetAllVariablesCommand, GetVariableCommand, SetVariableCommand,
|
||||
},
|
||||
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::error::BotError;
|
||||
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> {
|
||||
let roll = DiceRoll {
|
||||
target: 50,
|
||||
modifier: DiceRollModifier::Normal,
|
||||
};
|
||||
let roll = parse_regular_roll(input)?;
|
||||
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> {
|
||||
let pool = create_chance_die()?;
|
||||
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)),
|
||||
"r" | "roll" => parse_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)),
|
||||
"help" => help(&cmd_input).map(|command| Some(command)),
|
||||
// No recognized command, ignore this.
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
pub mod dice;
|
||||
pub mod parser;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use std::fmt;
|
||||
|
||||
/// A planned dice roll.
|
||||
#[derive(Clone, Copy)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct DiceRoll {
|
||||
pub target: u32,
|
||||
pub modifier: DiceRollModifier,
|
||||
|
@ -9,14 +9,14 @@ pub struct DiceRoll {
|
|||
|
||||
impl fmt::Display for DiceRoll {
|
||||
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)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Potential modifier on the die roll to be made.
|
||||
#[derive(Clone, Copy)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum DiceRollModifier {
|
||||
/// No bonuses or penalties.
|
||||
Normal,
|
||||
|
@ -37,11 +37,11 @@ pub enum DiceRollModifier {
|
|||
impl fmt::Display for DiceRollModifier {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let message = match self {
|
||||
Self::Normal => "none",
|
||||
Self::OneBonus => "one bonus",
|
||||
Self::TwoBonus => "two bonus",
|
||||
Self::OnePenalty => "one penalty",
|
||||
Self::TwoPenalty => "two penalty",
|
||||
Self::Normal => "no modifiers",
|
||||
Self::OneBonus => "one bonus die",
|
||||
Self::TwoBonus => "two bonus dice",
|
||||
Self::OnePenalty => "one penalty die",
|
||||
Self::TwoPenalty => "two penalty dice",
|
||||
};
|
||||
|
||||
write!(f, "{}", message)?;
|
||||
|
@ -100,6 +100,7 @@ pub struct RolledDice {
|
|||
target: u32,
|
||||
|
||||
/// Stored for informational purposes in display.
|
||||
#[allow(dead_code)]
|
||||
modifier: DiceRollModifier,
|
||||
}
|
||||
|
||||
|
@ -141,15 +142,25 @@ impl fmt::Display for RolledDice {
|
|||
|
||||
/// A planned advancement roll, where the target number is the
|
||||
/// existing skill amount.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct AdvancementRoll {
|
||||
/// The amount (0 to 100) of the existing skill. We must beat this
|
||||
/// target number to advance the skill, or roll above a 95.
|
||||
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.
|
||||
pub struct RolledAdvancement {
|
||||
existing_skill: u32,
|
||||
num_rolled: u32,
|
||||
advancement: u32,
|
||||
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 {
|
||||
fn roll(&mut self) -> u32;
|
||||
}
|
||||
|
@ -248,12 +280,14 @@ impl AdvancementRoll {
|
|||
|
||||
if percentile_roll < self.existing_skill || percentile_roll > 95 {
|
||||
RolledAdvancement {
|
||||
num_rolled: percentile_roll,
|
||||
existing_skill: self.existing_skill,
|
||||
advancement: roller.roll() + 1,
|
||||
successful: true,
|
||||
}
|
||||
} else {
|
||||
RolledAdvancement {
|
||||
num_rolled: percentile_roll,
|
||||
existing_skill: self.existing_skill,
|
||||
advancement: 0,
|
||||
successful: false,
|
||||
|
|
|
@ -1,16 +1,91 @@
|
|||
use super::dice::{AdvancementRoll, DiceRoll};
|
||||
use crate::error::BotError;
|
||||
use combine::error::StringStreamError;
|
||||
use combine::parser::char::{digit, letter, spaces, string};
|
||||
use combine::{choice, count, many, many1, one_of, Parser};
|
||||
use super::dice::{AdvancementRoll, DiceRoll, DiceRollModifier};
|
||||
use crate::parser::DiceParsingError;
|
||||
|
||||
pub fn parse_roll(input: &str) -> Result<DiceRoll, ParsingError> {
|
||||
Ok(DiceRoll {
|
||||
target: 50,
|
||||
modifier: DiceRollModifier::Normal,
|
||||
})
|
||||
//TOOD convert these to use parse_amounts from the common dice code.
|
||||
|
||||
pub fn parse_regular_roll(input: &str) -> Result<DiceRoll, DiceParsingError> {
|
||||
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> {
|
||||
Ok(AdvancementRoll { existing_skill: 50 })
|
||||
pub fn parse_advancement_roll(input: &str) -> Result<AdvancementRoll, DiceParsingError> {
|
||||
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)]
|
||||
pub enum DiceParsingError {
|
||||
#[error("invalid amount of dice")]
|
||||
#[error("invalid amount")]
|
||||
InvalidAmount,
|
||||
|
||||
#[error("modifiers not specified properly")]
|
||||
|
|
Loading…
Reference in New Issue