From 08b0e5819393bc638d307f1e4023b049602f64ed Mon Sep 17 00:00:00 2001 From: projectmoon Date: Sat, 31 Oct 2020 14:03:18 +0000 Subject: [PATCH] Implement parsing of Cthulhu dice, only basic for now. Does not understand anything besides single numbers at the moment. --- src/commands/cthulhu.rs | 21 ++++++++- src/commands/parser.rs | 19 +++++--- src/cthulhu.rs | 1 + src/cthulhu/dice.rs | 50 +++++++++++++++++---- src/cthulhu/parser.rs | 99 ++++++++++++++++++++++++++++++++++++----- src/parser.rs | 2 +- 6 files changed, 163 insertions(+), 29 deletions(-) diff --git a/src/commands/cthulhu.rs b/src/commands/cthulhu.rs index 508f626..9f18074 100644 --- a/src/commands/cthulhu.rs +++ b/src/commands/cthulhu.rs @@ -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!( + "

Roll: {}

Result: {}

", + self.0, roll + ); + + Execution { plain, html } + } +} diff --git a/src/commands/parser.rs b/src/commands/parser.rs index 1275af2..482a5b6 100644 --- a/src/commands/parser.rs +++ b/src/commands/parser.rs @@ -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, BotError> { } fn parse_cth_roll(input: &str) -> Result, 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, BotError> { + let roll = parse_advancement_roll(input)?; + Ok(Box::new(CthAdvanceRoll(roll))) +} + fn chance_die() -> Result, BotError> { let pool = create_chance_die()?; Ok(Box::new(PoolRollCommand(pool))) @@ -121,7 +123,10 @@ pub fn parse_command(input: &str) -> Result>, 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. diff --git a/src/cthulhu.rs b/src/cthulhu.rs index 99ad3e8..8406843 100644 --- a/src/cthulhu.rs +++ b/src/cthulhu.rs @@ -1 +1,2 @@ pub mod dice; +pub mod parser; diff --git a/src/cthulhu/dice.rs b/src/cthulhu/dice.rs index 0143d47..6449a9a 100644 --- a/src/cthulhu/dice.rs +++ b/src/cthulhu/dice.rs @@ -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, diff --git a/src/cthulhu/parser.rs b/src/cthulhu/parser.rs index 7b7f9fd..32f88a7 100644 --- a/src/cthulhu/parser.rs +++ b/src/cthulhu/parser.rs @@ -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 { - 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 { + 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 { - Ok(AdvancementRoll { existing_skill: 50 }) +pub fn parse_advancement_roll(input: &str) -> Result { + 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()); + } } diff --git a/src/parser.rs b/src/parser.rs index 5d74687..7285ce6 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -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")]