chess 3.0.1

This is a fast chess move generator. It has a very good set of documentation, so you should take advantage of that. It (now) generates all lookup tabels with a build.rs file, which means that very little pseudo-legal move generation requires branching. There are some convenience functions that are exposed to, for example, find all the squares between two squares. This uses a copy-on-make style structure, and the Board structure is as slimmed down as possible to reduce the cost of copying the board. There are places to improve perft-test performance further, but I instead opt to be more feature-complete to make it useful in real applications. For example, I generate both a hash of the board and a pawn-hash of the board for use in evaluation lookup tables (using Zobrist hashing). There are two ways to generate moves, one is faster, the other has more features that will be useful if making a chess engine. See the documentation for more details.
Documentation
use crate::board::{Board, BoardStatus};
use crate::chess_move::ChessMove;
use crate::color::Color;
use crate::movegen::MoveGen;
use crate::piece::Piece;

/// Contains all actions supported within the game
#[derive(Copy, Clone, PartialEq, PartialOrd, Debug, Eq)]
pub enum Action {
    MakeMove(ChessMove),
    OfferDraw(Color),
    AcceptDraw,
    DeclareDraw,
    Resign(Color),
}

/// What was the result of this game?
#[derive(Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Debug, Hash)]
pub enum GameResult {
    WhiteCheckmates,
    WhiteResigns,
    BlackCheckmates,
    BlackResigns,
    Stalemate,
    DrawAccepted,
    DrawDeclared,
}

/// For UI/UCI Servers, store a game object which allows you to determine
/// draw by 3 fold repitition, draw offers, resignations, and moves.
///
/// This structure is slow compared to using `Board` directly, so it is
/// not recommended for engines.
#[derive(Clone, Debug)]
pub struct Game {
    start_pos: Board,
    moves: Vec<Action>,
}

impl Game {
    /// Create a new `Game` with the initial position.
    ///
    /// ```
    /// use chess::{Game, Board};
    ///
    /// let game = Game::new();
    /// assert_eq!(game.current_position(), Board::default());
    /// ```
    pub fn new() -> Game {
        Game {
            start_pos: Board::default(),
            moves: vec![],
        }
    }

    /// Get all actions made in this game (moves, draw offers, resignations, etc.)
    ///
    /// ```
    /// use chess::{Game, MoveGen, Color};
    ///
    /// let mut game = Game::new();
    /// let mut movegen = MoveGen::new_legal(&game.current_position());
    ///
    /// game.make_move(movegen.next().expect("At least one valid move"));
    /// game.resign(Color::Black);
    /// assert_eq!(game.actions().len(), 2);
    /// ```
    pub fn actions(&self) -> &Vec<Action> {
        &self.moves
    }

    /// What is the status of this game?
    ///
    /// ```
    /// use chess::Game;
    ///
    /// let game = Game::new();
    /// assert!(game.result().is_none());
    /// ```
    pub fn result(&self) -> Option<GameResult> {
        match self.current_position().status() {
            BoardStatus::Checkmate => {
                if self.side_to_move() == Color::White {
                    Some(GameResult::BlackCheckmates)
                } else {
                    Some(GameResult::WhiteCheckmates)
                }
            }
            BoardStatus::Stalemate => Some(GameResult::Stalemate),
            BoardStatus::Ongoing => {
                if self.moves.len() == 0 {
                    None
                } else if self.moves[self.moves.len() - 1] == Action::AcceptDraw {
                    Some(GameResult::DrawAccepted)
                } else if self.moves[self.moves.len() - 1] == Action::DeclareDraw {
                    Some(GameResult::DrawDeclared)
                } else if self.moves[self.moves.len() - 1] == Action::Resign(Color::White) {
                    Some(GameResult::WhiteResigns)
                } else if self.moves[self.moves.len() - 1] == Action::Resign(Color::Black) {
                    Some(GameResult::BlackResigns)
                } else {
                    None
                }
            }
        }
    }

    /// Create a new `Game` object from an FEN string.
    ///
    /// ```
    /// use chess::{Game, Board};
    ///
    /// let game = Game::new_from_fen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1").expect("Valid FEN");
    /// let game2 = Game::new_from_fen("Invalid FEN");
    /// assert!(game2.is_none());
    /// ```
    pub fn new_from_fen(fen: &str) -> Option<Game> {
        let board = Board::from_fen(fen.to_string());
        match board {
            None => None,
            Some(b) => Some(Game {
                start_pos: b,
                moves: vec![],
            }),
        }
    }

