diff --git a/brogle/src/engine.rs b/brogle/src/engine.rs index 317957e..d9d7a94 100644 --- a/brogle/src/engine.rs +++ b/brogle/src/engine.rs @@ -487,7 +487,6 @@ impl Write for Engine { } /// Represents a custom command that can be sent to this engine. -#[derive(Clone, Debug)] pub enum EngineCommand { /// For displaying the list of available commands. Help, @@ -594,18 +593,21 @@ impl UciEngine for Engine { let timeout = if let Some(movetime) = options.move_time { movetime } else { - // Otherwise, search based on time remaining - let time_remaining = if self.game.current_player().is_white() { - options.w_time.unwrap_or(Duration::MAX) - } else if self.game.current_player().is_black() { - options.b_time.unwrap_or(Duration::MAX) + // Otherwise, search based on time remaining and increment + let (time, inc) = if self.game.current_player().is_white() { + (options.w_time, options.w_inc) } else { - Duration::MAX + (options.b_time, options.b_inc) }; - time_remaining / 20 // 5% of time remaining + let (time, inc) = (time.unwrap_or(Duration::MAX), inc.unwrap_or(Duration::ZERO)); + + // 5% of time remaining + time increment + time / 20 + inc }; + // eprintln!("Starting search for {timeout:?}"); + // Clone the arcs for whether we're searching and our found results let is_searching = Arc::clone(&self.is_searching); let sender = self.sender.clone().unwrap(); diff --git a/brogle/src/search/searcher.rs b/brogle/src/search/searcher.rs index 731659e..240cf69 100644 --- a/brogle/src/search/searcher.rs +++ b/brogle/src/search/searcher.rs @@ -1,64 +1,13 @@ use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use std::time::Duration; -use std::{ops::Neg, time::Instant}; +use std::time::Instant; use anyhow::{bail, Result}; use brogle_core::{Game, Move, PieceKind}; use crate::{value_of, Evaluator, Score, INF, MATE}; -/// The result of a search, containing the best move, score, and other metadata. -#[derive(Debug, Eq, Clone)] -pub struct SearchResult { - /// The best move found during this search. If `None`, then no valid move was found (i.e. when mated). - pub bestmove: Option, - /// The score of the best move. A higher score is better for the score player. - pub score: Score, -} - -impl SearchResult { - pub fn new(bestmove: Move) -> Self { - Self { - bestmove: Some(bestmove), - ..Default::default() - } - } -} - -impl Default for SearchResult { - /// A default search result has no best move and a Very Bad score. - fn default() -> Self { - Self { - bestmove: None, - score: -INF, // Initially, our score is Very Bad - } - } -} - -impl PartialEq for SearchResult { - /// Search results are compared by their `score` fields. - fn eq(&self, other: &Self) -> bool { - self.score.eq(&other.score) - } -} - -impl PartialOrd for SearchResult { - /// Search results are compared by their `score` fields. - fn partial_cmp(&self, other: &Self) -> Option { - self.score.partial_cmp(&other.score) - } -} - -impl Neg for SearchResult { - type Output = Self; - /// Negating a search result just negates its score. - fn neg(mut self) -> Self::Output { - self.score = self.score.neg(); - self - } -} - pub struct SearchData { pub nodes_searched: usize, pub score: Score, @@ -135,7 +84,6 @@ impl<'a> Searcher<'a> { let new_pos = self.game.clone().with_move_made(mv); if new_pos.is_repetition() || new_pos.can_draw_by_fifty() { - // eprintln!("Repetition in Search after {mv} on {}", new_pos.fen()); continue; } @@ -259,7 +207,6 @@ impl<'a> Searcher<'a> { } Ok(best) - // Ok(alpha) } fn quiescence( @@ -291,16 +238,10 @@ impl<'a> Searcher<'a> { // Only search captures for i in 0..captures.len() { - // for mv in moves.iter().filter(|mv| mv.is_capture()) { let mv = captures[i]; // Make the score move on the position, getting a new position in return let new_pos = game.clone().with_move_made(mv); - if new_pos.is_repetition() { - // eprintln!("Repetition in QSearch after {mv} on {}", new_pos.fen()); - continue; - } - // self.result.nodes_searched += 1; // Recursively search our opponent's responses let score = -self.quiescence(&new_pos, ply + 1, -beta, -alpha)?; @@ -314,6 +255,7 @@ impl<'a> Searcher<'a> { self.starttime.elapsed(), ); } + if score > best { best = score; @@ -340,7 +282,7 @@ impl<'a> Searcher<'a> { } } -// TODO: Refactor this into its own function and verify that its values are good: https://discord.com/channels/719576389245993010/719576389690589244/1268914745298391071 +// TODO: verify the values are good: https://discord.com/channels/719576389245993010/719576389690589244/1268914745298391071 fn mvv_lva(kind: PieceKind, captured: PieceKind) -> i32 { 10 * value_of(captured) - value_of(kind) } @@ -361,15 +303,16 @@ fn score_move(game: &Game, mv: &Move) -> i32 { } // Promoting is also a good idea - // if let Some(promotion) = mv.promotion() { - // score += value_of(promotion); - // } + if let Some(promotion) = mv.promotion() { + score += value_of(promotion); + } - // Going somewhere attacked by an opponent is not a good idea - // let attacks = game.attacks_by_color(game.current_player().opponent()); - // if attacks.get(mv.to()) { - // score -= value_of(kind); - // } + // Going somewhere attacked by an opponent is not a good idea, but may be necessary, + // so negate it, but not by much + let attacks = game.attacks_by_color(game.current_player().opponent()); + if attacks.get(mv.to()) { + score -= value_of(kind) / 10; + } -score // We're sorting, so a lower number is better } diff --git a/brogle_core/brogle_types/src/tile.rs b/brogle_core/brogle_types/src/tile.rs index a7ca4f8..36fad4e 100644 --- a/brogle_core/brogle_types/src/tile.rs +++ b/brogle_core/brogle_types/src/tile.rs @@ -294,6 +294,32 @@ impl Tile { } } + /// Iterating over [`Tile`]s increases their internal counter by 1. + /// + /// # Example + /// ``` + /// # use brogle_types::Tile; + /// assert_eq!(Tile::A1.next(), Some(Tile::B1)); + /// assert_eq!(Tile::H3.next(), Some(Tile::A4)); + /// assert_eq!(Tile::H8.next(), None); + /// ``` + pub fn next(self) -> Option { + (self.0 < Self::MAX).then(|| Self::from_bits_unchecked(self.0 + 1)) + } + + /// Iterating backwards over [`Tile`]s decreases their internal counter by 1. + /// + /// # Example + /// ``` + /// # use brogle_types::Tile; + /// assert_eq!(Tile::A1.prev(), None); + /// assert_eq!(Tile::A4.prev(), Some(Tile::H3)); + /// assert_eq!(Tile::H8.prev(), Some(Tile::G8)); + /// ``` + pub fn prev(self) -> Option { + (self.0 > Self::MIN).then(|| Self::from_bits_unchecked(self.0 - 1)) + } + /// Fetches the inner index value of the [`Tile`], which represented as a [`u8`]. /// /// # Example diff --git a/brogle_core/src/main.rs b/brogle_core/src/main.rs index a5bc472..8c6c2ee 100644 --- a/brogle_core/src/main.rs +++ b/brogle_core/src/main.rs @@ -2,7 +2,6 @@ use brogle_core::*; /// All contents of this file should be ignored. I just use this `main` to test small things in `brogle_core`. fn main() { - /* let mut game = Game::default(); println!("pos: {}\nkey: {}", game.position(), game.key()); game.make_move(Move::from_uci(&game, "b1a3").unwrap()); @@ -13,20 +12,22 @@ fn main() { println!("pos: {}\nkey: {}", game.position(), game.key()); game.make_move(Move::from_uci(&game, "a6b8").unwrap()); println!("pos: {}\nkey: {}", game.position(), game.key()); - */ // let fen = FEN_STARTPOS; // let moves = ["b1a3", "b8a6", "a3b1", "a6b8"]; // let fen = "k7/8/8/8/3p4/8/4P3/K7 w - - 0 1"; // Testing hash keys with en passant // let moves = ["e2e4", "d4e3"]; - let fen = "r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 1"; - let moves = ["e1g1"]; - let mut game = Game::from_fen(fen).unwrap(); + // let fen = "r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 1"; + // let moves = ["e1g1"]; - for mv in moves { - game.make_move(Move::from_uci(&game, mv).unwrap()); - // println!("repetition? {}", game.is_repetition()); - } + // let fen = FEN_KIWIPETE; + // let pos = Position::from_fen(fen).unwrap(); + + // let movegen = MoveGen::new(pos); + + // for mv in movegen { + // println!("{mv}"); + // } // let mut game = Game::default(); // game.make_move(Move::from_uci(&game, "b1a3").unwrap()); diff --git a/brogle_core/src/movegen.rs b/brogle_core/src/movegen.rs index a12f98c..8995366 100644 --- a/brogle_core/src/movegen.rs +++ b/brogle_core/src/movegen.rs @@ -9,6 +9,72 @@ use super::{ include!("blobs/magics.rs"); // TODO: Make these into blobs +/* +#[derive(Clone, Debug, PartialEq, Eq, Default)] +pub struct MoveGen { + pub(crate) position: Position, + moves: ArrayVec, + to_mask: Bitboard, + from_mask: Bitboard, + current: Tile, + checkers: Bitboard, + checkmask: Bitboard, + pinmask_ortho: Bitboard, + pinmask_diag: Bitboard, +} + +impl MoveGen { + pub fn new(position: Position) -> Self { + let color = position.current_player(); + let king_tile = position.king(color).to_tile_unchecked(); + + let mut discoverable_checks = Bitboard::EMPTY_BOARD; + let checkers = self.compute_attacks_to(&position, king_tile, color.opponent()); + let pinmasks = self.compute_pinmasks_for(&position, king_tile, color); + + // These are the rays containing the King and his Checkers + // They are used to prevent the King from retreating along a line he is checked on! + // Note: A pawn can't generate a discoverable check, as it can only capture 1 square away. + for checker in checkers & !position.kind(PieceKind::Pawn) { + discoverable_checks |= ray_containing(king_tile, checker) ^ checker.bitboard(); + } + + Self { + position, + moves: ArrayVec::default(), + to_mask: Bitboard::FULL_BOARD, + from_mask: Bitboard::FULL_BOARD, + current: Tile::A1, + checkers: Bitboard::EMPTY_BOARD, + checkmask: Bitboard::FULL_BOARD, + pinmask_ortho: Bitboard::EMPTY_BOARD, + pinmask_diag: Bitboard::EMPTY_BOARD, + } + } + + pub fn generate_moves_from(&mut self, tile: Tile) -> Option { + let _piece = self.position.piece_at(tile)?; + + None + } +} + +impl Iterator for MoveGen { + type Item = Move; + fn next(&mut self) -> Option { + self.current = self.current.next()?; + self.generate_moves_from(self.current) + } +} + +impl Deref for MoveGen { + type Target = Position; + fn deref(&self) -> &Self::Target { + &self.position + } +} + */ + #[derive(Clone, Debug, PartialEq, Eq)] pub struct MoveGenerator { pub(crate) position: Position, @@ -101,26 +167,6 @@ impl MoveGenerator { self.checkers().population() > 1 } - // pub fn is_in_checkmate(&self) -> bool { - // self.is_in_check() && self.num_legal_moves == 0 - // } - - // pub fn order_moves(&mut self, f: impl FnMut(&Move) -> K) { - // self.legal_moves[..self.num_legal_moves].sort_by_cached_key(f); - // } - - // pub fn num_legal_moves(&self) -> usize { - // self.num_legal_moves - // } - - // pub fn legal_moves(&self) -> &[Move] { - // &self.legal_moves[..self.num_legal_moves] - // } - - // pub fn legal_moves_mut(&mut self) -> &mut [Move] { - // &mut self.legal_moves[..self.num_legal_moves] - // } - pub fn legal_captures(&self) -> ArrayVec { self.legal_moves() .into_iter() @@ -169,14 +215,13 @@ impl MoveGenerator { // eprintln!("CHECKMASK:\n{checkmask:?}"); // eprintln!("ENEMY_OR_EMPTY:\n{enemy_or_empty:?}"); - // For sliding pieces, we need a blocker mask to compute pseudo-legal moves + // Pawns are... weird + self.compute_pawn_moves(color, checkmask, &mut moves); + // For sliding pieces, we need a blocker mask to compute pseudo-legal moves self.compute_normal_piece_moves(color, king_tile, checkmask, &mut moves); self.compute_king_moves(color, self.occupied(), &mut moves); // TODO: legal_mask for King? - // Pawns are... weird - self.compute_pawn_moves(color, checkmask, &mut moves); - moves } @@ -388,6 +433,7 @@ impl MoveGenerator { ) { // Loop over every tile containing this piece let normal_pieces = self.color(color) ^ self.king(color) ^ self.pawns(color); + // for from in self.piece_parts(color, PieceKind::Knight) { for from in normal_pieces { let pseudo_legal = self.attacks_by_tile(from);