Skip to main content

sokoban_elements/game_board/
iterator.rs

1use std::iter::FusedIterator;
2
3use super::GameBoard;
4use crate::TileKind;
5use crate::utilities::checked_index_1d;
6
7/// Row major iterator over `((x, y), tile)` pairs of [`GameBoard`](crate::GameBoard).
8#[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}