diff --git a/benches/perft.rs b/benches/perft.rs index ce09f5c..0a2c321 100644 --- a/benches/perft.rs +++ b/benches/perft.rs @@ -1,5 +1,5 @@ use chess_engine::board::Board; -use chess_engine::movegen::picker::MoveGenerator; +use chess_engine::movegen::picker::MovePicker; use chess_engine::movegen::legal_check::is_other_king_attacked; use criterion::{criterion_group, criterion_main, Criterion}; @@ -8,7 +8,7 @@ fn count_legal_moves_recursive(board: &mut Board, depth: u8) -> u64 { return 1_u64; } - let mut generator = MoveGenerator::new(); + let mut generator = MovePicker::new(); let mut leaf_nodes = 0_u64; while let Some(mv) = generator.next(board) { diff --git a/progress_tracking/collect_benchmarks.py b/progress_tracking/collect_benchmarks.py index 020588e..687967a 100644 --- a/progress_tracking/collect_benchmarks.py +++ b/progress_tracking/collect_benchmarks.py @@ -99,7 +99,7 @@ def main(): try: # The suite_score is still a raw float, e.g., 95.5 - suite_score = float(suite_result.stdout.strip()) + suite_score = float(suite_result.stdout.strip().splitlines()[-1].strip()) except ValueError: print(f"Error: Could not convert suite output to a number.") print(f"Received: '{suite_result.stdout}'") diff --git a/src/engine.rs b/src/engine.rs index 389f881..2544059 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -3,6 +3,7 @@ use crate::r#move::Move; use crate::search::alpha_beta; use crate::tt::TranspositionTable; // Import TT use std::time::{Instant, Duration}; +use crate::uci::UCI; pub struct Engine { pub name: String, @@ -43,7 +44,8 @@ impl Engine { let time_limit = Duration::from_millis(time_limit_ms); let mut nodes = 0; - + + // Initial search at depth 1 let (mut opt_move, mut _score) = alpha_beta( &mut self.board, 1, @@ -56,8 +58,12 @@ impl Engine { &mut self.tt ); + // If we timed out immediately at depth 1 (very rare), opt_move might be None. + // But usually, we have at least one move here. + let mut depth = 2; + // Iterative Deepening while start_time.elapsed() < time_limit { let (new_move, new_score) = alpha_beta( &mut self.board, @@ -71,19 +77,27 @@ impl Engine { &mut self.tt ); - if start_time.elapsed() > time_limit { + // FIX: Only update if we actually got a move back (didn't timeout) + if let Some(mv) = new_move { + opt_move = Some(mv); + _score = new_score; + + // Optional: Move send_info here to update the GUI after every completed depth + UCI::send_info(Some(depth as usize), Some(nodes), None, None, None, None); + } else { + // If new_move is None, the search was aborted due to time. + // We discard the partial results and stop increasing depth. break; } - opt_move = new_move; - _score = new_score; - depth += 1; } if let Some(mv) = opt_move { mv.to_algebraic() } else { + // Fallback: If even depth 1 failed (e.g. 0ms time limit), try to return *any* legal move + // or just return null if truly nothing works. "null".to_string() } } diff --git a/src/main.rs b/src/main.rs index bd43db4..d6362f5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,8 @@ use chess_engine::engine::Engine; -use chess_engine::uci::uci_mainloop; +use chess_engine::uci::UCI; fn main() { let mut engine = Engine::new("Yakari".to_string(), "EiSiMo".to_string()); - uci_mainloop(&mut engine); + let mut uci = UCI { engine }; + uci.uci_mainloop(); } diff --git a/src/movegen/picker.rs b/src/movegen/picker.rs index 834132e..e6a6f7c 100644 --- a/src/movegen/picker.rs +++ b/src/movegen/picker.rs @@ -1,6 +1,6 @@ use crate::board::Board; -use crate::movegen::non_sliders::{generate_king_moves, generate_knight_moves}; -use crate::movegen::pawns::generate_pawn_moves; +use crate::movegen::non_sliders::*; +use crate::movegen::pawns::*; use crate::movegen::sliders::*; use crate::r#move::{Move, MoveList}; @@ -30,12 +30,12 @@ impl GenStage { } } -pub struct MoveGenerator { +pub struct MovePicker { buffer: MoveList, stage: GenStage, } -impl MoveGenerator { +impl MovePicker { pub fn new() -> Self { Self { buffer: MoveList::new(), diff --git a/src/search.rs b/src/search.rs index ff3e06f..13ae146 100644 --- a/src/search.rs +++ b/src/search.rs @@ -2,7 +2,7 @@ use crate::board::{Board, Color}; use crate::eval::evaluate_board; use crate::movegen::legal_check::*; use crate::r#move::Move; -use crate::movegen::picker::MoveGenerator; +use crate::movegen::picker::MovePicker; use crate::tt::{TranspositionTable, NodeType}; use std::time::{Instant, Duration}; @@ -90,7 +90,7 @@ pub fn alpha_beta( let mut legal_moves_found = false; let alpha_orig = alpha; - let mut picker = MoveGenerator::new(); + let mut picker = MovePicker::new(); let mut moves_tried = 0; loop { diff --git a/src/uci.rs b/src/uci.rs index f8354d1..876719a 100644 --- a/src/uci.rs +++ b/src/uci.rs @@ -1,55 +1,99 @@ use std::io::{self, BufRead}; use crate::engine::Engine; -pub fn uci_mainloop(engine: &mut Engine) { - loop { - for line in io::stdin().lock().lines() { - let input = line.unwrap_or_else(|_| "quit".to_string()); - let tokens: Vec<&str> = input.split_whitespace().collect(); +pub struct UCI { + // TODO lifetime specifier + pub engine: Engine, +} - if tokens.is_empty() { - continue; - } +impl UCI { + pub fn uci_mainloop(&mut self) { + loop { + for line in io::stdin().lock().lines() { + let input = line.unwrap_or_else(|_| "quit".to_string()); + let tokens: Vec<&str> = input.split_whitespace().collect(); - match tokens[0] { - "uci" => { - println!("id name {}", engine.name); - println!("id author {}", engine.author); - println!("uciok"); + if tokens.is_empty() { + continue; } - "isready" => { - println!("readyok"); - } - "ucinewgame" => { - // not yet implemented - } - "position" => { - if tokens.len() > 1 { - if tokens[1] == "fen" { - let fen = tokens[2..].join(" "); - engine.setpos_fen(&fen); - } else if tokens[1] == "startpos" { - if tokens.len() > 2 && tokens[2] == "moves" { - engine.setpos_startpos(&tokens[3..]); - } else { - engine.setpos_startpos(&[]); + + match tokens[0] { + "uci" => { + println!("id name {}", self.engine.name); + println!("id author {}", self.engine.author); + println!("uciok"); + } + "isready" => { + println!("readyok"); + } + "ucinewgame" => { + // not yet implemented + } + "position" => { + if tokens.len() > 1 { + if tokens[1] == "fen" { + let fen = tokens[2..].join(" "); + self.engine.setpos_fen(&fen); + } else if tokens[1] == "startpos" { + if tokens.len() > 2 && tokens[2] == "moves" { + self.engine.setpos_startpos(&tokens[3..]); + } else { + self.engine.setpos_startpos(&[]); + } } } } - } - "go" => { - println!("bestmove {}", engine.search(1000_u64)); - } - "stop" => { - // TODO stop search as soon as possible - } - "quit" => { - return; - } - _ => { - // Unknown command, just ignore + "go" => { + println!("bestmove {}", self.engine.search(1000_u64)); + } + "stop" => { + // TODO stop search as soon as possible + } + "quit" => { + return; + } + _ => { + // Unknown command, just ignore + } } } } } + + pub fn send_info( + depth: Option, + nodes: Option, + time: Option, + nps: Option, + score_cp: Option, + pv: Option<&str> + ) { + let mut output = String::from("info"); + + if let Some(d) = depth { + output.push_str(&format!(" depth {}", d)); + } + + if let Some(s) = score_cp { + output.push_str(&format!(" score cp {}", s)); + } + + if let Some(n) = nodes { + output.push_str(&format!(" nodes {}", n)); + } + + if let Some(n) = nps { + output.push_str(&format!(" nps {}", n)); + } + + if let Some(t) = time { + output.push_str(&format!(" time {}", t)); + } + + if let Some(p) = pv { + output.push_str(&format!(" pv {}", p)); + } + + println!("{}", output); + } } \ No newline at end of file diff --git a/tests/perft.rs b/tests/perft.rs index f2bb9b5..5c27641 100644 --- a/tests/perft.rs +++ b/tests/perft.rs @@ -1,13 +1,13 @@ use chess_engine::board::Board; use chess_engine::movegen::legal_check::is_other_king_attacked; -use chess_engine::movegen::picker::MoveGenerator; +use chess_engine::movegen::picker::MovePicker; fn count_legal_moves_recursive(board: &mut Board, depth: u8) -> u64 { if depth == 0 { return 1_u64; } - let mut generator = MoveGenerator::new(); + let mut generator = MovePicker::new(); let mut leaf_nodes = 0_u64; while let Some(mv) = generator.next(board) {