Fix move generator bugs related to castling, enpassant, promotion

This commit is contained in:
2024-07-05 20:17:09 +03:00
parent 2a1a3224cb
commit e3123f17d3
4 changed files with 128 additions and 216 deletions

View File

@@ -4,7 +4,7 @@ use crate::attack::{
get_bishop_attacks, get_king_attacks, get_knight_attacks, get_pawn_attacks, get_queen_attacks,
get_rook_attacks,
};
use crate::game::State;
use crate::game::{Castle, State};
use crate::movegen::{
bishop_pseudo_moves, king_pseudo_moves, knight_pseudo_moves, pawn_pseudo_moves,
queen_pseudo_moves, rook_pseudo_moves,
@@ -88,12 +88,12 @@ impl Board {
pub fn is_attacked(&self, sq: usize, opponent_color: Color) -> bool {
let all_occupancies = self.get_all_occupancies();
let enemy = match opponent_color {
Color::Black => &self.black_pieces,
Color::White => &self.white_pieces,
let (enemy, own_color) = match opponent_color {
Color::Black => (&self.black_pieces, Color::White),
Color::White => (&self.white_pieces, Color::Black),
};
enemy[Kind::Pawn.idx()].bitboard & get_pawn_attacks(sq, opponent_color) != 0
enemy[Kind::Pawn.idx()].bitboard & get_pawn_attacks(sq, own_color) != 0
|| enemy[Kind::Knight.idx()].bitboard & get_knight_attacks(sq) != 0
|| enemy[Kind::Bishop.idx()].bitboard & get_bishop_attacks(all_occupancies, sq) != 0
|| enemy[Kind::Rook.idx()].bitboard & get_rook_attacks(all_occupancies, sq) != 0
@@ -156,14 +156,18 @@ impl Board {
pub fn make_move(&mut self, mv: Move, color: Color) -> bool {
self.update_board_state(&mv, color);
self.state.update_castling_state(mv.source as u8, color);
self.state.change_side();
let pieces = match color {
Color::White => &self.white_pieces,
Color::Black => &self.black_pieces,
};
self.state
.update_castling_state_quiet(mv.source as u8, color);
self.state
.update_castling_state_capture(mv.target as u8, Color::opponent_color(color));
self.state.change_side();
let own_king_square = pieces[Kind::King.idx()].bitboard.trailing_zeros() as usize;
self.is_move_legit(own_king_square, Color::opponent_color(color))
}
@@ -191,7 +195,10 @@ impl Board {
}
MoveType::DoublePush => {
Board::move_piece(mv.source as u8, mv.target as u8, own_pieces);
let en_passant = Some(mv.source as u8 + 8);
let en_passant = match color {
Color::White => Some((mv.source + 8) as u8),
Color::Black => Some((mv.source - 8) as u8),
};
self.state.set_en_passant_target_square(en_passant);
}
MoveType::Promotion(promote) => {
@@ -209,6 +216,7 @@ impl Board {
Board::move_piece(mv.source as u8, mv.target as u8, own_pieces);
Board::move_rook_castle(mv.target as u8, own_pieces);
self.state.set_en_passant_target_square(None);
self.state.set_castling_ability(color, Castle::None)
}
}
}
@@ -230,13 +238,7 @@ impl Board {
_ => return,
};
for p in pieces.iter_mut() {
if p.bitboard & 1_u64 << rook_source != 0 {
p.bitboard &= !(1_u64 << rook_source);
p.bitboard |= 1_u64 << rook_target;
break;
}
}
Board::move_piece(rook_source, rook_target, pieces);
}
fn promote_piece(square: u8, pieces: &mut [Piece; 6], promote: Promote) {
@@ -464,28 +466,6 @@ mod tests {
let mv = Move::new_with_type(4, 6, MoveType::Castle);
game.board.make_move(mv, Color::White);
assert_eq!(game, from_fen(FEN_CASTLE[1])?);
Ok(())
}
#[test]
fn test_perftree_snapshots() -> Result<(), String> {
init_attacks();
let mut game = from_fen("rnbqkbnr/ppppp1pp/5p2/7Q/8/4P3/PPPP1PPP/RNB1KBNR w KQkq - 0 1")?;
for mv in game.board.pseudo_moves_all(Color::Black) {
if game.board.make_move_and_reset(mv, Color::Black) {
println!("{:?}", mv)
}
}
let mut game = from_fen("rnbqkbnr/1ppppppp/p7/8/Q7/2P5/PP1PPPPP/RNB1KBNR w KQkq - 0 1")?;
for mv in game.board.pseudo_moves_all(Color::Black) {
if game.board.make_move_and_reset(mv, Color::Black) {
println!("{:?}", mv)
}
}
Ok(())
}
}

View File

@@ -78,26 +78,37 @@ impl State {
}
}
pub fn update_castling_state(&mut self, square: u8, color: Color) {
if square == 0 || square == 7 {
match (color, self.get_castling_ability(color)) {
(_, Castle::Both) => self.set_castling_ability(color, Castle::Long),
(_, Castle::Short) => self.set_castling_ability(color, Castle::None),
pub fn update_castling_state_quiet(&mut self, source: u8, color: Color) {
self.update_castling_state_capture(source, color);
match (source, color) {
(4, Color::White) => self.set_castling_ability(color, Castle::None),
(60, Color::Black) => self.set_castling_ability(color, Castle::None),
_ => (),
}
}
pub fn update_castling_state_capture(&mut self, target: u8, color: Color) {
let (short_square, long_square) = match color {
Color::White => (7, 0),
Color::Black => (63, 56),
};
if target == short_square {
match self.get_castling_ability(color) {
Castle::Both => self.set_castling_ability(color, Castle::Long),
Castle::Short => self.set_castling_ability(color, Castle::None),
_ => (),
}
}
if square == 56 || square == 63 {
match (color, self.get_castling_ability(color)) {
(_, Castle::Both) => self.set_castling_ability(color, Castle::Short),
(_, Castle::Short) => self.set_castling_ability(color, Castle::None),
if target == long_square {
match self.get_castling_ability(color) {
Castle::Both => self.set_castling_ability(color, Castle::Short),
Castle::Long => self.set_castling_ability(color, Castle::None),
_ => (),
}
}
if square == 4 || square == 60 {
self.set_castling_ability(color, Castle::None)
}
}
pub fn change_side(&mut self) {

View File

@@ -50,7 +50,7 @@ fn white_pawn_quiet_moves(pawns: Bitboard, occupancies: Bitboard) -> Vec<Move> {
let to = single_push_targets.trailing_zeros();
let from = to - 8;
if from as u64 & 0xff000000000000 != 0 {
if 1_u64 << from as u64 & 0xff000000000000 != 0 {
moves.extend([
Move::new_with_type(from, to, MoveType::Promotion(Promote::Knight)),
Move::new_with_type(from, to, MoveType::Promotion(Promote::Bishop)),
@@ -82,7 +82,7 @@ fn black_pawn_quiet_moves(pawns: Bitboard, occupancies: Bitboard) -> Vec<Move> {
let to = single_push_targets.trailing_zeros();
let from = to + 8;
if from as u64 & 0xff000000000000 != 0 {
if 1_u64 << from as u64 & 0xff00 != 0 {
moves.extend([
Move::new_with_type(from, to, MoveType::Promotion(Promote::Knight)),
Move::new_with_type(from, to, MoveType::Promotion(Promote::Bishop)),
@@ -119,7 +119,7 @@ fn white_pawn_capture_moves(
let from = w_pawns_capture_east.trailing_zeros();
let to = from + 9;
if from as u64 & 0xff000000000000 != 0 {
if 1_u64 << to as u64 & 0xff00000000000000 != 0 {
moves.extend([
Move::new_with_type(from, to, MoveType::PromotionCapture(Promote::Knight)),
Move::new_with_type(from, to, MoveType::PromotionCapture(Promote::Bishop)),
@@ -135,7 +135,7 @@ fn white_pawn_capture_moves(
let from = w_pawns_capture_west.trailing_zeros();
let to = from + 7;
if from as u64 & 0xff000000000000 != 0 {
if 1_u64 << to as u64 & 0xff00000000000000 != 0 {
moves.extend([
Move::new_with_type(from, to, MoveType::PromotionCapture(Promote::Knight)),
Move::new_with_type(from, to, MoveType::PromotionCapture(Promote::Bishop)),
@@ -150,13 +150,14 @@ fn white_pawn_capture_moves(
if let Some(en_passant_square) = en_passant_square {
let attacked_from = get_pawn_attacks(en_passant_square as usize, Color::Black);
let result = attacked_from & pawns;
if result != 0 {
let mut result = attacked_from & pawns;
while result != 0 {
moves.push(Move::new_with_type(
result.trailing_zeros(),
en_passant_square as u32,
MoveType::EnPassant,
));
result &= result - 1;
}
};
moves
@@ -174,7 +175,7 @@ fn black_pawn_capture_moves(
while b_pawns_capture_east != 0 {
let from = b_pawns_capture_east.trailing_zeros();
let to = from - 7;
if from as u64 & 0xff00 != 0 {
if 1_u64 << to as u64 & 0xff != 0 {
moves.extend([
Move::new_with_type(from, to, MoveType::PromotionCapture(Promote::Knight)),
Move::new_with_type(from, to, MoveType::PromotionCapture(Promote::Bishop)),
@@ -190,7 +191,7 @@ fn black_pawn_capture_moves(
while b_pawns_capture_west != 0 {
let from = b_pawns_capture_west.trailing_zeros();
let to = from - 9;
if from as u64 & 0xff00 != 0 {
if 1_u64 << to as u64 & 0xff != 0 {
moves.extend([
Move::new_with_type(from, to, MoveType::PromotionCapture(Promote::Knight)),
Move::new_with_type(from, to, MoveType::PromotionCapture(Promote::Bishop)),
@@ -205,13 +206,14 @@ fn black_pawn_capture_moves(
if let Some(en_passant_square) = en_passant_square {
let attacked_from = get_pawn_attacks(en_passant_square as usize, Color::White);
let result = attacked_from & pawns;
if result != 0 {
let mut result = attacked_from & pawns;
while result != 0 {
moves.push(Move::new_with_type(
result.trailing_zeros(),
en_passant_square as u32,
MoveType::EnPassant,
));
result &= result - 1;
}
};
moves
@@ -373,11 +375,16 @@ fn king_castling_moves(board: &Board, color: Color, all_occupancies: Bitboard) -
add_move_if_empty_path(path_short, king_to_short);
add_move_if_empty_path(path_long, king_to_long);
}
(Castle::Both, Castle::Short) | (Castle::Short, Castle::Short) => {
(Castle::Both, Castle::Short)
| (Castle::Short, Castle::Short)
| (Castle::Short, Castle::Both) => {
add_move_if_empty_path(path_short, king_to_short);
}
(Castle::Both, Castle::Long) | (Castle::Long, Castle::Long) => {
add_move_if_empty_path(path_long, king_to_long)
(Castle::Both, Castle::Long)
| (Castle::Long, Castle::Long)
| (Castle::Long, Castle::Both) => {
add_move_if_empty_path(path_long, king_to_long);
}
_ => (),
};
@@ -386,7 +393,7 @@ fn king_castling_moves(board: &Board, color: Color, all_occupancies: Bitboard) -
fn king_and_adj_square_safety(board: &Board, color: Color) -> Castle {
let (king_at, opponent_color) = match color {
Color::White => (3, Color::Black),
Color::White => (4, Color::Black),
Color::Black => (60, Color::White),
};
@@ -394,12 +401,12 @@ fn king_and_adj_square_safety(board: &Board, color: Color) -> Castle {
return Castle::None;
}
let short_attacked = board.is_attacked(king_at - 1, opponent_color);
let long_attacked = board.is_attacked(king_at + 1, opponent_color);
let short_attacked = board.is_attacked(king_at + 1, opponent_color);
let long_attacked = board.is_attacked(king_at - 1, opponent_color);
match (short_attacked, long_attacked) {
(true, true) => Castle::None,
(true, false) => Castle::Short,
(false, true) => Castle::Long,
(true, false) => Castle::Long,
(false, true) => Castle::Short,
(false, false) => Castle::Both,
}
}
@@ -495,7 +502,7 @@ mod tests {
];
let mut actual = new_game.board.pseudo_moves(Color::White, Kind::Bishop);
actual.sort();
assert_eq!(expected, actual);
assert_eq!(expected, actual);
Ok(())
}
@@ -594,16 +601,4 @@ mod tests {
Ok(())
}
#[test]
fn test_random() -> Result<(), String> {
init_attacks();
let game = from_fen("rnbqkbnr/ppppp1pp/5p2/7Q/8/4P3/PPPP1PPP/RNB1KBNR w KQkq - 0 1")?;
for mv in game.board.pseudo_moves_all(Color::Black) {
println!("{:?}", mv);
}
Ok(())
}
}

View File

@@ -1,14 +1,13 @@
use crate::game::Game;
pub fn perft_driver(game: &mut Game, nodes: &mut u64, depth: u8) {
pub fn driver(game: &mut Game, nodes: &mut u64, depth: u8) {
if depth == 0 {
*nodes += 1;
return;
}
let pseudo_moves =
game.board.pseudo_moves_all(game.board.state.next_turn());
let pseudo_moves = game.board.pseudo_moves_all(game.board.state.next_turn());
for mv in pseudo_moves {
let original_board = game.board.clone();
if !game.board.make_move(mv, game.board.state.next_turn()) {
@@ -16,20 +15,22 @@ pub fn perft_driver(game: &mut Game, nodes: &mut u64, depth: u8) {
continue;
}
// print_perftree(mv.source, mv.target, depth, nodes);
print_perftree(mv.source, mv.target, depth, nodes);
perft_driver(game, nodes, depth - 1);
driver(game, nodes, depth - 1);
game.board = original_board;
}
#[allow(dead_code)]
fn print_perftree(source: u32, target: u32, depth: u8, nodes: &mut u64) {
println!(
"{}{} - depth: {}",
square_to_notation(source as u8),
square_to_notation(target as u8),
depth
);
// println!(
// "{}{} - depth: {} - nodes: {}",
// square_to_notation(source as u8),
// square_to_notation(target as u8),
// depth,
// nodes
// );
if depth == MAX_DEPTH {
println!(
"{}{} {}",
@@ -39,16 +40,15 @@ pub fn perft_driver(game: &mut Game, nodes: &mut u64, depth: u8) {
);
}
}
}
const MAX_DEPTH: u8 = 4;
const MAX_DEPTH: u8 = 3;
pub fn perftree_script() {
let fen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
let fen = "r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 1";
let mut game = crate::fen::from_fen(fen).unwrap();
let (mut nodes, depth): (u64, u8) = (0, MAX_DEPTH);
perft_driver(&mut game, &mut nodes, depth);
driver(&mut game, &mut nodes, depth);
println!();
println!("{}", nodes)
}
@@ -81,7 +81,7 @@ pub fn square_to_notation(square: u8) -> &'static str {
mod tests {
use crate::{attack::init_attacks, fen::from_fen};
use super::perft_driver;
use super::driver;
// Examples from https://www.chessprogramming.org/Perft_Results
const FEN_PERFT: [&str; 6] = [
@@ -93,164 +93,90 @@ mod tests {
"r4rk1/1pp1qppp/p1np1n2/2b1p1B1/2B1P1b1/P1NP1N2/1PP1QPPP/R4RK1 w - - 0 10",
];
#[test]
fn test_perft_driver_1() -> Result<(), String> {
fn perft(fen: &str, depth: u8) -> Result<u64, String> {
init_attacks();
let mut game = from_fen(FEN_PERFT[0])?;
let (mut nodes, depth): (u64, u8) = (0, 1);
perft_driver(&mut game, &mut nodes, depth);
assert_eq!(nodes , 20);
let mut game = from_fen(fen)?;
let mut nodes = 0;
driver(&mut game, &mut nodes, depth);
let mut game = from_fen(FEN_PERFT[0])?;
let (mut nodes, depth): (u64, u8) = (0, 2);
perft_driver(&mut game, &mut nodes, depth);
assert_eq!(nodes, 400);
Ok(nodes)
}
let mut game = from_fen(FEN_PERFT[0])?;
let (mut nodes, depth): (u64, u8) = (0, 3);
perft_driver(&mut game, &mut nodes, depth);
assert_eq!(nodes, 8902);
#[test]
fn test_perft_1() -> Result<(), String> {
init_attacks();
let mut game = from_fen(FEN_PERFT[0])?;
let (mut nodes, depth): (u64, u8) = (0, 4);
perft_driver(&mut game, &mut nodes, depth);
assert_eq!(nodes, 197281);
assert_eq!(perft(FEN_PERFT[0], 1)?, 20);
assert_eq!(perft(FEN_PERFT[0], 2)?, 400);
assert_eq!(perft(FEN_PERFT[0], 3)?, 8902);
assert_eq!(perft(FEN_PERFT[0], 4)?, 197281);
assert_eq!(perft(FEN_PERFT[0], 5)?, 4865609);
Ok(())
}
#[test]
fn test_perft_driver_2() -> Result<(), String> {
fn test_perft_2() -> Result<(), String> {
init_attacks();
let mut game = from_fen(FEN_PERFT[1])?;
let (mut nodes, depth): (u64, u8) = (0, 1);
perft_driver(&mut game, &mut nodes, depth);
assert_eq!(nodes, 48);
let mut game = from_fen(FEN_PERFT[1])?;
let (mut nodes, depth): (u64, u8) = (0, 2);
perft_driver(&mut game, &mut nodes, depth);
assert_eq!(nodes, 2039);
// let mut game = from_fen(FEN_PERFT[1])?;
// let (mut nodes, depth): (u64, u8) = (0, 3);
// perft_driver(&mut game, &mut nodes, depth);
// assert_eq!(nodes, 97862);
// let mut game = from_fen(FEN_PERFT[1])?;
// let (mut nodes, depth): (u64, u8) = (0, 4);
// perft_driver(&mut game, &mut nodes, depth);
// assert_eq!(nodes, 4085603);
assert_eq!(perft(FEN_PERFT[1], 1)?, 48);
assert_eq!(perft(FEN_PERFT[1], 2)?, 2039);
assert_eq!(perft(FEN_PERFT[1], 3)?, 97862);
assert_eq!(perft(FEN_PERFT[1], 4)?, 4085603);
assert_eq!(perft(FEN_PERFT[1], 5)?, 193690690);
Ok(())
}
#[test]
fn test_perft_driver_3() -> Result<(), String> {
fn test_perft_3() -> Result<(), String> {
init_attacks();
// let mut game = from_fen(FEN_PERFT[2])?;
// let (mut nodes, depth): (u64, u8) = (0, 1);
// perft_driver(&mut game, &mut nodes, depth);
// assert_eq!(nodes, 14);
// let mut game = from_fen(FEN_PERFT[2])?;
// let (mut nodes, depth): (u64, u8) = (0, 2);
// perft_driver(&mut game, &mut nodes, depth);
// assert_eq!(nodes, 191);
// let mut game = from_fen(FEN_PERFT[2])?;
// let (mut nodes, depth): (u64, u8) = (0, 3);
// perft_driver(&mut game, &mut nodes, depth);
// assert_eq!(nodes, 2812);
// let mut game = from_fen(FEN_PERFT[2])?;
// let (mut nodes, depth): (u64, u8) = (0, 4);
// perft_driver(&mut game, &mut nodes, depth);
// assert_eq!(nodes, 43238);
assert_eq!(perft(FEN_PERFT[2], 1)?, 14);
assert_eq!(perft(FEN_PERFT[2], 2)?, 191);
assert_eq!(perft(FEN_PERFT[2], 3)?, 2812);
assert_eq!(perft(FEN_PERFT[2], 4)?, 43238);
assert_eq!(perft(FEN_PERFT[2], 5)?, 674624);
Ok(())
}
#[test]
fn test_perft_driver_4() -> Result<(), String> {
fn test_perft_4() -> Result<(), String> {
init_attacks();
let mut game = from_fen(FEN_PERFT[3])?;
let (mut nodes, depth): (u64, u8) = (0, 1);
perft_driver(&mut game, &mut nodes, depth);
assert_eq!(nodes , 6);
// let mut game = from_fen(FEN_PERFT[3])?;
// let (mut nodes, depth): (u64, u8) = (0, 2);
// perft_driver(&mut game, &mut nodes, depth);
// assert_eq!(nodes, 264);
// let mut game = from_fen(FEN_PERFT[3])?;
// let (mut nodes, depth): (u64, u8) = (0, 3);
// perft_driver(&mut game, &mut nodes, depth);
// assert_eq!(nodes, 9467);
// let mut game = from_fen(FEN_PERFT[3])?;
// let (mut nodes, depth): (u64, u8) = (0, 4);
// perft_driver(&mut game, &mut nodes, depth);
// assert_eq!(nodes, 422333);
assert_eq!(perft(FEN_PERFT[3], 1)?, 6);
assert_eq!(perft(FEN_PERFT[3], 2)?, 264);
assert_eq!(perft(FEN_PERFT[3], 3)?, 9467);
assert_eq!(perft(FEN_PERFT[3], 4)?, 422333);
assert_eq!(perft(FEN_PERFT[3], 5)?, 15833292);
Ok(())
}
#[test]
fn test_perft_driver_5() -> Result<(), String> {
fn test_perft_5() -> Result<(), String> {
init_attacks();
// let mut game = from_fen(FEN_PERFT[4])?;
// let (mut nodes, depth): (u64, u8) = (0, 1);
// perft_driver(&mut game, &mut nodes, depth);
// assert_eq!(nodes , 44);
// let mut game = from_fen(FEN_PERFT[4])?;
// let (mut nodes, depth): (u64, u8) = (0, 2);
// perft_driver(&mut game, &mut nodes, depth);
// assert_eq!(nodes, 1486);
// let mut game = from_fen(FEN_PERFT[4])?;
// let (mut nodes, depth): (u64, u8) = (0, 3);
// perft_driver(&mut game, &mut nodes, depth);
// assert_eq!(nodes, 62379);
// let mut game = from_fen(FEN_PERFT[4])?;
// let (mut nodes, depth): (u64, u8) = (0, 4);
// perft_driver(&mut game, &mut nodes, depth);
// assert_eq!(nodes, 2103487);
assert_eq!(perft(FEN_PERFT[4], 1)?, 44);
assert_eq!(perft(FEN_PERFT[4], 2)?, 1486);
assert_eq!(perft(FEN_PERFT[4], 3)?, 62379);
assert_eq!(perft(FEN_PERFT[4], 4)?, 2103487);
assert_eq!(perft(FEN_PERFT[4], 5)?, 89941194);
Ok(())
}
#[test]
fn test_perft_driver_6() -> Result<(), String> {
fn test_perft_6() -> Result<(), String> {
init_attacks();
let mut game = from_fen(FEN_PERFT[5])?;
let (mut nodes, depth): (u64, u8) = (0, 1);
perft_driver(&mut game, &mut nodes, depth);
assert_eq!(nodes , 46);
let mut game = from_fen(FEN_PERFT[5])?;
let (mut nodes, depth): (u64, u8) = (0, 2);
perft_driver(&mut game, &mut nodes, depth);
assert_eq!(nodes, 2079);
let mut game = from_fen(FEN_PERFT[5])?;
let (mut nodes, depth): (u64, u8) = (0, 3);
perft_driver(&mut game, &mut nodes, depth);
assert_eq!(nodes, 89890);
let mut game = from_fen(FEN_PERFT[5])?;
let (mut nodes, depth): (u64, u8) = (0, 4);
perft_driver(&mut game, &mut nodes, depth);
assert_eq!(nodes, 3894594);
assert_eq!(perft(FEN_PERFT[5], 1)?, 46);
assert_eq!(perft(FEN_PERFT[5], 2)?, 2079);
assert_eq!(perft(FEN_PERFT[5], 3)?, 89890);
assert_eq!(perft(FEN_PERFT[5], 4)?, 3894594);
assert_eq!(perft(FEN_PERFT[5], 5)?, 164075551);
Ok(())
}