use std::ops::Add; #[derive(Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Default, Copy, Clone)] pub enum TileStatus { #[default] Ignore, Nothing, Anything, Is(usize), 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) } } } } } #[derive(Clone, Debug, Default)] #[repr(transparent)] pub struct TileMatcher(pub [TileStatus; 9]); impl TileMatcher { 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, ]) } 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, ]) } pub fn matches(&self, layout: &TileLayout) -> bool { self.0 .iter() .zip(layout.0.iter()) .all(|(status, reality)| *status == *reality) } 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 } } } #[derive(Clone, Debug, Default)] #[repr(transparent)] pub struct TileLayout(pub [Option<usize>; 9]); impl TileLayout { pub fn single(value: usize) -> Self { TileLayout([None, None, None, None, Some(value), None, None, None, None]) } 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], ] } } #[derive(Clone, Debug, Default)] pub enum TileOutput { #[default] Skip, Single(usize), Random(Vec<usize>), } impl TileOutput { pub const fn single(value: usize) -> Self { TileOutput::Single(value) } pub const fn any(value: Vec<usize>) -> Self { TileOutput::Random(value) } } #[derive(Clone, Debug, Default)] pub struct AutoTileRule { pub matcher: TileMatcher, pub output: TileOutput, pub chance: f32, } impl AutoTileRule { pub const fn exact(input_value: usize, output_value: usize) -> Self { Self::exact_chance(input_value, output_value, 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, } } pub const fn single_when(matcher: TileMatcher, output_value: usize) -> Self { AutoTileRule { matcher, output: TileOutput::single(output_value), chance: 1.0, } } pub const fn single_any(input_value: usize, output_value: Vec<usize>) -> Self { Self::single_any_chance(input_value, output_value, 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, } } 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, } } #[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 } } #[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 } } } #[derive(Clone, Debug, Default)] pub struct AutoRuleSet(pub Vec<AutoTileRule>); impl Add<AutoRuleSet> for AutoRuleSet { type Output = AutoRuleSet; fn add(self, rhs: AutoRuleSet) -> Self::Output { AutoRuleSet([self.0.as_slice(), rhs.0.as_slice()].concat()) } } impl AutoRuleSet { #[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 } #[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 } }