From d30d966d732440664adb7f6b58fc236bc68306fb Mon Sep 17 00:00:00 2001 From: Matthew Todd Date: Thu, 9 Jul 2026 00:00:34 +0100 Subject: [PATCH 1/2] fix: reset halfmove counter on non-pawn captures --- src/chess/position.rs | 53 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 51 insertions(+), 2 deletions(-) diff --git a/src/chess/position.rs b/src/chess/position.rs index 4aabfb2..17e584f 100644 --- a/src/chess/position.rs +++ b/src/chess/position.rs @@ -161,6 +161,7 @@ impl Position { self.remove_piecetype(self.stm, pt, from); // capture piece + let mut is_capture = false; if mv.mtype() != MoveType::Castle { let captured = self.piece_at(to); if captured != Piece::none() { @@ -171,7 +172,7 @@ impl Position { self.update_castling_rights_color(!self.stm, from, to); } - self.halfm = 0; + is_capture = true; } } @@ -233,7 +234,7 @@ impl Position { // update state // Update halfmove clock - if pt == PieceType::Pawn { + if pt == PieceType::Pawn || is_capture { self.halfm = 0; } else { self.halfm += 1; @@ -631,4 +632,52 @@ mod tests { let pos = Position::new(); assert_eq!(pos, Position::from_fen(STARTPOS).unwrap()); } + + #[test] + fn test_halfm_reset() { + let fen = "r1bqkb1r/pppp1ppp/2n2n2/1B2p3/4P3/5N2/PPPP1PPP/RNBQK2R w KQkq - 4 4"; + + let pos = Position::from_fen(fen).unwrap(); + let mv = Move::new( + Square::from_string("b5").unwrap(), + Square::from_string("c6").unwrap(), + MoveType::Normal, + Piece::NONE, + ); + let pos = pos.after_move(mv); + assert_eq!(0, pos.rule50_counter()); + + let pos = Position::from_fen(fen).unwrap(); + let mv = Move::new( + Square::from_string("d2").unwrap(), + Square::from_string("d3").unwrap(), + MoveType::Normal, + Piece::NONE, + ); + let pos = pos.after_move(mv); + assert_eq!(0, pos.rule50_counter()); + + let pos = Position::from_fen(fen).unwrap(); + let mv = Move::new( + Square::from_string("b1").unwrap(), + Square::from_string("c3").unwrap(), + MoveType::Normal, + Piece::NONE, + ); + let pos = pos.after_move(mv); + assert_eq!(5, pos.rule50_counter()); + + let pos = Position::from_fen( + "r1bqk2r/pppp1ppp/2n2n2/1Bb1p3/4P3/2N2N2/PPPP1PPP/R1BQK2R w KQkq - 6 5", + ) + .unwrap(); + let mv = Move::new( + Square::from_string("e1").unwrap(), + Square::from_string("h1").unwrap(), + MoveType::Castle, + Piece::NONE, + ); + let pos = pos.after_move(mv); + assert_eq!(7, pos.rule50_counter()); + } } From 22bdde0a411a357e640d1e00ebb906558a587563 Mon Sep 17 00:00:00 2001 From: Matthew Todd Date: Thu, 9 Jul 2026 12:51:56 +0100 Subject: [PATCH 2/2] simplify by re-ordering the halfm reset checks --- src/chess/position.rs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/chess/position.rs b/src/chess/position.rs index 17e584f..2e12311 100644 --- a/src/chess/position.rs +++ b/src/chess/position.rs @@ -160,8 +160,14 @@ impl Position { // clear piece from start self.remove_piecetype(self.stm, pt, from); + // Update halfmove clock + if pt == PieceType::Pawn { + self.halfm = 0; + } else { + self.halfm += 1; + } + // capture piece - let mut is_capture = false; if mv.mtype() != MoveType::Castle { let captured = self.piece_at(to); if captured != Piece::none() { @@ -172,7 +178,7 @@ impl Position { self.update_castling_rights_color(!self.stm, from, to); } - is_capture = true; + self.halfm = 0; } } @@ -233,13 +239,6 @@ impl Position { // update state - // Update halfmove clock - if pt == PieceType::Pawn || is_capture { - self.halfm = 0; - } else { - self.halfm += 1; - } - // Update fullmove number if self.stm == Color::Black { self.fullm += 1;