-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathmovepick.cpp
357 lines (293 loc) · 11.3 KB
/
movepick.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
Copyright (C) 2008-2010 Marco Costalba, Joona Kiiski, Tord Romstad
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Stockfish is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
////
//// Includes
////
#include <cassert>
#include "history.h"
#include "movegen.h"
#include "movepick.h"
#include "search.h"
#include "value.h"
////
//// Local definitions
////
namespace {
enum MovegenPhase {
PH_TT_MOVES, // Transposition table move and mate killer
PH_GOOD_CAPTURES, // Queen promotions and captures with SEE values >= 0
PH_KILLERS, // Killer moves from the current ply
PH_NONCAPTURES, // Non-captures and underpromotions
PH_BAD_CAPTURES, // Queen promotions and captures with SEE values < 0
PH_EVASIONS, // Check evasions
PH_QCAPTURES, // Captures in quiescence search
PH_QCHECKS, // Non-capture checks in quiescence search
PH_STOP
};
CACHE_LINE_ALIGNMENT
const uint8_t MainSearchPhaseTable[] = { PH_TT_MOVES, PH_GOOD_CAPTURES, PH_KILLERS, PH_NONCAPTURES, PH_BAD_CAPTURES, PH_STOP};
const uint8_t EvasionsPhaseTable[] = { PH_TT_MOVES, PH_EVASIONS, PH_STOP};
const uint8_t QsearchWithChecksPhaseTable[] = { PH_TT_MOVES, PH_QCAPTURES, PH_QCHECKS, PH_STOP};
const uint8_t QsearchWithoutChecksPhaseTable[] = { PH_TT_MOVES, PH_QCAPTURES, PH_STOP};
}
////
//// Functions
////
/// Constructor for the MovePicker class. Apart from the position for which
/// it is asked to pick legal moves, MovePicker also wants some information
/// to help it to return the presumably good moves first, to decide which
/// moves to return (in the quiescence search, for instance, we only want to
/// search captures, promotions and some checks) and about how important good
/// move ordering is at the current node.
MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const History& h,
SearchStack* ss, Value beta) : pos(p), H(h) {
int searchTT = ttm;
ttMoves[0].move = ttm;
badCaptureThreshold = 0;
badCaptures = moves + MOVES_MAX;
pinned = p.pinned_pieces(pos.side_to_move());
if (ss && !p.is_check())
{
ttMoves[1].move = (ss->mateKiller == ttm) ? MOVE_NONE : ss->mateKiller;
searchTT |= ttMoves[1].move;
killers[0].move = ss->killers[0];
killers[1].move = ss->killers[1];
} else
ttMoves[1].move = killers[0].move = killers[1].move = MOVE_NONE;
if (p.is_check())
phasePtr = EvasionsPhaseTable;
else if (d > DEPTH_ZERO)
{
// Consider sligtly negative captures as good if at low
// depth and far from beta.
if (ss && ss->eval < beta - PawnValueMidgame && d < 3 * ONE_PLY)
badCaptureThreshold = -PawnValueMidgame;
phasePtr = MainSearchPhaseTable;
}
else if (d >= DEPTH_QS_CHECKS)
phasePtr = QsearchWithChecksPhaseTable;
else
{
phasePtr = QsearchWithoutChecksPhaseTable;
// Skip TT move if is not a capture or a promotion, this avoids
// qsearch tree explosion due to a possible perpetual check or
// similar rare cases when TT table is full.
if (ttm != MOVE_NONE && !pos.move_is_capture_or_promotion(ttm))
searchTT = ttMoves[0].move = MOVE_NONE;
}
phasePtr += int(!searchTT) - 1;
go_next_phase();
}
/// MovePicker::go_next_phase() generates, scores and sorts the next bunch
/// of moves when there are no more moves to try for the current phase.
void MovePicker::go_next_phase() {
curMove = moves;
phase = *(++phasePtr);
switch (phase) {
case PH_TT_MOVES:
curMove = ttMoves;
lastMove = curMove + 2;
return;
case PH_GOOD_CAPTURES:
lastMove = generate_captures(pos, moves);
score_captures();
return;
case PH_KILLERS:
curMove = killers;
lastMove = curMove + 2;
return;
case PH_NONCAPTURES:
lastMove = generate_noncaptures(pos, moves);
score_noncaptures();
sort_moves(moves, lastMove, &lastGoodNonCapture);
return;
case PH_BAD_CAPTURES:
// Bad captures SEE value is already calculated so just pick
// them in order to get SEE move ordering.
curMove = badCaptures;
lastMove = moves + MOVES_MAX;
return;
case PH_EVASIONS:
assert(pos.is_check());
lastMove = generate_evasions(pos, moves);
score_evasions();
return;
case PH_QCAPTURES:
lastMove = generate_captures(pos, moves);
score_captures();
return;
case PH_QCHECKS:
lastMove = generate_non_capture_checks(pos, moves);
return;
case PH_STOP:
lastMove = curMove + 1; // Avoids another go_next_phase() call
return;
default:
assert(false);
return;
}
}
/// MovePicker::score_captures(), MovePicker::score_noncaptures() and
/// MovePicker::score_evasions() assign a numerical move ordering score
/// to each move in a move list. The moves with highest scores will be
/// picked first by get_next_move().
void MovePicker::score_captures() {
// Winning and equal captures in the main search are ordered by MVV/LVA.
// Suprisingly, this appears to perform slightly better than SEE based
// move ordering. The reason is probably that in a position with a winning
// capture, capturing a more valuable (but sufficiently defended) piece
// first usually doesn't hurt. The opponent will have to recapture, and
// the hanging piece will still be hanging (except in the unusual cases
// where it is possible to recapture with the hanging piece). Exchanging
// big pieces before capturing a hanging piece probably helps to reduce
// the subtree size.
// In main search we want to push captures with negative SEE values to
// badCaptures[] array, but instead of doing it now we delay till when
// the move has been picked up in pick_move_from_list(), this way we save
// some SEE calls in case we get a cutoff (idea from Pablo Vazquez).
Move m;
// Use MVV/LVA ordering
for (MoveStack* cur = moves; cur != lastMove; cur++)
{
m = cur->move;
if (move_is_promotion(m))
cur->score = QueenValueMidgame;
else
cur->score = pos.midgame_value_of_piece_on(move_to(m))
- pos.type_of_piece_on(move_from(m));
}
}
void MovePicker::score_noncaptures() {
// First score by history, when no history is available then use
// piece/square tables values. This seems to be better then a
// random choice when we don't have an history for any move.
Move m;
Piece piece;
Square from, to;
for (MoveStack* cur = moves; cur != lastMove; cur++)
{
m = cur->move;
from = move_from(m);
to = move_to(m);
piece = pos.piece_on(from);
cur->score = H.value(piece, to) + H.gain(piece, to);
}
}
void MovePicker::score_evasions() {
// Try good captures ordered by MVV/LVA, then non-captures if
// destination square is not under attack, ordered by history
// value, and at the end bad-captures and non-captures with a
// negative SEE. This last group is ordered by the SEE score.
Move m;
int seeScore;
// Skip if we don't have at least two moves to order
if (lastMove < moves + 2)
return;
for (MoveStack* cur = moves; cur != lastMove; cur++)
{
m = cur->move;
if ((seeScore = pos.see_sign(m)) < 0)
cur->score = seeScore - HistoryMax; // Be sure are at the bottom
else if (pos.move_is_capture(m))
cur->score = pos.midgame_value_of_piece_on(move_to(m))
- pos.type_of_piece_on(move_from(m)) + HistoryMax;
else
cur->score = H.value(pos.piece_on(move_from(m)), move_to(m));
}
}
/// MovePicker::get_next_move() is the most important method of the MovePicker
/// class. It returns a new legal move every time it is called, until there
/// are no more moves left.
/// It picks the move with the biggest score from a list of generated moves taking
/// care not to return the tt move if has already been searched previously.
/// Note that this function is not thread safe so should be lock protected by
/// caller when accessed through a shared MovePicker object.
Move MovePicker::get_next_move() {
Move move;
while (true)
{
while (curMove != lastMove)
{
switch (phase) {
case PH_TT_MOVES:
move = (curMove++)->move;
if ( move != MOVE_NONE
&& move_is_legal(pos, move, pinned))
return move;
break;
case PH_GOOD_CAPTURES:
move = pick_best(curMove++, lastMove).move;
if ( move != ttMoves[0].move
&& move != ttMoves[1].move
&& pos.pl_move_is_legal(move, pinned))
{
// Check for a non negative SEE now
int seeValue = pos.see_sign(move);
if (seeValue >= badCaptureThreshold)
return move;
// Losing capture, move it to the tail of the array, note
// that move has now been already checked for legality.
(--badCaptures)->move = move;
badCaptures->score = seeValue;
}
break;
case PH_KILLERS:
move = (curMove++)->move;
if ( move != MOVE_NONE
&& move_is_legal(pos, move, pinned)
&& move != ttMoves[0].move
&& move != ttMoves[1].move
&& !pos.move_is_capture(move))
return move;
break;
case PH_NONCAPTURES:
// Sort negative scored moves only when we get there
if (curMove == lastGoodNonCapture)
insertion_sort<MoveStack>(lastGoodNonCapture, lastMove);
move = (curMove++)->move;
if ( move != ttMoves[0].move
&& move != ttMoves[1].move
&& move != killers[0].move
&& move != killers[1].move
&& pos.pl_move_is_legal(move, pinned))
return move;
break;
case PH_BAD_CAPTURES:
move = pick_best(curMove++, lastMove).move;
return move;
case PH_EVASIONS:
case PH_QCAPTURES:
move = pick_best(curMove++, lastMove).move;
if ( move != ttMoves[0].move
&& pos.pl_move_is_legal(move, pinned))
return move;
break;
case PH_QCHECKS:
move = (curMove++)->move;
if ( move != ttMoves[0].move
&& pos.pl_move_is_legal(move, pinned))
return move;
break;
case PH_STOP:
return MOVE_NONE;
default:
assert(false);
break;
}
}
go_next_phase();
}
}