2 * Copyright (C) 2007-2011 Vincent Ollivier
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
33 // Adaptive Null-Move Pruning (Heinz 1999)
34 #define R_ADAPT(c, d) ( \
35 2 + ((d) > (6 + ((pieces.get_nb_pieces(c) < 3) ? 2 : 0))))
37 // Array of pruning margin values indexed by depth. Idea from Crafty
38 static const int futility_depth
= 3;
39 static const int futility_margins
[futility_depth
+ 1] = {
46 int Game::perft(int depth
) {
47 if (depth
== 0) return 1;
49 Color c
= current_node().get_turn_color();
50 bool use_lazy_generation
= false; // Useless overhead in perft()
51 Moves
moves(board
, pieces
, current_node(), use_lazy_generation
);
53 while (!(move
= moves
.next()).is_null()) {
55 if (!is_check(c
)) nodes
+= perft(depth
- 1);
61 int Game::quiescence_search(int alpha
, int beta
, int depth
) {
63 if (time
.poll(nodes_count
)) return 0;
65 int stand_pat
= eval();
66 if (depth
< -MAX_DEPTH
) return stand_pat
;
68 if (stand_pat
>= beta
) return stand_pat
; // Beta cut-off
71 int delta
= PIECE_VALUE
[QUEEN
]; // TODO: Switch off in late endgame
72 if (stand_pat
< alpha
- delta
) return alpha
;
74 if (alpha
< stand_pat
) alpha
= stand_pat
; // New alpha
76 Color player
= current_node().get_turn_color();
78 Moves
moves(board
, pieces
, current_node());
80 while (!(move
= moves
.next()).is_null()) {
81 if (moves
.get_state() > GOOD_CAPTURES
) break; // Skip bad captures
84 if (is_check(player
)) { // Illegal move
89 score
= -quiescence_search(-beta
, -alpha
, depth
- 1);
91 if (time
.poll(nodes_count
)) return 0;
99 if (time
.poll(nodes_count
)) return 0;
104 * Replaced by Principal Variation Search
107 int Game::alphabeta_search(int alpha, int beta, int depth) {
109 int old_alpha = alpha;
111 Transposition trans = tt.lookup(current_node().hash());
112 if (trans.get_bound() != UNDEF_BOUND) {
113 if (trans.get_depth() >= depth) {
114 int trans_score = trans.get_value();
115 switch (trans.get_bound()) {
116 case EXACT: return trans_score;
117 case UPPER: if (trans_score < beta) beta = trans_score; break;
118 case LOWER: if (trans_score > alpha) alpha = trans_score; break;
119 default: assert(false);
121 if (alpha >= beta) return trans_score;
123 Move bm = trans.get_best_move();
124 if (!bm.is_null()) best_move = bm;
126 if (depth <= 0) return quiescence_search(alpha, beta, 0); // Quiescence
127 if (tree.has_repetition_draw()) return 0; // Repetition draw rules
128 Color player = current_node().get_turn_color();
129 bool legal_move_found = false;
130 Moves moves = movegen();
131 moves.sort(board, best_move);
132 for (int i = 0; i < moves.size(); ++i) {
133 Move move = moves.at(i);
135 if (is_check(player)) { // Skip illegal move
139 legal_move_found = true;
140 score = -alphabeta_search(-beta, -alpha, depth - 1);
143 tt.save(current_node().hash(), score, LOWER, depth, move);
144 return beta; // FIXME Should it be score?
151 if (!legal_move_found) {
152 if (is_check(player)) return -INF + 100 - depth; // Checkmate
153 else return 0; // Stalemate
155 Bound bound = (old_alpha <= alpha ? UPPER : EXACT); // FIXME probably wrong
156 tt.save(current_node().hash(), alpha, bound, depth, best_move);
161 int Game::pv_search(int alpha
, int beta
, int depth
, NodeType node_type
) {
162 if (time
.poll(nodes_count
)) return 0;
163 if (depth
<= 0) return quiescence_search(alpha
, beta
, 0); // Quiescence
164 if (tree
.has_repetition_draw()) return 0; // Repetition draw rules
167 int old_alpha
= alpha
;
168 Node pos
= current_node();
169 int best_score
= -INF
;
173 // Lookup in Transposition Table
174 Transposition trans
= tt
.lookup(pos
.hash());
175 if (!trans
.is_empty()) {
176 if (trans
.get_depth() >= depth
) {
177 int tr_score
= trans
.get_value();
178 switch (trans
.get_bound()) {
179 case EXACT
: return tr_score
; // Already searched node
180 case UPPER
: if (tr_score
< beta
) beta
= tr_score
; break;
181 case LOWER
: if (tr_score
> alpha
) alpha
= tr_score
; break;
182 default: assert(!"Corrupted Transposition Table");
184 if (alpha
>= beta
) return tr_score
; // TT cause a cut-off
186 Move bm
= trans
.get_best_move();
187 if (!bm
.is_null()) best_move
= bm
; // Save the best move
191 Color player
= pos
.get_turn_color();
192 bool is_in_check
= is_check(player
);
195 if (is_in_check
) ++depth
;
199 //bool can_do_null != pos.get_last_move().is_null(); // No successive
200 bool can_do_null
= !pos
.get_null_move_right(); // No more than one
201 bool is_pv
= (node_type
== PV_NODE
);
202 bool null_move_allowed
= !is_in_check
&& can_do_null
&& !is_pv
;
204 if (null_move_allowed
&& depth
>= 2) {
206 make_move(null_move
);
207 pos
.set_null_move_right(false); // Forbide more than one null move
208 int reduced_depth
= depth
- R_ADAPT(player
, depth
) - 1;
209 score
= -pv_search(-beta
, -beta
+ 1, reduced_depth
, node_type
);
210 undo_move(null_move
);
211 if (score
>= beta
) return score
;
213 else if (!can_do_null
) {
214 // Next move we will have the right to do another null-move
215 pos
.set_null_move_right(true);
220 bool legal_move_found
= false;
221 bool is_principal_variation
= true;
223 Moves
moves(board
, pieces
, current_node());
224 //if (is_legal(best_move))
225 moves
.add(best_move
, BEST
);
226 //moves.add(get_killer_move(depth, 0), KILLERS);
227 //moves.add(get_killer_move(depth, 1), KILLERS);
230 while (!(move
= moves
.next()).is_null()) {
232 // Killer moves need pseudo legality checking before we made them,
233 // but they can cause a cut-off and dispense to generate quiet moves
235 if (is_killer_move(depth
, move
) && !is_legal(move
)) continue;
238 cout << endl << board;
239 cout << (player == WHITE ? "White" : "Black") << " to play ";
240 cout << output_move(move) << " (" << move << ")";
241 cout << " is legal" << endl;
246 assert(is_legal(move
) || assert_msg(
247 endl
<< board
<< endl
<<
248 "m = " << output_move(move
) << " (" << move
<< ")" << endl
<<
249 "m is en passant: " << move
.is_en_passant() << endl
<<
250 "m is promotion: " << move
.is_promotion() << endl
<<
251 "m is legal: " << is_legal(move
) << endl
<<
252 "m is killer: " << is_killer_move(depth
, move
) << endl
<<
253 hex
<< current_node().hash()
258 if (is_check(player
)) { // Skip illegal move
262 legal_move_found
= true;
264 // PVS code from http://www.talkchess.com/forum/viewtopic.php?t=26974
265 if (is_principal_variation
) {
266 best_score
= -pv_search(-beta
, -alpha
, depth
- 1, PV_NODE
);
269 if (best_score
> alpha
) {
270 if (best_score
>= beta
) {
271 // Store the search to Transposition Table
272 tt
.save(pos
.hash(), best_score
, LOWER
, depth
, move
);
274 // Update killer moves
275 if (!move
.is_capture()) set_killer_move(depth
, move
);
282 is_principal_variation
= false;
286 bool is_giving_check
= is_check(Color(!player
));
289 if (depth
<= futility_depth
&&
290 !is_in_check
&& !is_giving_check
&&
291 !move
.is_capture() && !move
.is_promotion()) {
292 score
= eval() + futility_margins
[depth
]; // Idea from Crafty
294 if (score
> best_score
) best_score
= score
;
300 // Late Move Reduction
301 if (depth
> 2 && // Not near leaf
302 !is_killer_move(depth
, move
) &&
303 !is_in_check
&& !is_giving_check
&&
304 !move
.is_capture() && !move
.is_promotion()) {
306 // Do the search at a reduced depth
307 score
= -pv_search(-alpha
- 1, -alpha
, depth
- 2, ALL_NODE
);
310 score
= -pv_search(-alpha
- 1, -alpha
, depth
- 1, ALL_NODE
);
314 if (alpha
< score
&& score
< beta
) {
315 score
= -pv_search(-beta
, -alpha
, depth
- 1, ALL_NODE
);
322 if (time
.poll(nodes_count
)) return 0;
323 if (score
> best_score
) { // Found a new best move
324 if (score
>= beta
) {// Sufficient to cause a cut-off?
325 // Store the search to Transposition Table
326 tt
.save(pos
.hash(), score
, LOWER
, depth
, move
);
328 // Update killer moves
329 if (!move
.is_capture()) set_killer_move(depth
, move
);
339 if (time
.poll(nodes_count
)) return 0;
340 if (!legal_move_found
) { // End of game?
341 if (is_in_check
) return -INF
+ 100 - depth
; // Checkmate
342 else return 0; // Stalemate
345 // Store the search to Transposition Table
346 Bound bound
= (best_score
<= old_alpha
? UPPER
: EXACT
);
347 tt
.save(pos
.hash(), best_score
, bound
, depth
, best_move
);
352 Move
Game::root(int max_depth
) {
353 time
.start_thinking(current_node().get_ply());
354 Color player
= current_node().get_turn_color();
355 print_thinking_header();
360 assert(max_depth
<= MAX_DEPTH
);
361 int best_scores
[MAX_DEPTH
];
362 for (ply
= 1; ply
< max_depth
; ++ply
) { // Iterative Deepening
366 if (time
.is_out_of_time()) break; // Do not start this ply if no time
367 if (time
.get_allocated_time() - time
.get_elapsed_time() < 100) {
368 // Decrease polling interval if <1s left
369 time
.set_polling_interval(100000);
374 for (int i
= 1; i
< 4; ++i
) {
375 int val
= best_scores
[ply
- i
];
376 if (-INF
+ 100 < val
&& val
< INF
- 100) is_mate
= false;
378 if (is_mate
) break; // The position was mate in the 3 previous ply
381 Moves
moves(board
, pieces
, current_node());
382 moves
.add(best_move
, BEST
);
384 for (int i
= 0; !(move
= moves
.next()).is_null(); ++i
) {
386 if (is_check(player
)) { // Skip illegal move
390 NodeType node_type
= (i
== 0 ? PV_NODE
: ALL_NODE
);
391 score
= -pv_search(-beta
, -alpha
, ply
- 1, node_type
);
393 //print_thinking(ply, score, move);
394 if (time
.is_out_of_time()) break; // Discard this move
399 //if (nodes_count > 200000) { // Save CPU time at the beginning
400 print_thinking(ply
, alpha
, best_move
);
404 if (time
.is_out_of_time()) {
405 // TODO Restore best_move and best_score from previous ply?
406 break; // Discard this ply
408 if (!best_move
.is_null()) {
409 tt
.save(current_node().hash(), alpha
, EXACT
, ply
, best_move
);
411 best_scores
[ply
] = best_score
;
412 //print_thinking(ply, best_score, best_move);
414 if (!best_move
.is_null()) {
415 print_thinking(ply
, best_score
, best_move
);