use std::iter::FusedIterator;
use super::GameBoard;
use crate::TileKind;
use crate::utilities::checked_index_1d;
#[derive(Debug, Clone, PartialEq)]
pub struct GameBoardIter<'a> {
src: &'a GameBoard,
width: usize,
height: usize,
x: usize,
y: usize,
}
impl<'a> GameBoardIter<'a> {
pub(super) fn new(src: &'a GameBoard) -> Self {
Self {
src,
width: src.width(),
height: src.height(),
x: 0,
y: 0,
}
}
}
impl Iterator for GameBoardIter<'_> {
type Item = ((usize, usize), TileKind);
fn next(&mut self) -> Option<Self::Item> {
if self.x >= self.width || self.y >= self.height {
return None;
}
let position = (self.x, self.y);
let tile = self.src[position];
self.x += 1;
if self.x == self.width {
self.x = 0;
self.y += 1;
}
Some((position, tile))
}
fn size_hint(&self) -> (usize, Option<usize>) {
let total = self.width.saturating_mul(self.height);
let consumed = checked_index_1d(self.x, self.y, self.width).unwrap_or(total);
let remaining = total.saturating_sub(consumed);
(remaining, Some(remaining))
}
}
impl ExactSizeIterator for GameBoardIter<'_> {}
impl FusedIterator for GameBoardIter<'_> {}
impl<'a> IntoIterator for &'a GameBoard {
type IntoIter = GameBoardIter<'a>;
type Item = ((usize, usize), TileKind);
fn into_iter(self) -> Self::IntoIter { self.iter() }
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{BoardSyntaxError, Tessellation};
fn board_text() -> String {
[
" #####",
" # #",
" #$ #",
" ### $##",
" # $ $ #",
"### # ## # ######",
"# # ## ##### ..#",
"# $ $ ..#",
"##### ### #@## ..#",
" # #########",
" #######",
]
.join("\n")
}
fn board() -> Result<GameBoard, BoardSyntaxError> {
GameBoard::parsed(&board_text(), Tessellation::Sokoban)
}
#[test]
fn it_iterates_tiles_in_row_major_order() -> Result<(), BoardSyntaxError> {
let small = GameBoard::new(3, 2, Tessellation::Sokoban);
let positions: Vec<(usize, usize)> = small.iter().map(|(position, _)| position).collect();
assert_eq!(positions, vec![(0, 0), (1, 0), (2, 0), (0, 1), (1, 1), (2, 1)]);
let board = board()?;
assert_eq!(board.iter().next().map(|(position, _)| position), Some((0, 0)));
assert_eq!(
board.iter().last().map(|(position, _)| position),
Some((board.width() - 1, board.height() - 1))
);
Ok(())
}
#[test]
fn it_iterates_over_all_tiles() -> Result<(), BoardSyntaxError> {
let board = board()?;
let total = board.width() * board.height();
assert_eq!(board.iter().count(), total);
assert_eq!(board.iter().len(), total);
assert_eq!(board.iter().size_hint(), (total, Some(total)));
let mut it = board.iter();
it.next();
assert_eq!(it.size_hint(), (total - 1, Some(total - 1)));
assert_eq!(it.len(), total - 1);
let mut visited: usize = 0;
for (position, tile) in &board {
assert_eq!(board[position], tile);
visited += 1;
}
assert_eq!(visited, total);
Ok(())
}
#[test]
fn when_board_is_empty_it_iterates_nothing() -> Result<(), BoardSyntaxError> {
let board = GameBoard::parsed("", Tessellation::Sokoban)?;
assert_eq!(board.width(), 0);
assert_eq!(board.height(), 0);
assert!(board.iter().next().is_none());
assert_eq!(board.iter().len(), 0);
Ok(())
}
}