Implement parsing of Cthulhu dice, only basic for now.

Does not understand anything besides single numbers at the moment.
This commit is contained in:
projectmoon 2020-10-31 14:03:18 +00:00 committed by ProjectMoon
parent c290393ddf
commit 08b0e58193
6 changed files with 163 additions and 29 deletions

View File

@ -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 }
}
}

View File

@ -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.

View File

@ -1 +1,2 @@
pub mod dice;
pub mod parser;

View File

@ -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,

View File

@ -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());
}
}

View File

@ -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")]