chessrs 0.1.0

A command line implementation for the game of chess
Documentation
use crate::*;

pub mod checks;
mod default;
mod index_extensions;

#[derive(Debug)]
pub struct Game {
    board: [[Piece; 8]; 8],
    pub turn: u8,
    pub check: Checks,
    pub king_positions: [(usize, usize); 2],
}

impl Display for Game {
    fn fmt(&self, f: &mut Formatter) -> fmtResult {
        for row in self.board.iter() {
            for piece in row.iter() {
                write!(f, "{}", piece)?;
            }
            writeln!(f)?;
        }

        Ok(())
    }
}

type MoveResult = Result<(), String>;

impl Game {
    pub fn make_move(&mut self, coords: Coords) -> MoveResult {
        let previous_position = match PieceMove::from_piece_type(coords, self, false, self.turn) {
            Ok(x) => x,
            Err(e) => {
                return Err(e);
            }
        };

        if !previous_position.is_valid(self, None) {
            return Err("Invalid move".to_string());
        }

        let piece = self.move_piece(&previous_position, &coords);
        if self.is_king_in_check()[self.turn] {
            self.revert_move(&previous_position, &coords, piece);
            return Err("Invalid move, king in check".to_string());
        }

        Ok(())
    }

    fn move_piece(&mut self, previous_position: &PieceMove, coords: &Coords) -> Piece {
        let color_mask = if self.turn == 1 { 0b1000 } else { 0 };
        let piece = Piece::from_coords((coords.piece as u8) ^ color_mask, *coords);

        self[previous_position.piece.coords.y][previous_position.piece.coords.x] = Piece::empty();
        self[coords.y][coords.x] = piece;

        if piece.piece_type() == (PieceType::King as u8) {
            self.king_positions[self.turn as usize] = (coords.x, coords.y);
        }

        piece
    }

    fn revert_move(&mut self, previous_position: &PieceMove, coords: &Coords, piece: Piece) {
        self[previous_position.piece.coords.y][previous_position.piece.coords.x] =
            previous_position.piece;
        self[coords.y][coords.x] = Piece::empty();

        if piece.piece_type() == PieceType::King as u8 {
            self.king_positions[self.turn as usize] = (
                previous_position.piece.coords.x,
                previous_position.piece.coords.y,
            );
        }
    }

    pub fn is_path(&self, path_type: PiecePath, start_coords: Coords, end_coords: Coords) -> bool {
        match path_type {
            PiecePath::Straight => {
                if start_coords.x == end_coords.x {
                    let range = if start_coords.y < end_coords.y {
                        start_coords.y + 1..end_coords.y
                    } else {
                        end_coords.y + 1..start_coords.y
                    };
                    return range.step_by(1).all(|y| self[y][start_coords.x].is_empty());
                } else if start_coords.y == end_coords.y {
                    let range = if start_coords.x < end_coords.x {
                        start_coords.x + 1..end_coords.x
                    } else {
                        end_coords.x + 1..start_coords.x
                    };
                    return range.step_by(1).all(|x| self[start_coords.y][x].is_empty());
                }
                false
            }
            PiecePath::Diagonal => {
                if start_coords.x == end_coords.x || start_coords.y == end_coords.y {
                    return false;
                }
                let (step_x, step_y) = (
                    if start_coords.x < end_coords.x { 1 } else { -1 },
                    if start_coords.y < end_coords.y { 1 } else { -1 },
                );
                let (mut x, mut y) = (
                    start_coords.x as isize + step_x,
                    start_coords.y as isize + step_y,
                );
                while x != end_coords.x as isize
                    && y != end_coords.y as isize
                    && Game::in_bounds(x, y)
                {
                    if !self[y as usize][x as usize].is_empty() {
                        return false;
                    }
                    x += step_x;
                    y += step_y;
                }
                true
            }
        }
    }

    pub fn is_king_in_check(&mut self) -> Checks {
        [0, 1]
            .iter()
            .map(|color| {
                PieceType::iter().any(|piece_type| {
                    self.is_attacked(self.get_check_move_coords(piece_type, *color), *color)
                })
            })
            .collect::<Checks>()
    }

    fn get_check_move_coords(&self, piece_type: PieceType, color: u8) -> Coords {
        let (x, y) = if color == 0 {
            self.king_positions[0]
        } else {
            self.king_positions[1]
        };
        Coords {
            piece: piece_type,
            x,
            y,
            is_capture: true,
        }
    }

    fn is_attacked(&self, piece_coords: Coords, color: u8) -> bool {
        PieceMove::from_piece_type(piece_coords, self, true, color)
            .map(|attacker_move| attacker_move.is_valid(self, None))
            .unwrap_or(false)
    }

    fn in_bounds<T>(x: T, y: T) -> bool
    where
        T: Into<isize> + Copy,
    {
        x.into() >= 0 && x.into() <= 7 && y.into() >= 0 && y.into() <= 7
    }
}