Skip to content
Snippets Groups Projects
lib.rs 5.43 KiB
Newer Older
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
	}
}