feat: make move forfeits castling rights

This commit is contained in:
dogeystamp 2024-10-01 21:13:06 -04:00
parent ca0c17cbbe
commit 8804c0e1c4

View File

@ -1,7 +1,7 @@
//! Move generation. //! Move generation.
use crate::fen::{FromFen, ToFen, START_POSITION}; use crate::fen::{FromFen, ToFen, START_POSITION};
use crate::{BoardState, Color, Square, BOARD_WIDTH}; use crate::{BoardState, Color, Piece, Square, BOARD_HEIGHT, BOARD_WIDTH, N_SQUARES};
use std::rc::Rc; use std::rc::Rc;
/// Game tree node. /// Game tree node.
@ -71,7 +71,7 @@ impl Move {
Move::Castle(data) => todo!(), Move::Castle(data) => todo!(),
Move::Normal(data) => { Move::Normal(data) => {
let pc_src = node.pos.get_piece(data.src).unwrap(); let pc_src = node.pos.get_piece(data.src).unwrap();
if matches!(pc_src.pc, crate::Piece::Pawn) { if matches!(pc_src.pc, Piece::Pawn) {
// pawn moves are irreversible // pawn moves are irreversible
node.pos.half_moves = 0; node.pos.half_moves = 0;
@ -94,6 +94,30 @@ impl Move {
node.pos.ep_square = None; node.pos.ep_square = None;
} }
let castle = &mut node.pos.castle.0[pc_src.col as usize];
// forfeit castling rights
if matches!(pc_src.pc, Piece::King) {
castle.k = false;
castle.q = false;
} else if matches!(pc_src.pc, Piece::Rook) {
match pc_src.col {
Color::White => {
if data.src == Square(0) {
castle.q = false;
} else if data.src == Square(BOARD_WIDTH - 1) {
castle.k = false;
};
}
Color::Black => {
if data.src == Square((BOARD_HEIGHT - 1) * BOARD_WIDTH) {
castle.q = false;
} else if data.src == Square(N_SQUARES - 1) {
castle.k = false;
};
}
}
}
if let Some(_pc_dest) = node.pos.get_piece(data.dest) { if let Some(_pc_dest) = node.pos.get_piece(data.dest) {
// captures are irreversible // captures are irreversible
node.pos.half_moves = 0; node.pos.half_moves = 0;
@ -114,17 +138,22 @@ mod tests {
use super::*; use super::*;
use crate::fen::START_POSITION; use crate::fen::START_POSITION;
/// Test that make move and unmake move for simple piece pushes/captures. /// Test that make move and unmake move work as expected.
/// ///
/// Also tests en passant target square. /// Ensure that:
/// - En passant target is appropriately set
/// - Castling rights are respected
/// - Half-moves since last irreversible move counter is maintained
#[test] #[test]
fn test_normal_move() { fn test_make_unmake() {
let start_pos = START_POSITION;
// (src, dest, expected fen)
// FENs made with https://lichess.org/analysis // FENs made with https://lichess.org/analysis
// En-passant target square is manually added, since Lichess doesn't have it when // En-passant target square is manually added, since Lichess doesn't have it when
// en-passant is not legal. // en-passant is not legal.
let moves = [ let test_cases = [
(
START_POSITION,
vec![
// (src, dest, expected fen)
( (
"e2", "e2",
"e4", "e4",
@ -175,11 +204,34 @@ mod tests {
"e5", "e5",
"rnb1k2r/pppp1ppp/5n2/2b1q3/2B1P3/8/PPPPQPPP/RNB1K2R w KQkq - 0 6", "rnb1k2r/pppp1ppp/5n2/2b1q3/2B1P3/8/PPPPQPPP/RNB1K2R w KQkq - 0 6",
), ),
],
),
// castling rights test (kings)
(
"rnbqkbnr/pppp1ppp/8/4p3/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 0 2",
vec![
(
"e1",
"e2",
"rnbqkbnr/pppp1ppp/8/4p3/4P3/8/PPPPKPPP/RNBQ1BNR b kq - 1 2",
),
(
"e8",
"e7",
"rnbq1bnr/ppppkppp/8/4p3/4P3/8/PPPPKPPP/RNBQ1BNR w - - 2 3",
),
],
),
]; ];
for (i, test_case) in test_cases.iter().enumerate() {
let (start_pos, moves) = test_case;
// make move // make move
let mut node = Node::default(); println!("Starting test case {i}, make move.");
let mut node = Node {pos: BoardState::from_fen(start_pos.to_string()).unwrap(), prev: None};
for (src, dest, expect_fen) in moves { for (src, dest, expect_fen) in moves {
println!("Moving {src} to {dest}.");
let idx_src = Square::from_algebraic(src.to_string()).unwrap(); let idx_src = Square::from_algebraic(src.to_string()).unwrap();
let idx_dest = Square::from_algebraic(dest.to_string()).unwrap(); let idx_dest = Square::from_algebraic(dest.to_string()).unwrap();
let mv = Move::Normal(MoveData { let mv = Move::Normal(MoveData {
@ -191,13 +243,15 @@ mod tests {
} }
// unmake move // unmake move
println!("Starting test case {i}, unmake move.");
let mut cur_node = Rc::new(node.clone()); let mut cur_node = Rc::new(node.clone());
for (_, _, expect_fen) in moves.iter().rev().chain([("", "", START_POSITION)].iter()) { for (_, _, expect_fen) in moves.iter().rev().chain([("", "", *start_pos)].iter()) {
println!("{}", expect_fen); println!("{}", expect_fen);
assert_eq!(*cur_node.pos.to_fen(), expect_fen.to_string()); assert_eq!(*cur_node.pos.to_fen(), expect_fen.to_string());
if *expect_fen != START_POSITION { if *expect_fen != *start_pos {
cur_node = cur_node.prev.clone().unwrap(); cur_node = cur_node.prev.clone().unwrap();
} }
} }
} }
}
} }