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

Convert layout rules to work against 7x7 grids; add a number of conversion...

Convert layout rules to work against 7x7 grids; add a number of conversion methods for converting into the correct format; add custom alternate debug renders for grid based items
parent bc09e4cf
No related branches found
No related tags found
No related merge requests found
...@@ -42,8 +42,8 @@ index in some theoretical sprite sheet. ...@@ -42,8 +42,8 @@ index in some theoretical sprite sheet.
chance for that rule to be chosen. chance for that rule to be chosen.
```rust ```rust
use micro_autotile::AutoTileRule; use micro_autotile::AutoTileRule;
const GROUND: usize = 0; const GROUND: usize = 1;
const WALL: usize = 1; const WALL: usize = 2;
let alt_ground_rule = AutoTileRule::single_any_chance(GROUND, vec![123, 124, 125], 0.2); let alt_ground_rule = AutoTileRule::single_any_chance(GROUND, vec![123, 124, 125], 0.2);
let fallback_ground_rule = AutoTileRule::exact(GROUND, 126); let fallback_ground_rule = AutoTileRule::exact(GROUND, 126);
......
use std::fmt::{Debug, Formatter};
use std::ops::Add; use std::ops::Add;
use crate::{TileLayout, TileMatcher}; use crate::{TileLayout, TileMatcher};
use crate::output::TileOutput; use crate::output::TileOutput;
/// Checks tile layouts against a matcher instance, and uses the output to produce a value /// Checks tile layouts against a matcher instance, and uses the output to produce a value
#[derive(Clone, Debug, Default)] #[derive(Clone, Default)]
pub struct AutoTileRule { pub struct AutoTileRule {
/// The pattern that this rule will use for matching /// The pattern that this rule will use for matching
pub matcher: TileMatcher, pub matcher: TileMatcher,
...@@ -14,6 +15,17 @@ pub struct AutoTileRule { ...@@ -14,6 +15,17 @@ pub struct AutoTileRule {
pub chance: f32, pub chance: f32,
} }
impl Debug for AutoTileRule {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
if f.alternate() {
// Perform grid style formatting for matcher value
write!(f, "AutoTileRule \n{:#?}output: {:?}, chance: {:.2}", self.matcher, self.output, self.chance)
} else {
write!(f, "AutoTileRule {{ matcher: {:?}, output: {:?}, chance: {:.2} }}", self.matcher, self.output, self.chance)
}
}
}
impl AutoTileRule { impl AutoTileRule {
/// Create a rule that will always produce `output_value` when the target tile matches /// Create a rule that will always produce `output_value` when the target tile matches
/// `input_value` /// `input_value`
...@@ -25,7 +37,7 @@ impl AutoTileRule { ...@@ -25,7 +37,7 @@ impl AutoTileRule {
/// `input_value` and the selection chance is rolled under the value of `chance` (0.0 to 1.0) /// `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 { pub const fn exact_chance(input_value: i32, output_value: i32, chance: f32) -> Self {
AutoTileRule { AutoTileRule {
matcher: TileMatcher::single(input_value), matcher: TileMatcher::single_match(input_value),
output: TileOutput::single(output_value), output: TileOutput::single(output_value),
chance, chance,
} }
...@@ -56,7 +68,7 @@ impl AutoTileRule { ...@@ -56,7 +68,7 @@ impl AutoTileRule {
chance: f32, chance: f32,
) -> Self { ) -> Self {
AutoTileRule { AutoTileRule {
matcher: TileMatcher::single(input_value), matcher: TileMatcher::single_match(input_value),
output: TileOutput::any(output_value), output: TileOutput::any(output_value),
chance, chance,
} }
......
use std::fmt::{write, Debug, Formatter};
use crate::utils::IntoTile;
/// The size of the grid that can be matched; equal to the length of one side of the square grid
const RULE_MAGNITUDE: usize = 7;
/// Number of array entries required to store all the data for a grid
const TILE_GRID_SIZE: usize = RULE_MAGNITUDE.pow(2);
/// The index of the center tile in the grid
const GRID_CENTER: usize = (TILE_GRID_SIZE - 1) / 2;
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub struct NotSquareError;
impl std::fmt::Display for NotSquareError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Input is not a square grid")
}
}
impl std::error::Error for NotSquareError {}
/// Represents how a single tile location should be matched when evaluating a rule /// Represents how a single tile location should be matched when evaluating a rule
#[derive(Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Default, Copy, Clone)] #[derive(Ord, PartialOrd, Eq, PartialEq, Hash, Default, Copy, Clone)]
pub enum TileStatus { pub enum TileStatus {
/// This tile will always match, regardless of the input tile /// This tile will always match, regardless of the input tile
#[default] #[default]
...@@ -14,15 +33,29 @@ pub enum TileStatus { ...@@ -14,15 +33,29 @@ pub enum TileStatus {
IsNot(i32), IsNot(i32),
} }
impl Debug for TileStatus {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::Ignore => write!(f, "Any Or No Tile"),
Self::Nothing => write!(f, "No Tile"),
Self::Anything => write!(f, "Any Tile"),
Self::Is(value) => write!(f, "Must Match [{}]", value),
Self::IsNot(value) => write!(f, "Must Not Match [{}]", value),
}
}
}
impl PartialEq<Option<i32>> for TileStatus { impl PartialEq<Option<i32>> for TileStatus {
fn eq(&self, other: &Option<i32>) -> bool { fn eq(&self, other: &Option<i32>) -> bool {
match self { let matched = match self {
Self::Ignore => true, Self::Ignore => true,
Self::Nothing => other.is_none(), Self::Nothing => other.is_none(),
Self::Anything => other.is_some(), Self::Anything => other.is_some(),
Self::Is(value) => &Some(*value) == other, Self::Is(value) => &Some(*value) == other,
Self::IsNot(value) => &Some(*value) != other, Self::IsNot(value) => &Some(*value) != other,
} };
matched
} }
} }
...@@ -41,22 +74,24 @@ impl TileStatus { ...@@ -41,22 +74,24 @@ impl TileStatus {
/// Holds a grid of raw input data, as a more ideal format for interop and storage /// Holds a grid of raw input data, as a more ideal format for interop and storage
#[repr(transparent)] #[repr(transparent)]
pub struct TileLayout(pub [Option<i32>; 9]); pub struct TileLayout(pub [Option<i32>; TILE_GRID_SIZE]);
impl TileLayout { impl TileLayout {
/// Create a 1x1 grid of tile data /// Create a 1x1 grid of tile data
pub fn single(value: i32) -> Self { pub fn single(value: i32) -> Self {
TileLayout([None, None, None, None, Some(value), None, None, None, None]) let mut grid = [None; TILE_GRID_SIZE];
grid[GRID_CENTER] = Some(value);
TileLayout(grid)
} }
/// Construct a filled 3x3 grid of tile data /// Construct a filled grid of tile data
pub fn filled(values: [i32; 9]) -> Self { pub fn filled(values: [i32; TILE_GRID_SIZE]) -> Self {
TileLayout(values.map(Some)) TileLayout(values.map(Some))
} }
/// Construct a filled 3x3 grid of identical tile data /// Construct a filled grid of identical tile data
pub fn spread(value: i32) -> Self { pub fn spread(value: i32) -> Self {
TileLayout([Some(value); 9]) TileLayout([Some(value); TILE_GRID_SIZE])
} }
/// Filter the layout data so that it only contains the tiles surrounding the target tile. This /// Filter the layout data so that it only contains the tiles surrounding the target tile. This
...@@ -83,53 +118,88 @@ impl TileLayout { ...@@ -83,53 +118,88 @@ impl TileLayout {
} }
} }
/// Holds the evaluation rules for a 3x3 grid of tiles. A 1x1 grid of tile matchers impl Debug for TileLayout {
/// can be created by providing an array of `TileStatus` structs that are all `TileStatus::Ignore`, fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
/// except for the value in the fifth position if f.alternate() {
/// let lines = as_lines(&self.0);
/// e.g. let mut max_width = 1;
///
/// ``` for line in lines.iter() {
/// # use micro_autotile::{TileMatcher, TileStatus}; for value in line.iter() {
/// let matcher = TileMatcher([ if let Some(value) = value {
/// TileStatus::Ignore, TileStatus::Ignore, TileStatus::Ignore, max_width = max_width.max(value.to_string().len());
/// TileStatus::Ignore, TileStatus::Anything, TileStatus::Ignore, }
/// TileStatus::Ignore, TileStatus::Ignore, TileStatus::Ignore, }
/// ]); }
/// ```
#[derive(Clone, Debug, Default)] for line in lines {
for value in line {
if let Some(value) = value {
write!(f, "{:^max_width$?} ", value)?;
} else {
write!(f, "{:^max_width$} ", "#")?;
}
}
write!(f, "\n")?;
}
write!(f, "\n")
} else {
writeln!(f, "{:?}", self.0)
}
}
}
impl <T> TryFrom<&[T]> for TileLayout where T: IntoTile + Copy + Default + Debug {
type Error = NotSquareError;
fn try_from(value: &[T]) -> Result<Self, Self::Error> {
if is_square(value.len()) {
let formatted = transpose(value);
Ok(Self(formatted.map(|t| if t.into_tile() == 0 { None } else { Some(t.into_tile()) })))
} else {
Err(NotSquareError)
}
}
}
/// Holds parsed tile rules that are used to evaluate a layout
#[derive(Clone, Copy)]
#[repr(transparent)] #[repr(transparent)]
pub struct TileMatcher(pub [TileStatus; 9]); pub struct TileMatcher(pub [TileStatus; TILE_GRID_SIZE]);
impl Debug for TileMatcher {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
if f.alternate() {
let lines = as_lines(&self.0);
writeln!(f, "Tile Matcher")?;
for line in lines {
for value in line {
write!(f, "{:?} ", value)?;
}
write!(f, "\n")?;
}
writeln!(f, "\n")
} else {
write!(f, "TileMatcher({:?})", self.0)
}
}
}
impl Default for TileMatcher {
fn default() -> Self {
TileMatcher([TileStatus::default(); TILE_GRID_SIZE])
}
}
impl TileMatcher { impl TileMatcher {
/// Create a 1x1 matcher, where the target tile must be the supplied `value` pub const fn single(value: TileStatus) -> Self {
pub const fn single(value: i32) -> Self { let mut rules = [TileStatus::Ignore; TILE_GRID_SIZE];
Self([ rules[GRID_CENTER] = value;
TileStatus::Ignore, TileMatcher(rules)
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 /// Create a 1x1 matcher, with any rule for the target tile
pub const fn single_match(value: TileStatus) -> Self { pub const fn single_match(value: i32) -> Self {
Self([ Self::single(TileStatus::Is(value))
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 /// Check if the given input layout of tile data conforms to this matcher
...@@ -140,22 +210,165 @@ impl TileMatcher { ...@@ -140,22 +210,165 @@ impl TileMatcher {
.all(|(status, reality)| *status == *reality) .all(|(status, reality)| *status == *reality)
} }
/// Load data from an LDTK JSON file. Currently supports 1x1 and 3x3 matchers. /// Load data from an LDTK JSON file. Supports arbitrary sized matchers for any square grid.
/// Other sizes of matcher will result in `None` /// Other sizes of matcher will result in `None`
pub fn from_ldtk_array(value: Vec<i64>) -> Option<Self> { pub fn from_ldtk_array(value: Vec<i64>) -> Option<Self> {
if value.len() == 1 { if is_square(value.len()) {
let tile = value[0]; Some(Self(transpose(value.as_slice()).map(TileStatus::from)))
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 { } else {
None None
} }
} }
}
impl <T> TryFrom<&[T]> for TileMatcher where T: IntoTile + Copy + Default {
type Error = NotSquareError;
fn try_from(value: &[T]) -> Result<Self, Self::Error> {
if is_square(value.len()) {
let formatted = transpose(value);
Ok(Self(formatted.map(TileStatus::from)))
} else {
Err(NotSquareError)
}
}
}
impl TryFrom<&[TileStatus]> for TileMatcher {
type Error = NotSquareError;
fn try_from(value: &[TileStatus]) -> Result<Self, Self::Error> {
if is_square(value.len()) {
Ok(Self(transpose(value)))
} else {
Err(NotSquareError)
}
}
}
/// Convert a square grid of arbitrary size into one of the expected autotiler grid size (7x7). Odd
/// numbered grids will be centered in the output, while even numbered grids will be offset by 1 to
/// the top and left of the output.
///
/// This method will panic if the provided list of values is not a square grid
///
/// ## Example
///
/// For a 2x2 input grid:
///
/// ```text
/// 2 2
/// 2 2
/// ```
///
/// Applying this function will result in the following output grid:
///
/// ```text
/// 1 1 1 1 1 1 1
/// 1 1 1 1 1 1 1
/// 1 1 2 2 1 1 1
/// 1 1 2 2 1 1 1
/// 1 1 1 1 1 1 1
/// 1 1 1 1 1 1 1
/// 1 1 1 1 1 1 1
/// ```
///
/// ## Example
///
/// For a 3x3 input grid:
///
/// ```text
/// 3 3 3
/// 3 3 3
/// 3 3 3
/// ```
///
/// Applying this function will result in the following output grid:
///
/// ```text
/// 1 1 1 1 1 1 1
/// 1 1 1 1 1 1 1
/// 1 1 3 3 3 1 1
/// 1 1 3 3 3 1 1
/// 1 1 3 3 3 1 1
/// 1 1 1 1 1 1 1
/// 1 1 1 1 1 1 1
/// ```
///
fn transpose<Value>(input: &[Value]) -> [Value; TILE_GRID_SIZE] where Value: Default + Copy {
if input.len() == TILE_GRID_SIZE {
match input.try_into() {
Ok(output) => return output,
Err(_) => {
// This should never happen since we've already checked the length - just in case,
// we want it to fall through and run the rest of the algorithm
}
}
}
if !is_square(input.len()) {
// Length isn't square == it does not represent a square grid
panic!("Input must be a square grid");
}
let input_size = (input.len() as f64).sqrt() as usize;
let mut output = [Value::default(); TILE_GRID_SIZE];
let output_start = if input_size < RULE_MAGNITUDE {
// Even padding requires our initial x coord to be half the size difference from the edge.
// Even sized squares will be offset to by one to the left and top, which is preferable to
// rejecting even squares altogether
(RULE_MAGNITUDE - input_size) / 2
} else {
0
};
let input_start = if input_size > RULE_MAGNITUDE {
(input_size - RULE_MAGNITUDE) / 2
} else {
0
};
for x in 0..input_size {
for y in 0..input_size {
let adjusted_input_x = x + input_start;
let adjusted_input_y = y + input_start;
let input_idx = adjusted_input_y * input_size + adjusted_input_x;
let adjusted_output_x = x + output_start;
let adjusted_output_y = y + output_start;
let output_idx = adjusted_output_y * RULE_MAGNITUDE + adjusted_output_x;
output[output_idx] = input[input_idx];
}
}
output
}
fn is_square(n: usize) -> bool {
let sqrt_n = (n as f64).sqrt() as usize;
n == sqrt_n.pow(2)
}
fn as_lines<T>(input: &[T]) -> Vec<&[T]> {
input.chunks(RULE_MAGNITUDE).collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_transposes_odd_grids() {
let input = [2, 2, 2, 2, 2, 2, 2, 2, 2];
let expected = [
0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0,
0, 0, 2, 2, 2, 0, 0,
0, 0, 2, 2, 2, 0, 0,
0, 0, 2, 2, 2, 0, 0,
0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0,
];
assert_eq!(expected, transpose(&input));
}
} }
\ No newline at end of file
...@@ -46,8 +46,8 @@ ...@@ -46,8 +46,8 @@
//! use micro_autotile::{AutoTileRule, AutoRuleSet}; //! use micro_autotile::{AutoTileRule, AutoRuleSet};
//! //!
//! # fn main() { //! # fn main() {
//! const WALL_TILE: i32 = 0; //! const WALL_TILE: i32 = 1;
//! const GROUND_TILE: i32 = 1; //! const GROUND_TILE: i32 = 2;
//! //!
//! // 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);
...@@ -60,11 +60,12 @@ ...@@ -60,11 +60,12 @@
//! // More realistically, we might have some tile data for a ground tile with other data arround it. //! // More realistically, we might have some tile data for a ground tile with other data arround it.
//! // When we match against a rule, we're always trying to produce a value for the _central_ value in //! // When we match against a rule, we're always trying to produce a value for the _central_ value in
//! // our `TileLayout` (the fifth element) //! // our `TileLayout` (the fifth element)
//! let enclosed_ground = TileLayout([ //! let layout_3_x_3 = [
//! Some(WALL_TILE), Some(WALL_TILE), Some(WALL_TILE), //! Some(WALL_TILE), Some(WALL_TILE), Some(WALL_TILE),
//! Some(WALL_TILE), Some(GROUND_TILE), Some(WALL_TILE), //! Some(WALL_TILE), Some(GROUND_TILE), Some(WALL_TILE),
//! Some(WALL_TILE), Some(WALL_TILE), Some(WALL_TILE), //! Some(WALL_TILE), Some(WALL_TILE), Some(WALL_TILE),
//! ]); //! ];
//! let enclosed_ground = TileLayout::try_from(layout_3_x_3.as_slice()).unwrap();
//! //!
//! assert_eq!( //! assert_eq!(
//! match_1_x_1.resolve_match(&enclosed_ground), //! match_1_x_1.resolve_match(&enclosed_ground),
...@@ -73,7 +74,7 @@ ...@@ -73,7 +74,7 @@
//! //!
//! // There may also be situations in which you just want to know that a given layout matches a rule, without //! // There may also be situations in which you just want to know that a given layout matches a rule, without
//! // concern for producing a value for that layout. You can directly use a `TileMatcher` for this //! // concern for producing a value for that layout. You can directly use a `TileMatcher` for this
//! assert!(TileMatcher::single(GROUND_TILE).matches(&enclosed_ground)); //! assert!(TileMatcher::single_match(GROUND_TILE).matches(&enclosed_ground));
//! # } //! # }
//! ``` //! ```
//! //!
...@@ -92,21 +93,21 @@ ...@@ -92,21 +93,21 @@
//! //!
//! # fn main() { //! # fn main() {
//! use micro_autotile::{TileMatcher, TileStatus}; //! use micro_autotile::{TileMatcher, TileStatus};
//! const WALL_TILE: i32 = 0; //! const WALL_TILE: i32 = 1;
//! const GROUND_TILE: i32 = 1; //! const GROUND_TILE: i32 = 2;
//! const OTHER_TILE: i32 = 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::try_from([ // Top Left Corner
//! TileStatus::IsNot(WALL_TILE), TileStatus::IsNot(WALL_TILE), TileStatus::IsNot(WALL_TILE), //! TileStatus::IsNot(WALL_TILE), TileStatus::IsNot(WALL_TILE), TileStatus::IsNot(WALL_TILE),
//! TileStatus::IsNot(WALL_TILE), TileStatus::Is(WALL_TILE), TileStatus::Is(WALL_TILE), //! TileStatus::IsNot(WALL_TILE), TileStatus::Is(WALL_TILE), TileStatus::Is(WALL_TILE),
//! TileStatus::IsNot(WALL_TILE), TileStatus::Is(WALL_TILE), TileStatus::Is(WALL_TILE), //! TileStatus::IsNot(WALL_TILE), TileStatus::Is(WALL_TILE), TileStatus::Is(WALL_TILE),
//! ]), 54), //! ].as_slice()).unwrap(), 54),
//! AutoTileRule::single_when(TileMatcher([ // Top Right Corner //! AutoTileRule::single_when(TileMatcher::try_from([ // Top Right Corner
//! TileStatus::IsNot(WALL_TILE), TileStatus::IsNot(WALL_TILE), TileStatus::IsNot(WALL_TILE), //! TileStatus::IsNot(WALL_TILE), TileStatus::IsNot(WALL_TILE), TileStatus::IsNot(WALL_TILE),
//! TileStatus::Is(WALL_TILE), TileStatus::Is(WALL_TILE), TileStatus::IsNot(WALL_TILE), //! TileStatus::Is(WALL_TILE), TileStatus::Is(WALL_TILE), TileStatus::IsNot(WALL_TILE),
//! TileStatus::Is(WALL_TILE), TileStatus::Is(WALL_TILE), TileStatus::IsNot(WALL_TILE), //! TileStatus::Is(WALL_TILE), TileStatus::Is(WALL_TILE), TileStatus::IsNot(WALL_TILE),
//! ]), 55), //! ].as_slice()).unwrap(), 55),
//! // ... Etc //! // ... Etc
//! ]); //! ]);
//! //!
...@@ -120,15 +121,16 @@ ...@@ -120,15 +121,16 @@
//! // Easily merge rule sets in an ordered way //! // Easily merge rule sets in an ordered way
//! let combined_rules = wall_rules + ground_rules; //! let combined_rules = wall_rules + ground_rules;
//! //!
//! let sublayout = TileLayout([ //! let sublayout = TileLayout::try_from([
//! Some(OTHER_TILE), Some(GROUND_TILE), Some(GROUND_TILE), //! Some(OTHER_TILE), Some(GROUND_TILE), Some(GROUND_TILE),
//! Some(WALL_TILE), Some(WALL_TILE), Some(OTHER_TILE), //! Some(WALL_TILE), Some(WALL_TILE), Some(OTHER_TILE),
//! Some(WALL_TILE), Some(WALL_TILE), Some(GROUND_TILE), //! Some(WALL_TILE), Some(WALL_TILE), Some(GROUND_TILE),
//! ]); //! ].as_slice()).unwrap();
//! //!
//! // We've got a layout that represents the top right corner of a wall, the second rule in our //! // We've got a layout that represents the top right corner of a wall, the second rule in our
//! // set - the value of the tiles that match "IsNot(WALL_TILE)" are irrelevant, as long as they //! // set - the value of the tiles that match "IsNot(WALL_TILE)" are irrelevant, as long as they
//! // exist (Option::Some) //! // exist (Option::Some)
//!
//! let output = combined_rules.resolve_match(&sublayout); //! let output = combined_rules.resolve_match(&sublayout);
//! assert_eq!(output, Some(55)); //! assert_eq!(output, Some(55));
//! # } //! # }
......
...@@ -103,6 +103,23 @@ impl From<TileStatus> for i64 { ...@@ -103,6 +103,23 @@ impl From<TileStatus> for i64 {
} }
} }
impl <T: IntoTile> IntoTile for Option<T> {
fn into_tile(self) -> i32 {
match self {
Some(value) => value.into_tile(),
None => 0,
}
}
}
impl <T: IntoTile, E> IntoTile for Result<T, E> {
fn into_tile(self) -> i32 {
match self {
Ok(value) => value.into_tile(),
Err(_) => 0,
}
}
}
impl <I: IntoTile> From<I> for TileStatus { impl <I: IntoTile> From<I> for TileStatus {
fn from(value: I) -> Self { fn from(value: I) -> Self {
......
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