    /// Get the current position on the board from the `Game` object.
    ///
    /// ```
    /// use chess::{Game, Board};
    ///
    /// let game = Game::new();
    /// assert_eq!(game.current_position(), Board::default());
    /// ```
    pub fn current_position(&self) -> Board {
        let mut copy = self.start_pos;

        for x in self.moves.iter() {
            match *x {
                Action::MakeMove(m) => {
                    copy = copy.make_move_new(m);
                }
                _ => {}
            }
        }

        copy
    }

    /// Determine if a player can legally declare a draw by 3-fold repetition or 50-move rule.
    ///
    /// ```
    /// use chess::{Game, Square, Rank, File, ChessMove};
    ///
    /// let b1 = Square::make_square(Rank::First, File::B);
    /// let c3 = Square::make_square(Rank::Third, File::C);
    ///
    /// let b8 = Square::make_square(Rank::Eighth, File::B);
    /// let c6 = Square::make_square(Rank::Sixth, File::C);
    ///
    /// let b1c3 = ChessMove::new(b1, c3, None);
    /// let c3b1 = ChessMove::new(c3, b1, None);
    ///
    /// let b8c6 = ChessMove::new(b8, c6, None);
    /// let c6b8 = ChessMove::new(c6, b8, None);
    ///
    /// let mut game = Game::new();
    /// assert_eq!(game.can_declare_draw(), false);
    ///
    /// game.make_move(b1c3);
    /// game.make_move(b8c6);
    /// game.make_move(c3b1);
    /// game.make_move(c6b8);
    ///
    /// assert_eq!(game.can_declare_draw(), false); // position has shown up twice
    ///
    /// game.make_move(b1c3);
    /// game.make_move(b8c6);
    /// game.make_move(c3b1);
    /// game.make_move(c6b8);
    /// assert_eq!(game.can_declare_draw(), true); // position has shown up three times
    /// ```
    pub fn can_declare_draw(&self) -> bool {
        if self.result().is_some() {
            return false;
        }

        let mut legal_moves_per_move: Vec<Vec<ChessMove>> = vec![];

        let mut board = self.start_pos;
        let mut reversible_moves = 0;
        legal_moves_per_move.push(MoveGen::new_legal(&board).collect());
        for x in self.moves.iter() {
            match *x {
                Action::MakeMove(m) => {
                    let white_castle_rights = board.castle_rights(Color::White);
                    let black_castle_rights = board.castle_rights(Color::Black);
                    if board.piece_on(m.get_source()) == Some(Piece::Pawn) {
                        reversible_moves = 0;
                    } else if board.piece_on(m.get_dest()).is_some() {
                        reversible_moves = 0;
                    } else {
                        reversible_moves += 1;
                    }
                    board = board.make_move_new(m);

                    if board.castle_rights(Color::White) != white_castle_rights
                        || board.castle_rights(Color::Black) != black_castle_rights
                    {
                        reversible_moves = 0;
                    }
                    legal_moves_per_move.push(MoveGen::new_legal(&board).collect());
                }
                _ => {}
            }
        }

        if reversible_moves >= 100 {
            return true;
        }

        let last_moves = legal_moves_per_move[legal_moves_per_move.len() - 1].clone();

        for i in 1..(legal_moves_per_move.len() - 1) {
            for j in 0..i {
                if legal_moves_per_move[i] == last_moves && legal_moves_per_move[j] == last_moves {
                    return true;
                }
            }
        }

        return false;
    }

