Add minimax and adapt UCI tests
This commit is contained in:
@@ -46,15 +46,24 @@ impl MoveParameters {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_move(&mut self, mv: Move) {
|
pub fn build(board: &Board, mv: &Move) -> Self {
|
||||||
|
let mut move_parameters = Self::new();
|
||||||
|
move_parameters.add_move(*mv);
|
||||||
|
move_parameters.add_irreversible_parameters(board.state);
|
||||||
|
move_parameters.add_capture_and_promotion_piece(board, *mv, board.state.current_player());
|
||||||
|
|
||||||
|
move_parameters
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_move(&mut self, mv: Move) {
|
||||||
self.mv = Some(mv)
|
self.mv = Some(mv)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_captured_piece(&mut self, board: &Board, dst: usize, color: Color) {
|
fn add_captured_piece(&mut self, board: &Board, dst: usize, color: Color) {
|
||||||
self.captured_piece = board.piece_type_at(dst, color);
|
self.captured_piece = board.piece_type_at(dst, color);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_promoted_piece(&mut self, promote: Promote) {
|
fn add_promoted_piece(&mut self, promote: Promote) {
|
||||||
self.promoted_piece = Some(promote.into_piece_type())
|
self.promoted_piece = Some(promote.into_piece_type())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use std::{
|
|||||||
str::SplitWhitespace,
|
str::SplitWhitespace,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{board::game::Game, board::square::Square, movegen::r#move::Move, search::search};
|
use crate::{board::game::Game, movegen::r#move::Move, search};
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Debug)]
|
#[derive(PartialEq, Eq, Debug)]
|
||||||
pub enum Command {
|
pub enum Command {
|
||||||
@@ -91,7 +91,11 @@ pub fn uci_position(position: &mut SplitWhitespace) -> Result<Game, String> {
|
|||||||
let state = position.next().ok_or("Expected startpos or fen")?;
|
let state = position.next().ok_or("Expected startpos or fen")?;
|
||||||
let mut game = match state {
|
let mut game = match state {
|
||||||
"startpos" => Game::new(),
|
"startpos" => Game::new(),
|
||||||
"fen" => Game::new_from_fen(position.next().ok_or("Expected fen string")?)?,
|
"fen" => {
|
||||||
|
let fen_parts: Vec<&str> = position.take_while(|&part| part != "moves").collect();
|
||||||
|
let fen = fen_parts.join(" ");
|
||||||
|
Game::new_from_fen(&fen)?
|
||||||
|
}
|
||||||
_ => return Err("Expected startpos or fen".to_string()),
|
_ => return Err("Expected startpos or fen".to_string()),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -128,9 +132,11 @@ pub fn uci_go(go: &mut SplitWhitespace, game: &mut Game) -> Result<Move, String>
|
|||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
search::search(game, params.depth.unwrap_or(MAX_DEPTH));
|
Ok(
|
||||||
|
search::minimax::minimax(game, params.depth.unwrap_or(MAX_DEPTH))
|
||||||
Ok(Move::new(Square::B8, Square::C6))
|
.0
|
||||||
|
.expect("No move selected"),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn uci_loop<R: BufRead, W: Write>(input: R, mut output: W) -> Result<(), String> {
|
pub fn uci_loop<R: BufRead, W: Write>(input: R, mut output: W) -> Result<(), String> {
|
||||||
@@ -171,9 +177,9 @@ mod tests {
|
|||||||
use std::io::Cursor;
|
use std::io::Cursor;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
board::{fen::from_fen, game::Game, square::Square},
|
board::{fen::from_fen, square::Square},
|
||||||
interface::uci::{parse_command, Command},
|
interface::uci::{parse_command, Command},
|
||||||
movegen::r#move::Move,
|
movegen::{attack::init_attacks, r#move::Move},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::uci_go;
|
use super::uci_go;
|
||||||
@@ -204,22 +210,27 @@ mod tests {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const FEN_MATE_IN_1: &str = "8/8/8/8/8/4q1k1/8/5K2 b - - 0 1";
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_uci_go() -> Result<(), String> {
|
fn test_uci_go() -> Result<(), String> {
|
||||||
let mut game = Game::new();
|
init_attacks();
|
||||||
|
let mut game = from_fen(FEN_MATE_IN_1)?;
|
||||||
let command_go = "go depth 4";
|
let command_go = "go depth 4";
|
||||||
let mut parts = command_go.split_whitespace();
|
let mut parts = command_go.split_whitespace();
|
||||||
let response = uci_go(&mut parts, &mut game)?;
|
let response = uci_go(&mut parts, &mut game)?;
|
||||||
|
|
||||||
assert_eq!(Move::new(Square::B8, Square::C6), response);
|
assert_eq!(Move::new(Square::E3, Square::F2), response);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_uci_loop() -> Result<(), String> {
|
fn test_uci_loop() -> Result<(), String> {
|
||||||
|
init_attacks();
|
||||||
let commands = b"uci\n\
|
let commands = b"uci\n\
|
||||||
ucinewgame\n\
|
ucinewgame\n\
|
||||||
position startpos moves e2e4 e7e5\n\
|
position fen 8/8/8/8/8/4q1k1/8/5K2 b - - 0 1\n\
|
||||||
go\n\
|
go\n\
|
||||||
quit";
|
quit";
|
||||||
let input = Cursor::new(commands);
|
let input = Cursor::new(commands);
|
||||||
@@ -232,7 +243,7 @@ mod tests {
|
|||||||
uciok\n\
|
uciok\n\
|
||||||
Clear cache\n\
|
Clear cache\n\
|
||||||
Initialized position\n\
|
Initialized position\n\
|
||||||
bestmove b8c6\n";
|
bestmove e3f2\n";
|
||||||
let actual_response = String::from_utf8(output).expect("Invalid UTF-8 in output");
|
let actual_response = String::from_utf8(output).expect("Invalid UTF-8 in output");
|
||||||
|
|
||||||
assert_eq!(expected_response, actual_response);
|
assert_eq!(expected_response, actual_response);
|
||||||
|
|||||||
@@ -131,7 +131,7 @@ impl Board {
|
|||||||
Color::Black => (
|
Color::Black => (
|
||||||
&mut self.black_pieces,
|
&mut self.black_pieces,
|
||||||
&mut self.white_pieces,
|
&mut self.white_pieces,
|
||||||
Some(mv.src - 8),
|
Some(mv.src.saturating_sub(8)),
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
let pawn_move = Self::is_pawn_move(mv.src, &own_pieces[PieceType::Pawn]);
|
let pawn_move = Self::is_pawn_move(mv.src, &own_pieces[PieceType::Pawn]);
|
||||||
|
|||||||
87
src/search/minimax.rs
Normal file
87
src/search/minimax.rs
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
use crate::{
|
||||||
|
board::{
|
||||||
|
board::Color,
|
||||||
|
game::Game,
|
||||||
|
history::{History, MoveParameters},
|
||||||
|
},
|
||||||
|
evaluation::evaluation::evaluate_position,
|
||||||
|
movegen::r#move::Move,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn minimax(game: &mut Game, depth: u8) -> (Option<Move>, i32) {
|
||||||
|
let color = game.current_player();
|
||||||
|
|
||||||
|
if depth == 0 {
|
||||||
|
return (None, evaluate_position(&game.board, color));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut history = History::new();
|
||||||
|
let mut best_move: Option<Move> = None;
|
||||||
|
let (mut best_score, mate_score) = match color {
|
||||||
|
Color::White => (-100000, -49000 - depth as i32),
|
||||||
|
Color::Black => (100000, 49000 + depth as i32),
|
||||||
|
};
|
||||||
|
|
||||||
|
let moves = game.board.pseudo_moves_all(color);
|
||||||
|
let mut legal_moves = 0;
|
||||||
|
|
||||||
|
for mv in moves {
|
||||||
|
history.push_move_parameters(MoveParameters::build(&game.board, &mv));
|
||||||
|
game.board.make_move(&mv);
|
||||||
|
|
||||||
|
if !game.board.king_under_check(color) {
|
||||||
|
legal_moves += 1;
|
||||||
|
let (_, node_score) = minimax(game, depth - 1);
|
||||||
|
match color {
|
||||||
|
Color::White => {
|
||||||
|
if best_score < node_score {
|
||||||
|
best_score = node_score;
|
||||||
|
best_move = Some(mv);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Color::Black => {
|
||||||
|
if best_score > node_score {
|
||||||
|
best_score = node_score;
|
||||||
|
best_move = Some(mv);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
game.board
|
||||||
|
.unmake_move(history.pop_move_parameters().expect("Empty history stack"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if legal_moves == 0 {
|
||||||
|
if game.board.king_under_check(color) {
|
||||||
|
return (None, mate_score);
|
||||||
|
} else {
|
||||||
|
return (None, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(best_move, best_score)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::board::{fen::from_fen, square::Square};
|
||||||
|
use crate::movegen::attack::init_attacks;
|
||||||
|
use crate::movegen::r#move::Move;
|
||||||
|
use crate::search::minimax::minimax;
|
||||||
|
|
||||||
|
const FEN_MATE_IN_1: &str = "8/8/8/8/8/4q1k1/8/5K2 b - - 0 1";
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_minimax() -> Result<(), String> {
|
||||||
|
init_attacks();
|
||||||
|
let mut game = from_fen(FEN_MATE_IN_1)?;
|
||||||
|
|
||||||
|
let e3f2 = Move::new(Square::E3, Square::F2);
|
||||||
|
|
||||||
|
assert_eq!(e3f2, minimax(&mut game, 2).0.unwrap());
|
||||||
|
assert_eq!(e3f2, minimax(&mut game, 3).0.unwrap());
|
||||||
|
assert_eq!(e3f2, minimax(&mut game, 4).0.unwrap());
|
||||||
|
assert_eq!(e3f2, minimax(&mut game, 5).0.unwrap());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,2 +1,2 @@
|
|||||||
|
pub mod minimax;
|
||||||
pub mod perft;
|
pub mod perft;
|
||||||
pub mod search;
|
|
||||||
|
|||||||
@@ -1,42 +0,0 @@
|
|||||||
use crate::board::game::Game;
|
|
||||||
|
|
||||||
pub fn search(game: &mut Game, depth: u8) {
|
|
||||||
if depth == 0 {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let color = game.current_player();
|
|
||||||
let pseudo_moves = game.board.pseudo_moves_all(color);
|
|
||||||
|
|
||||||
for mv in pseudo_moves {
|
|
||||||
let original_board = game.board.clone();
|
|
||||||
game.board.make_move(&mv);
|
|
||||||
|
|
||||||
if game.board.king_under_check(color) {
|
|
||||||
game.board = original_board;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
search(game, depth - 1);
|
|
||||||
game.board = original_board;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use crate::board::fen::from_fen;
|
|
||||||
|
|
||||||
const FEN_QUIET: [&str; 2] = [
|
|
||||||
"rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1",
|
|
||||||
"rnbqkbnr/ppp2ppp/4p3/3pN3/3P4/8/PPP1PPPP/RNBQKB1R w KQkq - 0 1",
|
|
||||||
];
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_search() -> Result<(), String> {
|
|
||||||
let _game = from_fen(FEN_QUIET[0])?;
|
|
||||||
|
|
||||||
//TODO:
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user