Skip to content
Snippets Groups Projects
Verified Commit bc09e4cf authored by Louis's avatar Louis :fire:
Browse files

Refactor types into sub modules

parent 73baf0c5
No related branches found
No related tags found
No related merge requests found
...@@ -14,4 +14,4 @@ default = ["impl_fastrand"] ...@@ -14,4 +14,4 @@ default = ["impl_fastrand"]
impl_fastrand = ["dep:fastrand"] impl_fastrand = ["dep:fastrand"]
[dependencies] [dependencies]
fastrand = { version = "1.8.0", optional = true } fastrand = { version = "1.9.0", optional = true }
\ No newline at end of file \ No newline at end of file
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))
}
}
/// Represents how a single tile location should be matched when evaluating a rule
#[derive(Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Default, Copy, Clone)]
pub enum TileStatus {
/// This tile will always match, regardless of the input tile
#[default]
Ignore,
/// This tile will only match when there is no input tile (`None`)
Nothing,
/// This tile will always match as long as the tile exists (`Option::is_some`)
Anything,
/// This tile will match as long as the input tile exists and the input value is the same as this value
Is(i32),
/// This tile will match as long as the input tile exists and the input value is anything other than this value
IsNot(i32),
}
impl PartialEq<Option<i32>> for TileStatus {
fn eq(&self, other: &Option<i32>) -> bool {
match self {
Self::Ignore => true,
Self::Nothing => other.is_none(),
Self::Anything => other.is_some(),
Self::Is(value) => &Some(*value) == other,
Self::IsNot(value) => &Some(*value) != other,
}
}
}
impl TileStatus {
#[deprecated(since = "0.2.0", note = "Use `TileStatus::into` directly instead")]
pub fn to_ldtk_value(self) -> i64 {
self.into_tile() as i64
}
#[deprecated(since = "0.2.0", note = "Use `TileStatus::from` directly instead")]
pub fn from_ldtk_value(value: i64) -> Self {
Self::from(value)
}
}
/// Holds a grid of raw input data, as a more ideal format for interop and storage
#[repr(transparent)]
pub struct TileLayout(pub [Option<i32>; 9]);
impl TileLayout {
/// Create a 1x1 grid of tile data
pub fn single(value: i32) -> Self {
TileLayout([None, None, None, None, Some(value), None, None, None, None])
}
/// Construct a filled 3x3 grid of tile data
pub fn filled(values: [i32; 9]) -> Self {
TileLayout(values.map(Some))
}
/// Construct a filled 3x3 grid of identical tile data
pub fn spread(value: i32) -> Self {
TileLayout([Some(value); 9])
}
/// Filter the layout data so that it only contains the tiles surrounding the target tile. This
/// means that the array index of every entry before the center point will match the original data
/// array, but ever entry after the center point will have its index shifted down by 1
///
/// The main utility of this is to perform set operations on every tile _other_ than the target tile.
///
/// ## Examples
///
/// ```
/// # use micro_autotile::TileLayout;
/// let layout = TileLayout::single(123);
/// let has_any_surrounding_tiles = layout.surrounding()
/// .iter()
/// .any(|tile| tile.is_some());
///
/// assert_eq!(has_any_surrounding_tiles, false);
/// ```
pub fn surrounding(&self) -> [Option<i32>; 8] {
[
self.0[0], self.0[1], self.0[2], self.0[3], self.0[5], self.0[6], self.0[7], self.0[8],
]
}
}
/// Holds the evaluation rules for a 3x3 grid of tiles. A 1x1 grid of tile matchers
/// can be created by providing an array of `TileStatus` structs that are all `TileStatus::Ignore`,
/// except for the value in the fifth position
///
/// e.g.
///
/// ```
/// # use micro_autotile::{TileMatcher, TileStatus};
/// let matcher = TileMatcher([
/// TileStatus::Ignore, TileStatus::Ignore, TileStatus::Ignore,
/// TileStatus::Ignore, TileStatus::Anything, TileStatus::Ignore,
/// TileStatus::Ignore, TileStatus::Ignore, TileStatus::Ignore,
/// ]);
/// ```
#[derive(Clone, Debug, Default)]
#[repr(transparent)]
pub struct TileMatcher(pub [TileStatus; 9]);
impl TileMatcher {
/// Create a 1x1 matcher, where the target tile must be the supplied `value`
pub const fn single(value: i32) -> Self {
Self([
TileStatus::Ignore,
TileStatus::Ignore,
TileStatus::Ignore,
TileStatus::Ignore,
TileStatus::Is(value),
TileStatus::Ignore,
TileStatus::Ignore,
TileStatus::Ignore,
TileStatus::Ignore,
])
}
/// Create a 1x1 matcher, with any rule for the target tile
pub const fn single_match(value: TileStatus) -> Self {
Self([
TileStatus::Ignore,
TileStatus::Ignore,
TileStatus::Ignore,
TileStatus::Ignore,
value,
TileStatus::Ignore,
TileStatus::Ignore,
TileStatus::Ignore,
TileStatus::Ignore,
])
}
/// Check if the given input layout of tile data conforms to this matcher
pub fn matches(&self, layout: &TileLayout) -> bool {
self.0
.iter()
.zip(layout.0.iter())
.all(|(status, reality)| *status == *reality)
}
/// Load data from an LDTK JSON file. Currently supports 1x1 and 3x3 matchers.
/// Other sizes of matcher will result in `None`
pub fn from_ldtk_array(value: Vec<i64>) -> Option<Self> {
if value.len() == 1 {
let tile = value[0];
Some(Self::single_match(TileStatus::from(tile)))
} else if value.len() == 9 {
Some(TileMatcher(
[
value[0], value[1], value[2], value[3], value[4], value[5], value[6], value[7],
value[8],
]
.map(TileStatus::from),
))
} else {
None
}
}
}
\ No newline at end of file
...@@ -42,12 +42,12 @@ ...@@ -42,12 +42,12 @@
//! //!
//! e.g. //! e.g.
//! ```rust //! ```rust
//! # use micro_autotile::{AutoTileRule, TileLayout, TileOutput}; //! # use micro_autotile::{TileLayout, TileMatcher};
//! use micro_autotile::{AutoTileRule, AutoRuleSet};
//!
//! # fn main() { //! # fn main() {
//! use micro_autotile::TileMatcher; //! const WALL_TILE: i32 = 0;
//! // Tile maps often use unsigned integers to represent different types of tiles //! const GROUND_TILE: i32 = 1;
//! const WALL_TILE: usize = 0;
//! const GROUND_TILE: usize = 1;
//! //!
//! // Match a 1x1 ground tile, output the index within the spritesheet that we'll use for rendering //! // Match a 1x1 ground tile, output the index within the spritesheet that we'll use for rendering
//! let match_1_x_1 = AutoTileRule::exact(GROUND_TILE, 57); //! let match_1_x_1 = AutoTileRule::exact(GROUND_TILE, 57);
...@@ -87,12 +87,14 @@ ...@@ -87,12 +87,14 @@
//! instances //! instances
//! //!
//! ```rust //! ```rust
//! # use micro_autotile::{AutoTileRule, AutoRuleSet, TileLayout, TileOutput}; //! # use micro_autotile::{TileLayout, TileMatcher};
//! use micro_autotile::{AutoTileRule, AutoRuleSet};
//!
//! # fn main() { //! # fn main() {
//! use micro_autotile::{TileMatcher, TileStatus}; //! use micro_autotile::{TileMatcher, TileStatus};
//! const WALL_TILE: usize = 0; //! const WALL_TILE: i32 = 0;
//! const GROUND_TILE: usize = 1; //! const GROUND_TILE: i32 = 1;
//! const OTHER_TILE: usize = 342; //! const OTHER_TILE: i32 = 342;
//! //!
//! let wall_rules = AutoRuleSet(vec![ //! let wall_rules = AutoRuleSet(vec![
//! AutoTileRule::single_when(TileMatcher([ // Top Left Corner //! AutoTileRule::single_when(TileMatcher([ // Top Left Corner
...@@ -132,480 +134,12 @@ ...@@ -132,480 +134,12 @@
//! # } //! # }
//! ``` //! ```
use std::ops::Add; mod layout;
mod autotile;
/// Represents how a single tile location should be matched when evaluating a rule mod output;
#[derive(Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Default, Copy, Clone)] mod utils;
pub enum TileStatus {
/// This tile will always match, regardless of the input tile
#[default]
Ignore,
/// This tile will only match when there is no input tile (`None`)
Nothing,
/// This tile will always match as long as the tile exists (`Option::is_some`)
Anything,
/// This tile will match as long as the input tile exists and the input value is the same as this value
Is(usize),
/// This tile will match as long as the input tile exists and the input value is anything other than this value
IsNot(usize),
}
impl PartialEq<Option<usize>> for TileStatus {
fn eq(&self, other: &Option<usize>) -> bool {
match self {
Self::Ignore => true,
Self::Nothing => other.is_none(),
Self::Anything => other.is_some(),
Self::Is(value) => &Some(*value) == other,
Self::IsNot(value) => &Some(*value) != other,
}
}
}
impl TileStatus {
pub fn to_ldtk_value(&self) -> i64 {
match self {
Self::Ignore => 0,
Self::Nothing => -1000001,
Self::Anything => 1000001,
Self::Is(value) => *value as i64,
Self::IsNot(value) => -(*value as i64),
}
}
pub fn from_ldtk_value(value: i64) -> Self {
match value {
0 => Self::Ignore,
1000001 => Self::Anything,
-1000001 => Self::Nothing,
other => {
if other > 0 {
Self::Is(other as usize)
} else {
Self::IsNot(other.unsigned_abs() as usize)
}
}
}
}
}
/// Holds the evaluation rules for a 3x3 grid of tiles. A 1x1 grid of tile matchers
/// can be created by providing an array of `TileStatus` structs that are all `TileStatus::Ignore`,
/// except for the value in the fifth position
///
/// e.g.
///
/// ```
/// # use micro_autotile::{TileMatcher, TileStatus};
/// let matcher = TileMatcher([
/// TileStatus::Ignore, TileStatus::Ignore, TileStatus::Ignore,
/// TileStatus::Ignore, TileStatus::Anything, TileStatus::Ignore,
/// TileStatus::Ignore, TileStatus::Ignore, TileStatus::Ignore,
/// ]);
/// ```
#[derive(Clone, Debug, Default)]
#[repr(transparent)]
pub struct TileMatcher(pub [TileStatus; 9]);
impl TileMatcher {
/// Create a 1x1 matcher, where the target tile must be the supplied `value`
pub const fn single(value: usize) -> Self {
Self([
TileStatus::Ignore,
TileStatus::Ignore,
TileStatus::Ignore,
TileStatus::Ignore,
TileStatus::Is(value),
TileStatus::Ignore,
TileStatus::Ignore,
TileStatus::Ignore,
TileStatus::Ignore,
])
}
/// Create a 1x1 matcher, with any rule for the target tile
pub const fn single_match(value: TileStatus) -> Self {
Self([
TileStatus::Ignore,
TileStatus::Ignore,
TileStatus::Ignore,
TileStatus::Ignore,
value,
TileStatus::Ignore,
TileStatus::Ignore,
TileStatus::Ignore,
TileStatus::Ignore,
])
}
/// Check if the given input layout of tile data conforms to this matcher
pub fn matches(&self, layout: &TileLayout) -> bool {
self.0
.iter()
.zip(layout.0.iter())
.all(|(status, reality)| *status == *reality)
}
/// Load data from an LDTK JSON file. Currently supports 1x1 and 3x3 matchers.
/// Other sizes of matcher will result in `None`
pub fn from_ldtk_array(value: Vec<i64>) -> Option<Self> {
if value.len() == 1 {
let tile = value[0];
Some(Self::single_match(TileStatus::from_ldtk_value(tile)))
} else if value.len() == 9 {
Some(TileMatcher(
[
value[0], value[1], value[2], value[3], value[4], value[5], value[6], value[7],
value[8],
]
.map(TileStatus::from_ldtk_value),
))
} else {
None
}
}
}
/// Represents a grid of input data. What this data means is dependant on your application, and
/// could realistically correlate to anything. It is assumed to be a 3x3 slice of tile data from a
/// tile map
#[derive(Clone, Debug, Default)]
#[repr(transparent)]
pub struct TileLayout(pub [Option<usize>; 9]);
impl TileLayout {
/// Create a 1x1 grid of tile data
pub fn single(value: usize) -> Self {
TileLayout([None, None, None, None, Some(value), None, None, None, None])
}
/// Construct a filled 3x3 grid of tile data
pub fn filled(values: [usize; 9]) -> Self {
TileLayout(values.map(Some))
}
/// Construct a filled 3x3 grid of identical tile data
pub fn spread(value: usize) -> Self {
TileLayout([Some(value); 9])
}
/// Filter the layout data so that it only contains the tiles surrounding the target tile. The main
/// utility of this is to perform set operations on every tile _other_ than the target tile.
///
/// e.g.
///
/// ```
/// # use micro_autotile::TileLayout;
/// let layout = TileLayout::single(123);
/// let has_any_surrounding_tiles = layout.surrounding()
/// .iter()
/// .any(|tile| tile.is_some());
///
/// assert_eq!(has_any_surrounding_tiles, false);
/// ```
pub fn surrounding(&self) -> [Option<usize>; 8] {
[
self.0[0], self.0[1], self.0[2], self.0[3], self.0[5], self.0[6], self.0[7], self.0[8],
]
}
}
/// Represents the value produced when a rule is matched. Will need to be inspected to find out
/// the raw data value. This value will typically correspond to an index in a spritesheet, but
/// there is no proscribed meaning - it will be application dependant and could represent some
/// other index or meaning
#[derive(Clone, Debug, Default)]
pub enum TileOutput {
/// This output should be skipped. Noop equivalent
#[default]
Skip,
/// This exact value should be produces
Single(usize),
/// Some method should be used to select one of the values in this list
Random(Vec<usize>),
}
impl TileOutput {
/// Create an output that can produce the input value when this output is selected
pub const fn single(value: usize) -> Self {
TileOutput::Single(value)
}
/// Create an output that can produce any of these input values when this output is selected
pub const fn any(value: Vec<usize>) -> Self {
TileOutput::Random(value)
}
/// Produce the value this output represents. Will use a default randomly seeded RNG to
/// select from a list, if appropriate
#[cfg(feature = "impl_fastrand")]
pub fn resolve(&self) -> Option<usize> {
self.resolve_with(&fastrand::Rng::default())
}
/// Produce the value this output represents. Will use a default randomly seeded RNG to
/// select from a list, if appropriate
#[cfg(feature = "impl_fastrand")]
pub fn resolve_with(&self, rng: &fastrand::Rng) -> Option<usize> {
match self {
Self::Skip => None,
Self::Single(val) => Some(*val),
Self::Random(vals) => vals.get(rng.usize(0..vals.len())).copied(),
}
}
}
/// 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: usize, output_value: usize) -> 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: usize, output_value: usize, 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: usize) -> 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: usize, output_value: Vec<usize>) -> 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: usize,
output_value: Vec<usize>,
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 when `matcher` evaluates to `true` and the selection chacne is rolled under
/// the value of `chance` (0.0 to 1.0)
pub const fn any_any_chance(
input_value: TileMatcher,
output_value: Vec<usize>,
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<usize> {
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 a 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<usize> {
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, 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, 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. pub use layout::*;
/// A return value of `None` means that no rules have matched, or all matching results failed pub use autotile::*;
/// their chance roll or resolved to `TileOutput::Skip`. pub use output::*;
///
/// 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<usize> {
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<usize> {
self.get_match_seeded(input, seeded)
.and_then(|out| out.resolve_with(seeded))
}
}
/// Represents the value produced when a rule is matched. Will need to be inspected to find out
/// the raw data value. This value will typically correspond to an index in a spritesheet, but
/// there is no proscribed meaning - it will be application dependant and could represent some
/// other index or meaning
#[derive(Clone, Debug, Default)]
pub enum TileOutput {
/// This output should be skipped. Noop equivalent
#[default]
Skip,
/// This exact value should be produces
Single(i32),
/// Some method should be used to select one of the values in this list
Random(Vec<i32>),
}
impl TileOutput {
/// Create an output that can produce the input value when this output is selected
pub const fn single(value: i32) -> Self {
TileOutput::Single(value)
}
/// Create an output that can produce any of these input values when this output is selected
pub const fn any(value: Vec<i32>) -> Self {
TileOutput::Random(value)
}
/// Produce the value this output represents. Will use a default randomly seeded RNG to
/// select from a list, if appropriate
#[cfg(feature = "impl_fastrand")]
pub fn resolve(&self) -> Option<i32> {
self.resolve_with(&fastrand::Rng::default())
}
/// Produce the value this output represents. Will use a default randomly seeded RNG to
/// select from a list, if appropriate
#[cfg(feature = "impl_fastrand")]
pub fn resolve_with(&self, rng: &fastrand::Rng) -> Option<i32> {
match self {
Self::Skip => None,
Self::Single(val) => Some(*val),
Self::Random(vals) => vals.get(rng.usize(0..vals.len())).copied(),
}
}
}
\ No newline at end of file
use crate::TileStatus;
pub trait IntoTile {
fn into_tile(self) -> i32;
}
// Implementations for unsigned integer types
impl IntoTile for u8 {
fn into_tile(self) -> i32 {
self as i32
}
}
impl IntoTile for u16 {
fn into_tile(self) -> i32 {
self as i32
}
}
impl IntoTile for u32 {
fn into_tile(self) -> i32 {
self as i32
}
}
impl IntoTile for u64 {
fn into_tile(self) -> i32 {
self as i32
}
}
impl IntoTile for u128 {
fn into_tile(self) -> i32 {
self as i32
}
}
impl IntoTile for usize {
fn into_tile(self) -> i32 {
self as i32
}
}
// Implementations for signed integer types
impl IntoTile for i8 {
fn into_tile(self) -> i32 {
self as i32
}
}
impl IntoTile for i16 {
fn into_tile(self) -> i32 {
self as i32
}
}
impl IntoTile for i32 {
fn into_tile(self) -> i32 {
self
}
}
impl IntoTile for i64 {
fn into_tile(self) -> i32 {
self as i32
}
}
impl IntoTile for i128 {
fn into_tile(self) -> i32 {
self as i32
}
}
impl IntoTile for isize {
fn into_tile(self) -> i32 {
self as i32
}
}
// We don't actually implement IntoTile for TileStatus, as this would conflict with a later From impl
impl TileStatus {
pub fn into_tile(self) -> i32 {
match self {
Self::Ignore => 0,
Self::Nothing => -1000001,
Self::Anything => 1000001,
Self::Is(value) => value,
Self::IsNot(value) => -(value),
}
}
}
impl From<TileStatus> for i32 {
fn from(value: TileStatus) -> Self {
value.into_tile()
}
}
impl From<TileStatus> for i64 {
fn from(value: TileStatus) -> Self {
value.into_tile() as i64
}
}
impl <I: IntoTile> From<I> for TileStatus {
fn from(value: I) -> Self {
let value = value.into_tile();
match value {
0 => Self::Ignore,
1000001 => Self::Anything,
-1000001 => Self::Nothing,
other => {
if other > 0 {
Self::Is(other.into_tile())
} else {
Self::IsNot(other.abs().into_tile())
}
}
}
}
}
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment