diff --git a/src/analysis.rs b/src/analysis.rs index 0c512b4..b0cbdaa 100644 --- a/src/analysis.rs +++ b/src/analysis.rs @@ -110,7 +110,7 @@ impl Analyzer { self.start_time = Some(Instant::now()); self.current_per_second_timer = Some(Instant::now()); - let (max_score, best_move) = self.negamax(&self.node.clone(), MIN_F32, MAX_F32, 0); + let (max_score, best_move) = self.negamax(&mut self.node.clone(), MIN_F32, MAX_F32, 0); if let Some(m) = best_move { let log_str = format!("Best move {} evaluated {}", m.to_uci_string(), max_score); @@ -158,7 +158,7 @@ impl Analyzer { /// lower score bound and `beta` the upper bound. fn negamax( &mut self, - node: &Node, + node: &mut Node, alpha: f32, beta: f32, depth: u32, @@ -185,18 +185,18 @@ impl Analyzer { } // Get negamax for playable moves. - let moves = node.get_player_moves(); + let mut moves = node.get_player_moves(); let mut alpha = alpha; let mut best_score = MIN_F32; let mut best_move = None; - for m in moves { - let mut sub_node = node.clone(); - sub_node.apply_move(&m); - let result = self.negamax(&sub_node, -beta, -alpha, depth + 1); + for m in &mut moves { + node.apply_move(m); + let result = self.negamax(node, -beta, -alpha, depth + 1); + node.unmake_move(m); let score = -result.0; if score > best_score { best_score = score; - best_move = Some(m); + best_move = Some(m.to_owned()); } if best_score > alpha { alpha = best_score; diff --git a/src/board.rs b/src/board.rs index e95cdae..9fe4f5b 100644 --- a/src/board.rs +++ b/src/board.rs @@ -315,6 +315,14 @@ impl Board { self.set_square(dest, source_color, source_piece); } + /// Change the piece type at square. + #[inline] + pub fn set_piece(&mut self, square: Square, from_piece: Piece, to_piece: Piece) { + let bp = bit_pos(square); + self.pieces[from_piece] &= !bp; + self.pieces[to_piece] |= bp; + } + /// Find position of this king. pub fn find_king(&self, color: Color) -> Option { let king_bb = self.colors[color] & self.pieces[KING]; @@ -622,6 +630,14 @@ mod tests { assert_eq!(b.get_piece_on(E8), KING); } + #[test] + fn test_set_piece() { + let mut b = Board::new(); + b.set_piece(E1, KING, QUEEN); + assert_eq!(b.get_color_on(E1), WHITE); + assert_eq!(b.get_piece_on(E1), QUEEN); + } + #[test] fn test_find_king() { let b = Board::new_empty(); diff --git a/src/engine.rs b/src/engine.rs index 5362615..8cb3738 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -181,13 +181,8 @@ impl Engine { } /// Apply a series of moves to the current node. - fn apply_moves(&mut self, moves: &Vec) { - moves.iter().for_each(|m| self.apply_move(m)); - } - - /// Apply a move to the current node. - fn apply_move(&mut self, m: &Move) { - m.apply_to(&mut self.node.board, &mut self.node.game_state); + fn apply_moves(&mut self, moves: &mut Vec) { + moves.iter_mut().for_each(|m| m.apply_to(&mut self.node.board, &mut self.node.game_state)); } /// Start working on board, returning the best move found. @@ -234,7 +229,7 @@ impl Engine { self.apply_fen(&fen); }, uci::PositionArgs::Moves(moves) => { - self.apply_moves(&moves); + self.apply_moves(&mut moves.clone()); } } } diff --git a/src/movement.rs b/src/movement.rs index 86cccfa..44e6548 100644 --- a/src/movement.rs +++ b/src/movement.rs @@ -9,9 +9,16 @@ use crate::rules::GameState; /// A movement, with before/after positions and optional promotion. #[derive(Clone, PartialEq)] pub struct Move { + /// Square from which a piece moves. pub source: Square, + /// Square to which a piece moves. pub dest: Square, + /// Promotion piece for pawns reaching the last rank. pub promotion: Option, + /// Captured piece, if any. + pub capture: Option, + /// Castle options before the move. This is set when the move is first applied. + pub old_castles: Castle, } impl fmt::Debug for Move { @@ -26,16 +33,17 @@ pub const UCI_NULL_MOVE_STR: &str = "0000"; impl Move { /// Build a move from `source` to `dest`, no promotion. pub const fn new(source: Square, dest: Square) -> Move { - Move { source, dest, promotion: None } + Move { source, dest, promotion: None, capture: None, old_castles: 0 } } /// Build a move from `source` to `dest`, with a promotion. pub const fn new_promotion(source: Square, dest: Square, promotion: Piece) -> Move { - Move { source, dest, promotion: Some(promotion) } + Move { source, dest, promotion: Some(promotion), capture: None, old_castles: 0 } } /// Apply this move to `board` and `game_state`. - pub fn apply_to(&self, board: &mut Board, game_state: &mut GameState) { + pub fn apply_to(&mut self, board: &mut Board, game_state: &mut GameState) { + self.old_castles = game_state.castling; // If a king moves, remove it from castling options. if self.source == E1 { game_state.castling &= !CASTLE_WH_MASK; } else if self.source == E8 { game_state.castling &= !CASTLE_BL_MASK; } @@ -50,7 +58,7 @@ impl Move { } /// Apply the move into `board`. - pub fn apply_to_board(&self, board: &mut Board) { + pub fn apply_to_board(&mut self, board: &mut Board) { let piece = board.get_piece_on(self.source); // If a king is castling, apply special move. if piece == KING { @@ -65,13 +73,38 @@ impl Move { return } } + if !board.is_empty(self.dest) { + self.capture = Some(board.get_piece_on(self.dest)); + } board.move_square(self.source, self.dest); if let Some(piece) = self.promotion { - let color = board.get_color_on(self.dest); - board.set_square(self.dest, color, piece); + board.set_piece(self.dest, PAWN, piece); } } + /// Unmake a move. + pub fn unmake(&self, board: &mut Board, game_state: &mut GameState) { + if let Some(castle) = self.get_castle() { + match castle { + CASTLE_WH_K => { board.move_square(G1, E1); board.move_square(F1, H1); } + CASTLE_WH_Q => { board.move_square(C1, E1); board.move_square(D1, A1); } + CASTLE_BL_K => { board.move_square(G8, E8); board.move_square(F8, H8); } + CASTLE_BL_Q => { board.move_square(C8, E8); board.move_square(D8, A8); } + _ => { panic!("Invalid castle.") } + } + } else { + board.move_square(self.dest, self.source); + if let Some(piece) = self.promotion { + board.set_piece(self.source, piece, PAWN) + } + if let Some(piece) = self.capture { + board.set_square(self.dest, opposite(game_state.color), piece); + } + } + game_state.castling = self.old_castles; + game_state.color = opposite(game_state.color); + } + /// Get the corresponding castling flag for this move. pub fn get_castle(&self) -> Option { match (self.source, self.dest) { @@ -109,7 +142,9 @@ impl Move { }) } else { None - } + }, + capture: None, + old_castles: 0, } } @@ -148,16 +183,20 @@ mod tests { b.set_square(D4, WHITE, KNIGHT); b.set_square(F4, BLACK, KNIGHT); // Move white knight in a position attacked by black knight. - Move::new(D4, E6).apply_to_board(&mut b); + let mut m = Move::new(D4, E6); + m.apply_to_board(&mut b); assert!(b.is_empty(D4)); assert_eq!(b.get_color_on(E6), WHITE); assert_eq!(b.get_piece_on(E6), KNIGHT); assert_eq!(count_bits(b.combined()), 2); + assert!(m.capture.is_none()); // Sack it with black knight - Move::new(F4, E6).apply_to_board(&mut b); + let mut m = Move::new(F4, E6); + m.apply_to_board(&mut b); assert_eq!(b.get_color_on(E6), BLACK); assert_eq!(b.get_piece_on(E6), KNIGHT); assert_eq!(count_bits(b.combined()), 1); + assert_eq!(m.capture.unwrap(), KNIGHT); } #[test] @@ -198,6 +237,20 @@ mod tests { assert_eq!(gs.castling, 0); } + #[test] + fn test_unmake() { + let mut b = Board::new_empty(); + let mut gs = GameState::new(); + + b.set_square(D4, WHITE, PAWN); + let mut m = Move::new(D4, D5); + m.apply_to(&mut b, &mut gs); + m.unmake(&mut b, &mut gs); + assert!(b.is_empty(D5)); + assert_eq!(b.get_color_on(D4), WHITE); + assert_eq!(b.get_piece_on(D4), PAWN); + } + #[test] fn test_get_castle() { assert_eq!(Move::new(E1, C1).get_castle(), Some(CASTLE_WH_Q)); diff --git a/src/node.rs b/src/node.rs index 9acd85e..468c3f3 100644 --- a/src/node.rs +++ b/src/node.rs @@ -24,10 +24,14 @@ impl Node { } /// Apply a move to this node. - pub fn apply_move(&mut self, m: &Move) { + pub fn apply_move(&mut self, m: &mut Move) { m.apply_to(&mut self.board, &mut self.game_state); } + pub fn unmake_move(&mut self, m: &Move) { + m.unmake(&mut self.board, &mut self.game_state); + } + /// Return player moves from this node. pub fn get_player_moves(&self) -> Vec { rules::get_player_moves(&self.board, &self.game_state) diff --git a/src/rules.rs b/src/rules.rs index 1785067..956b3cb 100644 --- a/src/rules.rs +++ b/src/rules.rs @@ -279,7 +279,8 @@ mod tests { // Check that a pawn (here white queen's pawn) can move forward if the road is free. b.set_square(D3, WHITE, PAWN); let moves = get_legal_piece_moves(&b, &gs, D3, WHITE); - assert!(moves.len() == 1 && moves.contains(&Move::new(D3, D4))); + assert_eq!(moves.len(), 1); + assert!(moves.contains(&Move::new(D3, D4))); // Check that a pawn (here white king's pawn) can move 2 square forward on first move. b.set_square(E2, WHITE, PAWN); @@ -292,7 +293,8 @@ mod tests { // 1. black pawn 2 square forward; only 1 square forward available from start pos. b.set_square(E4, BLACK, PAWN); let moves = get_legal_piece_moves(&b, &gs, E2, WHITE); - assert!(moves.len() == 1 && moves.contains(&Move::new(E2, E3))); + assert_eq!(moves.len(), 1); + assert!(moves.contains(&Move::new(E2, E3))); // 2. black pawn 1 square forward; no square available. b.set_square(E3, BLACK, PAWN); let moves = get_legal_piece_moves(&b, &gs, E2, WHITE); @@ -305,18 +307,17 @@ mod tests { // Check that a pawn can take a piece diagonally. b.set_square(F3, BLACK, PAWN); let moves = get_legal_piece_moves(&b, &gs, E2, WHITE); - assert!(moves.len() == 1 && moves.contains(&Move::new(E2, F3))); + assert_eq!(moves.len(), 1); b.set_square(D3, BLACK, PAWN); let moves = get_legal_piece_moves(&b, &gs, E2, WHITE); assert_eq!(moves.len(), 2); - assert!(moves.contains( &Move::new(E2, F3) )); - assert!(moves.contains( &Move::new(E2, D3) )); // Check that a pawn moving to the last rank leads to queen promotion. // 1. by simply moving forward. b.set_square(A7, WHITE, PAWN); let moves = get_legal_piece_moves(&b, &gs, A7, WHITE); - assert!(moves.len() == 1 && moves.contains(&Move::new_promotion(A7, A8, QUEEN))); + assert_eq!(moves.len(), 1); + assert!(moves.contains(&Move::new_promotion(A7, A8, QUEEN))); } #[test]