    /// Declare a draw by 3-fold repitition or 50-move rule.
    ///
    /// ```
    /// use chess::{Game, Square, Rank, File, ChessMove};
    ///
    /// let b1 = Square::make_square(Rank::First, File::B);
    /// let c3 = Square::make_square(Rank::Third, File::C);
    ///
    /// let b8 = Square::make_square(Rank::Eighth, File::B);
    /// let c6 = Square::make_square(Rank::Sixth, File::C);
    ///
    /// let b1c3 = ChessMove::new(b1, c3, None);
    /// let c3b1 = ChessMove::new(c3, b1, None);
    ///
    /// let b8c6 = ChessMove::new(b8, c6, None);
    /// let c6b8 = ChessMove::new(c6, b8, None);
    ///
    /// let mut game = Game::new();
    /// assert_eq!(game.can_declare_draw(), false);
    ///
    /// game.make_move(b1c3);
    /// game.make_move(b8c6);
    /// game.make_move(c3b1);
    /// game.make_move(c6b8);
    ///
    /// assert_eq!(game.can_declare_draw(), false); // position has shown up twice
    ///
    /// game.make_move(b1c3);
    /// game.make_move(b8c6);
    /// game.make_move(c3b1);
    /// game.make_move(c6b8);
    /// assert_eq!(game.can_declare_draw(), true); // position has shown up three times
    /// game.declare_draw();
    /// ```
    pub fn declare_draw(&mut self) -> bool {
        if self.can_declare_draw() {
            self.moves.push(Action::DeclareDraw);
            true
        } else {
            false
        }
    }

    /// Make a chess move on the board
    ///
    /// ```
    /// use chess::{Game, MoveGen};
    ///
    /// let mut game = Game::new();
    ///
    /// let mut movegen = MoveGen::new_legal(&game.current_position());
    ///
    /// game.make_move(movegen.next().expect("At least one legal move"));
    /// ```
    pub fn make_move(&mut self, chess_move: ChessMove) -> bool {
        if self.result().is_some() {
            return false;
        }
        if self.current_position().legal(chess_move) {
            self.moves.push(Action::MakeMove(chess_move));
            true
        } else {
            false
        }
    }

    /// Who's turn is it to move?
    ///
    /// ```
    /// use chess::{Game, Color};
    ///
    /// let game = Game::new();
    /// assert_eq!(game.side_to_move(), Color::White);
    /// ```
    pub fn side_to_move(&self) -> Color {
        let move_count = self
            .moves
            .iter()
            .filter(|m| match *m {
                Action::MakeMove(_) => true,
                _ => false,
            })
            .count()
            + if self.start_pos.side_to_move() == Color::White {
                0
            } else {
                1
            };

        if move_count % 2 == 0 {
            Color::White
        } else {
            Color::Black
        }
    }

    /// Offer a draw to my opponent.  `color` is the player who offered the draw.  The draw must be
    /// accepted before my opponent moves.
    ///
    /// ```
    /// use chess::{Game, Color};
    ///
    /// let mut game = Game::new();
    /// game.offer_draw(Color::White);
    /// ```
    pub fn offer_draw(&mut self, color: Color) -> bool {
        if self.result().is_some() {
            return false;
        }
        self.moves.push(Action::OfferDraw(color));
        return true;
    }

    /// Accept a draw offer from my opponent.
    ///
    /// ```
    /// use chess::{Game, MoveGen, Color};
    ///
    /// let mut game = Game::new();
    /// game.offer_draw(Color::Black);
    /// assert_eq!(game.accept_draw(), true);
    ///
    /// let mut game2 = Game::new();
    /// let mut movegen = MoveGen::new_legal(&game2.current_position());
    /// game2.offer_draw(Color::Black);
    /// game2.make_move(movegen.next().expect("At least one legal move"));
    /// assert_eq!(game2.accept_draw(), false);
    /// ```
    pub fn accept_draw(&mut self) -> bool {
        if self.result().is_some() {
            return false;
        }
        if self.moves.len() > 0 {
            if self.moves[self.moves.len() - 1] == Action::OfferDraw(Color::White)
                || self.moves[self.moves.len() - 1] == Action::OfferDraw(Color::Black)
            {
                self.moves.push(Action::AcceptDraw);
                return true;
            }
        }

        if self.moves.len() > 1 {
            if self.moves[self.moves.len() - 2] == Action::OfferDraw(!self.side_to_move()) {
                self.moves.push(Action::AcceptDraw);
                return true;
            }
        }

        false
    }

    /// `color` resigns the game
    ///
    /// ```
    /// use chess::{Game, Color};
    ///
    /// let mut game = Game::new();
    /// game.resign(Color::White);
    /// ```
    pub fn resign(&mut self, color: Color) -> bool {
        if self.result().is_some() {
            return false;
        }
        self.moves.push(Action::Resign(color));
        return true;
    }
}