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