Newer
Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
/// 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
}
}
}