Move castling move generation logic to movegen.rs and refactor related repeated code

This commit is contained in:
2024-06-14 21:46:37 +03:00
parent a0959bf6b9
commit 07af7294fa
4 changed files with 100 additions and 140 deletions

View File

@@ -5,7 +5,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::{CastlingRights, State};
use crate::game::State;
#[derive(Debug, PartialEq, Eq)]
pub struct Board {
@@ -128,82 +128,7 @@ impl Board {
Kind::Bishop => bishop_pseudo_moves(pieces, all_occupancies, own_occupancies),
Kind::Rook => rook_pseudo_moves(pieces, all_occupancies, own_occupancies),
Kind::Queen => queen_pseudo_moves(pieces, all_occupancies, own_occupancies),
Kind::King => king_pseudo_moves(
pieces,
all_occupancies,
own_occupancies,
self.castling_ability(color),
color,
),
}
}
fn castling_ability(&self, color: Color) -> CastlingRights {
match (
self.castling_from_fen(color),
self.castling_pseudo_safety(color),
) {
(CastlingRights::Both, _) => self.castling_pseudo_safety(color),
(CastlingRights::Short, CastlingRights::Short) => CastlingRights::Short,
(CastlingRights::Long, CastlingRights::Long) => CastlingRights::Long,
_ => CastlingRights::None,
}
}
const fn castling_from_fen(&self, color: Color) -> CastlingRights {
let castling_fen = self.state.get_castling_ability();
let black_king_and_queen = (castling_fen >> 2) & 0b11 == 0b11;
let black_king = (castling_fen >> 3) & 1 == 1;
let black_queen = (castling_fen >> 2) & 1 == 1;
let white_king_and_queen = castling_fen & 0b11 == 0b11;
let white_king = (castling_fen >> 1) & 1 == 1;
let white_queen = castling_fen & 1 == 1;
match color {
Color::White => match (white_king_and_queen, white_king, white_queen) {
(true, _, _) => CastlingRights::Both,
(_, true, _) => CastlingRights::Short,
(_, _, true) => CastlingRights::Long,
_ => CastlingRights::None,
},
Color::Black => match (black_king_and_queen, black_king, black_queen) {
(true, _, _) => CastlingRights::Both,
(_, true, _) => CastlingRights::Short,
(_, _, true) => CastlingRights::Long,
_ => CastlingRights::None,
},
}
}
fn castling_pseudo_safety(&self, color: Color) -> CastlingRights {
match color {
Color::White => {
if self.is_attacked(4, Color::Black) {
return CastlingRights::None;
}
let d1_attacked = self.is_attacked(3, Color::Black);
let f1_attacked = self.is_attacked(5, Color::Black);
match (d1_attacked, f1_attacked) {
(true, true) => CastlingRights::None,
(true, false) => CastlingRights::Short,
(false, true) => CastlingRights::Long,
(false, false) => CastlingRights::Both,
}
}
Color::Black => {
if self.is_attacked(60, Color::White) {
return CastlingRights::None;
}
let d8_attacked = self.is_attacked(59, Color::White);
let f8_attacked = self.is_attacked(61, Color::White);
match (d8_attacked, f8_attacked) {
(true, true) => CastlingRights::None,
(true, false) => CastlingRights::Short,
(false, true) => CastlingRights::Long,
(false, false) => CastlingRights::Both,
}
}
Kind::King => king_pseudo_moves(pieces, all_occupancies, own_occupancies, self, color),
}
}
}

View File

