From ecf1de6c6a5430bae210ae6e92835f0604a23749 Mon Sep 17 00:00:00 2001 From: Moritz Eigenauer Date: Tue, 18 Nov 2025 15:19:30 +0100 Subject: [PATCH] iterative deepening --- progress_tracking/progress.xlsx | Bin 5602 -> 5634 bytes src/bin/suite.rs | 4 +-- src/engine.rs | 51 +++++++++++++++++++++++++++++--- src/search/alpha_beta.rs | 29 ++++++++++++++++-- src/uci.rs | 2 +- 5 files changed, 77 insertions(+), 9 deletions(-) diff --git a/progress_tracking/progress.xlsx b/progress_tracking/progress.xlsx index f28baa38f3b110df809a50a12df7a99c854fffa1..b0b512623f44491a2afe94ddeda100aea3fa2093 100644 GIT binary patch delta 1623 zcmV-d2B`VsD}pQxP)h>@6aWAK2mnudkqp8IdU9JMzRDSq)g}^3#d#0{bgd%FVPB^q zO&Zds-IEOg9Dj>#3|N%lR-LM~W5Vpb1EOJsIB@{QT`&-ODDE4pI1W!+5yQnNuYg2h zcuN!@FFA5D3VpK}y;)g`MQM(JP=f-Ej?jRTGmBuA`?F!HEM({8LbYuhwDH6Z z8&UF}Kfg}P`%*jPT7Z|OrC`DW$Hr#kk4)dyDY*#Kd1m%c0Oe|!z_2&4^G=nw`2L}I z^0Fw5);^^^T^Dr2l7vN@kI{A2e-^5ylzaI<@6aWAK2mnudvkn5{1r2&~TSOL|=vV{*0Anqa z{Rbd_Yj2}C6#Xj*X{GKL9>zQpC6SUAb}MbPRg=zscECwc0kg(w+J8SbBs33#GNV~g z*eX(Eu6?e3jve1?m%*0M*KCb3liuSf$%gWpv9zzMnZL#{$`q2~gx4$yjS=IIbfsn~ z#VE)@aijuGv(y-ci98zQwkJ9okPVB%1V2%KDci&`q8}3+k?l~{<-OrKT&-DdP)CCl zt?&%9my`qeqgwh2!Wbu6ND_(S(VF>JDdEcRzE5Zl7KpDO#(LZ6p*1T7L zO_l<@M4O0x8heTkH4B(!)ic@tiFXQ^d1pR}viz^KEiiyP@;4bH@!qp6#bHu>qxZdZ z2M8TpK!6> z6vkq?<#4WZn1~fQW0cp19LGlU$EiDin~$eY1C?=i!l?Q?=%gNc|MztIIIqCn*W-AH zv+*Bp1@NIB_~br&bLlS5ey_l~^;q}Y_%G=$t%Q2@(Amr5+?DQrJAtZP=~8Y`$_;W~ z;DRsRRCHYd8Zaf@=|fnhdcB99g^|4ss${PRF2i>mNGT;NinB~&6#1`VvZ|VYhQg7N zI}%&k+ep`-VnSOpdjs|DQ1$bsKyx@pv*$RvZCKE-j}cylb6c@&@hMv#X={|)8ui-R zrM8;WH?%5k>pfr#-~j4X+SUV6l&+DqzzlJLH^~KAjS5*Eh3|=6rW0Aun*!ZZ?4GSz zmhJFeR6jv@70!E#W0?l1Ruz_is=|6zozE3(_I0O9)p`#R@ljNzX+7*E@ktzqjK?v_ zU!!D&vno;HId7nGr8Ltp9Q{Pr^Tvj0Sx|w{v>|WK1JsGE=Lp~^rXfBjx~d18uuqa8 z6!8^BMTlg4N$f-G1L!~S-;-n7mY9R(^CrR)`$+Ln=DRteGcFgF`9cbR2@ToDZZ9uu z1munE06ZiVk>k^pV(R0Bg~G=hii!Yt+=K^^eY_!5qN@cb>RHCbwI}Cx&vPdZtgt&) z&zh7`s2rVIFuIBv>?||8v&?j@yVQ*CQnR{Ct=C;@P0SiUyH--uv}$VHek-%q4mAL} zOU>*qHK()Gj3zS3mZ&v81gh_yitHghBP%c)&D?}?X>r--h?B$Fb%WZaozo3ccU_j8K} zM$-H($pvAI#CdUzQGlsv!0VTUu<}6+p?t4;^c_%30|XQR000O8PkOUo44?u5dXf+5 z0eZ9P4}SpxdXs4p8Rr zSOfq7V=a?r6gdHUlb{qF0dteR6deJ2liL&=Ius880AF%tY;!Lza%F6DP)h*<6ay3h z000O8PkM4&tkihpQvm<~SONe54*&oF0000000000fC2rJF%=&HdXrHV907fkZWSs4 VdXt$I906jJwiPA@t`h(N0031+>`MRu delta 1591 zcmZ9MSyU1T6oy%HM>K&(4Rc9xO~s@nvnWI>#}tWDLCrJ+mpCp{E)`msOD-t5G@6>G zrlL)Oqk=giD~xaa@>d(VBi58q`AkvYWO1-uCk000yL zZnP{&n*uF@@+s->V_AbJ4Q0_@#e{P=3_AV&ZtJ^h_g74CRDnolbr{5x`%ZXta1`y| z@D12!eX!6jUSx?>bbx}81 z%gz;x5-yeXes7S}IKF=?%6eWkbb~bQ5EE@tv|*7U`VB|Ny^$N|52ijX7@upD>{lU^ zI-9>1FV~fzubs;6RS_R%m$#1uFIX2}k?xDjM$j&8iW5(#=X}Y@p6uoNOxjE3|NV=4 zC`cryD*MJoL0GokCIH}o31>q=j~T|0?9$vYKYr`*c%IO1QITC@L@B>XPMnaA7|NHT z!e{dPze%x5w}koBg?mN@)$EqL#jAF+!(WEk-*S83gr=jxi{acrFumJzl*E1MC)PY| zb2z+>lFNFQyzJ8GA>f{Z!%OTuN~*k%q_sI}f4bIayl0-HdaZ5Kej+=JDbnoAL}$D> zr$_I9B*Ic_y@)pVzi`)EV_xY);b9 z?VCSoK~o2?59RcEG2-n!kJUH}&jgm z;!YPmC}#!MyjX6Jqb-3z3+hX7e_siHQh%*}Uz& zeShSvXNT%F>e4~WZZ5|4s+n!V%o+rLEB2!No3yVV zDDysRE;D7#fcWTEvnpng?b?3a^#M<)Q*~*ZGC3*I*5`R&=(tUkRC7uxdrWY8mS?td zh|=7DocCs^?jHF@QC;f2+Mf6w$zVK;PEm6tHaeY5&=P?d*(w4F$V=xsFNInG^XkFh z4C-hIsm?uZe*rOgRJ};=p}Bo-CH`)eQ+XAU$*Wq$27D}1CR7cbNlwl*$g23r&wFAV zjqk81Z73$4V$`bfKes$~%b-)%H7$yFZxShruyYexSy^Jf@hgen(j%sVFV3$mkzx=TY(66{pvLU#Wbhr>dBHPP=QhKk?yYzD?CzD zRKyG2+5&?{We&tKx+B_VWS0@H>vm74ic`Nxt3=K}?Fk_~ofuEw)-smI;!6x3CrT60OiSU&J!?<^z%Sx-Xrz_pkM8aQ1i`^SDIYfB=9e2mnx%vHxeq j1IIZ;gs$vJI06au;bb7pWcr~@ALI-nQ1ThDADjOGPL String { - let (opt_move, _score) = alpha_beta(&mut self.board, depth, 0, -i32::MAX, i32::MAX); + pub fn search(&mut self, time_limit_ms: u64) -> String { + let start_time = Instant::now(); + let time_limit = Duration::from_millis(time_limit_ms); + + // We track nodes to limit how often we check the clock inside alpha_beta + let mut nodes = 0; + + // Initial search at depth 1 + let (mut opt_move, mut _score) = alpha_beta( + &mut self.board, + 1, + 0, + -i32::MAX, + i32::MAX, + start_time, + time_limit, + &mut nodes + ); + + let mut depth = 2; + + // Iterative Deepening + while start_time.elapsed() < time_limit { + let (new_move, new_score) = alpha_beta( + &mut self.board, + depth, + 0, + -i32::MAX, + i32::MAX, + start_time, + time_limit, + &mut nodes + ); + + // If time ran out during the search, alpha_beta returns garbage (None, 0). + // We must verify we still have time before accepting the new result. + if start_time.elapsed() > time_limit { + break; // Discard new_move, keep the one from the previous depth + } + + opt_move = new_move; + _score = new_score; + + depth += 1; + } if let Some(mv) = opt_move { mv.to_algebraic() @@ -42,4 +85,4 @@ impl Engine { "null".to_string() } } -} \ No newline at end of file +} diff --git a/src/search/alpha_beta.rs b/src/search/alpha_beta.rs index ea9e69a..7562d3a 100644 --- a/src/search/alpha_beta.rs +++ b/src/search/alpha_beta.rs @@ -1,8 +1,9 @@ -use crate::board::{Board, Color}; // <-- Assuming you have a Color enum (e.g., Color::White, Color::Black) +use crate::board::{Board, Color}; use crate::eval::basic::evaluate_board; use crate::movegen::generate_pseudo_legal_moves; use crate::movegen::legal_check::*; use crate::r#move::{Move, MoveList}; +use std::time::{Instant, Duration}; // A score high enough to be > any material eval, but low enough to not overflow when adding ply const MATE_SCORE: i32 = 1_000_000; @@ -21,7 +22,20 @@ pub fn alpha_beta( ply: u8, mut alpha: i32, beta: i32, + start_time: Instant, + time_limit: Duration, + nodes: &mut u64, ) -> (Option, i32) { + // Check for time usage every 4096 nodes to reduce system call overhead + if *nodes % 4096 == 0 { + if start_time.elapsed() > time_limit { + // Return immediately. The return value here effectively signals an abort, + // but the engine must discard this result. + return (None, 0); + } + } + *nodes += 1; + if depth == 0 { return (None, evaluate_board_relative(board)); } @@ -42,7 +56,18 @@ pub fn alpha_beta( legal_moves_found = true; // Recursive call with negated and swapped alpha/beta - let (_, score) = alpha_beta(board, depth - 1, ply + 1, -beta, -alpha); + // Pass time parameters and node counter down + let (_, score) = alpha_beta(board, depth - 1, ply + 1, -beta, -alpha, start_time, time_limit, nodes); + + // If we aborted deeper in the tree (returned 0 due to timeout), + // we should technically propagate that up, but checking elapsed() + // at the loop start (via recursion) handles it eventually. + // For a strict abort, we check here too: + if *nodes % 4096 == 0 && start_time.elapsed() > time_limit { + board.undo_move(undo_mv); + return (None, 0); + } + let current_score = -score; if current_score > best_score { diff --git a/src/uci.rs b/src/uci.rs index 47ef0e4..53526ad 100644 --- a/src/uci.rs +++ b/src/uci.rs @@ -42,7 +42,7 @@ pub fn uci_mainloop(engine: &mut Engine) { } "go" => { // TODO add a lot functionality - println!("bestmove {}", engine.search(6)); + println!("bestmove {}", engine.search(1000_u64)); } "stop" => { // TODO stop search as soon as possible