fyrox_autotile/auto.rs
1// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in all
11// copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19// SOFTWARE.
20
21use std::{fmt::Debug, ops::Deref};
22
23use super::*;
24
25/// The contraints that control how the autotiler searches for tiles at each position.
26pub trait AutoConstrain {
27 /// The type of a tile's position.
28 type Position: OffsetPosition;
29 /// The type of tile terrains. Each terrain represents a set of possible patterns.
30 type Terrain: TileTerrain;
31 /// An iterator for all the positions that the autotiler should consider when
32 /// choosing tiles. The autotiler will select tiles for position in the order
33 /// they are delivered by this iterator.
34 fn all_positions(&self) -> impl Iterator<Item = &Self::Position>;
35 /// The contraint for the given position. This allows the autotiler to examine
36 /// adjacent cells when deciding if a particular pattern is a valid choice
37 /// for the cell under consideration.
38 fn constraint_at(
39 &self,
40 position: &Self::Position,
41 ) -> TileConstraint<Self::Terrain, <Self::Terrain as TileTerrain>::Pattern>;
42 /// True if the given patterns may be placed adjacent to each other with the
43 /// given offset. For example, if `offset` is up, then return true if
44 /// `to` may be legally placed above `from`.
45 fn is_legal(
46 &self,
47 from: &<Self::Terrain as TileTerrain>::Pattern,
48 offset: &<Self::Position as OffsetPosition>::Offset,
49 to: &<Self::Terrain as TileTerrain>::Pattern,
50 ) -> bool;
51 /// True if the given patterns may be placed adjacent to each other with the
52 /// given offset. For example, if `offset` is up, then return true if
53 /// `to` may be legally placed above `from`.
54 fn is_legal_diagonal(
55 &self,
56 from: &<Self::Terrain as TileTerrain>::Pattern,
57 diagonal: &<Self::Position as OffsetPosition>::Diagonal,
58 to: &<Self::Terrain as TileTerrain>::Pattern,
59 ) -> bool;
60}
61
62/// For autotiling, tiles are grouped into patterns, and patterns are grouped
63/// into terrains. Each cell that is to be autotiled is given a terrain, and
64/// the autotiler must choose a pattern from within that terrain.
65pub trait TileTerrain {
66 /// The type of the patterns within this terrain.
67 type Pattern;
68 /// Iterate through the patterns within the terrain in the order of priority.
69 /// The autotiler will proceed through this iterator until it finds a pattern
70 /// that is legal according to the constraints, then ignore any remaining patterns.
71 fn all_patterns(&self) -> impl Iterator<Item = &Self::Pattern>;
72}
73
74/// Trait for objects that represent a particular autotiling problem to be solved,
75/// with a set of positions that need tiles and a terrain for each position to control
76/// which tiles are valid.
77pub trait TerrainSource {
78 /// The type of positions that need tiles.
79 type Position;
80 /// The type of the terrains that control which patterns are allowed at each position.
81 type Terrain;
82 /// Iterate over the positions that need tiles and the terrains at each position.
83 fn iter(&self) -> impl Iterator<Item = NeededTerrain<Self::Position, Self::Terrain>> + '_;
84 /// True if the given position needs to be tiled.
85 fn contains_position(&self, position: &Self::Position) -> bool;
86}
87
88/// This represents a cell that needs to be autotiled, including its position,
89/// its terrain, and the rules for what to do with the surrounding tiles.
90pub struct NeededTerrain<Pos, Ter> {
91 /// The position of the cell to be autotiled.
92 pub position: Pos,
93 /// The terrain which specifies the valid patterns for the cell.
94 pub terrain: Ter,
95 /// The rules for whether surrounding tiles may be changed while
96 /// autitiling this cell.
97 pub fill: ConstraintFillRules,
98}
99
100/// Trait for objects that represent a constraining environment for the autotiler.
101/// Given any position, a `PatternSource` object can supply a [`TileConstraint`] for that position.
102/// While a [`TerrainSource`] object alone may be enough to tell the autotiler what positions it needs
103/// to fill with tiles, a `PatternSource` can provide information about the neighboring tiles, and thus
104/// give the autotiler a complete picture of the environment it is tiling.
105pub trait PatternSource {
106 /// The type of positions that need tiles.
107 type Position;
108 /// The type of the terrains that control which patterns are allowed at each position.
109 type Terrain;
110 /// The type of the patterns that the autotiler must choose for each position.
111 type Pattern;
112 /// The contraint for the cell at the given position.
113 fn get(&self, position: &Self::Position) -> TileConstraint<Self::Terrain, Self::Pattern>;
114 /// The terrain of the pattern at the given position.
115 fn get_terrain(&self, position: &Self::Position) -> Option<Self::Terrain>;
116}
117
118/// When a cell is autotiled it may be desired that adjacent cells should be modified
119/// to fit with the new terrain of this cell. This object specifies whether that should
120/// be done. Cells that are added this way keep their current terrain, but their pattern
121/// may change.
122#[derive(Debug, Default, Clone, Copy)]
123pub struct ConstraintFillRules {
124 /// True if adjacent cells should be automatically added to the autotiling constraint
125 /// so that they may be modified.
126 pub include_adjacent: bool,
127 /// True if diagonal cells should be automatically added to the autotiling constraint
128 /// so that they may be modified.
129 pub include_diagonal: bool,
130}
131
132/// A pattern source that is stored in a hash map.
133#[derive(Clone)]
134pub struct HashConstraintMap<Pos, Ter, Pat> {
135 constraints: FxHashMap<Pos, TileConstraint<Ter, Pat>>,
136 terrain_cells: Vec<Pos>,
137}
138
139impl<Ter: Debug, Pat: Debug> Debug for HashConstraintMap<Vector2<i32>, Ter, Pat> {
140 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
141 f.write_str("HashConstraintMap:\n")?;
142 for (p, v) in self.constraints.iter() {
143 writeln!(f, " ({:2},{:2})->{v:?}", p.x, p.y)?;
144 }
145 write!(f, "terrain_cells: {:?}", self.terrain_cells)
146 }
147}
148
149impl<Pos, Ter, Pat> HashConstraintMap<Pos, Ter, Pat>
150where
151 Pos: Hash + Eq + OffsetPosition,
152 Ter: Clone,
153 Pat: Clone,
154{
155 /// Replace the content of this map using the given `PatternSource` and `TerrainSource`.
156 /// The `TerrainSource` specifies certain cells that need to be given tiles by specifying
157 /// the required terrain for those tiles. The terrains and patterns for the surrounding
158 /// cells are taken from the `PatternSource`.
159 ///
160 /// The immediately adjacent cells are added to the front of the list of cells to be
161 /// assigned tiles and their terrain is taken from the `PatternSource`. The cells that
162 /// are one further away are make immutable by constraining them to be exactly their
163 /// current pattern from `PatternSource`.
164 pub fn fill_from<T, P>(&mut self, terrains: &T, patterns: &P)
165 where
166 T: TerrainSource<Position = Pos, Terrain = Ter>,
167 P: PatternSource<Position = Pos, Terrain = Ter, Pattern = Pat>,
168 {
169 self.clear();
170 for ter in terrains.iter() {
171 if ter.fill.include_diagonal {
172 for offset in Pos::all_diagonals() {
173 let p = ter.position.clone() + offset;
174 if !self.terrain_cells.contains(&p) && !terrains.contains_position(&p) {
175 if let Some(terrain) = patterns.get_terrain(&p) {
176 self.insert(p.clone(), TileConstraint::Terrain(terrain));
177 }
178 }
179 }
180 }
181 if ter.fill.include_adjacent {
182 for offset in Pos::all_offsets() {
183 let p = ter.position.clone() + offset;
184 if !self.terrain_cells.contains(&p) && !terrains.contains_position(&p) {
185 if let Some(terrain) = patterns.get_terrain(&p) {
186 self.insert(p.clone(), TileConstraint::Terrain(terrain));
187 }
188 }
189 }
190 }
191 }
192 for ter in terrains.iter() {
193 self.insert(ter.position, TileConstraint::Terrain(ter.terrain));
194 }
195 for pos in self.terrain_cells.clone().iter() {
196 for offset in Pos::all_offsets() {
197 let p = pos.clone() + offset;
198 if !self.terrain_cells.contains(&p) {
199 self.insert(p.clone(), patterns.get(&p));
200 }
201 }
202 }
203 }
204}
205
206impl<Pos, Ter, Pat> HashConstraintMap<Pos, Ter, Pat>
207where
208 Pos: Hash + Eq + Clone,
209{
210 /// Makes this map empty, so all cells have [`TileConstraint::None`].
211 pub fn clear(&mut self) {
212 self.constraints.clear();
213 self.terrain_cells.clear();
214 }
215 /// Add the given constraint to this map.
216 pub fn insert(&mut self, pos: Pos, constraint: TileConstraint<Ter, Pat>) {
217 if constraint.is_terrain() {
218 self.terrain_cells.push(pos.clone());
219 }
220 _ = self.constraints.insert(pos, constraint);
221 }
222 /// Get the constraint for the given position.
223 pub fn get(&self, pos: &Pos) -> &TileConstraint<Ter, Pat> {
224 self.constraints.get(pos).unwrap_or_default()
225 }
226 /// Iterate over all the terrain cells that are to be tiled in the order
227 /// that they should be tiled.
228 pub fn all_positions(&self) -> impl Iterator<Item = &Pos> {
229 self.terrain_cells.iter()
230 }
231}
232
233impl<Pos, Ter, Pat> Default for HashConstraintMap<Pos, Ter, Pat> {
234 fn default() -> Self {
235 Self {
236 constraints: FxHashMap::default(),
237 terrain_cells: Vec::default(),
238 }
239 }
240}
241
242impl<Pos, Ter, Pat> Deref for HashConstraintMap<Pos, Ter, Pat> {
243 type Target = FxHashMap<Pos, TileConstraint<Ter, Pat>>;
244
245 fn deref(&self) -> &Self::Target {
246 &self.constraints
247 }
248}
249
250/// The ways in which a cell's choice of tile can be constrained.
251#[derive(Debug, Clone)]
252pub enum TileConstraint<T, P> {
253 /// No constraint. This means that the cell is outside of the area of consideration
254 /// for the autotiler, such as beyond the edge of the world. Cells with this constraint
255 /// put no limits on what may be adjacent in any direction.
256 None,
257 /// A terrain is a set of possible patterns. Any pattern within the terrain may be chosen
258 /// to be the pattern for this cell.
259 Terrain(T),
260 /// A pattern constraint means that the pattern for this cell has already been chosen and
261 /// may not change.
262 Pattern(P),
263}
264
265impl<T, P> Default for TileConstraint<T, P> {
266 fn default() -> Self {
267 Self::None
268 }
269}
270
271impl<T, P> Default for &TileConstraint<T, P> {
272 fn default() -> Self {
273 &TileConstraint::None
274 }
275}
276
277impl<T, P> TileConstraint<T, P> {
278 /// True if this is the None constraint that does not restrict which pattern
279 /// may be chosen.
280 pub fn is_none(&self) -> bool {
281 matches!(self, Self::None)
282 }
283 /// True if this constraint is not None, meaning it is either a pattern or a terrain.
284 pub fn is_some(&self) -> bool {
285 !self.is_none()
286 }
287 /// True if this constraint is a terrain, meaning there are zero or more permitted patterns.
288 pub fn is_terrain(&self) -> bool {
289 matches!(self, Self::Terrain(_))
290 }
291 /// Tue if this contraint is a pattern, meaning that only that exact pattern is permitted.
292 pub fn is_pattern(&self) -> bool {
293 matches!(self, Self::Pattern(_))
294 }
295 /// An iterator over all the patterns explicitly permitted by this contraint,
296 /// or an empty iterator if the constraint is None.
297 /// The None constraint permits any pattern, but it is not possible to create
298 /// an interator over all patterns here, so `all_patterns` should usually only
299 /// be called if [`is_some`](Self::is_some) returns true.
300 pub fn all_patterns(&self) -> TileConstraint<impl Iterator<Item = &P>, &P>
301 where
302 T: TileTerrain<Pattern = P>,
303 {
304 match self {
305 Self::None => TileConstraint::None,
306 Self::Terrain(t) => TileConstraint::Terrain(t.all_patterns()),
307 Self::Pattern(p) => TileConstraint::Pattern(p),
308 }
309 }
310}
311
312impl<T, P> Iterator for TileConstraint<T, P>
313where
314 T: Iterator<Item = P>,
315 P: Clone,
316{
317 type Item = P;
318
319 fn next(&mut self) -> Option<Self::Item> {
320 match self {
321 Self::None => None,
322 Self::Terrain(t) => t.next(),
323 Self::Pattern(p) => {
324 let p = p.clone();
325 *self = Self::None;
326 Some(p)
327 }
328 }
329 }
330}
331
332/// The object responsible for autotiling and owning a hash map
333/// were the result is stored.
334#[derive(Debug, Default, Clone)]
335pub struct AutoTiler<Pos, Pat> {
336 patterns: FxHashMap<Pos, Pat>,
337}
338
339impl<Pos, Pat> std::ops::Deref for AutoTiler<Pos, Pat> {
340 type Target = FxHashMap<Pos, Pat>;
341
342 fn deref(&self) -> &Self::Target {
343 &self.patterns
344 }
345}
346
347impl<Pos, Pat> std::ops::DerefMut for AutoTiler<Pos, Pat> {
348 fn deref_mut(&mut self) -> &mut Self::Target {
349 &mut self.patterns
350 }
351}
352
353impl<Pos: OffsetPosition, Pat: Clone + Debug> AutoTiler<Pos, Pat> {
354 /// Fill the autotile map with patterns according to the given constraint.
355 /// The hash map is not automatically cleared by this method, so it may be
356 /// called multiple times with different constraints to build up a single
357 /// autotile solution.
358 pub fn autotile<C, T>(&mut self, constraint: &C)
359 where
360 C: AutoConstrain<Position = Pos, Terrain = T>,
361 T: TileTerrain<Pattern = Pat> + Debug,
362 Pos: Debug,
363 <Pos as OffsetPosition>::Offset: Debug,
364 {
365 for pos in constraint.all_positions() {
366 if !self.patterns.contains_key(pos) {
367 if let Some(pat) = self.find_pattern(pos, constraint) {
368 _ = self.patterns.insert(pos.clone(), pat);
369 }
370 }
371 }
372 }
373 fn find_pattern<C, T>(&self, position: &Pos, constraint: &C) -> Option<Pat>
374 where
375 C: AutoConstrain<Position = Pos, Terrain = T>,
376 T: TileTerrain<Pattern = Pat> + Debug,
377 Pos: Debug,
378 <Pos as OffsetPosition>::Offset: Debug,
379 {
380 for pat in constraint.constraint_at(position).all_patterns() {
381 if self.is_pattern_legal(position, pat, constraint) {
382 return Some(pat.clone());
383 }
384 }
385 None
386 }
387 fn is_pattern_legal<C, T>(&self, position: &Pos, pattern: &Pat, constraint: &C) -> bool
388 where
389 C: AutoConstrain<Position = Pos, Terrain = T>,
390 T: TileTerrain<Pattern = Pat>,
391 <Pos as OffsetPosition>::Offset: Debug,
392 {
393 for diagonal in Pos::all_diagonals() {
394 let p = position.clone() + diagonal.clone();
395 if let Some(pat) = self.patterns.get(&p) {
396 if !constraint.is_legal_diagonal(pattern, &diagonal, pat) {
397 return false;
398 }
399 } else {
400 let cell_constraint = constraint.constraint_at(&p);
401 if cell_constraint.is_some()
402 && !cell_constraint
403 .all_patterns()
404 .any(|pat| constraint.is_legal_diagonal(pattern, &diagonal, pat))
405 {
406 return false;
407 }
408 }
409 }
410 for offset in Pos::all_offsets() {
411 let p = position.clone() + offset.clone();
412 if let Some(pat) = self.patterns.get(&p) {
413 if !constraint.is_legal(pattern, &offset, pat) {
414 return false;
415 }
416 } else {
417 let cell_constraint = constraint.constraint_at(&p);
418 if cell_constraint.is_some()
419 && !cell_constraint
420 .all_patterns()
421 .any(|pat| constraint.is_legal(pattern, &offset, pat))
422 {
423 return false;
424 }
425 }
426 }
427 true
428 }
429}
430
431/// This contains the background information required for autotiling
432/// independent of the particular cells to be filled, so it can be used
433/// for multiple autotiling tasks. It contains a hash map from terrain
434/// values to lists of patterns, representing both the patterns contained
435/// within each terrain and the order in which the patterns should be examined.
436/// It also contains and a hash map from patterns to a [`ProbabilitySet`] of tiles,
437/// representing how a tile should be randomly selected once a pattern is chosen.
438#[derive(Clone)]
439pub struct AutoTileContext<Ter, Pat, Tile> {
440 pattern_list_pool: Vec<Vec<Pat>>,
441 probability_set_pool: Vec<ProbabilitySet<Tile>>,
442 /// Hash map from terrain values to the list of pattern values that each terrain represents.
443 /// The pattern values should be sorted into the order in which the autotiler will try to insert
444 /// the patterns into the cells.
445 pub patterns: AutoTerrainPatternMap<Ter, Pat>,
446 /// Hash map from pattern values to [`ProbabilitySet`] of tile values, giving each tile the probability
447 /// it should have when randomly selecting a tile for a given pattern.
448 pub values: AutoPatternValueMap<Pat, Tile>,
449}
450
451impl<Ter: Debug, Pat: Debug, Tile: Debug> Debug for AutoTileContext<Ter, Pat, Tile> {
452 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
453 f.write_str("AutoTileContext\n")?;
454 f.write_str("patterns:\n")?;
455 for (p, v) in self.patterns.iter() {
456 writeln!(f, "{p:?} -> {v:?}")?;
457 }
458 f.write_str("values:\n")?;
459 for (p, v) in self.values.iter() {
460 writeln!(f, "{p:?} -> {v:?}")?;
461 }
462 Ok(())
463 }
464}
465
466impl<Ter, Pat, Tile> Default for AutoTileContext<Ter, Pat, Tile> {
467 fn default() -> Self {
468 Self {
469 pattern_list_pool: Default::default(),
470 probability_set_pool: Default::default(),
471 patterns: Default::default(),
472 values: Default::default(),
473 }
474 }
475}
476
477impl<Ter: Eq + Hash, Pat: Eq + Hash + Clone, Tile> AutoTileContext<Ter, Pat, Tile> {
478 /// True if this context contains no patterns.
479 pub fn is_empty(&self) -> bool {
480 self.patterns.is_empty()
481 }
482 /// Make the context empty in preparation for building a new context.
483 pub fn clear(&mut self) {
484 for (_, mut list) in self.patterns.drain() {
485 list.clear();
486 self.pattern_list_pool.push(list);
487 }
488 for (_, mut set) in self.values.drain() {
489 set.clear();
490 self.probability_set_pool.push(set);
491 }
492 }
493 /// Add a particular tile to the context and specify that tile's terrain, pattern,
494 /// and frequency. The higher the frequency, the more likely this tile will be chosen
495 /// if it shares the same pattern as another tile.
496 pub fn add(&mut self, key: Ter, pattern: Pat, frequency: f32, value: Tile) {
497 self.patterns
498 .entry(key)
499 .or_insert_with(|| self.pattern_list_pool.pop().unwrap_or_default())
500 .push(pattern.clone());
501 self.values
502 .entry(pattern)
503 .or_insert_with(|| self.probability_set_pool.pop().unwrap_or_default())
504 .add(frequency, value);
505 }
506 /// Sort the pattern values according to their natural order.
507 pub fn sort(&mut self)
508 where
509 Pat: Ord,
510 {
511 for pattern_list in self.patterns.values_mut() {
512 pattern_list.sort();
513 pattern_list.dedup();
514 }
515 }
516 /// Get a random tile that has the given pattern, if possible.
517 pub fn get_random_value<R: Rng + ?Sized>(&self, rng: &mut R, pattern: &Pat) -> Option<&Tile> {
518 self.values.get(pattern).and_then(|vs| vs.get_random(rng))
519 }
520}
521
522/// A hash map from terrain values to lists of patterns.
523/// Each pattern has a terrain, and this map is used to list all of
524/// the patterns for a given terrain id value in the order of priority.
525/// If multiple patterns in the list may be chosen for some cell,
526/// the pattern nearest the front of the list should be chosen.
527///
528/// * Ter: The type of the terrain ids that are to be associated with lists of patterns.
529/// * Pat: The type of a pattern.
530///
531/// See [`AutoTileContext`] for a way to construct an `AutoTerrainPatternMap`.
532/// Once an `AutoTerrainPatternMap` has been constructed, it may be
533/// used as part of an [`AutoPatternConstraint`].
534pub type AutoTerrainPatternMap<Ter, Pat> = FxHashMap<Ter, Vec<Pat>>;
535
536/// A hash map from patterns to tiles. It is possible for multiple tiles to share
537/// the same pattern, and in that case there is no way for the autotiler to deterministically
538/// choose a tile. This map gives each pattern a [`ProbabilitySet`] that can be used
539/// to randomly select a tile based upon the relative frequency of each tile.
540///
541/// * Pat: The type of a tile pattern.
542/// * Tile: The type of a tile.
543///
544/// See [`AutoTileContext`] for a way to construct an `AutoPatternValueMap`.
545pub type AutoPatternValueMap<Pat, Tile> = FxHashMap<Pat, ProbabilitySet<Tile>>;
546
547/// A pair of an [`HashConstraintMap`] and an [`AutoTerrainPatternMap`].
548/// - The constraint map tells the auto-tiler which cells have undecided patterns,
549/// which cells have fixed patterns, the terrain type of the undecided cells, and
550/// the order in which to fill them.
551/// - The terrain map tells the auto-tiler which patterns are available for each
552/// terrain type, and the order in which the patterns should be tried.
553///
554/// See [`AutoTileContext`] for a way to construct an [`AutoTerrainPatternMap`].
555pub struct AutoPatternConstraint<'a, 'b, Pos, Ter, Pat> {
556 /// The position constraints define the specific problem for the autotiler to solve
557 /// by giving it the constraints for each cell, including the cells whose tiles are already
558 /// determined and the cells that are yet to be determined.
559 pub position_constraints: &'a HashConstraintMap<Pos, Ter, Pat>,
560 /// The pattern contraints define the patterns that are available for each terrain.
561 /// This will usually remain fixed across many calls to the autotiler, and so it may be reused
562 /// so long as the set of tiles does not change.
563 /// See [`AutoTileContext`] for a way to construct an [`AutoTerrainPatternMap`].
564 pub pattern_constraints: &'b AutoTerrainPatternMap<Ter, Pat>,
565}
566
567impl<'b, Pos, Ter, Pat> AutoConstrain for AutoPatternConstraint<'_, 'b, Pos, Ter, Pat>
568where
569 Pos: OffsetPosition,
570 Ter: Hash + Eq,
571 Pat: TilePattern<Offset = Pos::Offset, Diagonal = Pos::Diagonal> + Clone,
572 Pat: Debug,
573 Pos::Offset: Debug,
574{
575 type Position = Pos;
576
577 type Terrain = ListTerrain<'b, Pat>;
578
579 fn all_positions(&self) -> impl Iterator<Item = &Self::Position> {
580 self.position_constraints.all_positions()
581 }
582
583 fn constraint_at(&self, position: &Pos) -> TileConstraint<ListTerrain<'b, Pat>, Pat> {
584 match self.position_constraints.get(position) {
585 TileConstraint::Terrain(ter) => match self.pattern_constraints.get(ter) {
586 Some(pat_list) => TileConstraint::Terrain(ListTerrain(pat_list)),
587 None => TileConstraint::None,
588 },
589 TileConstraint::Pattern(pat) => TileConstraint::Pattern(pat.clone()),
590 TileConstraint::None => TileConstraint::None,
591 }
592 }
593
594 fn is_legal(&self, from: &Pat, offset: &Pos::Offset, to: &Pat) -> bool {
595 from.is_legal(offset, to)
596 }
597 fn is_legal_diagonal(&self, from: &Pat, diagonal: &Pos::Diagonal, to: &Pat) -> bool {
598 from.is_legal_diagonal(diagonal, to)
599 }
600}
601
602/// An implementation of [`TileTerrain`] based upon a slice of patterns.
603#[derive(Debug, Clone)]
604pub struct ListTerrain<'a, P>(pub &'a [P]);
605
606impl<P> TileTerrain for ListTerrain<'_, P> {
607 type Pattern = P;
608
609 fn all_patterns(&self) -> impl Iterator<Item = &P> {
610 self.0.iter()
611 }
612}