use crate::engine::bitboard::{
set_bit, to_square, Bitboard, BLACK_BISHOPS, BLACK_KING, BLACK_KNIGHTS, BLACK_PAWNS,
BLACK_QUEENS, BLACK_ROOKS, EMPTY, WHITE_BISHOPS, WHITE_KING, WHITE_KNIGHTS, WHITE_PAWNS,
WHITE_QUEENS, WHITE_ROOKS,
};
use crate::engine::common::{ChessMan, Color, File, Move, Piece, Rank, Square};
use crate::engine::errors::ChessError;
use crate::engine::fen::Fen;
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub struct Board {
pub black_pawns: Bitboard,
pub black_knights: Bitboard,
pub black_bishops: Bitboard,
pub black_rooks: Bitboard,
pub black_queens: Bitboard,
pub black_king: Bitboard,
pub white_pawns: Bitboard,
pub white_knights: Bitboard,
pub white_bishops: Bitboard,
pub white_rooks: Bitboard,
pub white_queens: Bitboard,
pub white_king: Bitboard,
pub black: Bitboard,
pub white: Bitboard,
pub occupied: Bitboard,
pub en_passant: Bitboard,
pub king_side_castle_white: bool,
pub queen_side_castle_white: bool,
pub king_side_castle_black: bool,
pub queen_side_castle_black: bool,
pub turn: Color,
}
impl Board {
pub fn new() -> Self {
Self {
black_pawns: BLACK_PAWNS,
black_knights: BLACK_KNIGHTS,
black_bishops: BLACK_BISHOPS,
black_rooks: BLACK_ROOKS,
black_queens: BLACK_QUEENS,
black_king: BLACK_KING,
white_pawns: WHITE_PAWNS,
white_knights: WHITE_KNIGHTS,
white_bishops: WHITE_BISHOPS,
white_rooks: WHITE_ROOKS,
white_queens: WHITE_QUEENS,
white_king: WHITE_KING,
black: BLACK_PAWNS
| BLACK_KNIGHTS
| BLACK_BISHOPS
| BLACK_ROOKS
| BLACK_QUEENS
| BLACK_KING,
white: WHITE_PAWNS
| WHITE_KNIGHTS
| WHITE_BISHOPS
| WHITE_ROOKS
| WHITE_QUEENS
| WHITE_KING,
occupied: BLACK_PAWNS
| BLACK_KNIGHTS
| BLACK_BISHOPS
| BLACK_ROOKS
| BLACK_QUEENS
| BLACK_KING
| WHITE_PAWNS
| WHITE_KNIGHTS
| WHITE_BISHOPS
| WHITE_ROOKS
| WHITE_QUEENS
| WHITE_KING,
en_passant: EMPTY,
king_side_castle_white: true,
queen_side_castle_white: true,
king_side_castle_black: true,
queen_side_castle_black: true,
turn: Color::White,
}
}
pub fn empty() -> Self {
Self {
black_pawns: EMPTY,
black_knights: EMPTY,
black_bishops: EMPTY,
black_rooks: EMPTY,
black_queens: EMPTY,
black_king: EMPTY,
white_pawns: EMPTY,
white_knights: EMPTY,
white_bishops: EMPTY,
white_rooks: EMPTY,
white_queens: EMPTY,
white_king: EMPTY,
black: EMPTY,
white: EMPTY,
occupied: EMPTY,
en_passant: EMPTY,
king_side_castle_white: true,
queen_side_castle_white: true,
king_side_castle_black: true,
queen_side_castle_black: true,
turn: Color::White,
}
}
pub fn new_from_fen(data: Fen) -> Self {
let mut board = Self::empty();
board.turn = data.turn();
if let Some(e) = data.en_passant() {
board.en_passant = e.bitboard();
}
board.king_side_castle_white = data.king_side_castle_white;
board.queen_side_castle_white = data.queen_side_castle_white;
board.king_side_castle_black = data.king_side_castle_black;
board.queen_side_castle_black = data.queen_side_castle_black;
for (i, p) in data.iter_board().enumerate() {
match p {
Some(ChessMan {
color: Color::Black,
piece: Piece::Pawn,
}) => board.black_pawns |= 0b1 << i,
Some(ChessMan {
color: Color::Black,
piece: Piece::Rook,
}) => board.black_rooks |= 0b1 << i,
Some(ChessMan {
color: Color::Black,
piece: Piece::Knight,
}) => board.black_knights |= 0b1 << i,
Some(ChessMan {
color: Color::Black,
piece: Piece::Bishop,
}) => board.black_bishops |= 0b1 << i,
Some(ChessMan {
color: Color::Black,
piece: Piece::Queen,
}) => board.black_queens |= 0b1 << i,
Some(ChessMan {
color: Color::Black,
piece: Piece::King,
}) => board.black_king |= 0b1 << i,
Some(ChessMan {
color: Color::White,
piece: Piece::Pawn,
}) => board.white_pawns |= 0b1 << i,
Some(ChessMan {
color: Color::White,
piece: Piece::Rook,
}) => board.white_rooks |= 0b1 << i,
Some(ChessMan {
color: Color::White,
piece: Piece::Knight,
}) => board.white_knights |= 0b1 << i,
Some(ChessMan {
color: Color::White,
piece: Piece::Bishop,
}) => board.white_bishops |= 0b1 << i,
Some(ChessMan {
color: Color::White,
piece: Piece::Queen,
}) => board.white_queens |= 0b1 << i,
Some(ChessMan {
color: Color::White,
piece: Piece::King,
}) => board.white_king |= 0b1 << i,
_ => (),
}
}
board.set_black();
board.set_white();
board.set_occupied();
board
}
pub fn from_fen(data: &str) -> Result<Self, ChessError> {
let fen = Fen::parse(data)?;
Ok(Self::new_from_fen(fen))
}
fn set_black(&mut self) {
self.black = self.black_pawns
| self.black_knights
| self.black_bishops
| self.black_rooks
| self.black_queens
| self.black_king;
}
fn set_white(&mut self) {
self.white = self.white_pawns
| self.white_knights
| self.white_bishops
| self.white_rooks
| self.white_queens
| self.white_king;
}
fn set_occupied(&mut self) {
self.occupied = self.black | self.white;
}
fn toggle_piece(&mut self, bitboard: Bitboard, mov: Move) -> Bitboard {
(bitboard ^ set_bit(0, mov.from.file, mov.from.rank)) | set_bit(0, mov.to.file, mov.to.rank)
}
fn clear_square(&mut self, square: Square, color: Color) {
let bit = set_bit(0, square.file, square.rank);
let clear_bits = !bit;
match color {
Color::White => {
if self.white & bit != 0 {
self.white_pawns &= clear_bits;
self.white_rooks &= clear_bits;
self.white_knights &= clear_bits;
self.white_bishops &= clear_bits;
self.white_queens &= clear_bits;
}
}
Color::Black => {
if self.black & bit != 0 {
self.black_pawns &= clear_bits;
self.black_rooks &= clear_bits;
self.black_knights &= clear_bits;
self.black_bishops &= clear_bits;
self.black_queens &= clear_bits;
}
}
}
}
fn clear_square_on_bitboards(&mut self, square: Square) {
let bit = set_bit(0, square.file, square.rank);
let clear_bits = !bit;
self.black_pawns &= clear_bits;
self.black_rooks &= clear_bits;
self.black_knights &= clear_bits;
self.black_bishops &= clear_bits;
self.black_queens &= clear_bits;
self.white_pawns &= clear_bits;
self.white_rooks &= clear_bits;
self.white_knights &= clear_bits;
self.white_bishops &= clear_bits;
self.white_queens &= clear_bits;
}
fn set_move(&mut self, piece: Piece, mov: Move) {
match self.turn {
Color::White => match piece {
Piece::Pawn => self.white_pawns = self.toggle_piece(self.white_pawns, mov),
Piece::Rook => self.white_rooks = self.toggle_piece(self.white_rooks, mov),
Piece::Knight => self.white_knights = self.toggle_piece(self.white_knights, mov),
Piece::Bishop => self.white_bishops = self.toggle_piece(self.white_bishops, mov),
Piece::Queen => self.white_queens = self.toggle_piece(self.white_queens, mov),
Piece::King => self.white_king = self.toggle_piece(self.white_king, mov),
},
Color::Black => match piece {
Piece::Pawn => self.black_pawns = self.toggle_piece(self.black_pawns, mov),
Piece::Rook => self.black_rooks = self.toggle_piece(self.black_rooks, mov),
Piece::Knight => self.black_knights = self.toggle_piece(self.black_knights, mov),
Piece::Bishop => self.black_bishops = self.toggle_piece(self.black_bishops, mov),
Piece::Queen => self.black_queens = self.toggle_piece(self.black_queens, mov),
Piece::King => self.black_king = self.toggle_piece(self.black_king, mov),
},
}
self.clear_square(mov.to, self.turn.opposite());
}
fn set_piece(&mut self, piece: Piece, color: Color, square: Square) {
match color {
Color::White => match piece {
Piece::Pawn => self.white_pawns |= set_bit(0, square.file, square.rank),
Piece::Rook => self.white_rooks |= set_bit(0, square.file, square.rank),
Piece::Knight => self.white_knights |= set_bit(0, square.file, square.rank),
Piece::Bishop => self.white_bishops |= set_bit(0, square.file, square.rank),
Piece::Queen => self.white_queens |= set_bit(0, square.file, square.rank),
Piece::King => self.white_king |= set_bit(0, square.file, square.rank),
},
Color::Black => match piece {
Piece::Pawn => self.black_pawns |= set_bit(0, square.file, square.rank),
Piece::Rook => self.black_rooks |= set_bit(0, square.file, square.rank),
Piece::Knight => self.black_knights |= set_bit(0, square.file, square.rank),
Piece::Bishop => self.black_bishops |= set_bit(0, square.file, square.rank),
Piece::Queen => self.black_queens |= set_bit(0, square.file, square.rank),
Piece::King => self.black_king |= set_bit(0, square.file, square.rank),
},
}
}
pub fn switch_turn(&mut self) {
self.turn = match self.turn {
Color::White => Color::Black,
Color::Black => Color::White,
}
}
pub fn get_color_bitboard(&self, color: Color) -> Bitboard {
match color {
Color::White => self.white,
Color::Black => self.black,
}
}
pub fn get_piece_bitboard(&self, piece: Piece, color: Color) -> Bitboard {
match color {
Color::White => match piece {
Piece::Pawn => self.white_pawns,
Piece::Knight => self.white_knights,
Piece::Bishop => self.white_bishops,
Piece::Rook => self.white_rooks,
Piece::Queen => self.white_queens,
Piece::King => self.white_king,
},
Color::Black => match piece {
Piece::Pawn => self.black_pawns,
Piece::Knight => self.black_knights,
Piece::Bishop => self.black_bishops,
Piece::Rook => self.black_rooks,
Piece::Queen => self.black_queens,
Piece::King => self.black_king,
},
}
}
pub fn get_piece(&self, square: Bitboard, color: Color) -> Option<Piece> {
match color {
Color::White => {
if self.white_king & square != EMPTY {
return Some(Piece::King);
} else if self.white_queens & square != EMPTY {
return Some(Piece::Queen);
} else if self.white_rooks & square != EMPTY {
return Some(Piece::Rook);
} else if self.white_bishops & square != EMPTY {
return Some(Piece::Bishop);
} else if self.white_knights & square != EMPTY {
return Some(Piece::Knight);
} else if self.white_pawns & square != EMPTY {
return Some(Piece::Pawn);
}
}
Color::Black => {
if self.black_king & square != EMPTY {
return Some(Piece::King);
} else if self.black_queens & square != EMPTY {
return Some(Piece::Queen);
} else if self.black_rooks & square != EMPTY {
return Some(Piece::Rook);
} else if self.black_bishops & square != EMPTY {
return Some(Piece::Bishop);
} else if self.black_knights & square != EMPTY {
return Some(Piece::Knight);
} else if self.black_pawns & square != EMPTY {
return Some(Piece::Pawn);
}
}
}
None
}
pub fn enemy_or_empty(&self) -> Bitboard {
match self.turn {
Color::White => !self.white,
Color::Black => !self.black,
}
}
pub fn apply_move_for_piece(&self, piece: Piece, mov: Move) -> Self {
let mut new_board = *self;
if piece == Piece::Pawn
&& (mov.to.rank.index() - mov.from.rank.index()).abs() == 2
&& mov.from.file == mov.to.file
{
if mov.to.rank == Rank::Four {
new_board.en_passant = Square::new(mov.to.file, Rank::Three).bitboard();
} else if mov.to.rank == Rank::Five {
new_board.en_passant = Square::new(mov.to.file, Rank::Six).bitboard();
} else {
new_board.en_passant = EMPTY;
}
} else {
new_board.en_passant = EMPTY;
}
if piece == Piece::Pawn && self.en_passant & mov.to.bitboard() != EMPTY {
if mov.to.rank == Rank::Three {
new_board.clear_square(Square::new(mov.to.file, Rank::Four), self.turn.opposite());
} else if mov.to.rank == Rank::Six {
new_board.clear_square(Square::new(mov.to.file, Rank::Five), self.turn.opposite());
} else {
unreachable!("Invalid en passant move");
}
}
if piece == Piece::King {
match self.turn {
Color::White => {
new_board.king_side_castle_white = false;
new_board.queen_side_castle_white = false;
}
Color::Black => {
new_board.king_side_castle_black = false;
new_board.queen_side_castle_black = false;
}
}
}
if piece == Piece::Rook {
match self.turn {
Color::White => {
if mov.from == Square::new(File::A, Rank::One) {
new_board.queen_side_castle_white = false;
} else if mov.from == Square::new(File::H, Rank::One) {
new_board.king_side_castle_white = false;
}
}
Color::Black => {
if mov.from == Square::new(File::A, Rank::Eight) {
new_board.queen_side_castle_black = false;
} else if mov.from == Square::new(File::H, Rank::Eight) {
new_board.king_side_castle_black = false;
}
}
}
} else {
match self.turn {
Color::Black => {
if mov.to == Square::new(File::A, Rank::One) {
new_board.queen_side_castle_white = false;
} else if mov.to == Square::new(File::H, Rank::One) {
new_board.king_side_castle_white = false;
}
}
Color::White => {
if mov.to == Square::new(File::A, Rank::Eight) {
new_board.queen_side_castle_black = false;
} else if mov.to == Square::new(File::H, Rank::Eight) {
new_board.king_side_castle_black = false;
}
}
}
}
if piece == Piece::Pawn && mov.promotion.is_some() {
assert!(
(self.turn == Color::White && mov.to.rank == Rank::Eight)
|| (self.turn == Color::Black && mov.to.rank == Rank::One)
);
let promotion = mov.promotion.unwrap();
new_board.clear_square(mov.from, self.turn);
new_board.clear_square(mov.to, self.turn.opposite());
new_board.set_piece(promotion.into_piece(), self.turn, mov.to);
} else if piece == Piece::King {
if self.turn == Color::White {
match mov {
Move {
from:
Square {
file: File::E,
rank: Rank::One,
},
to:
Square {
file: File::G,
rank: Rank::One,
},
promotion: None,
} => {
assert!(self.king_side_castle_white);
new_board.set_move(piece, mov);
new_board.clear_square(Square::new(File::H, Rank::One), self.turn);
new_board.set_piece(
Piece::Rook,
self.turn,
Square::new(File::F, Rank::One),
);
}
Move {
from:
Square {
file: File::E,
rank: Rank::One,
},
to:
Square {
file: File::C,
rank: Rank::One,
},
promotion: None,
} => {
assert!(self.queen_side_castle_white);
new_board.set_move(piece, mov);
new_board.clear_square(Square::new(File::A, Rank::One), self.turn);
new_board.set_piece(
Piece::Rook,
self.turn,
Square::new(File::D, Rank::One),
);
}
_ => {
new_board.set_move(piece, mov);
}
}
} else {
match mov {
Move {
from:
Square {
file: File::E,
rank: Rank::Eight,
},
to:
Square {
file: File::G,
rank: Rank::Eight,
},
promotion: None,
} => {
assert!(self.king_side_castle_black);
new_board.set_move(piece, mov);
new_board.clear_square(Square::new(File::H, Rank::Eight), self.turn);
new_board.set_piece(
Piece::Rook,
self.turn,
Square::new(File::F, Rank::Eight),
);
}
Move {
from:
Square {
file: File::E,
rank: Rank::Eight,
},
to:
Square {
file: File::C,
rank: Rank::Eight,
},
promotion: None,
} => {
assert!(self.queen_side_castle_black);
new_board.set_move(piece, mov);
new_board.clear_square(Square::new(File::A, Rank::Eight), self.turn);
new_board.set_piece(
Piece::Rook,
self.turn,
Square::new(File::D, Rank::Eight),
);
}
_ => {
new_board.set_move(piece, mov);
}
}
}
} else {
new_board.set_move(piece, mov);
}
new_board.set_white();
new_board.set_black();
new_board.set_occupied();
new_board.switch_turn();
new_board
}
pub fn apply_move(&self, mov: Move) -> Option<Self> {
let piece = self.get_piece(mov.from.bitboard(), self.turn)?;
let piece = piece;
Some(self.apply_move_for_piece(piece, mov))
}
pub fn remove_piece(&self, square: Square) -> Self {
let mut new_board = *self;
new_board.clear_square_on_bitboards(square);
new_board.set_white();
new_board.set_black();
new_board.set_occupied();
new_board
}
pub fn into_fen(self) -> Fen {
let mut board: [[Option<ChessMan>; 8]; 8] = [[None; 8]; 8];
for (rank_idx, rank) in Rank::iter().enumerate() {
for (file_idx, file) in File::iter().enumerate() {
let square = Square::new(file, rank);
for color in Color::iter() {
let piece = self.get_piece(square.bitboard(), color);
if let Some(piece) = piece {
match piece {
Piece::Pawn => {
board[rank_idx][file_idx] = Some(ChessMan {
color,
piece: Piece::Pawn,
});
}
Piece::Knight => {
board[rank_idx][file_idx] = Some(ChessMan {
color,
piece: Piece::Knight,
});
}
Piece::Bishop => {
board[rank_idx][file_idx] = Some(ChessMan {
color,
piece: Piece::Bishop,
});
}
Piece::Rook => {
board[rank_idx][file_idx] = Some(ChessMan {
color,
piece: Piece::Rook,
});
}
Piece::Queen => {
board[rank_idx][file_idx] = Some(ChessMan {
color,
piece: Piece::Queen,
});
}
Piece::King => {
board[rank_idx][file_idx] = Some(ChessMan {
color,
piece: Piece::King,
});
}
}
}
}
}
}
let mut en_passant: Option<Square> = None;
if self.en_passant != EMPTY {
en_passant = Some(to_square(self.en_passant));
}
Fen::new_from_board(
board,
self.turn,
en_passant,
self.king_side_castle_white,
self.queen_side_castle_white,
self.king_side_castle_black,
self.queen_side_castle_black,
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::engine::common::File;
#[test]
fn board_new() {
let board = Board::new();
assert_eq!(board.black_pawns, 0b11111111 << (8 * 6));
assert_eq!(board.black_rooks, 0b10000001 << (8 * 7));
assert_eq!(board.black_knights, 0b01000010 << (8 * 7));
assert_eq!(board.black_bishops, 0b00100100 << (8 * 7));
assert_eq!(board.black_queens, 0b1 << 59);
assert_eq!(board.black_king, 0b1 << 60);
assert_eq!(board.white_pawns, 0b11111111 << 8);
assert_eq!(board.white_rooks, 0b10000001);
assert_eq!(board.white_knights, 0b01000010);
assert_eq!(board.white_bishops, 0b00100100);
assert_eq!(board.white_queens, 0b1 << 3);
assert_eq!(board.white_king, 0b1 << 4);
assert_eq!(board.black, 0b11111111 << (8 * 7) | 0b11111111 << (8 * 6));
assert_eq!(board.white, 0b11111111 << 8 | 0b11111111);
assert_eq!(
board.occupied,
0b11111111 << (8 * 7) | 0b11111111 << (8 * 6) | 0b11111111 << 8 | 0b11111111
)
}
#[test]
fn board_new_from_fen() {
let data =
Fen::parse("rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 0 1").unwrap();
let board = Board::new_from_fen(data);
let u: u64 = 0b00010000;
println!("{:#066b}", board.white_queens);
println!("{:#066b}", u);
assert_eq!(board.black_pawns, 0b11111111 << (8 * 6));
assert_eq!(board.black_rooks, 0b10000001 << (8 * 7));
assert_eq!(board.black_knights, 0b01000010 << (8 * 7));
assert_eq!(board.black_bishops, 0b00100100 << (8 * 7));
assert_eq!(board.black_queens, 0b1 << 59);
assert_eq!(board.black_king, 0b1 << 60);
assert_eq!(board.white_pawns, 0b00010000 << (8 * 3) | 0b11101111 << 8);
assert_eq!(board.white_rooks, 0b10000001);
assert_eq!(board.white_knights, 0b01000010);
assert_eq!(board.white_bishops, 0b00100100);
assert_eq!(board.white_queens, 0b1 << 3);
assert_eq!(board.white_king, 0b1 << 4);
assert_eq!(board.black, 0b11111111 << (8 * 7) | 0b11111111 << (8 * 6));
assert_eq!(
board.white,
0b00010000 << (8 * 3) | 0b11101111 << 8 | 0b11111111
);
assert_eq!(
board.occupied,
0b11111111 << (8 * 7)
| 0b11111111 << (8 * 6)
| 0b00010000 << (8 * 3)
| 0b11101111 << 8
| 0b11111111
)
}
#[test]
fn board_compare_default_and_fen() {
let board_default = Board::new();
let board_fen = Board::new_from_fen(
Fen::parse("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1").unwrap(),
);
assert_eq!(board_default, board_fen);
}
#[test]
fn test_enemy_or_empty_for_white() {
let board = Board::new();
let result = board.enemy_or_empty();
assert_eq!(result, u64::MAX ^ (0b11111111 << 8 | 0b11111111));
}
#[test]
fn test_apply_moves_allows_en_passant() {
let expected_board_after_move =
Board::from_fen("rnbqkbnr/2pppppp/p7/Pp6/8/8/1PPPPPPP/RNBQKBNR w KQkq b6 0 3").unwrap();
let board =
Board::from_fen("rnbqkbnr/1ppppppp/p7/P7/8/8/1PPPPPPP/RNBQKBNR b KQkq - 0 2").unwrap();
let board_after_move = board
.apply_move(Move::new(
Square::new(File::B, Rank::Seven),
Square::new(File::B, Rank::Five),
None,
))
.unwrap();
assert_eq!(expected_board_after_move, board_after_move,);
}
#[test]
fn test_to_fen() {
let data = "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR w KQkq -";
let fen = Fen::parse(data).unwrap();
let board = Board::new_from_fen(fen);
let result = board.into_fen();
assert_eq!(result.build_string(), data);
}
}