movement: add unmake

This commit is contained in:
dece 2020-06-24 19:48:44 +02:00
parent 3d5bbb8d2c
commit 85d5ba1a62
6 changed files with 101 additions and 32 deletions

View file

@ -110,7 +110,7 @@ impl Analyzer {
self.start_time = Some(Instant::now()); self.start_time = Some(Instant::now());
self.current_per_second_timer = 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 { if let Some(m) = best_move {
let log_str = format!("Best move {} evaluated {}", m.to_uci_string(), max_score); 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. /// lower score bound and `beta` the upper bound.
fn negamax( fn negamax(
&mut self, &mut self,
node: &Node, node: &mut Node,
alpha: f32, alpha: f32,
beta: f32, beta: f32,
depth: u32, depth: u32,
@ -185,18 +185,18 @@ impl Analyzer {
} }
// Get negamax for playable moves. // Get negamax for playable moves.
let moves = node.get_player_moves(); let mut moves = node.get_player_moves();
let mut alpha = alpha; let mut alpha = alpha;
let mut best_score = MIN_F32; let mut best_score = MIN_F32;
let mut best_move = None; let mut best_move = None;
for m in moves { for m in &mut moves {
let mut sub_node = node.clone(); node.apply_move(m);
sub_node.apply_move(&m); let result = self.negamax(node, -beta, -alpha, depth + 1);
let result = self.negamax(&sub_node, -beta, -alpha, depth + 1); node.unmake_move(m);
let score = -result.0; let score = -result.0;
if score > best_score { if score > best_score {
best_score = score; best_score = score;
best_move = Some(m); best_move = Some(m.to_owned());
} }
if best_score > alpha { if best_score > alpha {
alpha = best_score; alpha = best_score;

View file

@ -315,6 +315,14 @@ impl Board {
self.set_square(dest, source_color, source_piece); 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. /// Find position of this king.
pub fn find_king(&self, color: Color) -> Option<Square> { pub fn find_king(&self, color: Color) -> Option<Square> {
let king_bb = self.colors[color] & self.pieces[KING]; let king_bb = self.colors[color] & self.pieces[KING];
@ -622,6 +630,14 @@ mod tests {
assert_eq!(b.get_piece_on(E8), KING); 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] #[test]
fn test_find_king() { fn test_find_king() {
let b = Board::new_empty(); let b = Board::new_empty();

View file

@ -181,13 +181,8 @@ impl Engine {
} }
/// Apply a series of moves to the current node. /// Apply a series of moves to the current node.
fn apply_moves(&mut self, moves: &Vec<Move>) { fn apply_moves(&mut self, moves: &mut Vec<Move>) {
moves.iter().for_each(|m| self.apply_move(m)); moves.iter_mut().for_each(|m| m.apply_to(&mut self.node.board, &mut self.node.game_state));
}
/// 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);
} }
/// Start working on board, returning the best move found. /// Start working on board, returning the best move found.
@ -234,7 +229,7 @@ impl Engine {
self.apply_fen(&fen); self.apply_fen(&fen);
}, },
uci::PositionArgs::Moves(moves) => { uci::PositionArgs::Moves(moves) => {
self.apply_moves(&moves); self.apply_moves(&mut moves.clone());
} }
} }
} }

View file

@ -9,9 +9,16 @@ use crate::rules::GameState;
/// A movement, with before/after positions and optional promotion. /// A movement, with before/after positions and optional promotion.
#[derive(Clone, PartialEq)] #[derive(Clone, PartialEq)]
pub struct Move { pub struct Move {
/// Square from which a piece moves.
pub source: Square, pub source: Square,
/// Square to which a piece moves.
pub dest: Square, pub dest: Square,
/// Promotion piece for pawns reaching the last rank.
pub promotion: Option<Piece>, pub promotion: Option<Piece>,
/// Captured piece, if any.
pub capture: Option<Piece>,
/// Castle options before the move. This is set when the move is first applied.
pub old_castles: Castle,
} }
impl fmt::Debug for Move { impl fmt::Debug for Move {
@ -26,16 +33,17 @@ pub const UCI_NULL_MOVE_STR: &str = "0000";
impl Move { impl Move {
/// Build a move from `source` to `dest`, no promotion. /// Build a move from `source` to `dest`, no promotion.
pub const fn new(source: Square, dest: Square) -> Move { 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. /// Build a move from `source` to `dest`, with a promotion.
pub const fn new_promotion(source: Square, dest: Square, promotion: Piece) -> Move { 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`. /// 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 a king moves, remove it from castling options.
if self.source == E1 { game_state.castling &= !CASTLE_WH_MASK; } if self.source == E1 { game_state.castling &= !CASTLE_WH_MASK; }
else if self.source == E8 { game_state.castling &= !CASTLE_BL_MASK; } else if self.source == E8 { game_state.castling &= !CASTLE_BL_MASK; }
@ -50,7 +58,7 @@ impl Move {
} }
/// Apply the move into `board`. /// 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); let piece = board.get_piece_on(self.source);
// If a king is castling, apply special move. // If a king is castling, apply special move.
if piece == KING { if piece == KING {
@ -65,13 +73,38 @@ impl Move {
return return
} }
} }
if !board.is_empty(self.dest) {
self.capture = Some(board.get_piece_on(self.dest));
}
board.move_square(self.source, self.dest); board.move_square(self.source, self.dest);
if let Some(piece) = self.promotion { if let Some(piece) = self.promotion {
let color = board.get_color_on(self.dest); board.set_piece(self.dest, PAWN, piece);
board.set_square(self.dest, color, 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. /// Get the corresponding castling flag for this move.
pub fn get_castle(&self) -> Option<Castle> { pub fn get_castle(&self) -> Option<Castle> {
match (self.source, self.dest) { match (self.source, self.dest) {
@ -109,7 +142,9 @@ impl Move {
}) })
} else { } else {
None None
} },
capture: None,
old_castles: 0,
} }
} }
@ -148,16 +183,20 @@ mod tests {
b.set_square(D4, WHITE, KNIGHT); b.set_square(D4, WHITE, KNIGHT);
b.set_square(F4, BLACK, KNIGHT); b.set_square(F4, BLACK, KNIGHT);
// Move white knight in a position attacked by 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!(b.is_empty(D4));
assert_eq!(b.get_color_on(E6), WHITE); assert_eq!(b.get_color_on(E6), WHITE);
assert_eq!(b.get_piece_on(E6), KNIGHT); assert_eq!(b.get_piece_on(E6), KNIGHT);
assert_eq!(count_bits(b.combined()), 2); assert_eq!(count_bits(b.combined()), 2);
assert!(m.capture.is_none());
// Sack it with black knight // 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_color_on(E6), BLACK);
assert_eq!(b.get_piece_on(E6), KNIGHT); assert_eq!(b.get_piece_on(E6), KNIGHT);
assert_eq!(count_bits(b.combined()), 1); assert_eq!(count_bits(b.combined()), 1);
assert_eq!(m.capture.unwrap(), KNIGHT);
} }
#[test] #[test]
@ -198,6 +237,20 @@ mod tests {
assert_eq!(gs.castling, 0); 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] #[test]
fn test_get_castle() { fn test_get_castle() {
assert_eq!(Move::new(E1, C1).get_castle(), Some(CASTLE_WH_Q)); assert_eq!(Move::new(E1, C1).get_castle(), Some(CASTLE_WH_Q));

View file

@ -24,10 +24,14 @@ impl Node {
} }
/// Apply a move to this 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); 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. /// Return player moves from this node.
pub fn get_player_moves(&self) -> Vec<Move> { pub fn get_player_moves(&self) -> Vec<Move> {
rules::get_player_moves(&self.board, &self.game_state) rules::get_player_moves(&self.board, &self.game_state)

View file

@ -279,7 +279,8 @@ mod tests {
// Check that a pawn (here white queen's pawn) can move forward if the road is free. // Check that a pawn (here white queen's pawn) can move forward if the road is free.
b.set_square(D3, WHITE, PAWN); b.set_square(D3, WHITE, PAWN);
let moves = get_legal_piece_moves(&b, &gs, D3, WHITE); 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. // Check that a pawn (here white king's pawn) can move 2 square forward on first move.
b.set_square(E2, WHITE, PAWN); 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. // 1. black pawn 2 square forward; only 1 square forward available from start pos.
b.set_square(E4, BLACK, PAWN); b.set_square(E4, BLACK, PAWN);
let moves = get_legal_piece_moves(&b, &gs, E2, WHITE); 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. // 2. black pawn 1 square forward; no square available.
b.set_square(E3, BLACK, PAWN); b.set_square(E3, BLACK, PAWN);
let moves = get_legal_piece_moves(&b, &gs, E2, WHITE); 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. // Check that a pawn can take a piece diagonally.
b.set_square(F3, BLACK, PAWN); b.set_square(F3, BLACK, PAWN);
let moves = get_legal_piece_moves(&b, &gs, E2, WHITE); 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); b.set_square(D3, BLACK, PAWN);
let moves = get_legal_piece_moves(&b, &gs, E2, WHITE); let moves = get_legal_piece_moves(&b, &gs, E2, WHITE);
assert_eq!(moves.len(), 2); 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. // Check that a pawn moving to the last rank leads to queen promotion.
// 1. by simply moving forward. // 1. by simply moving forward.
b.set_square(A7, WHITE, PAWN); b.set_square(A7, WHITE, PAWN);
let moves = get_legal_piece_moves(&b, &gs, A7, WHITE); 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] #[test]