Skip to main content

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, Default)]
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    #[default]
257    None,
258    /// A terrain is a set of possible patterns. Any pattern within the terrain may be chosen
259    /// to be the pattern for this cell.
260    Terrain(T),
261    /// A pattern constraint means that the pattern for this cell has already been chosen and
262    /// may not change.
263    Pattern(P),
264}
265
266impl<T, P> Default for &TileConstraint<T, P> {
267    fn default() -> Self {
268        &TileConstraint::None
269    }
270}
271
272impl<T, P> TileConstraint<T, P> {
273    /// True if this is the None constraint that does not restrict which pattern
274    /// may be chosen.
275    pub fn is_none(&self) -> bool {
276        matches!(self, Self::None)
277    }
278    /// True if this constraint is not None, meaning it is either a pattern or a terrain.
279    pub fn is_some(&self) -> bool {
280        !self.is_none()
281    }
282    /// True if this constraint is a terrain, meaning there are zero or more permitted patterns.
283    pub fn is_terrain(&self) -> bool {
284        matches!(self, Self::Terrain(_))
285    }
286    /// Tue if this contraint is a pattern, meaning that only that exact pattern is permitted.
287    pub fn is_pattern(&self) -> bool {
288        matches!(self, Self::Pattern(_))
289    }
290    /// An iterator over all the patterns explicitly permitted by this contraint,
291    /// or an empty iterator if the constraint is None.
292    /// The None constraint permits any pattern, but it is not possible to create
293    /// an interator over all patterns here, so `all_patterns` should usually only
294    /// be called if [`is_some`](Self::is_some) returns true.
295    pub fn all_patterns(&self) -> TileConstraint<impl Iterator<Item = &P>, &P>
296    where
297        T: TileTerrain<Pattern = P>,
298    {
299        match self {
300            Self::None => TileConstraint::None,
301            Self::Terrain(t) => TileConstraint::Terrain(t.all_patterns()),
302            Self::Pattern(p) => TileConstraint::Pattern(p),
303        }
304    }
305}
306
307impl<T, P> Iterator for TileConstraint<T, P>
308where
309    T: Iterator<Item = P>,
310    P: Clone,
311{
312    type Item = P;
313
314    fn next(&mut self) -> Option<Self::Item> {
315        match self {
316            Self::None => None,
317            Self::Terrain(t) => t.next(),
318            Self::Pattern(p) => {
319                let p = p.clone();
320                *self = Self::None;
321                Some(p)
322            }
323        }
324    }
325}
326
327/// The object responsible for autotiling and owning a hash map
328/// were the result is stored.
329#[derive(Debug, Default, Clone)]
330pub struct AutoTiler<Pos, Pat> {
331    patterns: FxHashMap<Pos, Pat>,
332}
333
334impl<Pos, Pat> std::ops::Deref for AutoTiler<Pos, Pat> {
335    type Target = FxHashMap<Pos, Pat>;
336
337    fn deref(&self) -> &Self::Target {
338        &self.patterns
339    }
340}
341
342impl<Pos, Pat> std::ops::DerefMut for AutoTiler<Pos, Pat> {
343    fn deref_mut(&mut self) -> &mut Self::Target {
344        &mut self.patterns
345    }
346}
347
348impl<Pos: OffsetPosition, Pat: Clone + Debug> AutoTiler<Pos, Pat> {
349    /// Fill the autotile map with patterns according to the given constraint.
350    /// The hash map is not automatically cleared by this method, so it may be
351    /// called multiple times with different constraints to build up a single
352    /// autotile solution.
353    pub fn autotile<C, T>(&mut self, constraint: &C)
354    where
355        C: AutoConstrain<Position = Pos, Terrain = T>,
356        T: TileTerrain<Pattern = Pat> + Debug,
357        Pos: Debug,
358        <Pos as OffsetPosition>::Offset: Debug,
359    {
360        for pos in constraint.all_positions() {
361            if !self.patterns.contains_key(pos) {
362                if let Some(pat) = self.find_pattern(pos, constraint) {
363                    _ = self.patterns.insert(pos.clone(), pat);
364                }
365            }
366        }
367    }
368    fn find_pattern<C, T>(&self, position: &Pos, constraint: &C) -> Option<Pat>
369    where
370        C: AutoConstrain<Position = Pos, Terrain = T>,
371        T: TileTerrain<Pattern = Pat> + Debug,
372        Pos: Debug,
373        <Pos as OffsetPosition>::Offset: Debug,
374    {
375        for pat in constraint.constraint_at(position).all_patterns() {
376            if self.is_pattern_legal(position, pat, constraint) {
377                return Some(pat.clone());
378            }
379        }
380        None
381    }
382    fn is_pattern_legal<C, T>(&self, position: &Pos, pattern: &Pat, constraint: &C) -> bool
383    where
384        C: AutoConstrain<Position = Pos, Terrain = T>,
385        T: TileTerrain<Pattern = Pat>,
386        <Pos as OffsetPosition>::Offset: Debug,
387    {
388        for diagonal in Pos::all_diagonals() {
389            let p = position.clone() + diagonal.clone();
390            if let Some(pat) = self.patterns.get(&p) {
391                if !constraint.is_legal_diagonal(pattern, &diagonal, pat) {
392                    return false;
393                }
394            } else {
395                let cell_constraint = constraint.constraint_at(&p);
396                if cell_constraint.is_some()
397                    && !cell_constraint
398                        .all_patterns()
399                        .any(|pat| constraint.is_legal_diagonal(pattern, &diagonal, pat))
400                {
401                    return false;
402                }
403            }
404        }
405        for offset in Pos::all_offsets() {
406            let p = position.clone() + offset.clone();
407            if let Some(pat) = self.patterns.get(&p) {
408                if !constraint.is_legal(pattern, &offset, pat) {
409                    return false;
410                }
411            } else {
412                let cell_constraint = constraint.constraint_at(&p);
413                if cell_constraint.is_some()
414                    && !cell_constraint
415                        .all_patterns()
416                        .any(|pat| constraint.is_legal(pattern, &offset, pat))
417                {
418                    return false;
419                }
420            }
421        }
422        true
423    }
424}
425
426/// This contains the background information required for autotiling
427/// independent of the particular cells to be filled, so it can be used
428/// for multiple autotiling tasks. It contains a hash map from terrain
429/// values to lists of patterns, representing both the patterns contained
430/// within each terrain and the order in which the patterns should be examined.
431/// It also contains and a hash map from patterns to a [`ProbabilitySet`] of tiles,
432/// representing how a tile should be randomly selected once a pattern is chosen.
433#[derive(Clone)]
434pub struct AutoTileContext<Ter, Pat, Tile> {
435    pattern_list_pool: Vec<Vec<Pat>>,
436    probability_set_pool: Vec<ProbabilitySet<Tile>>,
437    /// Hash map from terrain values to the list of pattern values that each terrain represents.
438    /// The pattern values should be sorted into the order in which the autotiler will try to insert
439    /// the patterns into the cells.
440    pub patterns: AutoTerrainPatternMap<Ter, Pat>,
441    /// Hash map from pattern values to [`ProbabilitySet`] of tile values, giving each tile the probability
442    /// it should have when randomly selecting a tile for a given pattern.
443    pub values: AutoPatternValueMap<Pat, Tile>,
444}
445
446impl<Ter: Debug, Pat: Debug, Tile: Debug> Debug for AutoTileContext<Ter, Pat, Tile> {
447    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
448        f.write_str("AutoTileContext\n")?;
449        f.write_str("patterns:\n")?;
450        for (p, v) in self.patterns.iter() {
451            writeln!(f, "{p:?} -> {v:?}")?;
452        }
453        f.write_str("values:\n")?;
454        for (p, v) in self.values.iter() {
455            writeln!(f, "{p:?} -> {v:?}")?;
456        }
457        Ok(())
458    }
459}
460
461impl<Ter, Pat, Tile> Default for AutoTileContext<Ter, Pat, Tile> {
462    fn default() -> Self {
463        Self {
464            pattern_list_pool: Default::default(),
465            probability_set_pool: Default::default(),
466            patterns: Default::default(),
467            values: Default::default(),
468        }
469    }
470}
471
472impl<Ter: Eq + Hash, Pat: Eq + Hash + Clone, Tile> AutoTileContext<Ter, Pat, Tile> {
473    /// True if this context contains no patterns.
474    pub fn is_empty(&self) -> bool {
475        self.patterns.is_empty()
476    }
477    /// Make the context empty in preparation for building a new context.
478    pub fn clear(&mut self) {
479        for (_, mut list) in self.patterns.drain() {
480            list.clear();
481            self.pattern_list_pool.push(list);
482        }
483        for (_, mut set) in self.values.drain() {
484            set.clear();
485            self.probability_set_pool.push(set);
486        }
487    }
488    /// Add a particular tile to the context and specify that tile's terrain, pattern,
489    /// and frequency. The higher the frequency, the more likely this tile will be chosen
490    /// if it shares the same pattern as another tile.
491    pub fn add(&mut self, key: Ter, pattern: Pat, frequency: f32, value: Tile) {
492        self.patterns
493            .entry(key)
494            .or_insert_with(|| self.pattern_list_pool.pop().unwrap_or_default())
495            .push(pattern.clone());
496        self.values
497            .entry(pattern)
498            .or_insert_with(|| self.probability_set_pool.pop().unwrap_or_default())
499            .add(frequency, value);
500    }
501    /// Sort the pattern values according to their natural order.
502    pub fn sort(&mut self)
503    where
504        Pat: Ord,
505    {
506        for pattern_list in self.patterns.values_mut() {
507            pattern_list.sort();
508            pattern_list.dedup();
509        }
510    }
511    /// Get a random tile that has the given pattern, if possible.
512    pub fn get_random_value<R: Rng + ?Sized>(&self, rng: &mut R, pattern: &Pat) -> Option<&Tile> {
513        self.values.get(pattern).and_then(|vs| vs.get_random(rng))
514    }
515}
516
517/// A hash map from terrain values to lists of patterns.
518/// Each pattern has a terrain, and this map is used to list all of
519/// the patterns for a given terrain id value in the order of priority.
520/// If multiple patterns in the list may be chosen for some cell,
521/// the pattern nearest the front of the list should be chosen.
522///
523/// * Ter: The type of the terrain ids that are to be associated with lists of patterns.
524/// * Pat: The type of a pattern.
525///
526/// See [`AutoTileContext`] for a way to construct an `AutoTerrainPatternMap`.
527/// Once an `AutoTerrainPatternMap` has been constructed, it may be
528/// used as part of an [`AutoPatternConstraint`].
529pub type AutoTerrainPatternMap<Ter, Pat> = FxHashMap<Ter, Vec<Pat>>;
530
531/// A hash map from patterns to tiles. It is possible for multiple tiles to share
532/// the same pattern, and in that case there is no way for the autotiler to deterministically
533/// choose a tile. This map gives each pattern a [`ProbabilitySet`] that can be used
534/// to randomly select a tile based upon the relative frequency of each tile.
535///
536/// * Pat: The type of a tile pattern.
537/// * Tile: The type of a tile.
538///
539/// See [`AutoTileContext`] for a way to construct an `AutoPatternValueMap`.
540pub type AutoPatternValueMap<Pat, Tile> = FxHashMap<Pat, ProbabilitySet<Tile>>;
541
542/// A pair of an [`HashConstraintMap`] and an [`AutoTerrainPatternMap`].
543/// - The constraint map tells the auto-tiler which cells have undecided patterns,
544///   which cells have fixed patterns, the terrain type of the undecided cells, and
545///   the order in which to fill them.
546/// - The terrain map tells the auto-tiler which patterns are available for each
547///   terrain type, and the order in which the patterns should be tried.
548///
549/// See [`AutoTileContext`] for a way to construct an [`AutoTerrainPatternMap`].
550pub struct AutoPatternConstraint<'a, 'b, Pos, Ter, Pat> {
551    /// The position constraints define the specific problem for the autotiler to solve
552    /// by giving it the constraints for each cell, including the cells whose tiles are already
553    /// determined and the cells that are yet to be determined.
554    pub position_constraints: &'a HashConstraintMap<Pos, Ter, Pat>,
555    /// The pattern contraints define the patterns that are available for each terrain.
556    /// This will usually remain fixed across many calls to the autotiler, and so it may be reused
557    /// so long as the set of tiles does not change.
558    /// See [`AutoTileContext`] for a way to construct an [`AutoTerrainPatternMap`].
559    pub pattern_constraints: &'b AutoTerrainPatternMap<Ter, Pat>,
560}
561
562impl<'b, Pos, Ter, Pat> AutoConstrain for AutoPatternConstraint<'_, 'b, Pos, Ter, Pat>
563where
564    Pos: OffsetPosition,
565    Ter: Hash + Eq,
566    Pat: TilePattern<Offset = Pos::Offset, Diagonal = Pos::Diagonal> + Clone,
567    Pat: Debug,
568    Pos::Offset: Debug,
569{
570    type Position = Pos;
571
572    type Terrain = ListTerrain<'b, Pat>;
573
574    fn all_positions(&self) -> impl Iterator<Item = &Self::Position> {
575        self.position_constraints.all_positions()
576    }
577
578    fn constraint_at(&self, position: &Pos) -> TileConstraint<ListTerrain<'b, Pat>, Pat> {
579        match self.position_constraints.get(position) {
580            TileConstraint::Terrain(ter) => match self.pattern_constraints.get(ter) {
581                Some(pat_list) => TileConstraint::Terrain(ListTerrain(pat_list)),
582                None => TileConstraint::None,
583            },
584            TileConstraint::Pattern(pat) => TileConstraint::Pattern(pat.clone()),
585            TileConstraint::None => TileConstraint::None,
586        }
587    }
588
589    fn is_legal(&self, from: &Pat, offset: &Pos::Offset, to: &Pat) -> bool {
590        from.is_legal(offset, to)
591    }
592    fn is_legal_diagonal(&self, from: &Pat, diagonal: &Pos::Diagonal, to: &Pat) -> bool {
593        from.is_legal_diagonal(diagonal, to)
594    }
595}
596
597/// An implementation of [`TileTerrain`] based upon a slice of patterns.
598#[derive(Debug, Clone)]
599pub struct ListTerrain<'a, P>(pub &'a [P]);
600
601impl<P> TileTerrain for ListTerrain<'_, P> {
602    type Pattern = P;
603
604    fn all_patterns(&self) -> impl Iterator<Item = &P> {
605        self.0.iter()
606    }
607}