Refactor make_move, internal functions, move parsing and add more tests

This commit is contained in:
stefiosif
2024-09-14 20:55:00 +03:00
parent b26357a205
commit 49b413d24f
5 changed files with 490 additions and 301 deletions

View File

@@ -10,7 +10,7 @@ use crate::movegen::movegen::{
bishop_pseudo_moves, king_pseudo_moves, knight_pseudo_moves, pawn_pseudo_moves,
queen_pseudo_moves, rook_pseudo_moves,
};
use crate::movegen::r#move::Move;
use crate::movegen::r#move::{Move, MoveType, Promote};
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Board {
@@ -164,7 +164,7 @@ impl Board {
self.state = state;
}
pub fn piece_type_at(&self, square: usize, color: Color) -> Option<PieceType> {
pub fn piece_type_at_color(&self, square: usize, color: Color) -> Option<PieceType> {
let pieces = match color {
Color::White => &self.white_pieces,
Color::Black => &self.black_pieces,
@@ -175,6 +175,225 @@ impl Board {
.find(|p| have_common_bit(p.bitboard, square_to_bitboard(square)))
.map(|p| p.piece_type)
}
pub fn piece_type_at(&self, square: usize) -> Option<PieceType> {
if let Some(at_white) = self.piece_type_at_color(square, Color::White) {
return Some(at_white);
}
if let Some(at_black) = self.piece_type_at_color(square, Color::Black) {
return Some(at_black);
}
return None;
}
pub fn make_move(&mut self, mv: &Move) {
let color = self.state.current_player();
let pawn_move = self.is_pawn_move(mv.src);
let mut en_passant_square = None;
match &mv.move_type {
MoveType::Quiet => {
self.move_piece(mv.src, mv.dst);
}
MoveType::Capture => {
self.move_piece(mv.src, mv.dst);
self.remove_opponent_piece(mv.dst);
}
MoveType::EnPassant => {
self.move_piece(mv.src, mv.dst);
self.remove_pawn_enpassant(mv.dst, color);
}
MoveType::DoublePush => {
self.move_piece(mv.src, mv.dst);
en_passant_square = match color {
Color::White => Some(mv.src + 8),
Color::Black => Some(mv.src.saturating_sub(8)),
};
}
MoveType::Promotion(promote) => {
self.remove_own_piece(mv.src);
self.promote_piece(mv.dst, promote);
}
MoveType::PromotionCapture(promote) => {
self.remove_own_piece(mv.src);
self.remove_opponent_piece(mv.dst);
self.promote_piece(mv.dst, promote);
}
MoveType::Castle => {
self.move_piece(mv.src, mv.dst);
self.move_rook_castle(mv.dst);
self.state.set_castling_ability(color, Castle::None);
}
}
Self::update_game_state(&mut self.state, mv, color, pawn_move, en_passant_square);
}
pub fn unmake_move(&mut self, move_parameters: &MoveParameters) {
let color_before_move = self.state.change_side();
self.state.revert_full_move(color_before_move);
self.state.en_passant_square = move_parameters.en_passant_square;
if let Some(new_castling_ability) = move_parameters.castling_ability {
self.state.castling_ability = new_castling_ability;
}
if let Some(new_halfmove_clock) = move_parameters.halfmove_clock {
self.state.halfmove_clock = new_halfmove_clock;
}
let mv = move_parameters.mv.unwrap();
match &mv.move_type {
MoveType::Quiet | MoveType::DoublePush => {
self.move_piece(mv.dst, mv.src);
}
MoveType::Capture | MoveType::Promotion(_) | MoveType::PromotionCapture(_) => {
if let (Some(captured_piece_type), Some(promoted_piece_type)) = (
move_parameters.captured_piece,
move_parameters.promoted_piece,
) {
let (own_pieces, opponent_pieces) = self.all_pieces();
opponent_pieces[captured_piece_type].bitboard |= square_to_bitboard(mv.dst);
own_pieces[promoted_piece_type].bitboard &= !square_to_bitboard(mv.dst);
own_pieces[PieceType::Pawn].bitboard |= square_to_bitboard(mv.src);
} else if let Some(captured_piece_type) = move_parameters.captured_piece {
self.move_piece(mv.dst, mv.src);
let opponent_pieces = self.opponent_pieces();
opponent_pieces[captured_piece_type].bitboard |= square_to_bitboard(mv.dst);
} else if let Some(promoted_piece_type) = move_parameters.promoted_piece {
let own_pieces = self.own_pieces();
own_pieces[promoted_piece_type].bitboard &= !square_to_bitboard(mv.dst);
own_pieces[PieceType::Pawn].bitboard |= square_to_bitboard(mv.src);
}
}
MoveType::EnPassant => {
self.move_piece(mv.dst, mv.src);
let enemy_pawn_square = match color_before_move {
Color::White => mv.dst - 8,
Color::Black => mv.dst + 8,
};
let opponent_pieces = self.opponent_pieces();
opponent_pieces[PieceType::Pawn].bitboard |= square_to_bitboard(enemy_pawn_square);
}
MoveType::Castle => {
self.move_piece(mv.dst, mv.src);
let (rook_src, rook_dst) = match mv.dst {
Square::C1 | Square::C8 => (mv.dst - 2, mv.dst + 1),
Square::G1 | Square::G8 => (mv.dst + 1, mv.dst - 1),
_ => return,
};
let own_pieces = self.own_pieces();
own_pieces[PieceType::Rook].bitboard &= !square_to_bitboard(rook_dst);
own_pieces[PieceType::Rook].bitboard |= square_to_bitboard(rook_src);
}
}
}
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();
}
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);
});
}
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);
});
}
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);
});
}
fn move_rook_castle(&mut self, king_dst: usize) {
let (rook_src, rook_dst) = match king_dst {
Square::C1 | Square::C8 => (king_dst - 2, king_dst + 1),
Square::G1 | Square::G8 => (king_dst + 1, king_dst - 1),
_ => return,
};
self.move_piece(rook_src, rook_dst);
}
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),
};
}
fn remove_pawn_enpassant(&mut self, square: usize, color: Color) {
let piece_to_remove = match color {
Color::White => square - 8,
Color::Black => square + 8,
};
self.remove_opponent_piece(piece_to_remove);
}
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)
}
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,
}
}
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,
}
}
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 {
@@ -211,20 +430,16 @@ pub enum PieceType {
}
impl PieceType {
const fn idx(self) -> usize {
match self {
Self::Pawn => 0,
Self::Knight => 1,
Self::Bishop => 2,
Self::Rook => 3,
Self::Queen => 4,
Self::King => 5,
}
pub const fn idx(self) -> usize {
self as usize
}
}
use std::ops::{Index, IndexMut};
use super::bitboard::square_to_bitboard;
use super::history::MoveParameters;
use super::square::Square;
use super::state::Castle;
impl Index<PieceType> for [Piece] {
type Output = Piece;
@@ -257,7 +472,10 @@ impl Color {
#[cfg(test)]
mod tests {
use crate::{board::fen::from_fen, movegen::attack::init_attacks};
use crate::{
board::fen::from_fen,
movegen::{attack::init_attacks, r#move::MoveType},
};
use super::*;
@@ -266,9 +484,11 @@ mod tests {
#[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(())
}
@@ -276,8 +496,103 @@ mod tests {
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.board.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.board.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.board.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.board.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.board.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.board.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.board.make_move(&e1g1);
assert_eq!(game, from_fen(FEN_CASTLE)?);
Ok(())
}
}

View File

@@ -60,7 +60,7 @@ impl MoveParameters {
}
fn add_captured_piece(&mut self, board: &Board, dst: usize, color: Color) {
self.captured_piece = board.piece_type_at(dst, color);
self.captured_piece = board.piece_type_at_color(dst, color);
}
fn add_promoted_piece(&mut self, promote: Promote) {