arimaa-engine 0.1.0

A bitboard-based Arimaa game engine: move generation, game state, and Zobrist hashing.
Documentation
use lazy_static::lazy_static;
use std::{convert::TryFrom, fmt, iter::FromIterator, str::FromStr};
use tinyvec::ArrayVec;

use super::Direction;

lazy_static! {
    static ref PATH_SPARSE_LOOKUP: Vec<ArrayVec<[Direction; 4]>> = Path::create_sparse_path_lookup();
}

type Directions = u8;

#[derive(Hash, Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct Path(Directions);

impl fmt::Display for Path {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        for dir in self.iter() {
            write!(f, "{}", dir)?
        }

        Ok(())
    }
}

#[rustfmt::skip]
impl Path {
    pub fn invert(&self) -> Self {
        self.iter().map(|d| d.invert()).collect::<Path>()
    }

    pub fn vertical_symmetry(&self) -> Self {
        self.iter().map(|d| d.vertical_symmetry()).collect::<Path>()
    }

    pub fn iter(&self) -> impl Iterator<Item=Direction> {
        self.into_iter()
    }

    pub fn len(&self) -> usize {
        self.iter().count()
    }

    pub fn is_empty(&self) -> bool {
        false
    }

    pub fn as_u8(&self) -> u8 {
        self.0
    }

    fn get_direction_at_index(path: u8, index: u8) -> u8 {
        (path >> (index * 2)) & DIRECTION_MASK
    }

    fn get_direction_for_dir_bits(bits: u8) -> Direction {
        DIRECTION_BY_INDEX[bits as usize]
    }

    fn create_sparse_path_lookup() -> Vec<ArrayVec<[Direction; 4]>> {
        let mut lookup = Vec::with_capacity(256);

        for i in 0..=255 {
            let path = Path(i).path_into_array_vec_no_lookup_slow();
            lookup.push(path);
        }

        lookup
    }

    fn path_into_array_vec_no_lookup_slow(&self) -> ArrayVec<[Direction; 4]> {
        let mut directions = ArrayVec::new();
        let mut prev_dir = 0;

        for i in 0..4 {
            let curr_dir = Path::get_direction_at_index(self.0, i);

            if i != 0 && curr_dir ^ prev_dir == 0b11 {
                break;
            }
            
            directions.push(Path::get_direction_for_dir_bits(curr_dir));
            prev_dir = curr_dir;
        }

        directions
    }
}

impl FromStr for Path {
    type Err = anyhow::Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        s.chars().map(Direction::try_from).collect::<Result<Path, Self::Err>>()
    }
}

impl FromIterator<Direction> for Path {
    fn from_iter<I: IntoIterator<Item = Direction>>(iter: I) -> Self {
        let bits_for_dir = |dir: Direction| match dir {
            Direction::Up => 0b00,
            Direction::Right => 0b01,
            Direction::Left => 0b10,
            Direction::Down => 0b11,
        };

        let (last_dir_bit, mut path_bits, last_i) = iter.into_iter().enumerate().fold((0u8, 0u8, 0usize), |(_, mut path_bits, _), (i, dir)| {
            let dir_bits = bits_for_dir(dir);

            path_bits |= dir_bits << (i * 2);

            (dir_bits, path_bits, i)
        });

        if last_i < 3 {
            let opposite_dir = !last_dir_bit & DIRECTION_MASK;
            path_bits |= opposite_dir << ((last_i + 1) * 2);
        }

        Path(path_bits)
    }
}

impl<'a> FromIterator<&'a Direction> for Path {
    fn from_iter<I: IntoIterator<Item = &'a Direction>>(iter: I) -> Self {
        iter.into_iter().copied().collect::<Path>()
    }
}

// Order of directions is intended so that a XOR produces the opposite direction.
const DIRECTION_MASK: u8 = 0b11;
const DIRECTION_BY_INDEX: [Direction; 4] = [
    Direction::Up,    // b00
    Direction::Right, // b01
    Direction::Left,  // b10
    Direction::Down,  // b11
];

impl IntoIterator for Path {
    type Item = Direction;
    type IntoIter = tinyvec::ArrayVecIterator<[Direction; 4]>;

    fn into_iter(self) -> Self::IntoIter {
        (&self).into_iter()
    }
}

impl IntoIterator for &Path {
    type Item = Direction;
    type IntoIter = tinyvec::ArrayVecIterator<[Direction; 4]>;

    fn into_iter(self) -> Self::IntoIter {
        PATH_SPARSE_LOOKUP[self.0 as usize].into_iter()
    }
}

impl From<&[Direction]> for Path {
    fn from(value: &[Direction]) -> Self {
        value.iter().collect::<Path>()
    }
}