1 /* Copyright (C) 2007-2012 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/>.
25 inline bool Game::is_dangerous(Move m
)
27 // current_position() is used assuming
28 // that the move as already been made.
30 if (board
[m
.dest()].is(PAWN
)) {
31 const Color c
= !current_position().side();
32 if (m
.dest_rank() + RANK_6
* c
== RANK_7
) {
39 const Piece capture = current_position().capture();
40 if (capture.type() != PAWN) {
45 return m.is_promotion();
48 return m
.is_capture() || m
.is_promotion();
51 unsigned long long int Game::perft(unsigned int depth
)
56 unsigned long long int nodes
= 0;
57 Color c
= current_position().side();
58 bool use_lazy
= false; // Lazy moves generation is not usefull in perft()
59 Moves
moves(board
, pieces
, current_position(), search_moves
, use_lazy
);
61 while (!(move
= moves
.next()).is_null()) {
65 ++nodes
; // TODO: Finish is_legal()
71 nodes
+= perft(depth
- 1);
81 int Game::quiescence(int alpha
, int beta
, int depth
, const int ply
)
83 if (time
.poll(nodes_count
)) {
87 const int stand_pat
= eval(alpha
, beta
);
92 if (stand_pat
>= beta
) {
93 return stand_pat
; // Beta cut-off
97 const int delta
= PIECE_VALUE
[QUEEN
]; // TODO: Switch off in late endgame
98 if (stand_pat
< alpha
- delta
) {
102 if (alpha
< stand_pat
) {
103 alpha
= stand_pat
; // New alpha
106 Color player
= current_position().side();
108 Moves
moves(board
, pieces
, current_position(), search_moves
);
110 while (!(move
= moves
.next()).is_null()) {
111 if (moves
.state() > GOOD_CAPTURES
) {
112 break; // Skip bad captures
116 if (is_check(player
)) { // Illegal move
121 const int score
= -quiescence(-beta
, -alpha
, depth
- 1, ply
+ 1);
123 if (time
.poll(nodes_count
)) {
133 if (time
.poll(nodes_count
)) {
139 template<NodeType node_type
>
140 int Game::search(int alpha
, int beta
, int depth
, const int ply
)
142 if (time
.poll(nodes_count
)) {
146 return quiescence(alpha
, beta
, 0, ply
+ 1); // Quiescence
148 if (tree
.has_repetition_draw()) {
149 return 0; // Repetition draw rules
153 const int old_alpha
= alpha
;
154 Position pos
= current_position();
155 int best_score
= -INF
;
157 const bool is_pv
= (node_type
== PV
);
159 // Lookup in Transposition Table
161 Transposition trans
= tt
.lookup(pos
.hash(), &is_empty
);
163 // FIXME Avoid a potential bug with tt.lookup()
164 const bool discard
= pos
.hash() == 0 && trans
.bound() == UNDEF_BOUND
;
166 if (/*!is_pv &&*/ depth
<= trans
.depth() && !discard
) {
167 const int tr_score
= trans
.value();
168 switch (trans
.bound()) {
170 if (tr_score
> alpha
) {
175 if (tr_score
< beta
) {
180 return tr_score
; // Already searched node
185 return tr_score
; // TT cause a cut-off
189 // If the transposition does not contain the best move,
190 // best_move.is_null() will be true.
191 best_move
= trans
.best_move();
194 const Color player
= pos
.side();
195 const bool is_in_check
= is_check(player
);
196 const bool is_null_move
= !pos
.can_null_move(); // No more than one
207 const int nb_pieces
= pieces
.count(player
);
208 const int nb_pawns
= pieces
.count(player
, PAWN
);
209 const bool is_pawn_ending
= nb_pieces
== nb_pawns
+ 1; // Pawns + king
211 const bool nmp_allowed
=
217 if (nmp_allowed
&& depth
> NMP_DEPTH
&& nb_pieces
> 3) {
219 make_move(null_move
);
220 current_position().set_null_move_right(false); // No consecutive NM
221 const int r
= R_ADAPT(depth
, nb_pieces
);
222 score
= -search
<node_type
>(-beta
, -beta
+ 1, depth
- r
- 1, ply
+ 1);
223 undo_move(null_move
);
227 } else if (is_null_move
) {
228 // Next move we will again have the right to do another null-move
229 pos
.set_null_move_right(true);
234 // Internal Iterative Deepening
235 const bool iid_allowed
= !is_null_move
&& is_pv
;
236 if (iid_allowed
&& depth
> IID_DEPTH
&& best_move
.is_null()) {
237 search
<PV
>(alpha
, beta
, depth
/ 2, ply
);
238 best_move
= tt
.lookup(pos
.hash(), &is_empty
).best_move();
242 bool legal_move_found
= false;
243 bool is_principal_variation
= true;
245 Moves
moves(board
, pieces
, current_position(), search_moves
);
246 moves
.add(best_move
, BEST
);
248 // Killer moves need pseudo legality checking before we can use them,
249 // but they can cause a cut-off and dispense to generate quiet moves
251 for (const Move
&killer_move
: killers(depth
)) {
252 if (is_legal(killer_move
)) {
253 moves
.add(killer_move
, KILLERS
);
258 while (!(move
= moves
.next()).is_null()) {
260 if (is_check(player
)) { // Skip illegal move
264 legal_move_found
= true;
266 // PVS code from http://www.talkchess.com/forum/viewtopic.php?t=26974
267 if (is_principal_variation
) {
268 best_score
= -search
<PV
>(-beta
, -alpha
, depth
- 1, ply
+ 1);
271 if (best_score
> alpha
) {
272 if (best_score
>= beta
) { // Beta cut-off
273 // Update killer moves
274 if (!move
.is_capture()) {
275 set_killer(move
, depth
);
283 is_principal_variation
= false;
285 const bool is_giving_check
= is_check(!player
);
289 const bool fp_allowed
=
292 !is_killer(move
, depth
) &&
293 !is_dangerous(move
) &&
296 best_score
< INF
- MAX_PLY
&&
297 pieces
.count(!player
) > 3;
299 if (fp_allowed
&& depth
<= FUTILITY_DEPTH
) {
300 // Using an array of margins is an idea from Crafty
301 score
= material_eval() + FUTILITY_MARGINS
[depth
];
309 int r
= 0; // Depth reduction
312 // Late Move Reduction
313 const bool lmr_allowed
=
316 !is_killer(move
, depth
) &&
317 !move
.is_capture() &&
318 !move
.is_promotion();
320 if (lmr_allowed
&& depth
> LMR_DEPTH
) {
321 ++r
; // Do the search at a reduced depth
326 score
= -search
<ALL
>(-alpha
- 1, -alpha
, depth
- r
- 1, ply
+ 1);
329 if (alpha
< score
&& score
< beta
) {
330 score
= -search
<ALL
>(-beta
, -alpha
, depth
- 1, ply
+ 1);
337 if (time
.poll(nodes_count
)) {
340 if (score
> best_score
) { // Found a new best move
343 if (score
>= beta
) { // Sufficient to cause a cut-off?
344 if (!move
.is_capture()) {
345 set_killer(move
, depth
); // Update killer moves
353 if (time
.poll(nodes_count
)) {
356 if (!legal_move_found
) { // End of game?
358 return -INF
+ ply
; // Checkmate
360 return 0; // Stalemate
365 // Store the search to Transposition Table
366 //assert(!best_move.is_null());
367 if (depth
>= trans
.depth() /*&& !is_null_move*/) {
368 const int value
= best_score
;
369 const Bound bound
= (best_score
>= beta
? LOWER
:
370 (best_score
<= old_alpha
? UPPER
: EXACT
));
371 if (bound
== UPPER
) {
372 best_move
= Move(); // Don't store best move
374 tt
.save(pos
.hash(), value
, bound
, depth
, best_move
);
379 Move
Game::root(int max_depth
)
381 assert(max_depth
<= MAX_PLY
);
384 search_moves
.clear();
385 time
.start_thinking(tree
.ply());
386 print_thinking_header();
390 int best_scores
[MAX_PLY
];
392 for (depth
= 1; depth
< max_depth
; ++depth
) { // Iterative Deepening
396 if (time
.poll(nodes_count
)) {
397 break; // Do not start a new ply
400 // Increase poll frequency as time is running out
401 const int time_remaining
= time
.allocated() - time
.elapsed();
402 if (time_remaining
< 1000) {
403 time
.set_polling_interval(time_remaining
* 512);
409 for (int i
= 1; i
< 4; ++i
) {
410 int val
= best_scores
[depth
- i
];
411 if (-INF
+ MAX_PLY
< val
&& val
< INF
- MAX_PLY
) {
416 break; // The position was mate in the 3 previous plies
420 Moves
moves(board
, pieces
, current_position(), search_moves
);
421 moves
.add(best_move
, BEST
);
424 for (nb_moves
= 1; !(move
= moves
.next()).is_null(); ++nb_moves
) {
426 if (is_check(!current_position().side())) { // Skip illegal move
432 score
= -search
<PV
>(-beta
, -alpha
, depth
- 1, 1);
434 score
= -search
<ALL
>(-beta
, -alpha
, depth
- 1, 1);
437 if (time
.poll(nodes_count
)) {
438 break; // Discard the move
444 if (nodes_count
> 200000) { // Save CPU time at the beginning
445 print_thinking(depth
, alpha
, best_move
);
449 if (time
.poll(nodes_count
)) {
450 // TODO: restore best_move and best_score from previous ply?
451 break; // Discard this ply
453 if (!best_move
.is_null()) {
454 tt
.save(current_position().hash(), alpha
, EXACT
, depth
, best_move
);
456 best_scores
[depth
] = best_score
;
458 if (nb_moves
== 1) { // If only one move allowed,
459 break; // no need to do iterative deepening
462 if (!best_move
.is_null()) {
463 print_thinking(depth
, best_score
, best_move
);