@@ -1,5 +1,5 @@
use crate::board::{Board, Color, Kind, Piece};
use crate::game::{Game, State};
use crate::game::{Castle, Game, State};
use String as FenError;
pub fn from_fen(fen: &str) -> Result<Game, FenError> {
@@ -78,17 +78,15 @@ fn side_to_move(side: &str) -> Result<Color, FenError> {
}
}
/// Return castling ability as a bitflag. LSb K, 2nd LSb Q, 3rd LSb k, 4th LSb q. Ignore 4 MSb's
fn castling_ability(castling: &str) -> Result<u8, FenError> {
let mut rights = 0b0;
fn castling_ability(castling: &str) -> Result<[Castle; 2], FenError> {
let mut bitflag = 0b0;
for c in castling.chars() {
match c {
'K' => rights |= 0b1,
'Q' => rights |= 0b10,
'k' => rights |= 0b100,
'q' => rights |= 0b1000,
'-' => rights |= 0b0,
'K' => bitflag |= 0b1000,
'Q' => bitflag |= 0b100,
'k' => bitflag |= 0b10,
'q' => bitflag |= 0b1,
'-' => break,
_ => {
return Err(FenError::from(
"Found invalid character while parsing in castling_ability",
@@ -96,7 +94,30 @@ fn castling_ability(castling: &str) -> Result<u8, FenError> {
}
};
}
Ok(rights)
let mut castling_ability: [Castle; 2] = [Castle::None, Castle::None];
let white_king_and_queen = bitflag & 0b11 == 0b11;
let white_king = (bitflag >> 1) & 1 == 1;
let white_queen = bitflag & 1 == 1;
castling_ability[0] = match (white_king_and_queen, white_king, white_queen) {
(true, _, _) => Castle::Both,
(_, true, _) => Castle::Short,
(_, _, true) => Castle::Long,
_ => Castle::None,
};
let black_king_and_queen = (bitflag >> 2) & 0b11 == 0b11;
let black_king = (bitflag >> 3) & 1 == 1;
let black_queen = (bitflag >> 2) & 1 == 1;
castling_ability[1] = match (black_king_and_queen, black_king, black_queen) {
(true, _, _) => Castle::Both,
(_, true, _) => Castle::Short,
(_, _, true) => Castle::Long,
_ => Castle::None,
};
Ok(castling_ability)
}
use std::collections::HashMap;

View File

@@ -34,7 +34,7 @@ impl Default for Game {
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub struct State {
side_to_move: Color,
castling_ability: u8,
castling_ability: [Castle; 2],
en_passant_target_square: Option<u8>,
halfmove_clock: u8,
fullmove_counter: u8,
@@ -44,7 +44,7 @@ impl State {
pub const fn new() -> Self {
Self {
side_to_move: Color::White,
castling_ability: 0b1111,
castling_ability: [Castle::Both, Castle::Both],
en_passant_target_square: None,
halfmove_clock: 0,
fullmove_counter: 1,
@@ -53,7 +53,7 @@ impl State {
pub const fn load_state(
side_to_move: Color,
castling_ability: u8,
castling_ability: [Castle; 2],
en_passant_target_square: Option<u8>,
halfmove_clock: u8,
fullmove_counter: u8,
@@ -71,8 +71,11 @@ impl State {
self.en_passant_target_square
}
pub const fn get_castling_ability(&self) -> u8 {
self.castling_ability
pub const fn get_castling_ability(&self, color: Color) -> Castle {
match color {
Color::White => self.castling_ability[0],
Color::Black => self.castling_ability[1],
}
}
}
@@ -81,7 +84,7 @@ use std::fmt;
impl fmt::Display for State {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f,
"side_to_move: {:?}\ncastling_ability: {:b}\nen_passant_target_square: {:b}\nhalfmove_clock: {}\nfullmove_counter: {}\n",
"side_to_move: {:?}\ncastling_ability: {:?}\nen_passant_target_square: {:b}\nhalfmove_clock: {}\nfullmove_counter: {}\n",
self.side_to_move, self.castling_ability, 1_u64 << self.en_passant_target_square.unwrap_or(0), self.halfmove_clock, self.fullmove_counter)
}
}
@@ -92,7 +95,8 @@ impl Default for State {
}
}
pub enum CastlingRights {
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum Castle {
Short,
Long,
Both,

View File

@@ -2,8 +2,8 @@ use crate::attack::{
get_bishop_attacks, get_king_attacks, get_knight_attacks, get_pawn_attacks, get_queen_attacks,
get_rook_attacks,
};
use crate::board::Color;
use crate::game::CastlingRights;
use crate::board::{Board, Color};
use crate::game::Castle;
use u64 as Bitboard;
const NOT_A_FILE: Bitboard = 0xfefefefefefefefe;
@@ -246,7 +246,7 @@ pub fn king_pseudo_moves(
king: Bitboard,
all_occupancies: Bitboard,
own_occupancies: Bitboard,
castling_rights: CastlingRights,
board: &Board,
color: Color,
) -> Vec<Move> {
let mut moves: Vec<Move> = vec![];
@@ -260,50 +260,60 @@ pub fn king_pseudo_moves(
attacks &= attacks - 1;
}
match color {
Color::White => match castling_rights {
CastlingRights::Both => {
if all_occupancies & 0x60 == 0 {
moves.push(Move::new(4, 6))
} else if all_occupancies & 0xe == 0 {
moves.push(Move::new(4, 2))
}
}
CastlingRights::Short => {
if all_occupancies & 0x60 == 0 {
moves.push(Move::new(4, 6))
}
}
CastlingRights::Long => {
if all_occupancies & 0xe == 0 {
moves.push(Move::new(4, 2))
}
}
CastlingRights::None => (),
},
Color::Black => match castling_rights {
CastlingRights::Both => {
if all_occupancies & 0x6000000000000000 == 0 {
moves.push(Move::new(60, 62))
} else if all_occupancies & 0xe00000000000000 == 0 {
moves.push(Move::new(60, 58))
}
}
CastlingRights::Short => {
if all_occupancies & 0x6000000000000000 == 0 {
moves.push(Move::new(60, 62))
}
}
CastlingRights::Long => {
if all_occupancies & 0xe00000000000000 == 0 {
moves.push(Move::new(60, 58))
}
}
CastlingRights::None => (),
},
moves.extend(king_castling_moves(board, color, all_occupancies));
moves
}
fn king_castling_moves(board: &Board, color: Color, all_occupancies: Bitboard) -> Vec<Move> {
let mut moves = vec![];
let (king_from, king_to_short, king_to_long, path_short, path_long) = match color {
Color::White => (4, 2, 6, 0x60_u64, 0xe_u64),
Color::Black => (60, 58, 62, 0x6000000000000000_u64, 0xe00000000000000_u64),
};
let mut add_move_if_empty_path = |path_mask, king_to| {
if all_occupancies & path_mask != 0 {
moves.push(Move::new(king_from, king_to))
}
};
match (
board.state.get_castling_ability(color),
king_and_adj_square_safety(board, color),
) {
(Castle::Both, Castle::Both) => {
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) => {
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)
}
_ => (),
};
moves
}
fn king_and_adj_square_safety(board: &Board, color: Color) -> Castle {
let (king_at, opponent_color) = match color {
Color::White => (3, Color::Black),
Color::Black => (60, Color::White),
};
if board.is_attacked(king_at, opponent_color) {
return Castle::None;
}
moves
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,
(false, false) => Castle::Both,
}
}
#[cfg(test)]