use std::ops::Add; use crate::{TileLayout, TileMatcher}; use crate::output::TileOutput; /// Checks tile layouts against a matcher instance, and uses the output to produce a value #[derive(Clone, Debug, Default)] pub struct AutoTileRule { /// The pattern that this rule will use for matching pub matcher: TileMatcher, /// The value produced when this rule gets matched pub output: TileOutput, /// When used as part of a set of rules, this value (0.0 - 1.0) determines the chance that /// a successful match will generate an output from this rule pub chance: f32, } impl AutoTileRule { /// Create a rule that will always produce `output_value` when the target tile matches /// `input_value` pub const fn exact(input_value: i32, output_value: i32) -> Self { Self::exact_chance(input_value, output_value, 1.0) } /// Create a rule that will produce `output_value` when the target tile matches /// `input_value` and the selection chance is rolled under the value of `chance` (0.0 to 1.0) pub const fn exact_chance(input_value: i32, output_value: i32, chance: f32) -> Self { AutoTileRule { matcher: TileMatcher::single(input_value), output: TileOutput::single(output_value), chance, } } /// Create a rule that will always produce `output_value` when `matcher` evaluates to /// `true` pub const fn single_when(matcher: TileMatcher, output_value: i32) -> Self { AutoTileRule { matcher, output: TileOutput::single(output_value), chance: 1.0, } } /// Create a rule that will always produce one of the values contained in `output_value` /// when the target tile matches `input_value` pub const fn single_any(input_value: i32, output_value: Vec<i32>) -> Self { Self::single_any_chance(input_value, output_value, 1.0) } /// Create a rule that will produce one of the values contained in `output_value` /// when the target tile matches `input_value` and the selection chacne is rolled under the /// value of `chance` (0.0 to 1.0) pub const fn single_any_chance( input_value: i32, output_value: Vec<i32>, chance: f32, ) -> Self { AutoTileRule { matcher: TileMatcher::single(input_value), output: TileOutput::any(output_value), chance, } } /// Create a rule that will produce one of the values contained in `output_value` /// when `matcher` evaluates to `true` and the selection chance is rolled under /// the value of `chance` (0.0 to 1.0) pub const fn any_any_chance( input_value: TileMatcher, output_value: Vec<i32>, chance: f32, ) -> Self { AutoTileRule { matcher: input_value, output: TileOutput::any(output_value), chance, } } /// Evaluate this rule and return the unresolved output value. "None" represents either no /// match or a match that failed its chance roll. /// /// Will use a default randomly seeded RNG to evaluate the chance roll for this rule #[cfg(feature = "impl_fastrand")] pub fn get_match(&self, input: &TileLayout) -> Option<&TileOutput> { let chance = fastrand::f32(); if chance <= self.chance && self.matcher.matches(input) { Some(&self.output) } else { None } } /// Evaluate this rule and return the unresolved output value. "None" represents either no /// match or a match that failed its chance roll. /// /// Will use the provided RNG to evaluate the chance roll for this rule #[cfg(feature = "impl_fastrand")] pub fn get_match_seeded( &self, input: &TileLayout, seeded: &fastrand::Rng, ) -> Option<&TileOutput> { let chance = seeded.f32(); if chance <= self.chance && self.matcher.matches(input) { Some(&self.output) } else { None } } /// Evaluate this rule and produce an output, if a match is found. "None" represents either /// no match, a match that resolved to `TileOutput::Skip`, or a match that failed its chance /// roll. /// /// Will use a default randomly seeded RNG to select from a list, if the output resolves to /// a random selection #[cfg(feature = "impl_fastrand")] pub fn resolve_match(&self, input: &TileLayout) -> Option<i32> { self.get_match(input).and_then(|out| out.resolve()) } /// Evaluate this rule and produce an output, if a match is found. "None" represents either /// no match, a match that resolved to `TileOutput::Skip`, or a match that failed its chance /// roll. /// /// Will use the provided RNG to select from a list, if the output resolves to /// a random selection #[cfg(feature = "impl_fastrand")] pub fn resolve_match_seeded( &self, input: &TileLayout, seeded: &fastrand::Rng, ) -> Option<i32> { self.get_match_seeded(input, seeded) .and_then(|out| out.resolve_with(seeded)) } } /// Holds a list of rules, for efficiently evaluating a tile layout against multiple exclusive rules. /// Rules will be evaluated in the order they are added to the set, and will stop evaluating when /// a match is found #[derive(Clone, Debug, Default)] pub struct AutoRuleSet(pub Vec<AutoTileRule>); impl Add<AutoRuleSet> for AutoRuleSet { type Output = AutoRuleSet; /// Combine two AutoRuleSet values, where the rules in the right hand side /// will be appended to the end of the set represented by the left hand /// side fn add(self, rhs: AutoRuleSet) -> Self::Output { AutoRuleSet([self.0.as_slice(), rhs.0.as_slice()].concat()) } } impl From<AutoTileRule> for AutoRuleSet { /// Create a rule set from a single rule /// /// ```rust /// # use micro_autotile::AutoRuleSet; /// use micro_autotile::AutoTileRule; /// /// fn main() { /// use micro_autotile::TileLayout; /// let rule_set: AutoRuleSet = AutoTileRule::exact(1, 2).into(); /// /// assert_eq!(rule_set.resolve_match(&TileLayout::single(1)), Some(2)); /// # } /// ``` fn from(value: AutoTileRule) -> Self { Self(vec![value]) } } impl From<Vec<AutoTileRule>> for AutoRuleSet { /// Convert a set of rules into a rule set /// /// ```rust /// # use micro_autotile::AutoRuleSet; /// use micro_autotile::AutoTileRule; /// /// fn main() { /// use micro_autotile::TileLayout; /// let rule_set: AutoRuleSet = vec![ /// AutoTileRule::exact(1, 2), /// AutoTileRule::exact(5123, 231) /// ].into(); /// /// assert_eq!(rule_set.resolve_match(&TileLayout::single(1)), Some(2)); /// # } /// ``` fn from(value: Vec<AutoTileRule>) -> Self { Self(value) } } impl AutoRuleSet { /// Evaluate this set of rules and return the unresolved output value from the first match. /// A return value of `None` means that no rules have matched. /// /// Will use a default randomly seeded RNG to evaluate the chance roll for each matching rule #[cfg(feature = "impl_fastrand")] pub fn get_match(&self, input: &TileLayout) -> Option<&TileOutput> { for rule in self.0.iter() { let result = rule.get_match(input); if result.is_some() { return result; } } None } /// Evaluate this set of rules and return the unresolved output value from the first match. /// A return value of `None` means that no rules have matched, or all matching results failed /// their chance roll or resolved to `TileOutput::Skip`. /// /// Will use the provided RNG to evaluate the chance roll for each matching rule #[cfg(feature = "impl_fastrand")] pub fn get_match_seeded( &self, input: &TileLayout, seeded: &fastrand::Rng, ) -> Option<&TileOutput> { for rule in self.0.iter() { let result = rule.get_match_seeded(input, seeded); if result.is_some() { return result; } } None } /// Evaluate this set of rules and produce an output, if a match is found. /// A return value of `None` means that no rules have matched, or all matching results failed /// their chance roll or resolved to `TileOutput::Skip`. /// /// Will use a default randomly seeded RNG to select from a list, if the output resolves to /// a random selection #[cfg(feature = "impl_fastrand")] pub fn resolve_match(&self, input: &TileLayout) -> Option<i32> { self.get_match(input).and_then(|out| out.resolve()) } /// Evaluate this set of rules and produce an output, if a match is found. /// A return value of `None` means that no rules have matched, or all matching results failed /// their chance roll or resolved to `TileOutput::Skip`. /// /// Will use the provided RNG to select from a list, if the output resolves to /// a random selection #[cfg(feature = "impl_fastrand")] pub fn resolve_match_seeded( &self, input: &TileLayout, seeded: &fastrand::Rng, ) -> Option<i32> { self.get_match_seeded(input, seeded) .and_then(|out| out.resolve_with(seeded)) } }