fast-tak 0.4.2

Implementation of the abstract strategy board game Tak
Documentation
use std::array;

use takparse::{Color, Piece, Square, Stack as TpsStack};

use crate::stack::Stack;

#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct Board<const N: usize> {
    data: [[Stack; N]; N],
}

impl<const N: usize> Default for Board<N> {
    fn default() -> Self {
        Self {
            data: [[Stack::default(); N]; N],
        }
    }
}

// https://godbolt.org/z/434j733oW
impl<const N: usize> Clone for Board<N> {
    #[inline]
    fn clone(&self) -> Self {
        unsafe { std::mem::transmute_copy(self) }
    }
}

impl<const N: usize> Board<N> {
    #[inline]
    pub fn iter(&self) -> impl Iterator<Item = impl Iterator<Item = &Stack>> {
        self.data.iter().map(|row| row.iter())
    }

    #[inline]
    #[must_use]
    pub fn get(&self, square: Square) -> Option<&Stack> {
        self.data
            .get(square.row() as usize)
            .and_then(|r| r.get(square.column() as usize))
    }

    #[inline]
    pub fn get_mut(&mut self, square: Square) -> Option<&mut Stack> {
        self.data
            .get_mut(square.row() as usize)
            .and_then(|r| r.get_mut(square.column() as usize))
    }

    #[must_use]
    pub fn full(&self) -> bool {
        !self.data.iter().any(|row| row.iter().any(Stack::is_empty))
    }

    #[must_use]
    pub fn flat_diff(&self) -> i8 {
        self.data
            .iter()
            .flat_map(|row| row.iter())
            .map(|tile| match tile.top() {
                Some((Piece::Flat, Color::White)) => 1,
                Some((Piece::Flat, Color::Black)) => -1,
                _ => 0,
            })
            .sum()
    }

    #[must_use]
    pub fn has_road(&self, color: Color) -> bool {
        let road = self.data.map(|row| row.map(|s| s.road(color)));
        let mut seen = [[false; N]; N];
        let mut reached = [[false; N]; N];
        let mut stack = Vec::with_capacity(N * N);

        // Horizontal roads
        for row in 0..N {
            stack.push((row, 0));
        }
        Self::flood_fill(&road, &mut stack, &mut seen, &mut reached);
        if reached.iter().any(|row| row[N - 1]) {
            return true;
        }

        // Vertical roads
        stack.clear();
        reached = [[false; N]; N];
        seen = [[false; N]; N];
        for col in 0..N {
            stack.push((0, col));
        }
        Self::flood_fill(&road, &mut stack, &mut seen, &mut reached);
        reached[N - 1].iter().any(|b| *b)
    }

    fn flood_fill(
        road: &[[bool; N]; N],
        stack: &mut Vec<(usize, usize)>,
        seen: &mut [[bool; N]; N],
        reached: &mut [[bool; N]; N],
    ) {
        while let Some((row, col)) = stack.pop() {
            seen[row][col] = true;
            if road[row][col] {
                reached[row][col] = true;

                if let Some(r) = row.checked_sub(1) {
                    if !seen[r][col] {
                        stack.push((r, col));
                    }
                }
                if let Some(c) = col.checked_sub(1) {
                    if !seen[row][c] {
                        stack.push((row, c));
                    }
                }
                if row < N - 1 {
                    let r = row + 1;
                    if !seen[r][col] {
                        stack.push((r, col));
                    }
                }
                if col < N - 1 {
                    let c = col + 1;
                    if !seen[row][c] {
                        stack.push((row, c));
                    }
                }
            }
        }
    }
}

impl<'a, const N: usize> FromIterator<Option<&'a TpsStack>> for Board<N> {
    fn from_iter<T: IntoIterator<Item = Option<&'a TpsStack>>>(iter: T) -> Self {
        let mut iter = iter.into_iter().map(|square| {
            square.map_or_else(Stack::default, |stack| {
                Stack::exact(stack.top(), stack.colors().collect())
            })
        });
        let mut data = array::from_fn(|_| array::from_fn(|_| iter.next().unwrap_or_default()));
        data.reverse();
        Self { data }
    }
}