Basic implementation of CoC dice rolling.
Not hooked up to parser or commands yet.
This commit is contained in:
parent
9f598541fb
commit
d5034c11dc
|
@ -0,0 +1 @@
|
|||
pub mod dice;
|
|
@ -0,0 +1,203 @@
|
|||
/// A planned dice roll.
|
||||
pub struct DiceRoll {
|
||||
target: u32,
|
||||
modifier: DiceRollModifier,
|
||||
}
|
||||
|
||||
/// Potential modifier on the die roll to be made.
|
||||
pub enum DiceRollModifier {
|
||||
/// No bonuses or penalties.
|
||||
Normal,
|
||||
|
||||
/// Roll one extra die and pick the lower of two results.
|
||||
OneBonus,
|
||||
|
||||
/// Roll two extra dice and pick the lower of all results.
|
||||
TwoBonus,
|
||||
|
||||
/// Roll one extra die and pick the higher of two results.
|
||||
OnePenalty,
|
||||
|
||||
/// Roll two extra dice and pick the higher of all results.
|
||||
TwoPenalty,
|
||||
}
|
||||
|
||||
/// The outcome of a die roll, either some kind of success or failure.
|
||||
enum RollResult {
|
||||
/// Basic success. The rolled number was equal to or less than the target number.
|
||||
Success,
|
||||
|
||||
/// Hard success means the rolled number was equal to or less than
|
||||
/// the target number divided by 2 (rounded down).
|
||||
HardSuccess,
|
||||
|
||||
/// Extreme success means the rolled number was equal to or less
|
||||
/// than the target number divided by 5 (rounded down).
|
||||
ExtremeSuccess,
|
||||
|
||||
/// A critical success occurs on a roll of 1.
|
||||
CriticalSuccess,
|
||||
|
||||
/// A basic failure means that the roll was above the target number.
|
||||
Failure,
|
||||
|
||||
/// A fumble occurs if the target number is below 50 and the roll
|
||||
/// was 96 - 100, OR if the roll result was 100. This means lower
|
||||
/// target numbers are more likely to produce a fumble.
|
||||
Fumble,
|
||||
}
|
||||
|
||||
/// The outcome of a roll.
|
||||
pub struct RolledDice {
|
||||
/// The d100 result actually rolled.
|
||||
num_rolled: u32,
|
||||
|
||||
/// The number we must meet for the roll to be considered a
|
||||
/// success.
|
||||
target: u32,
|
||||
|
||||
/// Stored for informational purposes in display.
|
||||
modifier: DiceRollModifier,
|
||||
}
|
||||
|
||||
impl RolledDice {
|
||||
/// Calculate what type of success or failure this roll is.
|
||||
/// Consult the RollResult enum for descriptions of what each
|
||||
/// result requires.
|
||||
fn result(&self) -> RollResult {
|
||||
let hard_target = self.target / 2u32;
|
||||
let extreme_target = self.target / 5u32;
|
||||
if (self.target < 50 && self.num_rolled > 95) || self.num_rolled == 100 {
|
||||
RollResult::Fumble
|
||||
} else if self.num_rolled == 1 {
|
||||
RollResult::CriticalSuccess
|
||||
} else if self.num_rolled <= extreme_target {
|
||||
RollResult::ExtremeSuccess
|
||||
} else if self.num_rolled <= hard_target {
|
||||
RollResult::HardSuccess
|
||||
} else if self.num_rolled <= self.target {
|
||||
RollResult::Success
|
||||
} else {
|
||||
RollResult::Failure
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A planned advancement roll, where the target number is the
|
||||
/// existing skill amount.
|
||||
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.
|
||||
existing_skill: u32,
|
||||
}
|
||||
|
||||
/// A completed advancement roll.
|
||||
pub struct RolledAdvancement {
|
||||
existing_skill: u32,
|
||||
advancement: u32,
|
||||
successful: bool,
|
||||
}
|
||||
|
||||
impl RolledAdvancement {
|
||||
/// The new skill amount, which will be the same if the roll was a
|
||||
/// failure.
|
||||
pub fn new_skill_amount(&self) -> u32 {
|
||||
self.existing_skill + self.advancement
|
||||
}
|
||||
|
||||
/// How much the skill advanced (1 to 10). 0 if the advancement
|
||||
/// roll failed.
|
||||
pub fn advancement(&self) -> u32 {
|
||||
self.advancement
|
||||
}
|
||||
|
||||
/// Whether or not the advancement roll was successful.
|
||||
pub fn successful(&self) -> bool {
|
||||
self.successful
|
||||
}
|
||||
}
|
||||
|
||||
trait DieRoller {
|
||||
fn roll(&mut self) -> u32;
|
||||
}
|
||||
|
||||
///A version of DieRoller that uses a rand::Rng to roll numbers.
|
||||
struct RngDieRoller<R: rand::Rng>(R);
|
||||
|
||||
impl<R: rand::Rng> DieRoller for RngDieRoller<R> {
|
||||
fn roll(&mut self) -> u32 {
|
||||
self.0.gen_range(0, 10)
|
||||
}
|
||||
}
|
||||
|
||||
/// Roll a single percentile die according to the rules. We cannot
|
||||
/// simply roll a d100 due to the way the game calculates roll results
|
||||
/// with bonus/penalty dice. The unit roll (ones place) is added to
|
||||
/// the tens roll, unless both results are 0, in which case the result
|
||||
/// is 100.
|
||||
fn roll_percentile_dice<R: DieRoller>(roller: &mut R, unit_roll: u32) -> u32 {
|
||||
let tens_roll = roller.roll() * 10;
|
||||
|
||||
if tens_roll == 0 && unit_roll == 0 {
|
||||
100
|
||||
} else {
|
||||
tens_roll + unit_roll
|
||||
}
|
||||
}
|
||||
|
||||
/// Make a roll with a target number and potential modifier. In a
|
||||
/// normal roll, only one percentile die is rolled (1d100). With
|
||||
/// bonuses or penalties, more dice are rolled, and either the lowest
|
||||
/// (in case of bonus) or highest (in case of penalty) result is
|
||||
/// picked. Rolls are not simply d100; the unit roll (ones place) is
|
||||
/// rolled separately from the tens place, and then the unit number is
|
||||
/// added to each potential roll before picking the lowest/highest
|
||||
/// result.
|
||||
fn roll(roll: DiceRoll) -> RolledDice {
|
||||
use DiceRollModifier::*;
|
||||
let num_rolls = match roll.modifier {
|
||||
Normal => 1,
|
||||
OneBonus | OnePenalty => 2,
|
||||
TwoBonus | TwoPenalty => 3,
|
||||
};
|
||||
|
||||
let mut roller = RngDieRoller(rand::thread_rng());
|
||||
let unit_roll = roller.roll();
|
||||
|
||||
let rolls: Vec<u32> = (0..num_rolls)
|
||||
.map(|_| roll_percentile_dice(&mut roller, unit_roll))
|
||||
.collect();
|
||||
|
||||
let num_rolled = match roll.modifier {
|
||||
Normal => rolls.first(),
|
||||
OneBonus | TwoBonus => rolls.iter().min(),
|
||||
OnePenalty | TwoPenalty => rolls.iter().max(),
|
||||
}
|
||||
.unwrap();
|
||||
|
||||
RolledDice {
|
||||
modifier: roll.modifier,
|
||||
num_rolled: *num_rolled,
|
||||
target: roll.target,
|
||||
}
|
||||
}
|
||||
|
||||
fn advancement_roll(roll: AdvancementRoll) -> RolledAdvancement {
|
||||
let mut roller = RngDieRoller(rand::thread_rng());
|
||||
let unit_roll = roller.roll();
|
||||
let percentile_roll = roll_percentile_dice(&mut roller, unit_roll);
|
||||
|
||||
if percentile_roll < roll.existing_skill || percentile_roll > 95 {
|
||||
RolledAdvancement {
|
||||
existing_skill: roll.existing_skill,
|
||||
advancement: roller.roll() + 1,
|
||||
successful: true,
|
||||
}
|
||||
} else {
|
||||
RolledAdvancement {
|
||||
existing_skill: roll.existing_skill,
|
||||
advancement: 0,
|
||||
successful: false,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,6 +3,7 @@ pub mod cofd;
|
|||
pub mod commands;
|
||||
pub mod config;
|
||||
pub mod context;
|
||||
pub mod cthulhu;
|
||||
pub mod db;
|
||||
pub mod dice;
|
||||
pub mod error;
|
||||
|
|
Loading…
Reference in New Issue