diff --git a/rs/src/board.rs b/rs/src/board.rs index 3099cdb..4b39cf2 100644 --- a/rs/src/board.rs +++ b/rs/src/board.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; -#[derive(Eq, PartialEq, Clone)] +#[derive(Eq, PartialEq, Clone, Debug)] pub enum Color { Black, White, @@ -19,7 +19,7 @@ impl Color { Color::White => Rank::_2, } } - fn other(&self) -> Color { + pub fn other(&self) -> Color { match self { Color::Black => Color::White, Color::White => Color::Black, @@ -27,7 +27,7 @@ impl Color { } } -#[derive(PartialEq, Eq, Hash, Clone)] +#[derive(PartialEq, Eq, Hash, Clone, Debug)] pub struct Position { pub file: File, pub rank: Rank, @@ -64,7 +64,7 @@ impl Position { } } -#[derive(PartialEq, Eq, Hash, Clone, Copy)] +#[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)] pub enum Rank { _1, _2, @@ -76,7 +76,7 @@ pub enum Rank { _8, } -#[derive(PartialEq, Eq, Hash, Clone, Copy)] +#[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)] pub enum File { A, B, diff --git a/rs/src/engine.rs b/rs/src/engine.rs index 6203a1b..7e4fc77 100644 --- a/rs/src/engine.rs +++ b/rs/src/engine.rs @@ -10,13 +10,14 @@ impl board::PieceType { } } +#[derive(Debug)] pub struct Move_ { pub source: board::Position, pub target: board::Position, } pub struct Engine { - board: board::Board, + pub board: board::Board, } impl Engine { @@ -36,28 +37,27 @@ impl Engine { pub fn set_state(&mut self, board: board::Board) { self.board = board; } - pub fn get_legal_moves( - &self, + pub fn get_legal_moves<'a>( + board: &'a board::Board, position: &board::Position, - ) -> Result + '_, ()> { - Ok(self - .board - .find_moves(position)? - .chain(self.board.find_captures(position)?)) + ) -> Result + 'a, ()> { + Ok(board.find_moves(position)?.chain(board.find_captures(position)?)) } pub fn make_move( &mut self, source: &board::Position, target: board::Position, ) -> Result<(), ()> { - if !self.get_legal_moves(source)?.any(|pos| pos == target) { + if !Engine::get_legal_moves(&self.board, source)? + .any(|pos| pos == target) + { Err(()) } else { // We checked that there is a piece at source in get_legal_moves self.board.relocate(source, target) } } - pub fn evaluate_position(&self, board: &board::Board) -> f32 { + pub fn evaluate_position(board: &board::Board) -> f32 { board .iter() .map(|(_, piece)| match piece.color { @@ -67,24 +67,29 @@ impl Engine { .sum() } pub fn evaluate_move( - &self, - color: &board::Color, move_: &Move_, + board: &board::Board, + color: &board::Color, ) -> Result { - let mut board = self.board.clone(); + let mut board = board.clone(); board.relocate(&move_.source, move_.target.clone())?; Ok(match color { board::Color::White => 1.0, board::Color::Black => -1.0, - } * self.evaluate_position(&board)) + } * Engine::evaluate_position(&board)) } - pub fn choose_move(&self, color: &board::Color) -> Option { - let all_moves = self - .board + fn minmax( + depth: i8, + board: &board::Board, + color: &board::Color, + ) -> Option<(f32, Move_)> { + eprintln!("Entering minmax for {} at depth {}", color, depth); + let all_moves = board .iter() .filter_map(|(source, piece)| { if &piece.color == color { - if let Ok(targets) = self.get_legal_moves(source) { + if let Ok(targets) = Engine::get_legal_moves(board, source) + { Some(targets.map(|target| Move_ { source: source.clone(), target, @@ -97,20 +102,56 @@ impl Engine { } }) .flatten(); + if depth == 0 { + Engine::best_immediate_move(board, color, all_moves) + } else if depth > 0 { + let best_given_opponent = all_moves.map(|move_| { + eprintln!("Finding opponent moves for {:?}", move_); + let mut board = board.clone(); + board.relocate(&move_.source, move_.target.clone()).unwrap(); + let result = if let Some((opponent_score, _)) = + Engine::minmax(depth - 1, &board, &color.other()) + { + (-opponent_score, move_) + } else { + (0.0, move_) + }; + eprintln!( + "Move {:?} got opponent minmax score {} for {:?}", + result.1, result.0, color + ); + result + }); + best_given_opponent + .max_by(|(s1, _), (s2, _)| s1.partial_cmp(s2).unwrap()) + } else { + panic!(); + } + } + fn best_immediate_move( + board: &board::Board, + color: &board::Color, + moves: impl Iterator, + ) -> Option<(f32, Move_)> { let mut rng = rand::thread_rng(); Some( - all_moves + moves .map(|move_| { - ( - self.evaluate_move(color, &move_).unwrap() - + rng.gen::() * 0.1, - move_, - ) + let score = Engine::evaluate_move(&move_, board, color) + .unwrap() + + rng.gen::() * 0.05; + eprintln!( + "Move {:?} got immediate score {} for {:?}", + move_, score, color + ); + (score, move_) }) .max_by(|(score1, _), (score2, _)| { score1.partial_cmp(score2).unwrap() - })? - .1, + })?, ) } + pub fn choose_move(&self, color: &board::Color) -> Option { + Some(Engine::minmax(1, &self.board, color)?.1) + } } diff --git a/rs/src/ui.rs b/rs/src/ui.rs index d54d257..13a112b 100644 --- a/rs/src/ui.rs +++ b/rs/src/ui.rs @@ -219,7 +219,10 @@ impl Ui { board::Position::parse(position_str).map_err(|_| { format!("Error parsing position {}", position_str) })?; - match self.engine.get_legal_moves(&position) { + match engine::Engine::get_legal_moves( + &self.engine.board, + &position, + ) { Err(_) => { let error = format!("No moves possible from {}", position); Err(error)