//! This module contains [Board], the Object representing the current state of a chessboard.
//! All modifications to the current state of the board is done through this object, as well as
//! gathering information about the current state of the board.
//!
use templates::*;
use magic_helper::MagicHelper;
use movegen::MoveGen;
use bit_twiddles::*;
use piece_move::{BitMove, MoveType};
use std::option::*;
use std::sync::Arc;
use std::{mem, fmt, char};
lazy_static! {
/// Statically initialized lookup tables.
pub static ref MAGIC_HELPER: MagicHelper<'static,'static> = MagicHelper::new();
}
bitflags! {
/// Structure to help with recognizing the various possibilities of castling/
///
/// For internal use by the Board only
///
/// Keeps track two things for each player
/// 1) What sides are possible to castle from
/// 2) Has this player castled
///
/// Does not garauntee that the player containing a castling bit can castle at that
/// time. Rather marks that castling is a possibility, e.g. a Castling struct
/// containing a bit marking WHITE_Q means that neither the White King or Queen-side
/// rook has moved since the game started.
pub struct Castling: u8 {
const WHITE_K = 0b0000_1000; // White has King-side Castling ability
const WHITE_Q = 0b0000_0100; // White has Queen-side Castling ability
const BLACK_K = 0b0000_0010; // Black has King-side Castling ability
const BLACK_Q = 0b0000_0001; // White has Queen-side Castling ability
const WHITE_CASTLE = 0b0100_0000; // White has castled
const BLACK_CASTLE = 0b0001_0000; // Black has castled
const WHITE_ALL = WHITE_K.bits // White can castle for both sides
| WHITE_Q.bits;
const BLACK_ALL = BLACK_K.bits // Black can castle for both sides
| BLACK_Q.bits;
}
}
impl Castling {
/// Removes all castling possibility for a single player
pub fn remove_player_castling(&mut self, player: Player) {
match player {
Player::White => self.bits &= BLACK_ALL.bits,
Player::Black => self.bits &= WHITE_ALL.bits,
}
}
/// Removes King-Side castling possibility for a single player
pub fn remove_king_side_castling(&mut self, player: Player) {
match player {
Player::White => self.bits &= !WHITE_K.bits,
Player::Black => self.bits &= !BLACK_K.bits,
}
}
/// Removes Queen-Side castling possibility for a single player
pub fn remove_queen_side_castling(&mut self, player: Player) {
match player {
Player::White => self.bits &= !WHITE_Q.bits,
Player::Black => self.bits &= !BLACK_Q.bits,
}
}
/// Returns if a player can castle for a given side
pub fn castle_rights(&self, player: Player, side: CastleType) -> bool {
match player {
Player::White => {
match side {
CastleType::KingSide => self.contains(WHITE_K),
CastleType::QueenSide => self.contains(WHITE_Q),
}
}
Player::Black => {
match side {
CastleType::KingSide => self.contains(BLACK_K),
CastleType::QueenSide => self.contains(BLACK_Q),
}
}
}
}
/// Sets the bits to represent a given player has castled
pub fn set_castling(&mut self, player: Player) {
match player {
Player::White => self.bits |= WHITE_CASTLE.bits,
Player::Black => self.bits |= BLACK_CASTLE.bits,
}
}
/// Returns if a given player has castled
pub fn has_castled(&self, player: Player) -> bool {
match player {
Player::White => self.contains(WHITE_CASTLE),
Player::Black => self.contains(BLACK_CASTLE),
}
}
/// Returns if both players have lost their ability to castle
pub fn no_castling(&self) -> bool {
!self.contains(WHITE_K) && !self.contains(WHITE_Q) && !self.contains(BLACK_K) &&
!self.contains(BLACK_Q)
}
/// Returns a pretty String representing the castling state
///
/// Used for FEN Strings, with ('K' | 'Q') representing white castling abilities,
/// and ('k' | 'q') representing black castling abilities. If there are no bits set,
/// returns a String containing "-".
pub fn pretty_string(&self) -> String {
if self.no_castling() {
"-".to_owned()
} else {
let mut s = String::default();
if self.contains(WHITE_K) {
s.push('K');
}
if self.contains(WHITE_Q) {
s.push('Q');
}
if self.contains(BLACK_K) {
s.push('k');
}
if self.contains(BLACK_Q) {
s.push('q');
}
assert!(!s.is_empty());
s
}
}
}
impl fmt::Display for Castling {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.pretty_string())
}
}
/// Struct to allow fast lookups for any square.
///
/// Provides the Stores if there is any piece at a square, and if so provides its color and piece type.
///
/// Piece Locations is a BLIND structure, Providing a function of |sq| -> |Piece AND/OR Player|
/// The reverse cannot be done Looking up squares from a piece / player.
struct PieceLocations {
// Pieces are represented by the following bit_patterns:
// x000 -> Pawn (P)
// x001 -> Knight(N)
// x010 -> Bishop (B)
// x011 -> Rook(R)
// x100 -> Queen(Q)
// x101 -> King (K)
// x110 -> ??? Undefined ??
// x111 -> None
// 0xxx -> White Piece
// 1xxx -> Black Piece
// array of u8's, with standard ordering mapping index to square
data: [u8; 64],
}
impl Clone for PieceLocations {
// Need to use transmute copy as [_;64] does not automatically implement Clone.
fn clone(&self) -> PieceLocations {
unsafe { mem::transmute_copy(&*&self.data) }
}
}
impl PieceLocations {
/// Constructs a new Piece Locations with a defaulty of no pieces on the board
pub fn blank() -> PieceLocations {
PieceLocations { data: [0b0111; 64] }
}
/// Constructs a new Piece Locations with the memory at a default of Zeros
///
/// This function is unsafe as Zeros represent Pawns, and therefore care mus be taken
/// to iterate through every square and ensure the correct piece or lack of piece
/// is placed
pub unsafe fn default() -> PieceLocations {
PieceLocations { data: [0; 64] }
}
/// Places a given piece for a given player at a certain square
///
/// # Panics
/// Panics if Square is of index higher than 63
pub fn place(&mut self, square: SQ, player: Player, piece: Piece) {
assert!(sq_is_okay(square));
self.data[square as usize] = self.create_sq(player, piece);
}
/// Removes a Square
///
/// # Panics
///
/// Panics if Square is of index higher than 63
pub fn remove(&mut self, square: SQ) {
assert!(sq_is_okay(square));
self.data[square as usize] = 0b0111
}
/// Returns the Piece at a square, Or None if the square is empty.
///
/// # Panics
///
/// Panics if Square is of index higher than 63.
pub fn piece_at(&self, square: SQ) -> Option<Piece> {
assert!(sq_is_okay(square));
let byte: u8 = self.data[square as usize] & 0b0111;
match byte {
0b0000 => Some(Piece::P),
0b0001 => Some(Piece::N),
0b0010 => Some(Piece::B),
0b0011 => Some(Piece::R),
0b0100 => Some(Piece::Q),
0b0101 => Some(Piece::K),
0b0110 => unreachable!(), // Undefined
0b0111 => None,
_ => unreachable!(),
}
}
/// Returns the Piece at a square for a given player.
///
/// If there is no piece at that square, or there is a piece of another player at that square,
/// returns None.
///
/// # Panics
///
/// Panics if Square is of index higher than 63
pub fn piece_at_for_player(&self, square: SQ, player: Player) -> Option<Piece> {
let op = self.player_piece_at(square);
if op.is_some() {
let p = op.unwrap();
if p.0 == player {
Some(p.1)
} else {
None
}
} else {
None
}
}
/// Returns the player (if any) is occupying a square
///
/// # Panics
///
/// Panics if Square is of index higher than 63
pub fn player_at(&self, square: SQ) -> Option<Player> {
let byte: u8 = self.data[square as usize];
if byte == 0b0111 || byte == 0b1111 {
return None;
}
if byte < 8 {
Some(Player::White)
} else {
Some(Player::Black)
}
}
/// Returns a Tuple of (Player,Piece) of the player and associated piece at a
/// given square. Returns None if the square is unoccupied.
///
/// # Panics
///
/// Panics if Square is of index higher than 63
pub fn player_piece_at(&self, square: SQ) -> Option<(Player, Piece)> {
let byte: u8 = self.data[square as usize];
match byte {
0b0000 => Some((Player::White, Piece::P)),
0b0001 => Some((Player::White, Piece::N)),
0b0010 => Some((Player::White, Piece::B)),
0b0011 => Some((Player::White, Piece::R)),
0b0100 => Some((Player::White, Piece::Q)),
0b0101 => Some((Player::White, Piece::K)),
0b0110 => unreachable!(), // Undefined
0b0111 | 0b1111 => None,
0b1000 => Some((Player::Black, Piece::P)),
0b1001 => Some((Player::Black, Piece::N)),
0b1010 => Some((Player::Black, Piece::B)),
0b1011 => Some((Player::Black, Piece::R)),
0b1100 => Some((Player::Black, Piece::Q)),
0b1101 => Some((Player::Black, Piece::K)),
0b1110 => unreachable!(), // Undefined
_ => unreachable!(),
}
}
/// Helper method to return the bit representation of a given piece and player
fn create_sq(&self, player: Player, piece: Piece) -> u8 {
let mut loc: u8 = match piece {
Piece::P => 0b0000,
Piece::N => 0b0001,
Piece::B => 0b0010,
Piece::R => 0b0011,
Piece::Q => 0b0100,
Piece::K => 0b0101,
};
if player == Player::Black {
loc |= 0b1000;
}
loc
}
}
/// Holds useful information concerning the current state of the board.
///
/// This is information that is computed upon making a move, and requires expensive computation to do so as well.
/// It is stored in the Heap by 'Board' as an Arc<BoardState>, as cloning the board can lead to multiple
/// references to the same BoardState.
///
/// Allows for easy undo-ing of moves as these keep track of their previous board state, forming a
/// Tree-like persistent Stack
#[derive(Clone)]
pub struct BoardState {
// The Following Fields are easily copied from the previous version and possbily modified
pub castling: Castling,
pub rule_50: i16,
pub ply: u16,
pub ep_square: SQ,
// These fields MUST be Recomputed after a move
pub zobrast: u64,
pub captured_piece: Option<Piece>,
pub checkers_bb: BitBoard, // What squares is the current player receiving check from?
pub blockers_king: [BitBoard; PLAYER_CNT],
pub pinners_king: [BitBoard; PLAYER_CNT],
pub check_sqs: [BitBoard; PIECE_CNT],
pub prev_move: BitMove,
// Previous State of the board ( one move ago)
pub prev: Option<Arc<BoardState>>,
// castling -> Castling Bit Structure, keeping track of if either player can castle.
// as well as if they have castled.
// rule50 -> Moves since last capture, pawn move or castle. Used for Draws.
// ply -> How many moves deep this current thread is.
// ** NOTE: CURRENTLY UNUSED **
// ep_square -> If the last move was a double pawn push, this will be equal to the square behind.
// the push. ep_square = abs(sq_to - sq_from) / 2
// If last move was not a double push, this will equal NO_SQ (which is 64).
// zobrast -> Zobrist Key of the current board.
// capture_piece -> The Piece (if any) that was last captured
// checkers_bb -> Bitboard of all pieces who currently check the king
// blockers_king -> Per each player, bitboard of pieces blocking an attack on a that player's king.
// NOTE: Can contain opponents pieces. E.g. a Black Pawn can block an attack of a white king
// if there is a queen (or some other sliding piece) on the same line.
// pinners_king -> Per each player, bitboard of pieces currently pinning the opponent's king.
// e.g:, a Black Queen pinning a piece (of either side) to White's King
// check_sqs -> Array of BitBoards where for Each Piece, gives a spot the piece can move to where
// the opposing player's king would be in check.
}
impl BoardState {
/// Constructs a board state for the starting position.
pub fn default() -> BoardState {
BoardState {
castling: Castling::all(),
rule_50: 0,
ply: 0,
ep_square: NO_SQ,
zobrast: 0,
captured_piece: None,
checkers_bb: 0,
blockers_king: [0; PLAYER_CNT],
pinners_king: [0; PLAYER_CNT],
check_sqs: [0; PIECE_CNT],
prev_move: BitMove::null(),
prev: None,
}
}
/// Constructs a blank board state.
pub fn blank() -> BoardState {
BoardState {
castling: Castling::empty(),
rule_50: 0,
ply: 0,
ep_square: NO_SQ,
zobrast: 0,
captured_piece: None,
checkers_bb: 0,
blockers_king: [0; PLAYER_CNT],
pinners_king: [0; PLAYER_CNT],
check_sqs: [0; PIECE_CNT],
prev_move: BitMove::null(),
prev: None,
}
}
/// Constructs a partial clone of a BoardState.
///
/// Castling, rule_50, ply, and ep_square are copied. The copied fields need to be
/// modified accordingly, and the remaining fields need to be generated.
pub fn partial_clone(&self) -> BoardState {
BoardState {
castling: self.castling,
rule_50: self.rule_50,
ply: self.ply,
ep_square: self.ep_square,
zobrast: self.zobrast,
captured_piece: None,
checkers_bb: 0,
blockers_king: [0; PLAYER_CNT],
pinners_king: [0; PLAYER_CNT],
check_sqs: [0; PIECE_CNT],
prev_move: BitMove::null(),
prev: self.get_prev(),
}
}
/// Return the previous BoardState from one move ago.
pub fn get_prev(&self) -> Option<Arc<BoardState>> {
(&self).prev.as_ref().cloned()
}
pub fn backtrace(&self) {
self.print_info();
if self.prev.is_some() {
self.get_prev().unwrap().print_info();
}
}
pub fn print_info(&self) {
println!("ply: {}, move played: {}",self.ply, self.prev_move);
}
}
/// Represents a ChessBoard.
///
/// Board contains everything that needs to be known about the current state of the Game. It is used
/// by both Engines and Players / Bots alike.
///
/// Ideally, the Engine contains the original Representation of a board (owns the board), and utilizes
/// [Board::shallow_clone()] to share this representaion with Players.
///
/// # Examples
///
/// ```
/// use pleco::board::*;
///
/// fn main() {
/// let mut chessboard = Board::default();
///
/// let moves = chessboard.generate_moves();
/// chessboard.apply_move(moves[0]);
///
/// let b2 = chessboard.shallow_clone(); // boards allow for easy cloning
/// assert_eq!(chessboard.moves_played(), b2.moves_played());
/// }
/// ```
///
/// # BitBoard Representation
///
/// For the majority of the struct, the board utilizes [BitBoard]s, which is a u64 where each bit
/// represents an occupied location, and each bit index represents a certain square (as in bit 0 is
/// Square A1, bit 1 is B1, etc.). Indexes increase first horizontally by File, and then by Rank. See
/// [BitBoards article ChessWiki](https://chessprogramming.wikispaces.com/Bitboards) for more information.
///
/// The exact mapping from each square to bits is below,
/// ```
/// // 8 | 56 57 58 59 60 61 62 63
/// // 7 | 48 49 50 51 52 53 54 55
/// // 6 | 40 41 42 43 44 45 46 47
/// // 5 | 32 33 34 35 36 37 38 39
/// // 4 | 24 25 26 27 28 29 30 31
/// // 3 | 16 17 18 19 20 21 22 23
/// // 2 | 8 9 10 11 12 13 14 15
/// // 1 | 0 1 2 3 4 5 6 7
/// // -------------------------
/// // a b c d e f g h
/// ```
pub struct Board {
turn: Player, // Current turn
bit_boards: [[BitBoard; PIECE_CNT]; PLAYER_CNT], // Occupancy per player per piece
occ: [BitBoard; PLAYER_CNT], // Occupancy per Player
occ_all: BitBoard, // BitBoard of all pieces
half_moves: u16, // Total moves played
depth: u16, // Current depth since last shallow_copy
piece_counts: [[u8; PIECE_CNT]; PLAYER_CNT], // Count of each Piece
piece_locations: PieceLocations, // Mapping Squares to Pieces and Plauers
// State of the Board, Un modifiable.
// Arc to allow easy and quick copying of boards without copying memory
// or recomputing BoardStates.
state: Arc<BoardState>,
// List of Moves that have been played so far.
// Only gaurunteed to have the moves since last copy.
// undo_moves: Vec<BitMove>,
// Reference to the pre-computed information
pub magic_helper: &'static MAGIC_HELPER,
}
impl fmt::Display for Board {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.pretty_string())
}
}
impl Board {
/// Constructs a board from the starting position
///
/// # Examples
///
/// ```
/// use pleco::board::*;
/// use pleco::templates::Player;
///
/// let mut chessboard = Board::default();
/// assert_eq!(chessboard.count_pieces_player(Player::White),16);
/// ```
pub fn default() -> Board {
let mut b = Board {
turn: Player::White,
bit_boards: return_start_bb(),
occ: [START_WHITE_OCC, START_BLACK_OCC],
occ_all: START_OCC_ALL,
half_moves: 0,
depth: 0,
piece_counts: [[8, 2, 2, 2, 1, 1], [8, 2, 2, 2, 1, 1]],
piece_locations: unsafe { PieceLocations::default() },
state: Arc::new(BoardState::default()),
magic_helper: &MAGIC_HELPER,
};
// Create the Zobrist hash & set the Piece Locations structure
b.set_zob_hash();
b.set_piece_states();
b
}
/// Constructs a shallow clone of the Board.
///
/// Contains only the information necessary to apply future moves, more specifically
/// does not clone the moves list, and sets depth to zero. Intended for an Engine or
/// main thread to share the board to users wanting to search.
///
/// # Safety
///
/// After this method has called, [Board::undo_move()] cannot be called immediately after.
/// Undoing moves can only be done once a move has been played, and cannot be called more
/// times than moves have been played since calling [Board::shallow_clone()].
pub fn shallow_clone(&self) -> Board {
Board {
turn: self.turn,
bit_boards: copy_piece_bbs(&self.bit_boards),
occ: copy_occ_bbs(&self.occ),
occ_all: self.occ_all,
half_moves: self.half_moves,
depth: 0,
piece_counts: self.piece_counts.clone(),
piece_locations: self.piece_locations.clone(),
state: self.state.clone(),
magic_helper: &MAGIC_HELPER,
}
}
/// Constructs a parallel clone of the Board.
///
/// Similar to [Board::shallow_clone()], but keeps the current search depth the same.
/// Should be used when implementing a searcher, and want to search a list of moves
/// in parallel with different threads.
///
/// # Safety
///
/// After this method has called, [Board::undo_move()] cannot be called immediately after.
/// Undoing moves can only be done once a move has been played, and cannot be called more
/// times than moves have been played since calling [Board::parallel_clone()].
pub fn parallel_clone(&self) -> Board {
Board {
turn: self.turn,
bit_boards: copy_piece_bbs(&self.bit_boards),
occ: copy_occ_bbs(&self.occ),
occ_all: self.occ_all,
half_moves: self.half_moves,
depth: self.depth,
piece_counts: self.piece_counts.clone(),
piece_locations: self.piece_locations.clone(),
state: self.state.clone(),
magic_helper: &MAGIC_HELPER,
}
}
/// Returns an exact clone of the current board.
///
/// # Safety
///
/// This method is unsafe as it can give the impression of owning and operating a board
/// structure, rather than just being provided shallow clones.
pub unsafe fn deep_clone(&self) -> Board {
Board {
turn: self.turn,
bit_boards: copy_piece_bbs(&self.bit_boards),
occ: copy_occ_bbs(&self.occ),
occ_all: self.occ_all,
half_moves: self.half_moves,
depth: self.depth,
piece_counts: self.piece_counts.clone(),
piece_locations: self.piece_locations.clone(),
state: self.state.clone(),
magic_helper: &MAGIC_HELPER,
}
}
/// Helper method for setting the piece states on initialization.
///
/// Only used when creating the Board from scratch (e.g. default position).
///
/// # Safety
///
/// Assumes that the Board has all of its BitBoards completely set, including the BitBoards
/// for the individual pieces as well as occupancy per player BitBoards.
fn set_piece_states(&mut self) {
// Loop each piece and player and count all the pieces per player
for player in &ALL_PLAYERS {
for piece in &ALL_PIECES {
self.piece_counts[*player as usize][*piece as usize] =
popcount64(self.piece_bb(*player, *piece));
}
}
// Loop through each square and see if any bitboard contains something at that location, and set
// the Boards' PieceLocations accordingly.
for square in 0..SQ_CNT as u8 {
let bb = sq_to_bb(square);
if bb & self.get_occupied() != 0 {
let player = if bb & self.occupied_black() == 0 {
Player::White
} else {
Player::Black
};
let piece = if self.piece_bb(player, Piece::P) & bb != 0 {
Piece::P
} else if self.piece_bb(player, Piece::N) & bb != 0 {
Piece::N
} else if self.piece_bb(player, Piece::B) & bb != 0 {
Piece::B
} else if self.piece_bb(player, Piece::R) & bb != 0 {
Piece::R
} else if self.piece_bb(player, Piece::Q) & bb != 0 {
Piece::Q
} else if self.piece_bb(player, Piece::K) & bb != 0 {
Piece::K
} else {
panic!()
};
self.piece_locations.place(square, player, piece);
} else {
// Remove the square just in case nothing eas found. Can't assume that the PieceLocations
// represents that square as blank
self.piece_locations.remove(square);
}
}
}
/// Helper method for setting the BitBoards from a fully created PieceLocations.
///
/// Only used when creating the Board from a fen String.
///
/// # Safety
///
/// Assumes that the Board has its PieceLocations completely set.
fn set_bitboards(&mut self) {
for sq in 0..SQ_CNT as SQ {
let player_piece = self.piece_locations.player_piece_at(sq);
if player_piece.is_some() {
let player: Player = player_piece.unwrap().0;
let piece = player_piece.unwrap().1;
let bb = sq_to_bb(sq);
self.bit_boards[player as usize][piece as usize] |= bb;
self.occ[player as usize] |= bb;
}
}
self.occ_all = self.occupied_black() | self.occupied_white();
for player in &ALL_PLAYERS {
for piece in &ALL_PIECES {
self.piece_counts[*player as usize][*piece as usize] =
popcount64(self.piece_bb(*player, *piece));
}
}
}
/// Constructs a board from a FEN String.
///
/// FEN stands for Forsyth-Edwards Notation, and is a way of representing a board through a
/// string of characters. More information can be found on the [ChessWiki](https://chessprogramming.wikispaces.com/Forsyth-Edwards+Notation).
///
/// # Examples
///
/// ```
/// use pleco::board::*;
///
/// let board = Board::new_from_fen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1");
/// assert_eq!(board.count_all_pieces(),32);
/// ```
///
/// # Panics
///
/// The FEN string must be valid, or else the method will panic.
///
/// There is a possibility of the FEN string representing an unvalid position, with no panics resulting.
/// The Constructed Board may have some Undefined Behavior as a result. It is up to the user to give a
/// valid FEN string.
pub fn new_from_fen(fen: &str) -> Board {
// Create blank PieceLocations and PieceCount array
let mut piece_loc: PieceLocations = PieceLocations::blank();
let mut piece_cnt: [[u8; PIECE_CNT]; PLAYER_CNT] = [[0; PIECE_CNT]; PLAYER_CNT];
// split the string by white space
let det_split: Vec<&str> = fen.split_whitespace().collect();
// must have 6 parts :
// [ Piece Placement, Side to Move, Castling Ability, En Passant square, Half moves, full moves]
assert_eq!(det_split.len(), 6);
// Split the first part by '/' for locations
let b_rep: Vec<&str> = det_split[0].split('/').collect();
// 8 ranks, so 8 parts
assert_eq!(b_rep.len(), 8);
// Start with Piece Placement
for (i, file) in b_rep.iter().enumerate() {
// Index starts from A8, goes to H8, then A7, etc
// A8 is 56 in our BitBoards so we start there
let mut idx = (7 - i) * 8;
for char in file.chars() {
// must be a valid square
assert!(idx < 64);
// Count spaces
let dig = char.to_digit(10);
if dig.is_some() {
idx += dig.unwrap() as usize;
} else {
// if no space, then there is a piece here
let piece = match char {
'p' | 'P' => Piece::P,
'n' | 'N' => Piece::N,
'b' | 'B' => Piece::B,
'r' | 'R' => Piece::R,
'q' | 'Q' => Piece::Q,
'k' | 'K' => Piece::K,
_ => panic!(),
};
let player = if char.is_lowercase() {
Player::Black
} else {
Player::White
};
piece_loc.place(idx as u8, player, piece);
piece_cnt[player as usize][piece as usize] += 1;
idx += 1;
}
}
}
// Side to Move
let turn: Player = match det_split[1].chars().next().unwrap() {
'b' => Player::Black,
'w' => Player::White,
_ => panic!(),
};
// Castle Bytes
let mut castle_bytes = Castling::empty();
for char in det_split[2].chars() {
match char {
'K' => castle_bytes |= WHITE_K,
'Q' => castle_bytes |= WHITE_Q,
'k' => castle_bytes |= BLACK_K,
'q' => castle_bytes |= BLACK_Q,
'-' => {}
_ => panic!(),
}
}
// EP square
let mut ep_sq: SQ = NO_SQ;
for (i, char) in det_split[3].chars().enumerate() {
assert!(i < 2);
if i == 0 {
match char {
'a' => ep_sq += 0,
'b' => ep_sq += 1,
'c' => ep_sq += 2,
'd' => ep_sq += 3,
'e' => ep_sq += 4,
'f' => ep_sq += 5,
'g' => ep_sq += 6,
'h' => ep_sq += 7,
'-' => {}
_ => panic!(),
}
} else {
let digit = char.to_digit(10).unwrap() as u8;
// must be 3 or 6
assert!(digit == 3 || digit == 6);
ep_sq += 8 * digit;
}
}
// rule 50 counts
let rule_50 = det_split[4].parse::<i16>().unwrap();
// Total Moves Played
// Moves is defined as everyime White moves, so gotta translate to total moves
let mut total_moves = (det_split[5].parse::<u16>().unwrap() - 1) * 2;
if turn == Player::Black {
total_moves += 1
};
// Create the Board States
let mut board_s = Arc::new(BoardState {
castling: castle_bytes,
rule_50: rule_50,
ply: 0,
ep_square: ep_sq,
zobrast: 0,
captured_piece: None,
checkers_bb: 0,
blockers_king: [0; PLAYER_CNT],
pinners_king: [0; PLAYER_CNT],
check_sqs: [0; PIECE_CNT],
prev_move: BitMove::null(),
prev: None,
});
// Create the Board
let mut b = Board {
turn: turn,
bit_boards: [[0; PIECE_CNT]; PLAYER_CNT],
occ: [0, 0],
occ_all: 0,
half_moves: total_moves,
depth: 0,
piece_counts: piece_cnt,
piece_locations: piece_loc,
state: Arc::new(BoardState::default()),
magic_helper: &MAGIC_HELPER,
};
// Set the BitBoards
b.set_bitboards();
{
// Set Check info
let state: &mut BoardState = Arc::get_mut(&mut board_s).unwrap();
b.set_check_info(state);
}
b.state = board_s;
// Set Zobrist Hash
b.set_zob_hash();
// TODO: Check for a valid FEN String and /or resulting board
b
}
/// Creates a FEN String of the Given Board.
///
/// FEN stands for Forsyth-Edwards Notation, and is a way of representing a board through a
/// string of characters. More information can be found on the [ChessWiki](https://chessprogramming.wikispaces.com/Forsyth-Edwards+Notation).
///
/// # Examples
///
/// ```
/// use pleco::board::*;
///
/// let board = Board::default();
/// assert_eq!(board.get_fen(),"rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1");
/// ```
pub fn get_fen(&self) -> String {
let mut s = String::default();
let mut blanks = 0;
for idx in 0..SQ_CNT as u8 {
// Cause of weird fen ordering, gotta do it this way
let sq = (idx % 8) + (8 * (7 - (idx / 8)));
if file_of_sq(sq) == File::A && rank_of_sq(sq) != Rank::R8 {
if blanks != 0 {
// Only add a number if there is a space between pieces
s.push(char::from_digit(blanks, 10).unwrap());
blanks = 0;
}
s.push('/');
}
let piece = self.piece_at_sq(sq);
let player = self.player_at_sq(sq);
if piece.is_none() {
blanks += 1;
} else {
if blanks != 0 {
s.push(char::from_digit(blanks, 10).unwrap());
blanks = 0;
}
s.push(
PIECE_DISPLAYS[player.unwrap() as usize][piece.unwrap() as usize],
);
}
}
s.push(' ');
// current turn
s.push(match self.turn {
Player::White => 'w',
Player::Black => 'b',
});
s.push(' ');
// Castling State
s.push_str(&(self.state.castling.pretty_string()));
s.push(' ');
// EP Square
if self.ep_square() == NO_SQ {
s.push('-');
} else {
let ep = self.ep_square();
s.push(FILE_DISPLAYS[file_idx_of_sq(ep) as usize]);
s.push(RANK_DISPLAYS[rank_idx_of_sq(ep) as usize]);
}
s.push(' ');
s.push_str(&format!("{}", self.rule_50()));
s.push(' ');
s.push_str(&format!("{}", (self.half_moves / 2) + 1));
s
}
}
// Public Move Gen & Mutation Functions
impl Board {
/// Applies a move to the Board.
///
/// # Example
/// ```
/// use pleco::board::*;
///
/// fn main() {
/// let mut chessboard = Board::default();
///
/// let moves = chessboard.generate_moves();
/// chessboard.apply_move(moves[0]);
/// }
/// ```
///
/// # Panics
///
/// The supplied BitMove must be both a valid move for that position, as well as a
/// valid [BitMove], Otherwise, a panic will occur. Valid BitMoves can be generated with
/// [Board::generate_moves()], which guarantees that only Legal moves will be created.
pub fn apply_move(&mut self, bit_move: BitMove) {
// Check for stupidity
assert_ne!(bit_move.get_src(), bit_move.get_dest());
// Does this move give check?
let gives_check: bool = self.gives_check(bit_move);
// Zobrist Hash
let mut zob: u64 = self.state.zobrast ^ self.magic_helper.zobrist.side;
// New Arc for the board to have by making a partial clone of the current state
let mut next_arc_state = Arc::new(self.state.partial_clone());
{
// Seperate Block to allow derefencing the BoardState
// As there is garunteed only one owner of the Arc, this is allowed
let new_state: &mut BoardState = Arc::get_mut(&mut next_arc_state).unwrap();
// Set the prev state
new_state.prev = Some(self.state.clone());
// Increment these
self.half_moves += 1;
self.depth += 1;
new_state.rule_50 += 1;
new_state.ply += 1;
new_state.prev_move = bit_move;
let us = self.turn;
let them = other_player(self.turn);
let from: SQ = bit_move.get_src();
let to: SQ = bit_move.get_dest();
let piece: Piece = self.piece_at_sq(from).unwrap();
let captured: Option<Piece> = if bit_move.is_en_passant() {
Some(Piece::P)
} else {
self.piece_at_sq(to)
};
// Sanity checks
assert_eq!(self.color_of_sq(from).unwrap(), us);
if bit_move.is_castle() {
// Sanity Checks, moved piece should be K, "captured" should be R
// As this is the encoding of Castling
assert_eq!(captured.unwrap(), Piece::R);
assert_eq!(piece, Piece::K);
let mut k_to: SQ = 0;
let mut r_to: SQ = 0;
// yay helper methods
self.apply_castling(us, from, to, &mut k_to, &mut r_to);
zob ^= self.magic_helper.z_piece_at_sq(Piece::R, k_to) ^
self.magic_helper.z_piece_at_sq(Piece::R, r_to);
new_state.captured_piece = None;
// TODO: Set castling rights Zobrist
new_state.castling.remove_player_castling(us);
new_state.castling.set_castling(us);
} else if captured.is_some() {
let mut cap_sq: SQ = to;
let cap_p: Piece = captured.unwrap(); // This shouldn't panic unless move is void
if cap_p == Piece::P && bit_move.is_en_passant() {
assert_eq!(cap_sq, self.state.ep_square);
match us {
Player::White => cap_sq -= 8,
Player::Black => cap_sq += 8,
};
assert_eq!(piece, Piece::P);
assert_eq!(relative_rank(us, Rank::R6), rank_of_sq(to));
assert!(self.piece_at_sq(to).is_none());
assert_eq!(self.piece_at_sq(cap_sq).unwrap(), Piece::P);
assert_eq!(self.player_at_sq(cap_sq).unwrap(), them);
self.remove_piece_c(Piece::P, cap_sq, them);
} else {
self.remove_piece_c(cap_p, cap_sq, them);
}
zob ^= self.magic_helper.z_piece_at_sq(cap_p, cap_sq);
// Reset Rule 50
new_state.rule_50 = 0;
new_state.captured_piece = Some(cap_p);
}
// Update hash for moving piece
zob ^= self.magic_helper.z_piece_at_sq(piece, to) ^
self.magic_helper.z_piece_at_sq(piece, from);
if self.state.ep_square != NO_SQ {
zob ^= self.magic_helper.z_ep_file(self.state.ep_square);
new_state.ep_square = NO_SQ;
}
// Update castling rights
if !new_state.castling.is_empty() && !bit_move.is_castle() {
if piece == Piece::K {
new_state.castling.remove_player_castling(us);
} else if piece == Piece::R {
match us {
Player::White => {
if from == ROOK_WHITE_KSIDE_START {
new_state.castling.remove_king_side_castling(Player::White);
} else if from == ROOK_WHITE_QSIDE_START {
new_state.castling.remove_queen_side_castling(Player::White);
}
}
Player::Black => {
if from == ROOK_BLACK_KSIDE_START {
new_state.castling.remove_king_side_castling(Player::Black);
} else if from == ROOK_BLACK_QSIDE_START {
new_state.castling.remove_queen_side_castling(Player::Black);
}
}
}
}
}
// Actually move the piece
if !bit_move.is_castle() && !bit_move.is_promo() {
self.move_piece_c(piece, from, to, us);
}
// Pawn Moves need special help :(
if piece == Piece::P {
if self.magic_helper.distance_of_sqs(to, from) == 2 {
// Double Push
new_state.ep_square = (to + from) / 2;
zob ^= self.magic_helper.z_ep_file(new_state.ep_square);
} else if bit_move.is_promo() {
let promo_piece: Piece = bit_move.promo_piece();
self.remove_piece_c(Piece::P, from, us);
self.put_piece_c(promo_piece, to, us);
zob ^= self.magic_helper.z_piece_at_sq(promo_piece, to) ^
self.magic_helper.z_piece_at_sq(piece, from);
}
new_state.rule_50 = 0;
}
new_state.captured_piece = captured;
new_state.zobrast = zob;
if gives_check {
new_state.checkers_bb =
self.attackers_to(self.king_sq(them), self.get_occupied()) &
self.get_occupied_player(us);
}
self.turn = them;
self.set_check_info(new_state); // Set the checking information
}
self.state = next_arc_state;
assert!(self.is_okay());
}
/// Un-does the previously applied move, allowing the Board to return to it's most recently held state.
///
/// # Panics
///
/// Cannot be done if after a [Board::shallow_clone()] or [Board::parallel_clone()] has been done
/// and no subsequent moves have been played.
/// ```rust,should_panic
/// use pleco::board::*;
///
///
/// let mut chessboard = Board::default();
///
/// let moves = chessboard.generate_moves();
/// chessboard.apply_move(moves[0]);
///
/// let board_clone = chessboard.shallow_clone();
///
/// chessboard.undo_move(); // works, chessboard existed before the move was played
/// board_clone.undo_move(); // error: board_clone was created after the move was applied
///
/// ```
pub fn undo_move(&mut self) {
assert!(self.state.prev.is_some());
assert!(!self.state.prev_move.is_null());
let undo_move: BitMove = self.state.prev_move;
self.turn = other_player(self.turn);
let us: Player = self.turn;
let from: SQ = undo_move.get_src();
let to: SQ = undo_move.get_dest();
let mut piece_on: Option<Piece> = self.piece_at_sq(to);
// Make sure the piece moved from is not there, or there is a castle
assert!(self.piece_at_sq(from).is_none() || undo_move.is_castle());
if undo_move.is_promo() {
assert_eq!(piece_on.unwrap(), undo_move.promo_piece());
// Remove Promo piece and place Pawn in same square
self.remove_piece_c(piece_on.unwrap(), to, us);
self.put_piece_c(Piece::P, to, us);
piece_on = Some(Piece::P);
}
if undo_move.is_castle() {
self.remove_castling(us, from, to);
} else {
self.move_piece_c(piece_on.unwrap(), to, from, us);
let cap_piece = self.state.captured_piece;
if cap_piece.is_some() {
let mut cap_sq: SQ = to;
if undo_move.is_en_passant() {
match us {
Player::White => cap_sq -= 8,
Player::Black => cap_sq += 8,
};
}
self.put_piece_c(cap_piece.unwrap(), cap_sq, other_player(us));
}
}
self.state = self.state.get_prev().unwrap();
self.half_moves -= 1;
self.depth -= 1;
assert!(self.is_okay());
}
/// Apply a "Null Move" to the board, essentially swapping the current turn of
/// the board without moving any pieces.
///
/// # Safety
///
/// This method should only be used for special evaluation purposes, as it does not give an
/// accurate or legal state of the chess board.
///
/// Unsafe as it allows for Null Moves to be applied in states of check, which is never a valid
/// state of a chess game.
///
/// # Panics
///
/// Panics if the Board is currently in check.
pub unsafe fn apply_null_move(&mut self) {
assert!(self.checkers() != 0);
let mut zob: u64 = self.state.zobrast ^ self.magic_helper.zobrist.side;
self.depth += 1;
// New Arc for the board to have by making a partial clone of the current state
let mut next_arc_state = Arc::new(self.state.partial_clone());
{
let new_state: &mut BoardState = Arc::get_mut(&mut next_arc_state).unwrap();
new_state.prev_move = BitMove::null();
new_state.rule_50 += 1;
new_state.ply += 1;
new_state.prev = Some(self.state.clone());
if self.state.ep_square != NO_SQ {
zob ^= self.magic_helper.z_ep_file(self.state.ep_square);
new_state.ep_square = NO_SQ;
}
new_state.zobrast = zob;
self.turn = other_player(self.turn);
self.set_check_info(new_state);
}
self.state = next_arc_state;
assert!(self.is_okay());
}
/// Undo a "Null Move" to the Board, returning to the previous state.
///
/// # Safety
///
/// This method should only be used if it can be guaranteed that the last played move from
/// the current state is a Null-Move. Otherwise, a panic will occur.
pub unsafe fn undo_null_move(&mut self) {
assert!(self.state.prev_move.is_null());
self.turn = other_player(self.turn);
self.state = self.state.get_prev().unwrap();
}
/// Get a List of legal [BitMove]s for the player whose turn it is to move.
///
/// This method already takes into account if the Board is currently in check, and will return
/// legal moves only.
pub fn generate_moves(&self) -> Vec<BitMove> {
MoveGen::generate(&self, GenTypes::All)
}
/// Get a List of legal [BitMove]s for the player whose turn it is to move or a certain type.
///
/// This method already takes into account if the Board is currently in check, and will return
/// legal moves only. If a non-ALL GenType is supplied, only a subset of the total moves will be given.
///
/// # Panics
///
/// Panics if given [GenTypes::QuietChecks] while the current board is in check
pub fn generate_moves_of_type(&self, gen_type: GenTypes) -> Vec<BitMove> {
MoveGen::generate(&self, gen_type)
}
}
// Private Mutating Functions
impl Board {
/// Helper method, used after a move is made, creates information concerning checking and
/// possible checks.
///
/// Specifically, sets Blockers, Pinners, and Check Squares for each piece.
fn set_check_info(&self, board_state: &mut BoardState) {
// Set the Pinners and Blockers
let mut white_pinners = 0;
{
board_state.blockers_king[Player::White as usize] = self.slider_blockers(
self.occupied_black(),
self.king_sq(Player::White),
&mut white_pinners,
)
};
board_state.pinners_king[Player::White as usize] = white_pinners;
let mut black_pinners = 0;
{
board_state.blockers_king[Player::Black as usize] = self.slider_blockers(
self.occupied_white(),
self.king_sq(Player::Black),
&mut black_pinners,
)
};
board_state.pinners_king[Player::Black as usize] = black_pinners;
let ksq: SQ = self.king_sq(other_player(self.turn));
let occupied = self.get_occupied();
board_state.check_sqs[Piece::P as usize] = self.magic_helper
.pawn_attacks_from(ksq, other_player(self.turn));
board_state.check_sqs[Piece::N as usize] = self.magic_helper.knight_moves(ksq);
board_state.check_sqs[Piece::B as usize] = self.magic_helper.bishop_moves(occupied, ksq);
board_state.check_sqs[Piece::R as usize] = self.magic_helper.rook_moves(occupied, ksq);
board_state.check_sqs[Piece::Q as usize] = board_state.check_sqs[Piece::B as usize] |
board_state.check_sqs[Piece::R as usize];
board_state.check_sqs[Piece::K as usize] = 0;
}
/// Removes a Piece from the Board, if the color is unknown.
///
/// # Panics
///
/// Panics if there is not piece at the given square.
fn remove_piece(&mut self, piece: Piece, square: SQ) {
let player = self.color_of_sq(square).unwrap();
self.remove_piece_c(piece, square, player);
}
/// Moves a Piece on the Board (if the color is unknown) from square 'from'
/// to square 'to'.
///
/// # Panics
///
/// Panics if there is not piece at the given square.
fn move_piece(&mut self, piece: Piece, from: SQ, to: SQ) {
let player = self.color_of_sq(from).unwrap();
self.move_piece_c(piece, from, to, player);
}
/// Places a Piece on the board at a given square and player.
///
/// # Safety
///
/// Assumes there is not already a piece at that square. If there already is,
/// Undefined Behavior will result.
fn put_piece_c(&mut self, piece: Piece, square: SQ, player: Player) {
let bb = sq_to_bb(square);
self.occ_all |= bb;
self.occ[player as usize] |= bb;
self.bit_boards[player as usize][piece as usize] |= bb;
self.piece_locations.place(square, player, piece);
self.piece_counts[player as usize][piece as usize] += 1;
// Note: Should We set captured Piece?
}
/// Removes a Piece from the Board for a given player.
///
/// # Panics
///
/// Panics if there is a piece at the given square.
fn remove_piece_c(&mut self, piece: Piece, square: SQ, player: Player) {
assert_eq!(self.piece_at_sq(square).unwrap(), piece);
let bb = sq_to_bb(square);
self.occ_all ^= bb;
self.occ[player as usize] ^= bb;
self.bit_boards[player as usize][piece as usize] ^= bb;
self.piece_locations.remove(square);
self.piece_counts[player as usize][piece as usize] -= 1;
}
/// Moves a Piece on the Board of a given player from square 'from'
/// to square 'to'.
///
/// # Panics
///
/// Panics if the two and from square are equal
fn move_piece_c(&mut self, piece: Piece, from: SQ, to: SQ, player: Player) {
assert_ne!(from, to);
let comb_bb = sq_to_bb(from) | sq_to_bb(to);
self.occ_all ^= comb_bb;
self.occ[player as usize] ^= comb_bb;
self.bit_boards[player as usize][piece as usize] ^= comb_bb;
self.piece_locations.remove(from);
self.piece_locations.place(to, player, piece);
}
/// Helper function to apply a Castling for a given player.
///
/// Takes in the player to castle, alongside the original king square and the original rook square.
/// the k_dst and r_dst squares are pointers to values, modifying them to have the correct king and
/// rook destination squares.
///
/// # Safety
///
/// Assumes that k_src and r_src are legal squares, and the player can legally castle.
fn apply_castling(
&mut self,
player: Player,
k_src: SQ,
r_src: SQ,
k_dst: &mut SQ,
r_dst: &mut SQ,
) {
let king_side: bool = k_src < r_src;
if king_side {
*k_dst = relative_square(player, 6);
*r_dst = relative_square(player, 5);
} else {
*k_dst = relative_square(player, 2);
*r_dst = relative_square(player, 3);
}
self.move_piece_c(Piece::K, k_src, *k_dst, player);
self.move_piece_c(Piece::R, r_src, *r_dst, player);
}
/// Helper function to remove a Castling for a given player.
///
/// Takes in the player to castle, alongside the post-castle king rook squares.
///
/// # Safety
///
/// Assumes the last move played was a castle for the given player.
fn remove_castling(&mut self, player: Player, k_src: SQ, r_src: SQ) {
let k_dst: SQ = self.king_sq(player);
let king_side: bool = k_src < r_src;
let r_dst: SQ = if king_side {
relative_square(player, 5)
} else {
relative_square(player, 3)
};
self.move_piece_c(Piece::K, k_dst, k_src, player);
self.move_piece_c(Piece::R, r_dst, r_src, player);
}
/// Helper function to that outputs the Blockers of a given square
fn slider_blockers(&self, sliders: BitBoard, s: SQ, pinners: &mut BitBoard) -> BitBoard {
let mut result: BitBoard = 0;
*pinners = 0;
let occupied: BitBoard = self.get_occupied();
let mut snipers: BitBoard = sliders &
((self.magic_helper.rook_moves(0, s) &
(self.piece_two_bb_both_players(Piece::R, Piece::Q))) |
(self.magic_helper.bishop_moves(0, s) &
(self.piece_two_bb_both_players(Piece::B, Piece::Q))));
while snipers != 0 {
let lsb: BitBoard = lsb(snipers);
let sniper_sq: SQ = bb_to_sq(lsb);
let b: BitBoard = self.magic_helper.between_bb(s, sniper_sq) & occupied;
if !more_than_one(b) {
result |= b;
let other_occ = self.get_occupied_player(self.player_at_sq(s).unwrap());
if b & other_occ != 0 {
*pinners |= sq_to_bb(sniper_sq);
}
}
snipers &= !lsb;
}
result
}
// pub struct Zobrist {
// sq_piece: [[u64; PIECE_CNT]; SQ_CNT],
// en_p: [u64; FILE_CNT],
// castle: [u64; CASTLING_CNT],
// side: u64,
// }
/// Sets the Zobrist hash when the board is initialized or created from a FEN string.
///
/// Assumes the rest of the board is initialized.
fn set_zob_hash(&mut self) {
let mut zob: u64 = 0;
let mut b: BitBoard = self.get_occupied();
while b != 0 {
let sq: SQ = bit_scan_forward(b);
let lsb: BitBoard = lsb(b);
b &= !lsb;
let piece = self.piece_at_bb_all(lsb);
zob ^= self.magic_helper.z_piece_at_sq(piece.unwrap(), sq);
}
let ep = self.state.ep_square;
if ep != 0 && ep < 64 {
zob ^= self.magic_helper.z_ep_file(ep);
}
match self.turn {
Player::Black => zob ^= self.magic_helper.z_side(),
Player::White => {}
};
Arc::get_mut(&mut self.state).unwrap().zobrast = zob;
}
}
// General information
impl Board {
/// Get the Player whose turn it is to move.
pub fn turn(&self) -> Player {
self.turn
}
/// Return the Zobrist Hash.
pub fn zobrist(&self) -> u64 {
self.state.zobrast
}
/// Get the total number of moves played.
pub fn moves_played(&self) -> u16 {
self.half_moves
}
/// Get the current depth (half moves from a [Board::shallow_clone()].
pub fn depth(&self) -> u16 {
self.depth
}
/// Get the number of half-moves since a Pawn Push, castle, or capture.
pub fn rule_50(&self) -> i16 {
self.state.rule_50
}
/// Return the Piece, if any, that was last captured.
pub fn piece_captured_last_turn(&self) -> Option<Piece> {
self.state.captured_piece
}
/// Get a reference to the MagicHelper pre-computed BitBoards.
pub fn magic_helper(&self) -> &'static MagicHelper {
&MAGIC_HELPER
}
/// Get the current ply of the board.
pub fn ply(&self) -> u16 {
self.state.ply
}
/// Get the current square of en_passant.
///
/// If the current en-passant square is none, it should return 64.
pub fn ep_square(&self) -> SQ {
self.state.ep_square
}
}
// Position Representation
impl Board {
/// Gets the BitBoard of all pieces.
pub fn get_occupied(&self) -> BitBoard {
self.occ_all
}
/// Get the BitBoard of the squares occupied by the given player.
pub fn get_occupied_player(&self, player: Player) -> BitBoard {
self.occ[player as usize]
}
/// Returns a Bitboard consisting of only the squares occupied by the White Player.
pub fn occupied_white(&self) -> BitBoard {
self.occ[Player::White as usize]
}
/// Returns a BitBoard consisting of only the squares occupied by the Black Player.
pub fn occupied_black(&self) -> BitBoard {
self.occ[Player::Black as usize]
}
/// Returns BitBoard of a single player and that one type of piece.
pub fn piece_bb(&self, player: Player, piece: Piece) -> BitBoard {
self.bit_boards[player as usize][piece as usize]
}
/// Returns the BitBoard of the Queens and Rooks of a given player.
pub fn sliding_piece_bb(&self, player: Player) -> BitBoard {
self.bit_boards[player as usize][Piece::R as usize] ^
self.bit_boards[player as usize][Piece::Q as usize]
}
/// Returns the BitBoard of the Queens and Bishops of a given player.
pub fn diagonal_piece_bb(&self, player: Player) -> BitBoard {
self.bit_boards[player as usize][Piece::B as usize] ^
self.bit_boards[player as usize][Piece::Q as usize]
}
/// Returns the combined BitBoard of both players for a given piece.
pub fn piece_bb_both_players(&self, piece: Piece) -> BitBoard {
self.bit_boards[Player::White as usize][piece as usize] ^
self.bit_boards[Player::Black as usize][piece as usize]
}
/// Returns the combined BitBoard of both players for two pieces.
pub fn piece_two_bb_both_players(&self, piece: Piece, piece2: Piece) -> BitBoard {
self.piece_bb_both_players(piece) | self.piece_bb_both_players(piece2)
}
/// Get the total number of pieces of the given piece and player.
pub fn count_piece(&self, player: Player, piece: Piece) -> u8 {
self.piece_counts[player as usize][piece as usize]
}
/// Get the total number of piees a given player has.
pub fn count_pieces_player(&self, player: Player) -> u8 {
self.piece_counts[player as usize].iter().sum()
}
/// Get the total number of pieces on the board.
pub fn count_all_pieces(&self) -> u8 {
self.count_pieces_player(Player::White) + self.count_pieces_player(Player::Black)
}
/// Returns the piece (if any) at the given BitBoard for a given player.
///
/// # Safety
///
/// Number of bits must be equal to 1, or else a panic will occur.
pub fn piece_at_bb(&self, src_bit: BitBoard, player: Player) -> Option<Piece> {
let sq: SQ = bb_to_sq(src_bit);
assert!(sq_is_okay(sq));
self.piece_locations.piece_at_for_player(sq, player)
}
/// Returns the piece (if any) at the given BitBoard for either player.
///
/// # Safety
///
/// Number of bits must be equal to 1, or else a panic will occur.
pub fn piece_at_bb_all(&self, src_bit: BitBoard) -> Option<Piece> {
let square: SQ = bb_to_sq(src_bit);
assert!(sq_is_okay(square));
self.piece_locations.piece_at(square)
}
/// Returns the Piece, if any, at the square.
pub fn piece_at_sq(&self, sq: SQ) -> Option<Piece> {
assert!(sq < 64);
self.piece_locations.piece_at(sq)
}
/// Returns the Player, if any, occupying the square.
pub fn color_of_sq(&self, sq: SQ) -> Option<Player> {
assert!(sq < 64);
let bb: BitBoard = sq_to_bb(sq);
if bb & self.occupied_black() != 0 {
return Some(Player::Black);
}
if bb & self.occupied_white() != 0 {
return Some(Player::White);
}
None
}
/// Returns the player, if any, at the square.
pub fn player_at_sq(&self, s: SQ) -> Option<Player> {
// TODO: Roll into color_of_square
self.piece_locations.player_at(s)
}
/// Returns the square of the King for a given player
pub fn king_sq(&self, player: Player) -> SQ {
bb_to_sq(self.bit_boards[player as usize][Piece::K as usize])
}
/// Returns the pinned pieces of the given player.
///
/// Pinned is defined as pinned to the same players king
pub fn pinned_pieces(&self, player: Player) -> BitBoard {
self.state.blockers_king[player as usize] & self.get_occupied_player(player)
}
/// Returns the pinned pieces for a given players king. Can contain piece of from both players,
/// but all are garunteed to be pinned to the given player's king.
pub fn all_pinned_pieces(&self, player: Player) -> BitBoard {
self.state.blockers_king[player as usize]
}
/// Returns the pinning pieces of a given player.
/// e.g, pieces that are pinning a piece to the opponent's king.
pub fn pinning_pieces(&self, player: Player) -> BitBoard {
self.state.pinners_king[player as usize]
}
/// Return if a player has the possibility of castling for a given CastleType.
pub fn can_castle(&self, player: Player, castle_type: CastleType) -> bool {
self.state.castling.castle_rights(player, castle_type)
}
/// Check if the castle path is impeded for the current player.
pub fn castle_impeded(&self, castle_type: CastleType) -> bool {
let path: BitBoard = CASTLING_PATH[self.turn as usize][castle_type as usize];
path & self.occ_all != 0
}
/// Square of the Rook that is involved with the current player's castle.
pub fn castling_rook_square(&self, castle_type: CastleType) -> SQ {
CASTLING_ROOK_START[self.turn as usize][castle_type as usize]
}
/// Return the last move played, if any.
pub fn last_move(&self) -> Option<BitMove> {
if self.state.prev_move.is_null() {
None
} else {
Some(self.state.prev_move)
}
}
/// Returns if the current player has castled ever.
pub fn has_castled(&self, player: Player) -> bool {
self.state.castling.has_castled(player)
}
/// Return if the piece (if any) that was captured last move.
pub fn piece_last_captured(&self) -> Option<Piece> {
self.state.captured_piece
}
}
// Checking
impl Board {
/// Return if current side to move is in check
pub fn in_check(&self) -> bool {
self.state.checkers_bb != 0
}
/// Return if the current side to move is in check_mate.
///
/// This method can be computationally expensive, do not use outside of Engines.
pub fn checkmate(&self) -> bool {
self.in_check() && self.generate_moves().is_empty()
}
/// Return if the current side to move is in stalemate.
///
/// This method can be computationally expensive, do not use outside of Engines.
pub fn stalemate(&self) -> bool {
!self.in_check() && self.generate_moves().is_empty()
}
/// Return the BitBoard of Checks on the current player's king.
pub fn checkers(&self) -> BitBoard {
self.state.checkers_bb
}
/// Returns the BitBoard of pieces the current side can move to discover check.
pub fn discovered_check_candidates(&self) -> BitBoard {
self.state.blockers_king[other_player(self.turn) as usize] &
self.get_occupied_player(self.turn)
}
/// Gets the Pinned pieces for the given player.
pub fn pieces_pinned(&self, player: Player) -> BitBoard {
// TODO: combine with Board::piece_pinned
self.state.blockers_king[player as usize] & self.get_occupied_player(player)
}
/// Returns a BitBoard of possible attacks / defends to a square with a given occupancy.
pub fn attackers_to(&self, sq: SQ, occupied: BitBoard) -> BitBoard {
(self.magic_helper.pawn_attacks_from(sq, Player::Black) &
self.piece_bb(Player::White, Piece::P)) |
(self.magic_helper.pawn_attacks_from(sq, Player::White) &
self.piece_bb(Player::Black, Piece::P)) |
(self.magic_helper.knight_moves(sq) & self.piece_bb_both_players(Piece::N)) |
(self.magic_helper.rook_moves(occupied, sq) &
(self.sliding_piece_bb(Player::White) | self.sliding_piece_bb(Player::Black))) |
(self.magic_helper.bishop_moves(occupied, sq) &
(self.diagonal_piece_bb(Player::White) | self.diagonal_piece_bb(Player::Black))) |
(self.magic_helper.king_moves(sq) & self.piece_bb_both_players(Piece::K))
}
}
// Move Testing
impl Board {
/// Tests if a given move is legal.
pub fn legal_move(&self, m: BitMove) -> bool {
let them: Player = other_player(self.turn);
let src: SQ = m.get_src();
let src_bb: BitBoard = sq_to_bb(src);
let dst: SQ = m.get_dest();
// Special en_passant case
if m.move_type() == MoveType::EnPassant {
let k_sq: SQ = self.king_sq(self.turn);
let dst_bb: BitBoard = sq_to_bb(dst);
let captured_sq: SQ = (dst as i8).wrapping_sub(pawn_push(self.turn)) as u8;
let occupied: BitBoard = (self.get_occupied() ^ src_bb ^ sq_to_bb(captured_sq)) |
dst_bb;
return (self.magic_helper.rook_moves(occupied, k_sq) &
self.sliding_piece_bb(them) == 0) &&
(self.magic_helper.queen_moves(occupied, k_sq) & self.diagonal_piece_bb(them) == 0);
}
// If Moving the king, check if the square moved to is not being attacked
// Castles are checking during move gen for check, so we goo dthere
if self.piece_at_sq(src).unwrap() == Piece::K {
return m.move_type() == MoveType::Castle ||
(self.attackers_to(dst, self.get_occupied()) & self.get_occupied_player(them)) == 0;
}
// Making sure not moving a pinned piece
(self.pinned_pieces(self.turn) & src_bb) == 0 ||
self.magic_helper.aligned(src, dst, self.king_sq(self.turn))
}
// Used to check for Hashing errors from TT Tables
// pub fn pseudo_legal_move(&self, m: BitMove) -> bool {
// let us = self.turn;
// let them = other_player(us);
//
// }
/// Returns if a move will give check to the opposing player's King.
pub fn gives_check(&self, m: BitMove) -> bool {
// I am too drunk to be making this right now
let src: SQ = m.get_src();
let dst: SQ = m.get_dest();
let src_bb: BitBoard = sq_to_bb(src);
let dst_bb: BitBoard = sq_to_bb(dst);
let opp_king_sq: SQ = self.king_sq(other_player(self.turn));
// Stupidity Checks
assert_ne!(src, dst);
assert_eq!(self.color_of_sq(src).unwrap(), self.turn);
// Searches for direct checks from the pre-computed array
if self.state.check_sqs[self.piece_at_sq(src).unwrap() as usize] & dst_bb != 0 {
return true;
}
// Discovered (Indirect) checks, where a sniper piece is attacking the king
if (self.discovered_check_candidates() & src_bb != 0) // check if the piece is blocking a sniper
&& !self.magic_helper.aligned(src, dst, opp_king_sq) { // Make sure the dst square is not aligned
return true;
}
match m.move_type() {
MoveType::Normal => false, // Nothing to check here
MoveType::Promotion => {
// check if the Promo Piece attacks king
let attacks_bb = match m.promo_piece() {
Piece::N => self.magic_helper.knight_moves(dst),
Piece::B => {
self.magic_helper
.bishop_moves(self.get_occupied() ^ src_bb, dst)
}
Piece::R => {
self.magic_helper
.rook_moves(self.get_occupied() ^ src_bb, dst)
}
Piece::Q => {
self.magic_helper
.queen_moves(self.get_occupied() ^ src_bb, dst)
}
_ => unreachable!(),
};
attacks_bb & sq_to_bb(opp_king_sq) != 0
}
MoveType::EnPassant => {
// Check for indirect check from the removal of the captured pawn
let captured_sq: SQ = make_sq(file_of_sq(dst), rank_of_sq(src));
let b: BitBoard = (self.get_occupied() ^ src_bb ^ sq_to_bb(captured_sq)) | dst_bb;
let turn_sliding_p: BitBoard = self.sliding_piece_bb(self.turn);
let turn_diag_p: BitBoard = self.diagonal_piece_bb(self.turn);
// TODO: is this right?
(self.magic_helper.rook_moves(b, opp_king_sq) | turn_sliding_p) &
(self.magic_helper.bishop_moves(b, opp_king_sq) | turn_diag_p) !=
0
}
MoveType::Castle => {
// Check if the rook attacks the King now
let k_from: SQ = src;
let r_from: SQ = dst;
let k_to: SQ = relative_square(self.turn, { if r_from > k_from { 6 } else { 2 } });
let r_to: SQ = relative_square(self.turn, { if r_from > k_from { 5 } else { 3 } });
let opp_k_bb = sq_to_bb(opp_king_sq);
(self.magic_helper.rook_moves(0, r_to) & opp_k_bb != 0) &&
(self.magic_helper.rook_moves(
sq_to_bb(r_to) | sq_to_bb(k_to) |
(self.get_occupied() ^ sq_to_bb(k_from) ^ sq_to_bb(r_from)),
r_to,
) & opp_k_bb) != 0
}
}
}
/// Returns the piece that was moved from a given BitMove.
pub fn moved_piece(&self, m: BitMove) -> Piece {
let src = m.get_src();
self.piece_at_sq(src).unwrap() // panics if no piece here :)
}
/// Returns the piece that was captured, if any from a given BitMove.
pub fn captured_piece(&self, m: BitMove) -> Option<Piece> {
if m.is_en_passant() {
return Some(Piece::P);
}
let dst = m.get_dest();
self.piece_at_bb(sq_to_bb(dst), other_player(self.turn))
}
}
// Printing and Debugging Functions
impl Board {
/// Returns a prettified String of the current board, for Quick Display.
///
/// Capital Letters represent White pieces, while lower case represents Black pieces.
pub fn pretty_string(&self) -> String {
let mut s = String::with_capacity(SQ_CNT * 2 + 8);
for sq in SQ_DISPLAY_ORDER.iter() {
let op = self.piece_locations.player_piece_at(*sq);
let char = if op.is_some() {
let player = op.unwrap().0;
let piece = op.unwrap().1;
PIECE_DISPLAYS[player as usize][piece as usize]
} else {
'-'
};
s.push(char);
s.push(' ');
if sq % 8 == 7 {
s.push('\n');
}
}
s
}
/// Return the current ARC count of the board's BoardState
pub fn get_arc_strong_count(&self) -> usize {
Arc::strong_count(&self.state)
}
/// Get Debug Information.
pub fn print_debug_info(&self) {
println!("White Pinners ");
print_bitboard(self.state.pinners_king[0]);
println!("Black Pinners ");
print_bitboard(self.state.pinners_king[1]);
println!("White Blockers ");
print_bitboard(self.state.blockers_king[0]);
println!("Black Blockers ");
print_bitboard(self.state.blockers_king[1]);
println!("Checkers ");
print_bitboard(self.state.checkers_bb);
println!("Bishop check sqs");
print_bitboard(self.state.check_sqs[Piece::B as usize]);
println!("Rook check sqs");
print_bitboard(self.state.check_sqs[Piece::R as usize]);
println!("Queen check sqs");
print_bitboard(self.state.check_sqs[Piece::Q as usize]);
}
/// Prints a prettified representation of the board.
pub fn pretty_print(&self) {
println!("{}", self.pretty_string());
}
/// Print the board alongside useful information.
///
/// Mostly for Debugging useage.
pub fn fancy_print(&self) {
self.pretty_print();
println!(
"Castling bits: {:b}, Rule 50: {}, ep_sq: {}",
self.state.castling,
self.state.rule_50,
self.state.ep_square
);
println!(
"Total Moves: {}, ply: {}, depth: {}",
self.half_moves,
self.state.ply,
self.depth
);
println!("Zobrist: {:x}", self.state.zobrast);
println!();
}
// Checks the current state of the Board
// yup
pub fn is_okay(&self) -> bool {
const QUICK_CHECK: bool = false;
if QUICK_CHECK {
return self.check_basic();
}
self.check_basic() && self.check_bitboards() && self.check_king() &&
self.check_state_info() && self.check_lists() && self.check_castling()
}
}
// Debugging helper Functions
// Returns false if the board is not good
impl Board {
fn check_basic(&self) -> bool {
assert_eq!(
self.piece_at_sq(self.king_sq(Player::White)).unwrap(),
Piece::K
);
assert_eq!(
self.piece_at_sq(self.king_sq(Player::Black)).unwrap(),
Piece::K
);
assert!(
self.state.ep_square == 0 || self.state.ep_square == 64 ||
relative_rank_of_sq(self.turn, self.state.ep_square) == Rank::R6
);
true
}
fn check_king(&self) -> bool {
// TODO: Implement attacks to opposing king must be zero
assert_eq!(self.count_piece(Player::White, Piece::K), 1);
assert_eq!(self.count_piece(Player::Black, Piece::K), 1);
true
}
fn check_bitboards(&self) -> bool {
assert_eq!(self.occupied_white() & self.occupied_black(), 0);
assert_eq!(
self.occupied_black() | self.occupied_white(),
self.get_occupied()
);
// TODO: Loop through all pieces and make sure no two pieces are on the same square
true
}
fn check_state_info(&self) -> bool {
true
}
fn check_lists(&self) -> bool {
true
}
fn check_castling(&self) -> bool {
true
}
}
#[cfg(test)]
mod tests {
extern crate rand;
use board::Board;
#[test]
fn random_move_apply() {
let mut board = Board::default();
let mut ply = 1000;
while ply > 0 && !board.checkmate() && !board.stalemate() {
let moves = board.generate_moves();
let picked_move = moves[rand::random::<usize>() % moves.len()];
board.apply_move(picked_move);
ply -= 1;
}
}
#[test]
fn fen_equality() {
let mut board = Board::default();
let mut ply = 1000;
let mut fen_stack = Vec::new();
while ply > 0 && !board.checkmate() && !board.stalemate() {
fen_stack.push(board.get_fen());
let moves = board.generate_moves();
let picked_move = moves[rand::random::<usize>() % moves.len()];
board.apply_move(picked_move);
ply -= 1;
}
while !fen_stack.is_empty() {
board.undo_move();
assert_eq!(board.get_fen(),fen_stack.pop().unwrap());
}
}
#[test]
fn zob_equality() {
let mut board = Board::default();
let mut ply = 1000;
let mut zobrist_stack = Vec::new();
while ply > 0 && !board.checkmate() && !board.stalemate() {
zobrist_stack.push(board.zobrist());
let moves = board.generate_moves();
let picked_move = moves[rand::random::<usize>() % moves.len()];
board.apply_move(picked_move);
ply -= 1;
}
while !zobrist_stack.is_empty() {
board.undo_move();
assert_eq!(board.zobrist(),zobrist_stack.pop().unwrap());
}
}
}