1 /* Copyright (C) 2007-2011 Vincent Ollivier
3 * Purple Haze is free software: you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation, either version 3 of the License, or
6 * (at your option) any later version.
8 * Purple Haze is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
27 unsigned long long int Game::perft(unsigned int depth
)
29 if (depth
== 0) return 1;
30 unsigned int nodes
= 0;
31 Color c
= current_position().get_turn_color();
32 bool use_lazy
= false; // Lazy moves generation is not usefull in perft()
33 Moves
moves(board
, pieces
, current_position(), search_moves
, use_lazy
);
35 while (!(move
= moves
.next()).is_null()) {
38 if (is_legal(move
)) ++nodes
; // TODO: Finish is_legal()
42 if (!is_check(c
)) nodes
+= perft(depth
- 1);
51 int Game::quiescence(int alpha
, int beta
, int depth
, const int ply
)
53 if (time
.poll(nodes_count
)) return 0;
55 const int stand_pat
= eval(alpha
, beta
);
56 if (ply
>= MAX_PLY
) return stand_pat
;
58 if (stand_pat
>= beta
) return stand_pat
; // Beta cut-off
61 const int delta
= PIECE_VALUE
[QUEEN
]; // TODO: Switch off in late endgame
62 if (stand_pat
< alpha
- delta
) return alpha
;
64 if (alpha
< stand_pat
) alpha
= stand_pat
; // New alpha
66 Color player
= current_position().get_turn_color();
68 Moves
moves(board
, pieces
, current_position(), search_moves
);
70 while (!(move
= moves
.next()).is_null()) {
71 if (moves
.get_state() > GOOD_CAPTURES
) break; // Skip bad captures
74 if (is_check(player
)) { // Illegal move
79 const int score
= -quiescence(-beta
, -alpha
, depth
- 1, ply
+ 1);
81 if (time
.poll(nodes_count
)) return 0;
89 if (time
.poll(nodes_count
)) return 0;
93 template<NodeType node_type
>
94 int Game::search(int alpha
, int beta
, int depth
, const int ply
)
96 if (time
.poll(nodes_count
)) return 0;
97 if (depth
<= 0) return quiescence(alpha
, beta
, 0, ply
+ 1); // Quiescence
98 if (tree
.has_repetition_draw()) return 0; // Repetition draw rules
101 const int old_alpha
= alpha
;
102 Position pos
= current_position();
103 int best_score
= -INF
;
105 const bool is_pv
= (node_type
== PV
);
107 // Lookup in Transposition Table
109 Transposition trans
= tt
.lookup(pos
.hash(), &is_empty
);
111 // FIXME Avoid a potential bug with tt.lookup()
112 const bool discard
= pos
.hash() == 0 &&
113 trans
.get_bound() == UNDEF_BOUND
;
115 if (/*!is_pv &&*/ depth
<= trans
.get_depth() && !discard
) {
116 const int tr_score
= trans
.get_value();
117 switch (trans
.get_bound()) {
118 case EXACT
: return tr_score
; // Already searched node
119 case UPPER
: if (tr_score
< beta
) beta
= tr_score
; break;
120 case LOWER
: if (tr_score
> alpha
) alpha
= tr_score
; break;
121 default: assert(false);
123 if (alpha
>= beta
) return tr_score
; // TT cause a cut-off
125 Move bm
= trans
.get_best_move();
126 if (!bm
.is_null()) best_move
= bm
; // Save the best move
129 const Color player
= pos
.get_turn_color();
130 const bool is_in_check
= is_check(player
);
131 const bool is_null_move
= !pos
.get_null_move_right(); // No more than one
134 if (is_in_check
) ++depth
;
138 const bool null_move_allowed
= !is_in_check
&& !is_null_move
&& !is_pv
;
140 const int nb_pieces
= pieces
.get_nb_pieces(player
);
141 if (null_move_allowed
&& depth
> NMP_DEPTH
&& nb_pieces
> 3) {
143 make_move(null_move
);
144 current_position().set_null_move_right(false); // No consecutive NM
145 const int r_depth
= depth
- R_ADAPT(depth
, nb_pieces
) - 1;
146 score
= -search
<node_type
>(-beta
, -beta
+ 1, r_depth
, ply
+ 1);
147 undo_move(null_move
);
151 } else if (is_null_move
) {
152 // Next move we will again have the right to do another null-move
153 pos
.set_null_move_right(true);
157 // Internal Iterative Deepening
158 if (depth
> IID_DEPTH
&& best_move
.is_null() && !is_null_move
&& is_pv
) {
159 Moves
moves(board
, pieces
, current_position(), search_moves
);
160 int internal_best_score
= -INF
;
162 while (!(move
= moves
.next()).is_null()) {
164 if (is_check(player
)) {
168 score
= -search
<PV
>(-beta
, -alpha
, depth
/ 2, ply
+ 1);
170 if (score
> internal_best_score
) {
171 internal_best_score
= score
;
175 // TODO Call search directly and find the best move in TT?
177 tt.save(pos.hash(), 0, UNDEF_BOUND, 0, Move());
178 Transposition trans_test = tt.lookup(pos.hash());
179 pos.set_null_move_right(false);
180 score = search<PV>(alpha, beta, depth / 2, ply);
181 if (score <= alpha) {
182 // Avoid storing lower bound in TT
183 score = search<PV>(-INF, beta, depth / 2, ply);
185 pos.set_null_move_right(true);
186 Move trans_move = tt.lookup(pos.hash()).get_best_move();
187 if (trans_move != best_move) {
188 cout << "score=" << score << " alpha=" << alpha << " ";
189 cout << trans_test.to_string() << " ";
190 cout << internal_best_score << " " << best_move << " ";
191 cout << tt.lookup(pos.hash()).to_string() << endl;
193 //assert(trans_move == best_move); // trans_move is sometime null
197 bool legal_move_found
= false;
198 bool is_principal_variation
= true;
200 Moves
moves(board
, pieces
, current_position(), search_moves
);
201 moves
.add(best_move
, BEST
);
203 // Killer moves need pseudo legality checking before we can use them,
204 // but they can cause a cut-off and dispense to generate quiet moves
206 for (int i
= 0; i
< MAX_KILLERS
; ++i
) {
207 Move killer
= get_killer_move(depth
, i
);
208 if (is_legal(killer
)) moves
.add(killer
, KILLERS
);
212 while (!(move
= moves
.next()).is_null()) {
213 if (move
.is_capture()) {
214 if (board
.get_piece(move
.get_dest()).get_type() == KING
) {
215 return INF
- ply
; // Checkmate
219 if (is_check(player
)) { // Skip illegal move
223 legal_move_found
= true;
225 // PVS code from http://www.talkchess.com/forum/viewtopic.php?t=26974
226 if (is_principal_variation
) {
227 best_score
= -search
<PV
>(-beta
, -alpha
, depth
- 1, ply
+ 1);
230 if (best_score
> alpha
) {
231 if (best_score
>= beta
) { // Beta cut-off
232 // Update killer moves
233 if (!move
.is_capture()) set_killer_move(depth
, move
);
240 is_principal_variation
= false;
242 const bool is_giving_check
= is_check(!player
);
245 if (depth
<= FUTILITY_DEPTH
&&
246 //!best_move.is_null() &&
247 !is_in_check
&& !is_giving_check
&&
248 !is_killer_move(depth
, move
) &&
249 !move
.is_capture() && !move
.is_promotion()) {
250 // Using an array of margins is an idea from Crafty
251 score
= material_eval() + FUTILITY_MARGINS
[depth
];
253 if (score
> best_score
) best_score
= score
;
259 // Late Move Reduction
260 if (depth
> LMR_DEPTH
&& // TODO find the best minimal depth
261 //!best_move.is_null() &&
262 !is_in_check
&& !is_giving_check
&&
263 !is_killer_move(depth
, move
) &&
264 !move
.is_capture() && !move
.is_promotion()) {
266 // Do the search at a reduced depth
267 score
= -search
<ALL
>(-alpha
- 1, -alpha
, depth
- 2, ply
+ 1);
269 score
= -search
<ALL
>(-alpha
- 1, -alpha
, depth
- 1, ply
+ 1);
273 if (alpha
< score
&& score
< beta
) {
274 score
= -search
<ALL
>(-beta
, -alpha
, depth
- 1, ply
+ 1);
281 if (time
.poll(nodes_count
)) return 0;
282 if (score
> best_score
) { // Found a new best move
285 if (score
>= beta
) {// Sufficient to cause a cut-off?
286 // Update killer moves
287 if (!move
.is_capture()) set_killer_move(depth
, move
);
294 if (time
.poll(nodes_count
)) return 0;
295 if (!legal_move_found
) { // End of game?
296 if (is_in_check
) return -INF
+ ply
; // Checkmate
297 return 0; // Stalemate
301 // Store the search to Transposition Table
302 //assert(!best_move.is_null());
303 if (depth
>= trans
.get_depth() /*&& !is_null_move*/) {
304 const int value
= best_score
;
305 const Bound bound
= (best_score
>= beta
? LOWER
:
306 (best_score
<= old_alpha
? UPPER
: EXACT
));
307 if (bound
== UPPER
) best_move
= Move(); // Don't store best move
308 tt
.save(pos
.hash(), value
, bound
, depth
, best_move
);
314 Move
Game::root(int max_depth
)
316 time
.start_thinking(current_position().get_ply());
317 Color player
= current_position().get_turn_color();
318 print_thinking_header();
322 assert(max_depth
<= MAX_PLY
);
323 int it
; // Iteration of Depth
324 int best_scores
[MAX_PLY
];
325 for (it
= 1; it
< max_depth
; ++it
) { // Iterative Deepening
329 if (time
.is_out_of_time()) break; // Do not start this ply if no time
330 if (time
.get_allocated_time() - time
.get_elapsed_time() < 100) {
331 // Decrease polling interval if <1s left
332 time
.set_polling_interval(100000);
337 for (int i
= 1; i
< 4; ++i
) {
338 int val
= best_scores
[it
- i
];
339 if (-INF
+ MAX_PLY
< val
&& val
< INF
- MAX_PLY
) {
343 if (is_mate
) break; // The position was mate in the 3 previous ply
346 Moves
moves(board
, pieces
, current_position(), search_moves
);
347 moves
.add(best_move
, BEST
);
350 for (nb_moves
= 1; !(move
= moves
.next()).is_null(); ++nb_moves
) {
352 if (is_check(player
)) { // Skip illegal move
358 //NodeType node_type = (nb_moves ? PV : ALL);
359 //score = -search<node_type>(-beta, -alpha, it - 1, 1);
361 score
= -search
<PV
>(-beta
, -alpha
, it
- 1, 1);
363 score
= -search
<ALL
>(-beta
, -alpha
, it
- 1, 1);
366 if (time
.is_out_of_time()) break; // Discard this move
371 if (nodes_count
> 200000) { // Save CPU time at the beginning
372 print_thinking(it
, alpha
, best_move
);
376 if (time
.is_out_of_time()) {
377 // TODO: restore best_move and best_score from previous ply?
378 break; // Discard this ply
380 if (!best_move
.is_null()) {
381 tt
.save(current_position().hash(), alpha
, EXACT
, it
, best_move
);
383 best_scores
[it
] = best_score
;
385 // If there is only one legal move, no iterative deepening needed
386 if (nb_moves
== 1) break;
388 if (!best_move
.is_null()) {
389 print_thinking(it
, best_score
, best_move
);