sokoban_elements/game_board/
iterator.rs1use std::iter::FusedIterator;
2
3use super::GameBoard;
4use crate::TileKind;
5use crate::utilities::checked_index_1d;
6
7#[derive(Debug, Clone, PartialEq)]
9pub struct GameBoardIter<'a> {
10 src: &'a GameBoard,
11 width: usize,
12 height: usize,
13 x: usize,
14 y: usize,
15}
16
17impl<'a> GameBoardIter<'a> {
18 pub(super) fn new(src: &'a GameBoard) -> Self {
19 Self {
20 src,
21 width: src.width(),
22 height: src.height(),
23 x: 0,
24 y: 0,
25 }
26 }
27}
28
29impl Iterator for GameBoardIter<'_> {
30 type Item = ((usize, usize), TileKind);
31
32 fn next(&mut self) -> Option<Self::Item> {
33 if self.x >= self.width || self.y >= self.height {
34 return None;
35 }
36
37 let position = (self.x, self.y);
38 let tile = self.src[position];
39
40 self.x += 1;
41 if self.x == self.width {
42 self.x = 0;
43 self.y += 1;
44 }
45
46 Some((position, tile))
47 }
48
49 fn size_hint(&self) -> (usize, Option<usize>) {
50 let total = self.width.saturating_mul(self.height);
51 let consumed = checked_index_1d(self.x, self.y, self.width).unwrap_or(total);
52 let remaining = total.saturating_sub(consumed);
53
54 (remaining, Some(remaining))
55 }
56}
57
58impl ExactSizeIterator for GameBoardIter<'_> {}
59
60impl FusedIterator for GameBoardIter<'_> {}
61
62impl<'a> IntoIterator for &'a GameBoard {
63 type IntoIter = GameBoardIter<'a>;
64 type Item = ((usize, usize), TileKind);
65
66 fn into_iter(self) -> Self::IntoIter { self.iter() }
67}
68
69#[cfg(test)]
70mod tests {
71 use super::*;
72 use crate::{BoardSyntaxError, Tessellation};
73
74 fn board_text() -> String {
75 [
76 " #####",
77 " # #",
78 " #$ #",
79 " ### $##",
80 " # $ $ #",
81 "### # ## # ######",
82 "# # ## ##### ..#",
83 "# $ $ ..#",
84 "##### ### #@## ..#",
85 " # #########",
86 " #######",
87 ]
88 .join("\n")
89 }
90
91 fn board() -> Result<GameBoard, BoardSyntaxError> {
92 GameBoard::parsed(&board_text(), Tessellation::Sokoban)
93 }
94
95 #[test]
96 fn it_iterates_tiles_in_row_major_order() -> Result<(), BoardSyntaxError> {
97 let small = GameBoard::new(3, 2, Tessellation::Sokoban);
98 let positions: Vec<(usize, usize)> = small.iter().map(|(position, _)| position).collect();
99
100 assert_eq!(positions, vec![(0, 0), (1, 0), (2, 0), (0, 1), (1, 1), (2, 1)]);
101
102 let board = board()?;
103
104 assert_eq!(board.iter().next().map(|(position, _)| position), Some((0, 0)));
105 assert_eq!(
106 board.iter().last().map(|(position, _)| position),
107 Some((board.width() - 1, board.height() - 1))
108 );
109
110 Ok(())
111 }
112
113 #[test]
114 fn it_iterates_over_all_tiles() -> Result<(), BoardSyntaxError> {
115 let board = board()?;
116 let total = board.width() * board.height();
117
118 assert_eq!(board.iter().count(), total);
119 assert_eq!(board.iter().len(), total);
120 assert_eq!(board.iter().size_hint(), (total, Some(total)));
121
122 let mut it = board.iter();
123 it.next();
124 assert_eq!(it.size_hint(), (total - 1, Some(total - 1)));
125 assert_eq!(it.len(), total - 1);
126
127 let mut visited: usize = 0;
128 for (position, tile) in &board {
129 assert_eq!(board[position], tile);
130 visited += 1;
131 }
132 assert_eq!(visited, total);
133
134 Ok(())
135 }
136
137 #[test]
138 fn when_board_is_empty_it_iterates_nothing() -> Result<(), BoardSyntaxError> {
139 let board = GameBoard::parsed("", Tessellation::Sokoban)?;
140
141 assert_eq!(board.width(), 0);
142 assert_eq!(board.height(), 0);
143 assert!(board.iter().next().is_none());
144 assert_eq!(board.iter().len(), 0);
145
146 Ok(())
147 }
148}