451 lines
15 KiB
Rust
451 lines
15 KiB
Rust
use u64 as Bitboard;
|
|
|
|
use crate::board::bitboard::{have_common_bit, lsb};
|
|
use crate::board::state::State;
|
|
use crate::movegen::attack_generator::{
|
|
fetch_bishop_attacks, fetch_king_attacks, fetch_knight_attacks, fetch_pawn_attacks,
|
|
fetch_queen_attacks, fetch_rook_attacks,
|
|
};
|
|
use crate::movegen::move_generator::{
|
|
bishop_pseudo_moves, king_pseudo_moves, knight_pseudo_moves, pawn_pseudo_moves,
|
|
queen_pseudo_moves, rook_pseudo_moves,
|
|
};
|
|
use crate::movegen::r#move::{Move, Promote};
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
pub struct Board {
|
|
pub white_pieces: [Piece; 6],
|
|
pub black_pieces: [Piece; 6],
|
|
pub state: State,
|
|
}
|
|
|
|
impl Board {
|
|
pub const fn new() -> Self {
|
|
Self {
|
|
white_pieces: [
|
|
Piece::new(0xff00, PieceType::Pawn, Color::White),
|
|
Piece::new(0x42, PieceType::Knight, Color::White),
|
|
Piece::new(0x24, PieceType::Bishop, Color::White),
|
|
Piece::new(0x81, PieceType::Rook, Color::White),
|
|
Piece::new(0x8, PieceType::Queen, Color::White),
|
|
Piece::new(0x10, PieceType::King, Color::White),
|
|
],
|
|
black_pieces: [
|
|
Piece::new(0xff000000000000, PieceType::Pawn, Color::Black),
|
|
Piece::new(0x4200000000000000, PieceType::Knight, Color::Black),
|
|
Piece::new(0x2400000000000000, PieceType::Bishop, Color::Black),
|
|
Piece::new(0x8100000000000000, PieceType::Rook, Color::Black),
|
|
Piece::new(0x800000000000000, PieceType::Queen, Color::Black),
|
|
Piece::new(0x1000000000000000, PieceType::King, Color::Black),
|
|
],
|
|
state: State::new(),
|
|
}
|
|
}
|
|
|
|
pub const fn empty_board() -> Self {
|
|
Self {
|
|
white_pieces: [
|
|
Piece::new(0x0, PieceType::Pawn, Color::White),
|
|
Piece::new(0x0, PieceType::Knight, Color::White),
|
|
Piece::new(0x0, PieceType::Bishop, Color::White),
|
|
Piece::new(0x0, PieceType::Rook, Color::White),
|
|
Piece::new(0x0, PieceType::Queen, Color::White),
|
|
Piece::new(0x0, PieceType::King, Color::White),
|
|
],
|
|
black_pieces: [
|
|
Piece::new(0x0, PieceType::Pawn, Color::Black),
|
|
Piece::new(0x0, PieceType::Knight, Color::Black),
|
|
Piece::new(0x0, PieceType::Bishop, Color::Black),
|
|
Piece::new(0x0, PieceType::Rook, Color::Black),
|
|
Piece::new(0x0, PieceType::Queen, Color::Black),
|
|
Piece::new(0x0, PieceType::King, Color::Black),
|
|
],
|
|
state: State::new(),
|
|
}
|
|
}
|
|
|
|
pub fn white_occupancies(&self) -> Bitboard {
|
|
self.white_pieces.iter().fold(0, |acc, p| p.bitboard | acc)
|
|
}
|
|
|
|
pub fn black_occupancies(&self) -> Bitboard {
|
|
self.black_pieces.iter().fold(0, |acc, p| p.bitboard | acc)
|
|
}
|
|
|
|
pub fn all_occupancies(&self) -> Bitboard {
|
|
self.white_occupancies() | self.black_occupancies()
|
|
}
|
|
|
|
pub fn is_attacked(&self, square: usize, opponent_color: Color) -> bool {
|
|
let all_occupancies = self.all_occupancies();
|
|
let (opponent, own_color) = match opponent_color {
|
|
Color::Black => (&self.black_pieces, Color::White),
|
|
Color::White => (&self.white_pieces, Color::Black),
|
|
};
|
|
|
|
let pawns = opponent[PieceType::Pawn].bitboard;
|
|
let knights = opponent[PieceType::Knight].bitboard;
|
|
let bishops = opponent[PieceType::Bishop].bitboard;
|
|
let rooks = opponent[PieceType::Rook].bitboard;
|
|
let queens = opponent[PieceType::Queen].bitboard;
|
|
let king = opponent[PieceType::King].bitboard;
|
|
|
|
have_common_bit(pawns, fetch_pawn_attacks(square, own_color))
|
|
|| have_common_bit(knights, fetch_knight_attacks(square))
|
|
|| have_common_bit(bishops, fetch_bishop_attacks(all_occupancies, square))
|
|
|| have_common_bit(rooks, fetch_rook_attacks(all_occupancies, square))
|
|
|| have_common_bit(queens, fetch_queen_attacks(all_occupancies, square))
|
|
|| have_common_bit(king, fetch_king_attacks(square))
|
|
}
|
|
|
|
pub fn king_under_check(&self, color: Color) -> bool {
|
|
let own_king_square = match color {
|
|
Color::White => lsb(self.white_pieces[PieceType::King].bitboard),
|
|
Color::Black => lsb(self.black_pieces[PieceType::King].bitboard),
|
|
};
|
|
|
|
self.is_attacked(own_king_square, color.opponent())
|
|
}
|
|
|
|
pub fn pseudo_moves_all(&self) -> Vec<Move> {
|
|
let color = self.state.current_player();
|
|
let mut moves = vec![];
|
|
|
|
moves.extend(self.pseudo_moves(color, PieceType::Pawn));
|
|
moves.extend(self.pseudo_moves(color, PieceType::Knight));
|
|
moves.extend(self.pseudo_moves(color, PieceType::Bishop));
|
|
moves.extend(self.pseudo_moves(color, PieceType::Rook));
|
|
moves.extend(self.pseudo_moves(color, PieceType::Queen));
|
|
moves.extend(self.pseudo_moves(color, PieceType::King));
|
|
moves
|
|
}
|
|
|
|
pub fn pseudo_moves(&self, color: Color, piece_type: PieceType) -> Vec<Move> {
|
|
let all_occupancies = self.all_occupancies();
|
|
let (pieces, opponent_occupancies, own_occupancies) = match color {
|
|
Color::White => (
|
|
self.white_pieces[piece_type].bitboard,
|
|
self.black_occupancies(),
|
|
self.white_occupancies(),
|
|
),
|
|
Color::Black => (
|
|
self.black_pieces[piece_type].bitboard,
|
|
self.white_occupancies(),
|
|
self.black_occupancies(),
|
|
),
|
|
};
|
|
|
|
match piece_type {
|
|
PieceType::Pawn => pawn_pseudo_moves(
|
|
pieces,
|
|
all_occupancies,
|
|
opponent_occupancies,
|
|
self.state.en_passant_square(),
|
|
color,
|
|
),
|
|
PieceType::Knight => knight_pseudo_moves(pieces, all_occupancies, own_occupancies),
|
|
PieceType::Bishop => bishop_pseudo_moves(pieces, all_occupancies, own_occupancies),
|
|
PieceType::Rook => rook_pseudo_moves(pieces, all_occupancies, own_occupancies),
|
|
PieceType::Queen => queen_pseudo_moves(pieces, all_occupancies, own_occupancies),
|
|
PieceType::King => {
|
|
king_pseudo_moves(pieces, all_occupancies, own_occupancies, self, color)
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn set_piece(&mut self, piece: &Piece) {
|
|
match piece.color {
|
|
Color::Black => self.black_pieces[piece.piece_type].bitboard |= piece.bitboard,
|
|
Color::White => self.white_pieces[piece.piece_type].bitboard |= piece.bitboard,
|
|
};
|
|
}
|
|
|
|
pub fn set_state(&mut self, state: State) {
|
|
self.state = state;
|
|
}
|
|
|
|
pub fn update_game_state(
|
|
state: &mut State,
|
|
mv: &Move,
|
|
color: Color,
|
|
pawn_move: bool,
|
|
en_passant_square: Option<usize>,
|
|
) {
|
|
state.set_en_passant_square(en_passant_square);
|
|
state.update_castling_state_quiet(mv.src, color);
|
|
state.update_castling_state_capture(mv.dst, color.opponent());
|
|
state.update_half_move(mv.move_type, pawn_move);
|
|
state.update_full_move(color);
|
|
state.change_side();
|
|
}
|
|
|
|
pub fn move_piece(&mut self, src: usize, dst: usize) {
|
|
let pieces = self.own_pieces();
|
|
pieces
|
|
.iter_mut()
|
|
.filter(|p| have_common_bit(p.bitboard, square_to_bitboard(src)))
|
|
.for_each(|p| {
|
|
p.bitboard &= !square_to_bitboard(src);
|
|
p.bitboard |= square_to_bitboard(dst);
|
|
});
|
|
|
|
//TODO:
|
|
// pieces[piece_type].bitboard &= !square_to_bitboard(src);
|
|
// pieces[piece_type].bitboard |= square_to_bitboard(dst);
|
|
}
|
|
|
|
pub fn remove_own_piece(&mut self, square: usize) {
|
|
let pieces = self.own_pieces();
|
|
pieces
|
|
.iter_mut()
|
|
.filter(|p| have_common_bit(p.bitboard, square_to_bitboard(square)))
|
|
.for_each(|p| {
|
|
p.bitboard &= !square_to_bitboard(square);
|
|
});
|
|
}
|
|
|
|
pub fn remove_opponent_piece(&mut self, square: usize) {
|
|
let pieces = self.opponent_pieces();
|
|
pieces
|
|
.iter_mut()
|
|
.filter(|p| have_common_bit(p.bitboard, square_to_bitboard(square)))
|
|
.for_each(|p| {
|
|
p.bitboard &= !square_to_bitboard(square);
|
|
});
|
|
}
|
|
|
|
pub fn promote_piece(&mut self, square: usize, promote: &Promote) {
|
|
let pieces = self.own_pieces();
|
|
match promote {
|
|
Promote::Knight => pieces[PieceType::Knight].bitboard |= square_to_bitboard(square),
|
|
Promote::Bishop => pieces[PieceType::Bishop].bitboard |= square_to_bitboard(square),
|
|
Promote::Rook => pieces[PieceType::Rook].bitboard |= square_to_bitboard(square),
|
|
Promote::Queen => pieces[PieceType::Queen].bitboard |= square_to_bitboard(square),
|
|
};
|
|
}
|
|
|
|
pub fn is_pawn_move(&mut self, square: usize) -> bool {
|
|
let pieces = self.own_pieces();
|
|
have_common_bit(square_to_bitboard(square), pieces[PieceType::Pawn].bitboard)
|
|
}
|
|
|
|
pub fn own_pieces(&mut self) -> &mut [Piece; 6] {
|
|
match self.state.current_player() {
|
|
Color::White => &mut self.white_pieces,
|
|
Color::Black => &mut self.black_pieces,
|
|
}
|
|
}
|
|
|
|
pub fn opponent_pieces(&mut self) -> &mut [Piece; 6] {
|
|
match self.state.current_player() {
|
|
Color::White => &mut self.black_pieces,
|
|
Color::Black => &mut self.white_pieces,
|
|
}
|
|
}
|
|
|
|
pub fn all_pieces(&mut self) -> (&mut [Piece; 6], &mut [Piece; 6]) {
|
|
match self.state.current_player() {
|
|
Color::White => (&mut self.white_pieces, &mut self.black_pieces),
|
|
Color::Black => (&mut self.black_pieces, &mut self.white_pieces),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Default for Board {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
|
pub struct Piece {
|
|
pub bitboard: Bitboard,
|
|
pub piece_type: PieceType,
|
|
pub color: Color,
|
|
}
|
|
|
|
impl Piece {
|
|
pub const fn new(bitboard: Bitboard, piece_type: PieceType, color: Color) -> Self {
|
|
Self {
|
|
bitboard,
|
|
piece_type,
|
|
color,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
|
pub enum PieceType {
|
|
Pawn,
|
|
Knight,
|
|
Bishop,
|
|
Rook,
|
|
Queen,
|
|
King,
|
|
}
|
|
|
|
impl PieceType {
|
|
pub const fn idx(self) -> usize {
|
|
self as usize
|
|
}
|
|
}
|
|
use std::ops::{Index, IndexMut};
|
|
|
|
use super::bitboard::square_to_bitboard;
|
|
|
|
impl Index<PieceType> for [Piece] {
|
|
type Output = Piece;
|
|
|
|
fn index(&self, piece_type: PieceType) -> &Self::Output {
|
|
&self[piece_type.idx()]
|
|
}
|
|
}
|
|
|
|
impl IndexMut<PieceType> for [Piece] {
|
|
fn index_mut(&mut self, piece_type: PieceType) -> &mut Self::Output {
|
|
&mut self[piece_type.idx()]
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
|
pub enum Color {
|
|
White,
|
|
Black,
|
|
}
|
|
|
|
impl Color {
|
|
pub const fn opponent(self) -> Self {
|
|
match self {
|
|
Self::White => Self::Black,
|
|
Self::Black => Self::White,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use crate::{
|
|
board::{fen::from_fen, square::Square},
|
|
movegen::{attack_generator::init_attacks, r#move::MoveType},
|
|
};
|
|
|
|
use super::*;
|
|
|
|
const FEN_EXAMPLE: [&str; 1] = ["8/6P1/4n2b/1p6/1Kp5/6n1/4b3/1k6 w - - 0 1"];
|
|
|
|
#[test]
|
|
fn test_occupancies() -> Result<(), String> {
|
|
let new_game = from_fen(FEN_EXAMPLE[0])?;
|
|
|
|
assert_eq!(new_game.board.white_occupancies(), 0x40000002000000);
|
|
assert_eq!(new_game.board.black_occupancies(), 0x900204401002);
|
|
assert_eq!(new_game.board.all_occupancies(), 0x40900206401002);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn test_is_attacked() -> Result<(), String> {
|
|
let new_game = from_fen(FEN_EXAMPLE[0])?;
|
|
init_attacks();
|
|
|
|
assert!(new_game.board.is_attacked(54, Color::Black));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
const FEN: &str = "1r2k2r/2P1pq1p/2npb3/1p3ppP/p3P3/P2B1Q2/1P1PNPP1/R3K2R w KQk g6 0 1";
|
|
const FEN_QUIET: &str = "1r2k2r/2P1pq1p/2npb3/1p3ppP/p3P3/P2BQ3/1P1PNPP1/R3K2R b KQk - 1 1";
|
|
|
|
#[test]
|
|
fn test_make_move_quiet() -> Result<(), String> {
|
|
let mut game = from_fen(FEN)?;
|
|
let b1c3 = Move::new(Square::F3, Square::E3);
|
|
game.make_move(&b1c3);
|
|
|
|
assert_eq!(game, from_fen(FEN_QUIET)?);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
const FEN_CAPTURE: &str = "1r2k2r/2P1pq1p/2npb3/1p3QpP/p3P3/P2B4/1P1PNPP1/R3K2R b KQk - 0 1";
|
|
|
|
#[test]
|
|
fn test_make_move_capture() -> Result<(), String> {
|
|
let mut game = from_fen(FEN)?;
|
|
let f3f5 = Move::new_with_type(Square::F3, Square::F5, MoveType::Capture);
|
|
game.make_move(&f3f5);
|
|
|
|
assert_eq!(game, from_fen(FEN_CAPTURE)?);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
const FEN_EN_PASSANT: &str =
|
|
"1r2k2r/2P1pq1p/2npb1P1/1p3p2/p3P3/P2B1Q2/1P1PNPP1/R3K2R b KQk - 0 1";
|
|
|
|
#[test]
|
|
fn test_make_move_en_passant() -> Result<(), String> {
|
|
let mut game = from_fen(FEN)?;
|
|
let h5g6 = Move::new_with_type(Square::H5, Square::G6, MoveType::EnPassant);
|
|
game.make_move(&h5g6);
|
|
|
|
assert_eq!(game, from_fen(FEN_EN_PASSANT)?);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
const FEN_DOUBLE_PUSH: &str =
|
|
"1r2k2r/2P1pq1p/2npb3/1p3ppP/pP2P3/P2B1Q2/3PNPP1/R3K2R b KQk b3 0 1";
|
|
|
|
#[test]
|
|
fn test_make_move_double_push() -> Result<(), String> {
|
|
let mut game = from_fen(FEN)?;
|
|
let b2b4 = Move::new_with_type(Square::B2, Square::B4, MoveType::DoublePush);
|
|
game.make_move(&b2b4);
|
|
assert_eq!(game, from_fen(FEN_DOUBLE_PUSH)?);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
const FEN_PROMOTION: &str = "1rQ1k2r/4pq1p/2npb3/1p3ppP/p3P3/P2B1Q2/1P1PNPP1/R3K2R b KQk - 0 1";
|
|
|
|
#[test]
|
|
fn test_make_move_promotion() -> Result<(), String> {
|
|
let mut game = from_fen(FEN)?;
|
|
let c7c8 = Move::new_with_type(Square::C7, Square::C8, MoveType::Promotion(Promote::Queen));
|
|
game.make_move(&c7c8);
|
|
assert_eq!(game, from_fen(FEN_PROMOTION)?);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
const FEN_PROMOTION_CAPTURE: &str =
|
|
"1Q2k2r/4pq1p/2npb3/1p3ppP/p3P3/P2B1Q2/1P1PNPP1/R3K2R b KQk - 0 1";
|
|
|
|
#[test]
|
|
fn test_make_move_promotion_capture() -> Result<(), String> {
|
|
let mut game = from_fen(FEN)?;
|
|
let c7b8 = Move::new_with_type(
|
|
Square::C7,
|
|
Square::B8,
|
|
MoveType::PromotionCapture(Promote::Queen),
|
|
);
|
|
game.make_move(&c7b8);
|
|
assert_eq!(game, from_fen(FEN_PROMOTION_CAPTURE)?);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
const FEN_CASTLE: &str = "1r2k2r/2P1pq1p/2npb3/1p3ppP/p3P3/P2B1Q2/1P1PNPP1/R4RK1 b k - 1 1";
|
|
|
|
#[test]
|
|
fn test_make_move_castle() -> Result<(), String> {
|
|
let mut game = from_fen(FEN)?;
|
|
let e1g1 = Move::new_with_type(Square::E1, Square::G1, MoveType::Castle);
|
|
game.make_move(&e1g1);
|
|
assert_eq!(game, from_fen(FEN_CASTLE)?);
|
|
Ok(())
|
|
}
|
|
}
|