podch 0.1.0

Game engine for the podch abstract board game
Documentation
use crate::{Board, BinBoard};

/// Iterator for similar situations
///
/// Similar situations are those resulting from symmetry, rotations and color inversion,
/// including the original situation.
/// Only situations of the same size are included.
///
/// # Examples
/// ```
/// use podch::Stone::*;
/// let board = podch::VecBoard::from_vec(vec![None, Dark, None, None, None, Light], 3);
/// let similar = podch::SimilarSituations::new(&board);
/// let mut similar_str: Vec<_> = similar.map(|b| podch::BoardDisplay::from(&b).to_string()).collect();
/// similar_str.reverse();
/// assert_eq!(similar_str, vec![
///     "010\n002", "002\n010", "010\n200", "200\n010",
///     "020\n001", "001\n020", "020\n100", "100\n020",
/// ]);
/// ```
/// ```
/// use podch::Board;
/// let mut square = podch::BinBoard::new(3, 3);
/// square.set(0, 1, podch::Stone::Dark);
/// square.set(2, 0, podch::Stone::Light);
/// let similar = podch::SimilarSituations::new(&square);
/// let mut similar_str: Vec<_> = similar.map(|b| podch::BoardDisplay::from(&b).to_string()).collect();
/// similar_str.reverse();
/// assert_eq!(similar_str, vec![
///     "010\n000\n200", "200\n000\n010", "010\n000\n002", "002\n000\n010",
///     "020\n000\n100", "100\n000\n020", "020\n000\n001", "001\n000\n020",
///     "002\n100\n000", "200\n001\n000", "000\n100\n002", "000\n001\n200",
///     "001\n200\n000", "100\n002\n000", "000\n200\n001", "000\n002\n100",
/// ]);
/// ```
#[derive(Debug, Clone)]
pub struct SimilarSituations<'a, T: Board> {
    board: &'a T,
    mask: u8,
}

impl<'a, T: Board> SimilarSituations<'a, T> {
    /// Construct iterator for situations similar to the one on `board`
    pub fn new(board: &'a T) -> Self {
        Self {
            board,
            mask: if board.size().0 == board.size().1 { 16 } else { 8 },
        }
    }

    /// Reverse `board` vertically, i.e. reverse the order of rows
    ///
    /// # Examples
    /// ```
    /// use podch::Stone::*;
    /// let board = podch::VecBoard::from_vec(vec![Dark, None, None, None, Light, None], 3);
    /// let reversed = podch::SimilarSituations::reverse_v(&board);
    /// assert_eq!(podch::BoardDisplay::from(&reversed).to_string(), "020\n100");
    /// ```
    pub fn reverse_v(board: &'a T) -> BinBoard {
        let height = board.size().0;
        let width = board.size().1;
        BinBoard::from_iter(
            &mut (0..height).map(|x| (0..width)
                .map(move |y| board.get(height - x - 1, y))).flatten(),
            width,
        )
    }

    /// Reverse `board` horizontally, i.e. reverse the order of columns
    ///
    /// # Examples
    /// ```
    /// use podch::Stone::*;
    /// let board = podch::VecBoard::from_vec(vec![Dark, None, None, None, Light, None], 3);
    /// let reversed = podch::SimilarSituations::reverse_h(&board);
    /// assert_eq!(podch::BoardDisplay::from(&reversed).to_string(), "001\n020");
    /// ```
    pub fn reverse_h(board: &'a T) -> BinBoard {
        let height = board.size().0;
        let width = board.size().1;
        BinBoard::from_iter(
            &mut (0..height).map(|x| (0..width)
                .map(move |y| board.get(x, width - y - 1))).flatten(),
            width,
        )
    }

    /// Invert colors in the `board`
    ///
    /// # Examples
    /// ```
    /// use podch::Stone::*;
    /// let board = podch::VecBoard::from_vec(vec![Dark, None, None, None, Light, None], 3);
    /// let inverted = podch::SimilarSituations::negate(&board);
    /// assert_eq!(podch::BoardDisplay::from(&inverted).to_string(), "200\n010");
    /// ```
    pub fn negate(board: &'a T) -> BinBoard {
        let height = board.size().0;
        let width = board.size().1;
        BinBoard::from_iter(
            &mut (0..height).map(|x| (0..width)
                .map(move |y| -board.get(x, y))).flatten(),
            width,
        )
    }

    /// Transpose `board`, i.e. flip it over its main diagonal
    ///
    /// # Examples
    /// ```
    /// use podch::{Stone::*, Board};
    /// let board = podch::VecBoard::from_vec(vec![Dark, None, None, None, Light, None], 3);
    /// let transposed = podch::SimilarSituations::transpose(&board);
    /// assert_eq!(transposed.size(), (3, 2));  // NB: size is changed
    /// assert_eq!(podch::BoardDisplay::from(&transposed).to_string(), "10\n02\n00");
    /// ```
    pub fn transpose(board: &'a T) -> BinBoard {
        let height = board.size().0;
        let width = board.size().1;
        BinBoard::from_iter(
            &mut (0..width).map(|x| (0..height)
                .map(move |y| board.get(y, x))).flatten(),
            height,
        )
    }
}

impl<T: Board> Iterator for SimilarSituations<'_, T> {
    type Item = BinBoard;

    fn next(&mut self) -> Option<Self::Item> {
        if self.mask == 0 {
            None
        } else {
            self.mask -= 1;
            let mut board = BinBoard::from(self.board);
            if self.mask & 1 != 0 {
                board = SimilarSituations::reverse_v(&board);
            }
            if self.mask & 2 != 0 {
                board = SimilarSituations::reverse_h(&board);
            }
            if self.mask & 4 != 0 {
                board = SimilarSituations::negate(&board);
            }
            if self.mask & 8 != 0 {
                board = SimilarSituations::transpose(&board);
            }
            Some(board)
        }
    }
}