dcc_tiler/
tile.rs

1use std::collections::HashSet;
2
3#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
4pub enum Direction {
5    Up,
6    Down,
7    Left,
8    Right,
9    UpLeft,
10    UpRight,
11    DownRight,
12    DownLeft,
13}
14
15#[derive(Debug, Copy, Clone)]
16pub enum Axis {
17    Vertical,
18    Horizontal,
19}
20
21impl Direction {
22    /// Returns the opposite of this direction
23    ///
24    /// # Examples
25    ///
26    /// ```
27    /// // Example code here
28    /// ```
29    pub fn opposite(self) -> Self {
30        match self {
31            Direction::Up => Direction::Down,
32            Direction::Right => Direction::Left,
33            Direction::Down => Direction::Up,
34            Direction::Left => Direction::Right,
35            Direction::UpLeft => Direction::DownRight,
36            Direction::DownRight => Direction::UpLeft,
37            Direction::UpRight => Direction::DownLeft,
38            Direction::DownLeft => Direction::UpRight,
39        }
40    }
41
42    pub fn rotate(self) -> Self {
43        match self {
44            Direction::Up => Direction::Right,
45            Direction::Right => Direction::Down,
46            Direction::Down => Direction::Left,
47            Direction::Left => Direction::Up,
48            Direction::UpLeft => Direction::UpRight,
49            Direction::UpRight => Direction::DownRight,
50            Direction::DownRight => Direction::DownLeft,
51            Direction::DownLeft => Direction::UpLeft,
52        }
53    }
54
55    pub fn reflect(self, axis: Axis) -> Self {
56        match axis {
57            Axis::Horizontal => match self {
58                Direction::Up => Direction::Down,
59                Direction::Down => Direction::Up,
60                Direction::UpLeft => Direction::DownLeft,
61                Direction::UpRight => Direction::DownRight,
62                Direction::DownLeft => Direction::UpLeft,
63                Direction::DownRight => Direction::UpRight,
64                x => x,
65            },
66            Axis::Vertical => match self {
67                Direction::Left => Direction::Right,
68                Direction::Right => Direction::Left,
69                Direction::UpLeft => Direction::UpRight,
70                Direction::UpRight => Direction::UpLeft,
71                Direction::DownLeft => Direction::DownRight,
72                Direction::DownRight => Direction::DownLeft,
73                x => x,
74            },
75        }
76    }
77}
78
79#[derive(Clone, Debug, PartialEq, Eq, Hash)]
80pub struct Tile {
81    pub directions: Vec<Direction>,
82}
83
84impl Tile {
85    pub fn new(directions: Vec<Direction>) -> Self {
86        Tile { directions }
87    }
88
89    /// Returns an L-shaped tile consisting of n + 1 blocks
90    ///
91    /// # Panics
92    ///
93    /// Will panic if length = 0
94    ///
95    /// # Examples
96    ///
97    /// ```
98    /// use dcc_tiler::tile::{Tile, Direction};
99    ///
100    /// let tile = Tile::l_tile(2);
101    /// assert_eq!(tile.directions, vec![Direction::Left, Direction::Up]);
102    /// ```
103    pub fn l_tile(length: usize) -> Self {
104        assert!(length > 0);
105
106        let mut directions = vec![Direction::Left];
107
108        for _ in 0..(length - 1) {
109            directions.push(Direction::Up);
110        }
111
112        Tile::new(directions)
113    }
114
115    pub fn box_tile() -> Self {
116        Tile::new(Vec::new())
117    }
118
119    pub fn t_tile(length: usize) -> Self {
120        assert!(length > 0);
121
122        let mut directions = Vec::new();
123
124        for _ in 0..length {
125            directions.push(Direction::Right);
126        }
127        directions.push(Direction::Up);
128        directions.push(Direction::DownRight);
129
130        for _ in 0..(length - 1) {
131            directions.push(Direction::Right);
132        }
133
134        Tile::new(directions)
135    }
136
137    /// Returns a rotated (by 90 degrees clockwise) copy of this tile.
138    ///
139    /// # Examples
140    ///
141    /// ```rust
142    /// use dcc_tiler::tile::{Tile, Direction};
143    ///
144    /// let l = Tile::new(vec![Direction::Left, Direction::Up, Direction::Left]);
145    /// let q = Tile::new(vec![Direction::Up, Direction::Right, Direction::Up]);
146    /// assert_eq!(l.rotate(), q);
147    /// ```
148    pub fn rotate(&self) -> Tile {
149        Tile::new(self.directions.iter().map(|d| d.rotate()).collect())
150    }
151
152    /// Returns a reflected (about the specified axis) copy of this tile
153    ///
154    /// # Examples
155    ///
156    /// ```rust
157    /// use dcc_tiler::tile::{Tile, Direction, Axis};
158    ///
159    /// let tile = Tile::new(vec![Direction::Left, Direction::Up, Direction::Right]);
160    /// // Reflect our tile about a vertical line
161    /// let reflected_tile = tile.reflect(Axis::Vertical);
162    ///
163    /// assert_eq!(reflected_tile, Tile::new(vec![Direction::Right, Direction::Up, Direction::Left]));
164    /// ```
165    pub fn reflect(&self, axis: Axis) -> Tile {
166        Tile::new(self.directions.iter().map(|d| d.reflect(axis)).collect())
167    }
168}
169
170#[derive(Debug, Clone)]
171pub struct TileCollection {
172    tiles: Vec<Tile>,
173    contains_single_tile: bool,
174}
175
176impl TileCollection {
177    pub fn new(tiles: Vec<Tile>) -> Self {
178        TileCollection {
179            contains_single_tile: tiles.iter().any(|b| b.directions.is_empty()),
180            tiles,
181        }
182    }
183
184    pub fn contains_single_tile(&self) -> bool {
185        self.contains_single_tile
186    }
187
188    pub fn iter<'b>(&'b self) -> Box<dyn Iterator<Item = &Tile> + 'b> {
189        Box::new(self.tiles.iter())
190    }
191}
192
193impl From<Tile> for TileCollection {
194    fn from(tile: Tile) -> Self {
195        /// Generates the orbit of this tile under the symmetry + rotate actions
196        fn symmetry_orbit(tile: Tile) -> TileCollection {
197            let mut orbit = HashSet::new();
198
199            // our starting set of directions
200            orbit.insert(tile);
201
202            loop {
203                // in each iteration, we check whether our directions set
204                // increased.  If it didn't, then we've got the entire orbit
205                let current_size = orbit.len();
206
207                let mut to_insert = Vec::new();
208
209                for directions in &orbit {
210                    // apply the rotate function
211                    to_insert.push(directions.rotate());
212                    // apply the two axis reflections
213                    to_insert.push(directions.reflect(Axis::Horizontal));
214                    to_insert.push(directions.reflect(Axis::Vertical));
215                }
216
217                orbit.extend(to_insert);
218
219                if orbit.len() == current_size {
220                    break;
221                }
222            }
223
224            TileCollection::new(orbit.into_iter().collect())
225        }
226        symmetry_orbit(tile)
227    }
228}