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) {

View File

@@ -58,6 +58,7 @@ impl fmt::Display for Response {
}
}
#[derive(Debug)]
struct UciParameters {
movetime: Option<usize>,
depth: Option<u8>,
@@ -109,19 +110,23 @@ pub fn uci_position(position: &mut SplitWhitespace) -> Result<Game, String> {
}
for mv_str in position {
let mv = Move::parse_from_str(mv_str)?;
let mv = Move::parse_from_str(&game.board, mv_str)?;
game.board.make_move(&mv);
}
Ok(game)
}
const MAX_DEPTH: u8 = 5;
const MAX_DEPTH: u8 = 3;
pub fn uci_go(go: &mut SplitWhitespace, game: &mut Game) -> Result<Move, String> {
let mut params = UciParameters::new();
while let Some(subcommand) = go.next() {
match subcommand {
// TODO: Add new commands
"wtime" => (),
"btime" => (),
"movestogo" => (),
"depth" => {
let depth_str = go.next().ok_or("Expected depth value")?;
let depth = depth_str.parse::<u8>().map_err(|_| "Invalid depth value")?;
@@ -161,6 +166,7 @@ pub fn uci_loop<R: BufRead, W: Write>(input: R, mut output: W) -> Result<(), Str
Command::UciNewGame => Response::Info("Clear cache".to_string()),
Command::Position => {
params.add_game(uci_position(&mut parts)?);
// dbg!(&params);
Response::Info("Initialized position".to_string())
}
Command::Go => {
@@ -237,7 +243,7 @@ mod tests {
#[test]
fn test_uci_loop() -> Result<(), String> {
init_attacks();
let commands = b"uci\n\
let commands = "uci\n\
ucinewgame\n\
position fen 8/8/8/8/8/4q1k1/8/5K2 b - - 0 1\n\
go\n\
@@ -259,4 +265,30 @@ mod tests {
Ok(())
}
#[test]
fn test_cute_chess_bug() -> Result<(), String> {
init_attacks();
let commands = "uci\n\
ucinewgame\n\
position startpos moves b1c3 g7g6 d2d4 f8g7 e2e4 f7f5 e4f5 e7e6 f5g6 h7g6 g1f3 b8c6 f1c4 d7d6 c1e3 e6e5 d4d5 c6e7 c3b5 c7c6 d5c6 b7c6 b5d6 d8d6 d1d6 c8d7 f3e5 d7e6 d6e6 g8f6 e5g6 f6g8 e6f7 e8d7 f7g7 g8f6 g7e7 d7c8 g6h8 c8b8 e7d8 b8b7 d8f6 b7c7 e3a7 a8a7 h8g6 c7b6 g6e5 a7c7 f6c6 b6a7
go\n\
quit";
let input = Cursor::new(commands);
let mut output: Vec<_> = vec![];
uci_loop(input, &mut output)?;
let expected_response = "id name ippos\n\
id author stefiosif\n\
uciok\n\
Clear cache\n\
Initialized position\n\
bestmove c6c7\n";
let actual_response = String::from_utf8(output).expect("Invalid UTF-8 in output");
assert_eq!(expected_response, actual_response);
Ok(())
}
}

View File

@@ -1,11 +1,9 @@
use core::fmt;
use std::str::Chars;
use crate::board::{
bitboard::{have_common_bit, square_to_bitboard},
board::{Board, Color, Piece, PieceType},
history::MoveParameters,
square::{coords_to_square, square_to_algebraic, Square},
state::{Castle, State},
board::{Board, PieceType},
square::{coords_to_square, square_to_algebraic},
};
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
@@ -85,7 +83,17 @@ impl Move {
}
}
pub fn parse_from_str(mv: &str) -> Result<Self, String> {
pub fn parse_promotion(mut mv_chars: Chars<'_>) -> Result<Promote, String> {
match mv_chars.next().unwrap() {
'n' => Ok(Promote::Knight),
'b' => Ok(Promote::Bishop),
'r' => Ok(Promote::Rook),
'q' => Ok(Promote::Queen),
_ => Err("Unrecongisable character in promotion piece index".to_string()),
}
}
pub fn parse_from_str(board: &Board, mv: &str) -> Result<Self, String> {
if mv.len() != 4 && mv.len() != 5 {
return Err("Invalid move characters length".to_string());
}
@@ -99,19 +107,52 @@ impl Move {
let src = coords_to_square(src_rank, src_file);
let dst = coords_to_square(dst_rank, dst_file);
if mv.len() == 5 {
let promotion_move = match mv_chars.next().unwrap() {
'n' => Self::new_with_type(src, dst, MoveType::Promotion(Promote::Knight)),
'b' => Self::new_with_type(src, dst, MoveType::Promotion(Promote::Bishop)),
'r' => Self::new_with_type(src, dst, MoveType::Promotion(Promote::Rook)),
'q' => Self::new_with_type(src, dst, MoveType::Promotion(Promote::Queen)),
_ => return Err("Unrecongisable character in promotion piece index".to_string()),
let promote_into = if mv.len() == 5 {
Some(Self::parse_promotion(mv_chars)?)
} else {
None
};
return Ok(promotion_move);
Ok(Self::build_with_type(board, src, dst, promote_into))
}
Ok(Self::new(src, dst))
fn build_with_type(
board: &Board,
src: usize,
dst: usize,
promote_into: Option<Promote>,
) -> Self {
let moving = board.piece_type_at(src);
let captured = board.piece_type_at(dst);
match (captured, promote_into) {
(Some(_), None) => return Self::new_with_type(src, dst, MoveType::Capture),
(None, Some(promote_into)) => {
return Self::new_with_type(src, dst, MoveType::Promotion(promote_into))
}
(Some(_), Some(promote_into)) => {
return Self::new_with_type(src, dst, MoveType::PromotionCapture(promote_into))
}
_ => (),
}
if moving == Some(PieceType::Pawn) {
if src.abs_diff(dst) == 16 {
return Self::new_with_type(src, dst, MoveType::DoublePush);
}
if captured.is_none() && src.abs_diff(dst) != 8 {
return Self::new_with_type(src, dst, MoveType::EnPassant);
}
}
if moving == Some(PieceType::King) {
if src.abs_diff(dst) == 2 {
return Self::new_with_type(src, dst, MoveType::Castle);
}
}
Self::new(src, dst)
}
pub fn parse_into_str(&self) -> String {
@@ -119,306 +160,104 @@ impl Move {
}
}
impl Board {
pub fn make_move(&mut self, mv: &Move) {
let color = self.state.current_player();
let (own_pieces, opponent_pieces, en_passant_square) = match color {
Color::White => (
&mut self.white_pieces,
&mut self.black_pieces,
Some(mv.src + 8),
),
Color::Black => (
&mut self.black_pieces,
&mut self.white_pieces,
Some(mv.src.saturating_sub(8)),
),
};
let pawn_move = Self::is_pawn_move(mv.src, &own_pieces[PieceType::Pawn]);
Self::update_game_state(&mut self.state, mv, color, pawn_move);
match &mv.move_type {
MoveType::Quiet => {
Self::move_piece(mv.src, mv.dst, own_pieces);
}
MoveType::Capture => {
Self::move_piece(mv.src, mv.dst, own_pieces);
Self::remove_piece(mv.dst, opponent_pieces);
}
MoveType::EnPassant => {
Self::move_piece(mv.src, mv.dst, own_pieces);
Self::remove_pawn_enpassant(mv.dst, opponent_pieces, color);
}
MoveType::DoublePush => {
Self::move_piece(mv.src, mv.dst, own_pieces);
self.state.set_en_passant_square(en_passant_square);
}
MoveType::Promotion(promote) => {
Self::remove_piece(mv.src, own_pieces);
Self::promote_piece(mv.dst, own_pieces, *promote);
}
MoveType::PromotionCapture(promote) => {
Self::remove_piece(mv.src, own_pieces);
Self::remove_piece(mv.dst, opponent_pieces);
Self::promote_piece(mv.dst, own_pieces, *promote);
}
MoveType::Castle => {
Self::move_piece(mv.src, mv.dst, own_pieces);
Self::move_rook_castle(mv.dst, own_pieces);
self.state.set_castling_ability(color, Castle::None);
}
}
}
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();
let (own_pieces, opponent_pieces) = match color_before_move {
Color::White => (&mut self.white_pieces, &mut self.black_pieces),
Color::Black => (&mut self.black_pieces, &mut self.white_pieces),
};
match mv.move_type {
MoveType::Quiet | MoveType::DoublePush => {
Self::move_piece(mv.dst, mv.src, own_pieces);
}
MoveType::Capture | MoveType::Promotion(_) | MoveType::PromotionCapture(_) => {
if let (Some(captured_piece_type), Some(promoted_piece_type)) = (
move_parameters.captured_piece,
move_parameters.promoted_piece,
) {
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, own_pieces);
opponent_pieces[captured_piece_type].bitboard |= square_to_bitboard(mv.dst);
} else if let Some(promoted_piece_type) = move_parameters.promoted_piece {
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, own_pieces);
let enemy_pawn_square = match color_before_move {
Color::White => mv.dst - 8,
Color::Black => mv.dst + 8,
};
opponent_pieces[PieceType::Pawn].bitboard |= square_to_bitboard(enemy_pawn_square);
}
MoveType::Castle => {
Self::move_piece(mv.dst, mv.src, own_pieces);
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,
};
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) {
state.set_en_passant_square(None);
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(src: usize, dst: usize, pieces: &mut [Piece; 6]) {
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 move_rook_castle(king_dst: usize, pieces: &mut [Piece; 6]) {
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, pieces);
}
fn promote_piece(square: usize, pieces: &mut [Piece; 6], promote: Promote) {
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_piece(square: usize, pieces: &mut [Piece; 6]) {
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_pawn_enpassant(square: usize, pieces: &mut [Piece; 6], color: Color) {
let piece_to_remove = match color {
Color::White => square - 8,
Color::Black => square + 8,
};
Self::remove_piece(piece_to_remove, pieces);
}
const fn is_pawn_move(square: usize, pawns: &Piece) -> bool {
have_common_bit(square_to_bitboard(square), pawns.bitboard)
}
}
#[cfg(test)]
mod tests {
use crate::board::{fen::from_fen, square::Square};
use crate::board::fen::from_fen;
use crate::board::square::Square;
use crate::movegen::r#move::{Move, MoveType, Promote};
const FEN_QUIET: [&str; 2] = [
"r3k2r/2p1p1qp/2npb3/1p3p2/p3P1pP/P1PB1Q2/1P1PNPP1/R3K2R w KQkq - 0 1",
"r3k2r/2p1p1qp/2npb3/1p3p2/pP2P1pP/P1PB1Q2/3PNPP1/R3K2R b KQkq b3 0 1",
];
const FEN: &'static str = "1r2k2r/2P1pq1p/2npb3/1p3ppP/p3P3/P2B1Q2/1P1PNPP1/R3K2R w KQk g6 0 1";
#[test]
fn test_make_move_quiet() -> Result<(), String> {
let mut game = from_fen(FEN_QUIET[0])?;
let mv = Move::new_with_type(9, 25, MoveType::DoublePush);
game.board.make_move(&mv);
assert_eq!(game, from_fen(FEN_QUIET[1])?);
fn test_parse_from_str_quiet() -> Result<(), String> {
let game = from_fen(FEN)?;
let mv_str = "b2b3";
let actual = Move::parse_from_str(&game.board, &mv_str)?;
let expected = Move::new(Square::B2, Square::B3);
assert_eq!(expected, actual);
Ok(())
}
const FEN_CAPTURE: [&str; 2] = [
"r3k2r/2p1p1qp/2npb3/1p3p2/p3P1pP/P1PB1Q2/1P1PNPP1/R3K2R w KQkq - 0 1",
"r3k2r/2p1p1qp/2npb3/1p3Q2/p3P1pP/P1PB4/1P1PNPP1/R3K2R b KQkq - 0 1",
];
#[test]
fn test_make_move_capture() -> Result<(), String> {
let mut game = from_fen(FEN_CAPTURE[0])?;
let mv = Move::new_with_type(21, 37, MoveType::Capture);
game.board.make_move(&mv);
assert_eq!(game, from_fen(FEN_CAPTURE[1])?);
fn test_parse_from_str_capture() -> Result<(), String> {
let game = from_fen(FEN)?;
let mv_str = "d3b5";
let actual = Move::parse_from_str(&game.board, &mv_str)?;
let expected = Move::new_with_type(Square::D3, Square::B5, MoveType::Capture);
assert_eq!(expected, actual);
Ok(())
}
const FEN_EN_PASSANT: [&str; 2] = [
"r3k2r/2p1p1qp/2npb3/1p3p2/p3P1pP/P1PB1Q2/1P1PNPP1/R3K2R b KQkq h3 0 1",
"r3k2r/2p1p1qp/2npb3/1p3p2/p3P3/P1PB1Q1p/1P1PNPP1/R3K2R w KQkq - 0 2",
];
#[test]
fn test_make_move_en_passant() -> Result<(), String> {
let mut game = from_fen(FEN_EN_PASSANT[0])?;
let mv = Move::new_with_type(30, 23, MoveType::EnPassant);
game.board.make_move(&mv);
assert_eq!(game, from_fen(FEN_EN_PASSANT[1])?);
fn test_parse_from_str_en_passant() -> Result<(), String> {
let game = from_fen(FEN)?;
let mv_str = "h5g6";
let actual = Move::parse_from_str(&game.board, &mv_str)?;
let expected = Move::new_with_type(Square::H5, Square::G6, MoveType::EnPassant);
assert_eq!(expected, actual);
Ok(())
}
const FEN_DOUBLE_PUSH: [&str; 2] = [
"rnbqkbnr/p1pppppp/8/8/1p5P/8/PPPPPPP1/RNBQKBNR w KQkq - 0 1",
"rnbqkbnr/p1pppppp/8/8/1pP4P/8/PP1PPPP1/RNBQKBNR b KQkq c3 0 1",
];
#[test]
fn test_make_move_double_push() -> Result<(), String> {
let mut game = from_fen(FEN_DOUBLE_PUSH[0])?;
let mv = Move::new_with_type(10, 26, MoveType::DoublePush);
game.board.make_move(&mv);
assert_eq!(game, from_fen(FEN_DOUBLE_PUSH[1])?);
Ok(())
}
fn test_parse_from_str_double_push() -> Result<(), String> {
let game = from_fen(FEN)?;
let mv_str = "b2b4";
let actual = Move::parse_from_str(&game.board, &mv_str)?;
let expected = Move::new_with_type(Square::B2, Square::B4, MoveType::DoublePush);
const FEN_PROMOTION: [&str; 2] = [
"8/6P1/4n2b/1p6/1Kp5/6n1/4b3/1k6 w - - 0 1",
"6Q1/8/4n2b/1p6/1Kp5/6n1/4b3/1k6 b - - 0 1",
];
#[test]
fn test_make_move_promotion() -> Result<(), String> {
let mut game = from_fen(FEN_PROMOTION[0])?;
let mv = Move::new_with_type(54, 62, MoveType::Promotion(Promote::Queen));
game.board.make_move(&mv);
assert_eq!(game, from_fen(FEN_PROMOTION[1])?);
assert_eq!(expected, actual);
Ok(())
}
const FEN_PROMOTION_CAPTURE: [&str; 2] = [
"5n2/6P1/7b/1p6/1Kp5/6n1/4b3/1k6 w - - 0 1",
"5Q2/8/7b/1p6/1Kp5/6n1/4b3/1k6 b - - 0 1",
];
#[test]
fn test_make_move_promotion_capture() -> Result<(), String> {
let mut game = from_fen(FEN_PROMOTION_CAPTURE[0])?;
let mv = Move::new_with_type(54, 61, MoveType::PromotionCapture(Promote::Queen));
game.board.make_move(&mv);
assert_eq!(game, from_fen(FEN_PROMOTION_CAPTURE[1])?);
Ok(())
}
const FEN_CASTLE: [&str; 2] = [
"r3k2r/2p1p1qp/2npb3/1p3p2/p3P1pP/P1PB1Q2/1P1PNPP1/R3K2R w KQkq - 0 1",
"r3k2r/2p1p1qp/2npb3/1p3p2/p3P1pP/P1PB1Q2/1P1PNPP1/R4RK1 b kq - 1 1",
];
#[test]
fn test_make_move_castle() -> Result<(), String> {
let mut game = from_fen(FEN_CASTLE[0])?;
let mv = Move::new_with_type(4, 6, MoveType::Castle);
game.board.make_move(&mv);
assert_eq!(game, from_fen(FEN_CASTLE[1])?);
Ok(())
}
#[test]
fn test_parse_from_str() -> Result<(), String> {
let mv_str = "a1a8";
let actual = Move::parse_from_str(&mv_str)?;
let expected = Move::new(Square::A1, Square::A8);
assert_eq!(actual, expected);
let mv_str = "b7b8q";
let actual = Move::parse_from_str(&mv_str)?;
fn test_parse_from_str_promotion() -> Result<(), String> {
let game = from_fen(FEN)?;
let mv_str = "c7c8q";
let actual = Move::parse_from_str(&game.board, &mv_str)?;
let expected =
Move::new_with_type(Square::B7, Square::B8, MoveType::Promotion(Promote::Queen));
assert_eq!(actual, expected);
Move::new_with_type(Square::C7, Square::C8, MoveType::Promotion(Promote::Queen));
assert_eq!(expected, actual);
Ok(())
}
#[test]
fn test_parse_from_str_promotion_capture() -> Result<(), String> {
let game = from_fen(FEN)?;
let mv_str = "c7b8q";
let actual = Move::parse_from_str(&game.board, &mv_str)?;
let expected = Move::new_with_type(
Square::C7,
Square::B8,
MoveType::PromotionCapture(Promote::Queen),
);
assert_eq!(expected, actual);
Ok(())
}
#[test]
#[should_panic(expected = "Invalid move characters length")]
fn test_parse_from_str_panic() -> () {
Move::parse_from_str(&"a7a8qk").unwrap();
fn test_parse_from_str_panic_1() {
let game = from_fen(FEN);
let mv_str = "c7c8qq";
Move::parse_from_str(&game.unwrap().board, &mv_str).unwrap();
}
#[test]
#[should_panic(expected = "Unrecongisable character in promotion piece index")]
fn test_parse_from_str_panic_2() -> () {
let game = from_fen(FEN);
let mv_str = "c7c8w";
Move::parse_from_str(&game.unwrap().board, &mv_str).unwrap();
}
}

View File

@@ -466,12 +466,15 @@ mod tests {
Ok(())
}
const FEN_BISHOP_MOVES: &str = "rn1qk1nr/pbppbppp/1p6/8/2B1Pp2/3P1N2/PPB3PP/RN1QK2R w - - 0 1";
const FEN_BISHOP_MOVES: [&str; 2] = [
"rn1qk1nr/pbppbppp/1p6/8/2B1Pp2/3P1N2/PPB3PP/RN1QK2R w - - 0 1",
"r5kr/ppp3pp/2n2n2/3pp3/P7/b1PP1Nq1/1PK1P3/2B1N3 b kq - 0 1",
];
#[test]
fn test_bishop_pseudo_moves() -> Result<(), String> {
init_attacks();
let new_game = from_fen(FEN_BISHOP_MOVES)?;
let new_game = from_fen(FEN_BISHOP_MOVES[0])?;
let expected = vec![
Move::new(10, 17),
Move::new(10, 24),