movement: add unmake
This commit is contained in:
parent
3d5bbb8d2c
commit
85d5ba1a62
|
@ -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;
|
||||||
|
|
16
src/board.rs
16
src/board.rs
|
@ -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();
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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));
|
||||||
|
|
|
@ -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)
|
||||||
|
|
13
src/rules.rs
13
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.
|
// 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]
|
||||||
|
|
Reference in a new issue