66#include < atomic>
77#include < moves_io.h>
88#include < position.h>
9+ #include < printers.h>
910using namespace chess ;
1011namespace engine {
1112TranspositionTable search::tt (16 );
1213std::atomic<bool > stopSearch{false };
13- void search::stop () {
14- tt.clear ();
15- stopSearch.store (true , std::memory_order_relaxed);
16- }
14+ void search::stop () { stopSearch.store (true , std::memory_order_relaxed); }
1715struct Session {
1816 timeman::TimeManagement tm;
1917 timeman::LimitsType tc;
@@ -29,7 +27,11 @@ void update_pv(Move *pv, Move move, const Move *childPv) {
2927}
3028Value qsearch (Board &board, Value alpha, Value beta, Session &session,
3129 int ply = 0 ) {
30+ if (session.tm .elapsed () >= session.tm .optimum () ||
31+ stopSearch.load (std::memory_order_relaxed))
32+ return VALUE_NONE;
3233 session.nodes ++;
34+ session.seldepth = std::max (session.seldepth , ply);
3335 int standPat = eval::eval (board);
3436 Value maxScore = standPat;
3537 if (maxScore >= beta)
@@ -40,8 +42,11 @@ Value qsearch(Board &board, Value alpha, Value beta, Session &session,
4042 board.legals <MoveGenType::CAPTURE>(moves);
4143 for (Move move : moves) {
4244 board.doMove (move);
43- Value score = - qsearch (board, -beta, -alpha, session, ply + 1 );
45+ Value score = qsearch (board, -beta, -alpha, session, ply + 1 );
4446 board.undoMove ();
47+ if (score == VALUE_NONE)
48+ return VALUE_NONE;
49+ score = -score;
4550 if (score >= beta)
4651 return score;
4752 if (score > maxScore)
@@ -51,7 +56,7 @@ Value qsearch(Board &board, Value alpha, Value beta, Session &session,
5156 }
5257 return maxScore;
5358}
54- Value doSearch (Board & board, int depth, Value alpha, Value beta,
59+ Value doSearch (Board board, int depth, Value alpha, Value beta,
5560 Session &session, int ply = 0 ) {
5661 if (ply >= MAX_PLY - 1 )
5762 return eval::eval (board);
@@ -97,7 +102,7 @@ Value doSearch(Board &board, int depth, Value alpha, Value beta,
97102 preferred = Move (entry->getMove ());
98103 }
99104 if (depth == 0 ) {
100- return qsearch (board, alpha, beta, session, ply + 1 );
105+ return qsearch (board, alpha, beta, session, ply);
101106 }
102107 Value maxScore = -VALUE_INFINITE;
103108 Movelist moves;
@@ -107,20 +112,73 @@ Value doSearch(Board &board, int depth, Value alpha, Value beta,
107112 return board.checkers () ? -MATE (ply) : 0 ;
108113 }
109114 movepick::orderMoves (board, moves, preferred, ply);
110- for (Move move : moves) {
115+ if (bool useNMP = depth >= 3 && !board.checkers () && ply > 0 ) {
116+ int R = 2 + depth / 6 ;
117+ board.doNullMove ();
118+ Value score =
119+ doSearch (board, depth - 1 - R, -beta, -beta + 1 , session, ply + 1 );
120+
121+ if (score == VALUE_NONE) {
122+ board.undoMove ();
123+ return VALUE_NONE;
124+ }
125+ score = -score;
126+ board.undoMove ();
127+ if (score >= beta)
128+ return score;
129+ }
130+ for (size_t i = 0 ; i < moves.size (); ++i) {
131+ Move move = moves[i];
132+
133+ bool isCapture = board.isCapture (move);
134+ bool givesCheck = board.givesCheck (move) != CheckType::NO_CHECK;
135+
136+ // --- LMR reduction ---
137+ int reduction = 0 ;
138+ if (i >= 3 && depth >= 3 && !isCapture && !givesCheck) {
139+ reduction = 1 + (int )(i / 6 ) + (depth / 8 );
140+
141+ // history heuristic: good moves get reduced less
142+ if (movepick::historyHeuristic[(int )move.from ()][(int )move.to ()] > 0 )
143+ reduction--;
144+
145+ reduction = std::max (0 , reduction);
146+ reduction = std::min (reduction, depth - 2 );
147+ }
111148
112149 board.doMove (move);
113150
114- Value childScore =
115- doSearch (board, depth - 1 , -beta, -alpha, session, ply + 1 );
151+ Value score;
116152
117- board.undoMove ();
153+ if (i == 0 ) {
154+ // --- First move: full window (PVS root move) ---
155+ score = -doSearch (board, depth - 1 , -beta, -alpha, session, ply + 1 );
118156
119- // ---- ABORT PROPAGATION ----
120- if (childScore == VALUE_NONE)
121- return VALUE_NONE;
157+ if (score == VALUE_NONE) {
158+ board.undoMove ();
159+ return VALUE_NONE;
160+ }
161+ } else {
162+ // --- Null-window search (PVS + LMR) ---
163+ score = doSearch (board, depth - 1 - reduction, -alpha - 1 , -alpha,
164+ session, ply + 1 );
165+ if (score == VALUE_NONE) {
166+ board.undoMove ();
167+ return VALUE_NONE;
168+ }
169+ score = -score;
170+ // --- Re-search if it improves alpha ---
171+ if (score > alpha) {
172+ score = doSearch (board, depth - 1 , -beta, -alpha, session, ply + 1 );
173+ if (score == VALUE_NONE) {
174+ board.undoMove ();
175+ return VALUE_NONE;
176+ }
177+ score = -score;
178+ }
179+ }
122180
123- Value score = -childScore ;
181+ board. undoMove () ;
124182
125183 if (score > maxScore) {
126184 maxScore = score;
@@ -129,18 +187,20 @@ Value doSearch(Board &board, int depth, Value alpha, Value beta,
129187
130188 if (score > alpha) {
131189 alpha = score;
132- if (!board.isCapture (move))
190+
191+ if (!isCapture)
133192 movepick::historyHeuristic[(int )move.from ()][(int )move.to ()] +=
134193 depth * depth;
135194 }
195+
136196 if (alpha >= beta) {
137- if (!board.isCapture (move)) {
197+ // killer moves
198+ if (!isCapture) {
138199 if (movepick::killerMoves[ply][0 ] != move) {
139200 movepick::killerMoves[ply][1 ] = movepick::killerMoves[ply][0 ];
140201 movepick::killerMoves[ply][0 ] = move;
141202 }
142203 }
143-
144204 break ;
145205 }
146206
@@ -165,6 +225,7 @@ Value doSearch(Board &board, int depth, Value alpha, Value beta,
165225}
166226void search::search (const chess::Board &board,
167227 const timeman::LimitsType timecontrol) {
228+ stopSearch = false ;
168229 static double originalTimeAdjust = -1 ;
169230 Session session;
170231 session.tc = timecontrol;
@@ -178,12 +239,11 @@ void search::search(const chess::Board &board,
178239 // since MAX_PLY=64
179240 session.pv [_][j] = Move::none ();
180241 }
181- session.nodes = 0 ;
182242 auto board_ = board;
183243 Value score_ =
184244 doSearch (board_, i, -VALUE_INFINITE, VALUE_INFINITE, session);
185245 if (session.tm .elapsed () >= session.tm .optimum () ||
186- stopSearch.load (std::memory_order_relaxed) || score_ == VALUE_NONE)
246+ stopSearch.load (std::memory_order_relaxed) || abs ( score_) == VALUE_NONE)
187247 break ;
188248 InfoFull info{};
189249 info.depth = i;
@@ -195,6 +255,18 @@ void search::search(const chess::Board &board,
195255 info.timeMs = session.tm .elapsed ();
196256 info.multiPV = 1 ;
197257 info.score = score_;
258+ TTEntry *entry = tt.lookup (board.hash ());
259+ if (entry)
260+ switch (entry->getFlag ()) {
261+ case LOWERBOUND:
262+ info.bound = " lowerbound" ;
263+ break ;
264+ case UPPERBOUND:
265+ info.bound = " upperbound" ;
266+ break ;
267+ default :
268+ break ;
269+ }
198270 std::string pv = " " ;
199271 for (Move *m = session.pv [0 ]; *m != Move::none (); m++)
200272 pv += chess::uci::moveToUci (*m, board.chess960 ()) + " " ;
@@ -233,7 +305,7 @@ void search::search(const chess::Board &board,
233305 info.nodes = 1 ;
234306 info.score = 0 ;
235307 info.multiPV = 1 ;
236- info.pv = chess::uci::moveToUci (best, board.chess960 ());
308+ info.pv = std::string ( chess::uci::moveToUci (best, board.chess960 () ));
237309 report (info);
238310
239311 report (chess::uci::moveToUci (best, board.chess960 ()));
0 commit comments