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}