sokoban_elements/game_board/
index.rs1use std::ops::{Index, IndexMut};
2
3use super::GameBoard;
4use crate::utilities::{index_x, index_y};
5use crate::{Point2D, TileKind};
6
7impl Index<(usize, usize)> for GameBoard {
8 type Output = TileKind;
9
10 #[expect(clippy::panic)]
16 fn index(&self, index: (usize, usize)) -> &Self::Output {
17 self.data
18 .get(index.1)
19 .and_then(|row| row.get(index.0))
20 .unwrap_or_else(|| panic!("Invalid GameBoard index {index:?}!"))
21 }
22}
23
24impl IndexMut<(usize, usize)> for GameBoard {
25 #[expect(clippy::panic)]
31 fn index_mut(&mut self, index: (usize, usize)) -> &mut Self::Output {
32 self.data
33 .get_mut(index.1)
34 .and_then(|row| row.get_mut(index.0))
35 .unwrap_or_else(|| panic!("Invalid GameBoard index {index:?}!"))
36 }
37}
38
39impl Index<usize> for GameBoard {
40 type Output = TileKind;
41
42 #[expect(clippy::panic)]
48 fn index(&self, index: usize) -> &Self::Output {
49 let width = self.width();
50 assert!(width != 0, "Invalid GameBoard index {index}!");
51
52 self.data
53 .get(index_y(index, width))
54 .and_then(|row| row.get(index_x(index, width)))
55 .unwrap_or_else(|| panic!("Invalid GameBoard index {index}!"))
56 }
57}
58
59impl IndexMut<usize> for GameBoard {
60 #[expect(clippy::panic)]
66 fn index_mut(&mut self, index: usize) -> &mut Self::Output {
67 let width = self.width();
68 assert!(width != 0, "Invalid GameBoard index {index}!");
69
70 self.data
71 .get_mut(index_y(index, width))
72 .and_then(|row| row.get_mut(index_x(index, width)))
73 .unwrap_or_else(|| panic!("Invalid GameBoard index {index}!"))
74 }
75}
76
77impl Index<Point2D> for GameBoard {
78 type Output = TileKind;
79
80 #[expect(clippy::panic)]
86 fn index(&self, index: Point2D) -> &Self::Output {
87 self.data
90 .get(index.y)
91 .and_then(|row| row.get(index.x))
92 .unwrap_or_else(|| panic!("Invalid GameBoard index {index}!"))
93 }
94}
95
96impl IndexMut<Point2D> for GameBoard {
97 #[expect(clippy::panic)]
103 fn index_mut(&mut self, index: Point2D) -> &mut Self::Output {
104 self.data
105 .get_mut(index.y)
106 .and_then(|row| row.get_mut(index.x))
107 .unwrap_or_else(|| panic!("Invalid GameBoard index {index}!"))
108 }
109}
110
111#[cfg(test)]
112mod tests {
113 use super::*;
114 use crate::{BoardSyntaxError, Tessellation};
115
116 fn board_text() -> String {
117 [
118 " #####",
119 " # #",
120 " #$ #",
121 " ### $##",
122 " # $ $ #",
123 "### # ## # ######",
124 "# # ## ##### ..#",
125 "# $ $ ..#",
126 "##### ### #@## ..#",
127 " # #########",
128 " #######",
129 ]
130 .join("\n")
131 }
132
133 fn board() -> Result<GameBoard, BoardSyntaxError> {
134 GameBoard::parsed(&board_text(), Tessellation::Sokoban)
135 }
136
137 #[test]
138 fn it_reads_tiles_by_x_and_y() -> Result<(), BoardSyntaxError> {
139 let board = board()?;
140
141 assert_eq!(board[(0, 0)], TileKind::Floor);
142 assert_eq!(board[(4, 0)], TileKind::Wall);
143 assert_eq!(board[(5, 2)], TileKind::Box);
144 assert_eq!(board[(16, 6)], TileKind::Goal);
145 assert_eq!(board[(11, 8)], TileKind::Pusher);
146
147 Ok(())
148 }
149
150 #[test]
151 fn when_indexed_by_position_it_matches_indexing_by_x_and_y() -> Result<(), BoardSyntaxError> {
152 let board = board()?;
153
154 for y in 0..board.height() {
155 for x in 0..board.width() {
156 assert_eq!(board[(x, y)], board[y * board.width() + x]);
157 }
158 }
159
160 Ok(())
161 }
162
163 #[test]
164 fn it_writes_tiles_by_x_and_y() -> Result<(), BoardSyntaxError> {
165 let mut board = board()?;
166 let boxes_count = board.boxes_count();
167
168 assert_eq!(board[(0, 0)], TileKind::Floor);
169
170 board[(0, 0)] = TileKind::Box;
171
172 assert_eq!(board[(0, 0)], TileKind::Box);
173 assert_eq!(board.boxes_count(), boxes_count + 1);
174
175 Ok(())
176 }
177
178 #[test]
179 fn it_writes_tiles_by_linear_position() -> Result<(), BoardSyntaxError> {
180 let mut board = board()?;
181 let position: usize = 4;
182
183 assert_eq!(board[position], TileKind::Wall);
184
185 board[position] = TileKind::Goal;
186
187 assert_eq!(board[position], TileKind::Goal);
188 assert_eq!(board[(4, 0)], TileKind::Goal);
189
190 Ok(())
191 }
192
193 #[test]
194 fn it_reads_tiles_by_point() -> Result<(), BoardSyntaxError> {
195 let board = board()?;
196
197 assert_eq!(board[Point2D::new(0, 0)], TileKind::Floor);
198 assert_eq!(board[Point2D::new(4, 0)], TileKind::Wall);
199 assert_eq!(board[Point2D::new(5, 2)], TileKind::Box);
200 assert_eq!(board[Point2D::new(16, 6)], TileKind::Goal);
201 assert_eq!(board[Point2D::new(11, 8)], TileKind::Pusher);
202
203 Ok(())
204 }
205
206 #[test]
207 fn when_indexed_by_point_it_matches_indexing_by_x_and_y() -> Result<(), BoardSyntaxError> {
208 let board = board()?;
209
210 for y in 0..board.height() {
211 for x in 0..board.width() {
212 assert_eq!(board[Point2D::new(x, y)], board[(x, y)]);
213 assert_eq!(board[Point2D::new(x, y)], board[y * board.width() + x]);
214 }
215 }
216
217 Ok(())
218 }
219
220 #[test]
221 fn it_writes_tiles_by_point() -> Result<(), BoardSyntaxError> {
222 let mut board = board()?;
223 let boxes_count = board.boxes_count();
224 let position = Point2D::new(5, 2);
227
228 assert_eq!(board[position], TileKind::Box);
229 assert_eq!(board[(2, 5)], TileKind::Wall);
230
231 board[position] = TileKind::Goal;
232
233 assert_eq!(board[position], TileKind::Goal);
234 assert_eq!(board[(5, 2)], TileKind::Goal);
235 assert_eq!(board[(2, 5)], TileKind::Wall, "x and y must not be swapped");
236 assert_eq!(board.boxes_count(), boxes_count - 1);
237
238 Ok(())
239 }
240
241 #[test]
242 #[should_panic(expected = "Invalid GameBoard index")]
243 fn it_panics_when_x_and_y_are_out_of_bounds() {
244 let board = GameBoard::new(3, 3, Tessellation::Sokoban);
245 let _ = board[(0, board.height())];
246 }
247
248 #[test]
249 #[should_panic(expected = "Invalid GameBoard index")]
250 fn it_panics_when_position_is_out_of_bounds() {
251 let board = GameBoard::new(3, 3, Tessellation::Sokoban);
252 let _ = board[board.width() * board.height()];
253 }
254
255 #[test]
256 #[should_panic(expected = "Invalid GameBoard index")]
257 fn when_board_is_empty_indexing_by_position_panics() {
258 let board = GameBoard::new(0, 0, Tessellation::Sokoban);
259 let position: usize = 0;
260 let _ = board[position];
261 }
262
263 #[test]
268 #[should_panic(expected = "Invalid GameBoard index")]
269 fn it_panics_when_point_x_is_out_of_bounds() {
270 let board = GameBoard::new(3, 3, Tessellation::Sokoban);
271 let _ = board[Point2D::new(board.width(), 0)];
272 }
273
274 #[test]
275 #[should_panic(expected = "Invalid GameBoard index")]
276 fn it_panics_when_point_y_is_out_of_bounds() {
277 let board = GameBoard::new(3, 3, Tessellation::Sokoban);
278 let _ = board[Point2D::new(0, board.height())];
279 }
280
281 #[test]
282 #[should_panic(expected = "Invalid GameBoard index")]
283 fn when_board_is_empty_indexing_by_point_panics() {
284 let board = GameBoard::new(0, 0, Tessellation::Sokoban);
285 let _ = board[Point2D::origin()];
286 }
287}