From 7d0d81905e952e75b821fbaaf96f8270a0e4871f Mon Sep 17 00:00:00 2001 From: dogeystamp Date: Sat, 26 Oct 2024 19:53:20 -0400 Subject: [PATCH] feat: basic basic search --- src/bin/engine.rs | 4 +-- src/eval.rs | 2 +- src/lib.rs | 1 + src/movegen.rs | 2 +- src/search.rs | 69 +++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 74 insertions(+), 4 deletions(-) create mode 100644 src/search.rs diff --git a/src/bin/engine.rs b/src/bin/engine.rs index 189b764..cdd203c 100644 --- a/src/bin/engine.rs +++ b/src/bin/engine.rs @@ -15,6 +15,7 @@ Copyright © 2024 dogeystamp use chess_inator::fen::FromFen; use chess_inator::movegen::{FromUCIAlgebraic, Move, MoveGen, MoveGenType, ToUCIAlgebraic}; +use chess_inator::search::best_move; use chess_inator::Board; use std::io; @@ -87,8 +88,7 @@ fn cmd_position(mut tokens: std::str::SplitWhitespace<'_>) -> Board { /// Play the game. fn cmd_go(mut _tokens: std::str::SplitWhitespace<'_>, board: &mut Board) { - let mvs: Vec<_> = board.gen_moves(MoveGenType::Legal).into_iter().collect(); - let chosen = mvs.first(); + let chosen = best_move(board); match chosen { Some(mv) => println!("bestmove {}", mv.to_uci_algebraic()), None => println!("bestmove 0000"), diff --git a/src/eval.rs b/src/eval.rs index 8167f3d..6aaeeed 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -18,7 +18,7 @@ use crate::{Board, Color, N_PIECES}; /// Signed centipawn type. /// /// Positive is good for White, negative good for Black. -type EvalInt = i16; +pub type EvalInt = i16; pub trait Eval { /// Evaluate a position and assign it a score. diff --git a/src/lib.rs b/src/lib.rs index 2de6234..5783e86 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,6 +19,7 @@ use std::str::FromStr; pub mod eval; pub mod fen; pub mod movegen; +pub mod search; use crate::fen::{FromFen, ToFen, START_POSITION}; diff --git a/src/movegen.rs b/src/movegen.rs index 5cc22d2..5ed1601 100644 --- a/src/movegen.rs +++ b/src/movegen.rs @@ -96,7 +96,7 @@ pub struct AntiMove { impl AntiMove { /// Undo the move. - fn unmake(self, pos: &mut Board) { + pub fn unmake(self, pos: &mut Board) { pos.move_piece(self.dest, self.src); pos.half_moves = self.half_moves; pos.castle = self.castle; diff --git a/src/search.rs b/src/search.rs new file mode 100644 index 0000000..99a92d9 --- /dev/null +++ b/src/search.rs @@ -0,0 +1,69 @@ +/* + +This file is part of chess_inator. + +chess_inator is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. + +chess_inator is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with chess_inator. If not, see https://www.gnu.org/licenses/. + +Copyright © 2024 dogeystamp +*/ + +//! Game-tree search. + +use crate::eval::{Eval, EvalInt}; +use crate::movegen::{Move, MoveGen, MoveGenType}; +use crate::Board; +use std::cmp::max; + +/// Search the game tree to find the absolute (positive good) eval for the current player. +fn minmax(board: &mut Board, depth: usize) -> EvalInt { + if depth == 0 { + let eval = board.eval(); + match board.turn { + crate::Color::White => return eval, + crate::Color::Black => return -eval, + } + } + + let mvs: Vec<_> = board.gen_moves(MoveGenType::Legal).into_iter().collect(); + + let mut abs_best = EvalInt::MIN; + + for mv in mvs { + let anti_mv = mv.make(board); + abs_best = max(abs_best, -minmax(board, depth - 1)); + anti_mv.unmake(board); + } + + abs_best +} + +/// Find the best move for a position (internal interface). +fn search(board: &mut Board) -> Option { + const DEPTH: usize = 4; + let mvs: Vec<_> = board.gen_moves(MoveGenType::Legal).into_iter().collect(); + + // absolute eval value + let mut best_eval = EvalInt::MIN; + let mut best_mv: Option = None; + + for mv in mvs { + let anti_mv = mv.make(board); + let abs_eval = -minmax(board, DEPTH); + if abs_eval > best_eval { + best_eval = abs_eval; + best_mv = Some(mv); + } + anti_mv.unmake(board); + } + + best_mv +} + +/// Find the best move. +pub fn best_move(board: &mut Board) -> Option { + search(board) +}