11#include " search.hpp"
2- namespace search
3- {
4- TranspositionTable tt (20 ); // 16MB
5- chess::Move PV[128 ];
6- int pvLength = 0 ;
7- const auto INVALID_MOVE = chess::Move();
8- void clearPV ()
9- {
10- std::memset (PV, 0 , sizeof (PV));
11- pvLength = 0 ;
12- }
132
14- void printPV (chess::Position &board)
15- {
16- std::cout << " PV: " ;
17- for (int i = 0 ; i < pvLength && PV[i] != INVALID_MOVE; ++i)
18- std::cout << PV[i] << " " ;
19- std::cout << std::endl;
20- }
3+ namespace search {
214
22- int16_t quiescence (chess::Position &board, int16_t alpha, int16_t beta, int ply)
23- {
24- nodes++;
25- int16_t standPat = eval (board);
26-
27- if (standPat >= beta)
28- return beta;
29- if (standPat > alpha)
30- alpha = standPat;
31-
32- chess::Movelist moves;
33- chess::movegen::legalmoves<chess::movegen::MoveGenType::CAPTURE>(moves, board);
34- move_ordering::scoreMoves (board, moves, ply);
35-
36- for (auto &move : moves)
37- {
38- board.makeMove (move);
39- int16_t score = -quiescence (board, -beta, -alpha, ply + 1 );
40- board.unmakeMove (move);
41-
42- if (score >= beta)
43- return beta;
44- if (score > alpha)
45- alpha = score;
46- }
47-
48- return alpha;
49- }
50- void updatePV (int ply, chess::Move move)
51- {
52-
53- // Clear the rest to avoid garbage from previous searches
54- for (int j = ply; j < 128 ; ++j)
55- PV[j] = INVALID_MOVE;
56- PV[ply] = move;
57- // Count PV length from root
58- pvLength = 0 ;
59- while (pvLength < 128 && PV[pvLength] != INVALID_MOVE)
60- ++pvLength;
61- }
62- int16_t alphaBeta (chess::Position &board, int16_t alpha, int16_t beta, int depth, int ply)
63- {
64- ++nodes;
65-
66- auto hash = board.hash ();
67- if (TTEntry *entry = tt.probe (hash))
68- {
69- if (entry->depth () >= depth)
70- {
71- if (entry->flag () == 0 )
72- {
73- // No PV update here (exact TT hit)
74- return entry->score ();
75- }
76- else if (entry->flag () == 1 && entry->score () >= beta)
77- {
78- // Beta cutoff
79- return entry->score ();
80- }
81- else if (entry->flag () == 2 && entry->score () <= alpha)
82- {
83- // Alpha cutoff
84- return entry->score ();
85- }
86- }
87- }
5+ TranspositionTable tt (20 ); // 16MB
6+ chess::Move PV[128 ];
7+ int pvLength = 0 ;
8+ const auto INVALID_MOVE = chess::Move();
889
89- chess::Movelist moves;
90- chess::movegen::legalmoves (moves, board);
91-
92- if (moves.empty ())
93- {
94- if (board.inCheck ())
95- {
96- return MATE (ply);
97- }
98- else
99- {
100- return 0 ;
101- }
102- }
10+ void clearPV () {
11+ std::memset (PV, 0 , sizeof (PV));
12+ pvLength = 0 ;
13+ }
10314
104- if (ply == 0 )
105- {
106- clearPV ();
107- }
15+ void printPV (chess::Position &board) {
16+ std::cout << " PV: " ;
17+ for (int i = 0 ; i < pvLength && PV[i] != INVALID_MOVE; ++i)
18+ std::cout << PV[i] << " " ;
19+ std::cout << std::endl;
20+ }
21+
22+ int16_t quiescence (chess::Position &board, int16_t alpha, int16_t beta,
23+ int ply) {
24+ nodes++;
25+ int16_t standPat = eval (board);
26+
27+ if (standPat >= beta)
28+ return beta;
29+ if (standPat > alpha)
30+ alpha = standPat;
31+
32+ chess::Movelist moves;
33+ chess::movegen::legalmoves<chess::movegen::MoveGenType::CAPTURE>(moves,
34+ board);
35+ move_ordering::scoreMoves (board, moves, ply);
36+
37+ for (auto &move : moves) {
38+ board.makeMove (move);
39+ int16_t score = -quiescence (board, -beta, -alpha, ply + 1 );
40+ board.unmakeMove (move);
41+
42+ if (score >= beta)
43+ return beta;
44+ if (score > alpha)
45+ alpha = score;
46+ }
47+
48+ return alpha;
49+ }
50+
51+ void updatePV (int ply, chess::Move move) {
52+ for (int j = ply; j < 128 ; ++j)
53+ PV[j] = INVALID_MOVE;
54+ PV[ply] = move;
55+ pvLength = 0 ;
56+ while (pvLength < 128 && PV[pvLength] != INVALID_MOVE)
57+ ++pvLength;
58+ }
59+
60+ int16_t alphaBeta (chess::Position &board, int16_t alpha, int16_t beta,
61+ int depth, int ply) {
62+ ++nodes;
10863
109- if (depth == 0 )
110- {
111- return eval (board); // Evaluate the leaf node
64+ auto hash = board.hash ();
65+ if (TTEntry *entry = tt.probe (hash)) {
66+ if (entry->depth () >= depth) {
67+ if (entry->flag () == 0 ) return entry->score ();
68+ if (entry->flag () == 1 && entry->score () >= beta) return entry->score ();
69+ if (entry->flag () == 2 && entry->score () <= alpha) return entry->score ();
11270 }
71+ }
11372
114- move_ordering::scoreMoves (board, moves, ply);
115- chess::Move bestMove;
116- int16_t bestScore = -MATE (0 );
73+ if (!board.inCheck () && depth >= 3 ) {
74+ board.makeNullMove ();
75+ int16_t nullScore = -alphaBeta (board, -beta, -beta + 1 , depth - 3 , ply + 1 );
76+ board.unmakeNullMove ();
11777
118- // Search with first move (narrow window) PVS (Principal Variation Search)
119- auto firstMove = moves.begin ();
120- board.makeMove (*firstMove);
121- int16_t score = -alphaBeta (board, -beta, -alpha + 1 , depth - 1 , ply + 1 );
122- board.pop ();
123-
124- if (score > bestScore)
125- {
126- bestScore = score;
127- bestMove = *firstMove;
128-
129- // Update PV for this best move
130- updatePV (ply, bestMove);
78+ if (nullScore >= beta) {
79+ return beta; // Null move cutoff
13180 }
81+ }
82+
83+ if (!board.inCheck () && depth <= 3 ) {
84+ const int RFP_MARGIN[] = {0 , 200 , 150 , 100 };
85+ int16_t standPat = eval (board);
86+ if (standPat + RFP_MARGIN[depth] <= alpha)
87+ return alpha;
88+ }
89+
90+ chess::Movelist moves;
91+ chess::movegen::legalmoves (moves, board);
92+
93+ if (moves.empty ()) {
94+ return board.inCheck () ? MATE (ply) : 0 ;
95+ }
96+
97+ if (ply == 0 ) clearPV ();
98+ if (depth == 0 ) return quiescence (board, alpha, beta, ply);
99+
100+ move_ordering::scoreMoves (board, moves, ply);
101+ chess::Move bestMove;
102+ int16_t bestScore = -MATE (0 );
103+ int moveCount = 0 ;
104+
105+ for (auto &move : moves) {
106+ ++moveCount;
132107
133- // Now search the remaining moves (full window)
134- for (auto &move : moves)
135- {
136- if (move == bestMove)
137- continue ; // Skip the best move (already searched)
108+ board.makeMove (move);
109+ int newDepth = depth - 1 ;
138110
111+ bool doLMR = depth >= 3 && moveCount > 3 && !board.inCheck ();
112+ if (doLMR && newDepth > 1 ) newDepth--;
113+
114+ if (newDepth <= 0 ) newDepth = 1 ;
115+
116+ int16_t score = -alphaBeta (board, -alpha - 1 , -alpha, newDepth, ply + 1 );
117+ board.pop ();
118+
119+ if (score > bestScore) {
120+ if (score > alpha && score < beta && doLMR && depth > 1 ) {
139121 board.makeMove (move);
140- score = -alphaBeta (board, -alpha - 1 , -alpha, depth - 1 , ply + 1 );
122+ score = -alphaBeta (board, -beta , -alpha, depth - 1 , ply + 1 );
141123 board.pop ();
124+ }
142125
143- if (score > bestScore)
144- {
145- bestScore = score;
146- bestMove = move;
147-
148- // Update PV for this best move
149- updatePV (ply, bestMove);
150- }
151-
152- if (score >= beta)
153- {
154- tt.store (hash, move, beta, depth, 1 ); // Store as lowerbound
155- move_ordering::updateKillers (move, ply);
156- return beta;
157- }
126+ bestScore = score;
127+ bestMove = move;
128+ updatePV (ply, bestMove);
158129 }
159130
160- // Store best move in TT
161- uint8_t flag = (bestScore <= alpha) ? 2 : (bestScore >= beta) ? 1 : 0 ;
162- tt.store (board.hash (), bestMove, bestScore, depth, flag);
131+ if (score >= beta) {
132+ tt.store (hash, move, beta, depth, 1 );
133+ move_ordering::updateKillers (move, ply);
134+ return beta;
135+ }
163136
164- // After the full search, update pvLength based on the current PV
165- if (ply == 0 )
166- {
167- pvLength = 0 ;
168- while (pvLength < 128 && PV[pvLength] != INVALID_MOVE)
169- ++pvLength;
137+ if (score > alpha) {
138+ alpha = score;
170139 }
140+ }
141+
142+ uint8_t flag = (bestScore <= alpha) ? 2 : (bestScore >= beta) ? 1 : 0 ;
143+ tt.store (board.hash (), bestMove, bestScore, depth, flag);
144+
145+ if (ply == 0 ) {
146+ pvLength = 0 ;
147+ while (pvLength < 128 && PV[pvLength] != INVALID_MOVE)
148+ ++pvLength;
149+ }
171150
172- return bestScore;
151+ return bestScore;
173152}
174153
175- }
154+ } // namespace search
0 commit comments