1 //////////////////////////////////////////////////////////////////////
2 //
3 // FILE: engine.cpp
4 // Engine class methods
5 //
6 // Part of: Scid (Shane's Chess Information Database)
7 // Version: 3.5
8 //
9 // Notice: Copyright (c) 2002-2003 Shane Hudson. All rights reserved.
10 //
11 // Author: Shane Hudson (sgh@users.sourceforge.net)
12 //
13 //////////////////////////////////////////////////////////////////////
14
15 #include "attacks.h"
16 #include "engine.h"
17 #include "recog.h"
18 #include "sqmove.h"
19 #include <algorithm>
20
21 // The Engine class implements the Scid built-in chess engine.
22 // See engine.h for details.
23
24 // sqDir[][]: Array listing the direction between any two squares.
25 // For example, sqDir[A1][B2] == UP_RIGHT, and sqDir[A1][C2] == NULL_DIR.
26 extern directionT sqDir[66][66];
27
28
29 // Evaluation constants:
30
31 static const int Infinity = 32000;
32 static const int KingValue = 10000;
33 static const int QueenValue = 900;
34 static const int RookValue = 500;
35 static const int BishopValue = 300;
36 static const int KnightValue = 300;
37 static const int PawnValue = 100;
38
39 // EndgameValue, MiddlegameValue:
40 // If the combined material score of pieces on both sides (excluding
41 // kings and pawns) is less than this value, we are in an endgame.
42 // If it is greater than MiddlegameValue, we use middlegame scoring.
43 // For anything in between, the score will be a weighted average of
44 // the middlegame and endgame scores.
45 //
46 static const int EndgameValue = 2400;
47 static const int MiddlegameValue = 4000;
48
49 // Bonuses and penalties:
50 //
51 static const int RookHalfOpenFile = 8;
52 static const int RookOpenFile = 20;
53 static const int RookPasserFile = 25; // Rook on passed pawn file.
54 static const int RookOnSeventh = 25; // Rook on its 7th rank.
55 static const int DoubledRooks = 20; // Two rooks on same file.
56 static const int RookEyesKing = 12; // Attacks squares near enemy king.
57 static const int KingTrapsRook = 35; // E.g. King on f1, Rook on h1
58 static const int DoubledPawn = 8;
59 static const int IsolatedPawn = 16;
60 static const int BackwardPawn = 10; // Pawn at base of pawn chain.
61 // static const int DispersedPawn = 10; // Not in pawn chain/duo. (Unused)
62 static const int BlockedHomePawn = 15; // Blocked pawn on d2/e2/d7/e7.
63 static const int BishopPair = 25; // Pair of bishops.
64 static const int BishopEyesKing = 12; // Bishop targets enemy king.
65 static const int BishopTrapped = 120; // E.g. Bxa7? ...b6!
66 static const int KnightOutpost = 15; // 4th/5th/6th rank outpost.
67 static const int KnightBadEndgame = 30; // Enemy pawns on both wings.
68 static const int BadPieceTrade = 80; // Bad trade, e.g. minor for pawns.
69 static const int CanCastle = 10; // Bonus for castling rights.
70 static const int Development = 8; // Moved minor pieces in opening.
71 static const int CentralPawnPair = 15; // For d4/d5 + e4/e5 pawns.
72 static const int CoverPawn = 12; // Pawn cover for king.
73 static const int PassedPawnRank[8] = {
74 // 1 2 3 4 5 6 7 8th rank
75 0, 10, 15, 25, 50, 80, 120, 0
76 };
77
78 // Bishops (and rooks in endings) need to be mobile to be useful:
79 static const int BishopMobility[16] = {
80 // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
81 -20, -15, -10, -6, -3, 0, 3, 6, 9, 12, 15, 15, 15, 15, 15, 15
82 };
83 static const int RookEndgameMobility[16] = {
84 // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
85 -25, -20, -15, -10, -5, 2, 0, 2, 4, 6, 8, 8, 8, 8, 8, 8
86 };
87
88 // Piece distance to enemy king bonuses: 1 2 3 4 5 6 7
89 static const int KnightKingDist [8] = { 0, 10, 14, 10, 5, 2, 0, 0 };
90 static const int BishopKingDist [8] = { 0, 8, 6, 4, 2, 1, 0, 0 };
91 static const int RookKingDist [8] = { 0, 8, 6, 4, 2, 1, 0, 0 };
92 static const int QueenKingDist [8] = { 0, 15, 12, 9, 6, 3, 0, 0 };
93
94 // LazyEvalMargin
95 // A score that is further than this margin outside the current
96 // alpha-beta window after material evaluation is returned as-is.
97 // A larger margin is used for endgames (especially pawn endings)
98 // since positional bonuses can be much larger for them.
99 static const int LazyEvalMargin = 250;
100 static const int LazyEvalEndingMargin = 400;
101 static const int LazyEvalPawnEndingMargin = 800;
102
103 // NullMoveReduction:
104 // The default reduced depth for a null move search.
105 static const int NullMoveReduction = 2;
106
107 // AspirationWindow:
108 // The window around the score of the previous depth iteration
109 // when searching at the root.
110 static const int AspirationWindow = 35;
111
112 // PawnSquare:
113 // Gives bonuses to advanced pawns, especially in the centre.
114 static const int
115 PawnSquare [64] = {
116 0, 0, 0, 0, 0, 0, 0, 0, // A8 - H8
117 4, 8, 12, 16, 16, 12, 8, 4,
118 4, 8, 12, 16, 16, 12, 8, 4,
119 3, 6, 9, 12, 12, 9, 6, 3,
120 2, 4, 6, 8, 8, 6, 4, 2,
121 1, 2, 3, 4, 4, 3, 2, 1,
122 0, 0, 0, -4, -4, 0, 0, 0,
123 0, 0, 0, 0, 0, 0, 0, 0 // A1 - H1
124 };
125
126 // PawnStorm:
127 // Bonus when side is castled queenside and opponent is
128 // castled kingside. Gives a bonus for own sheltering pawns
129 // and a penalty for pawns on the opposing wing to make them
130 // disposable and encourage them to move forwards.
131 static const int
132 PawnStorm [64] = {
133 0, 0, 0, 0, 0, 0, 0, 0, // A8 - H8
134 0, 0, 0, 0, 2, 2, 2, 2,
135 0, 0, 0, 0, 4, 2, 2, 2,
136 0, 0, 0, 4, 6, 0, 0, 0,
137 4, 4, 4, 4, 4, -4, -4, -4,
138 8, 8, 8, 0, 0, -8, -8, -8,
139 12, 12, 12, 0, 0, -12, -12, -12,
140 0, 0, 0, 0, 0, 0, 0, 0 // A1 - H1
141 };
142
143 // KnightSquare:
144 // Rewards well-placed knights.
145 static const int
146 KnightSquare [64] = {
147 -24, -12, -6, -6, -6, -6, -12, -24,
148 -8, 0, 0, 0, 0, 0, 0, -8,
149 -6, 5, 10, 10, 10, 10, 5, -6,
150 -6, 0, 10, 10, 10, 10, 0, -6,
151 -6, 0, 5, 8, 8, 5, 0, -6,
152 -6, 0, 5, 5, 5, 5, 0, -6,
153 -6, 0, 0, 0, 0, 0, 0, -8,
154 -10, -8, -5, -6, -6, -6, -6, -10
155 };
156
157 // BishopSquare:
158 // Bonus array for bishops.
159 static const int
160 BishopSquare [64] = {
161 -10, -5, 0, 0, 0, 0, -5, -10,
162 -5, 8, 0, 5, 5, 0, 8, -5,
163 0, 0, 5, 5, 5, 5, 0, 0,
164 0, 5, 10, 5, 5, 10, 5, 0,
165 0, 5, 10, 5, 5, 10, 5, 0,
166 0, 0, 5, 5, 5, 5, 0, 0,
167 -5, 8, 0, 5, 5, 0, 8, -5,
168 -10, -5, -2, -2, -2, -2, -5, -10
169 };
170
171 // RookFile:
172 // Bonus array for Rooks, by file.
173 static const int /* a b c d e f g h */
174 RookFile [8] = { 0, 0, 4, 8, 8, 4, 0, 0 };
175
176 // QueenSquare:
177 // Bonus array for Queens.
178 static const int
179 QueenSquare [64] = {
180 -5, 0, 0, 0, 0, 0, 0, -5, // A8 - H8
181 -5, 0, 3, 3, 3, 3, 0, -5,
182 0, 3, 6, 9, 9, 6, 3, 0,
183 0, 3, 9, 12, 12, 9, 3, 0,
184 -5, 3, 9, 12, 12, 9, 3, -5,
185 -5, 3, 6, 9, 9, 6, 3, -5,
186 -5, 0, 3, 3, 3, 3, 0, -5,
187 -10, -5, 0, 0, 0, 0, -5, -10 // A1 - H1
188 };
189
190 // KingSquare:
191 // Bonus array for kings in the opening and middlegame.
192 static const int
193 KingSquare [64] = {
194 -50, -50, -50, -50, -50, -50, -50, -50,
195 -50, -50, -50, -50, -50, -50, -50, -50,
196 -50, -50, -50, -50, -50, -50, -50, -50,
197 -50, -50, -50, -60, -60, -50, -50, -50,
198 -40, -40, -40, -60, -60, -40, -40, -40,
199 -15, -15, -15, -20, -20, -15, -15, -15,
200 5, 5, 0, 0, 0, 0, 5, 5,
201 20, 20, 15, 5, 5, 5, 20, 20
202 };
203
204 // EndgameKingSquare:
205 // Rewards King centralisation in endgames.
206 // TODO: Add separate king square tables for endgames where all
207 // pawns are on one side of the board.
208 static const int
209 KingEndgameSquare [64] = {
210 -10, -5, 0, 5, 5, 0, -5, -10,
211 -5, 0, 5, 10, 10, 5, 0, -5,
212 0, 5, 10, 15, 15, 10, 5, 0,
213 5, 10, 15, 20, 20, 15, 10, 5,
214 5, 10, 15, 20, 20, 15, 10, 5,
215 0, 5, 10, 15, 15, 10, 5, 0,
216 -5, 0, 5, 10, 10, 5, 0, -5,
217 -10, -5, 0, 5, 5, 0, -5, -10
218 };
219
220 static const int
221 pieceValues [8] = {
222 0, // Invalid
223 KingValue,
224 QueenValue,
225 RookValue,
226 BishopValue,
227 KnightValue,
228 PawnValue,
229 0 // Empty
230 };
231
232 inline int
PieceValue(pieceT piece)233 Engine::PieceValue (pieceT piece)
234 {
235 return pieceValues[piece_Type(piece)];
236 }
237
238 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
239 // isOutpost
240 // Returns true if the square is on the 4th/5th/6th rank (3rd/4th/5th
241 // for Black) and cannot be attacked by an enemy pawn.
242 static bool
isOutpost(const pieceT * board,squareT sq,colorT color)243 isOutpost (const pieceT * board, squareT sq, colorT color)
244 {
245 pieceT enemyPawn = piece_Make (color_Flip(color), PAWN);
246 rankT rank = square_Rank(sq);
247 fyleT fyle = square_Fyle(sq);
248
249 // Build the list of squares to check for enemy pawns:
250 SquareList squares;
251 if (color == WHITE) {
252 if (rank < RANK_4 || rank > RANK_6) { return false; }
253 if (fyle > A_FYLE) {
254 squares.Add(square_Make(fyle-1,RANK_7));
255 if (rank == RANK_5) { squares.Add(square_Make(fyle-1,RANK_6)); }
256 }
257 if (fyle < H_FYLE) {
258 squares.Add(square_Make(fyle+1,RANK_7));
259 if (rank == RANK_5) { squares.Add(square_Make(fyle+1,RANK_6)); }
260 }
261 } else {
262 if (rank < RANK_3 || rank > RANK_5) { return false; }
263 if (fyle > A_FYLE) {
264 squares.Add(square_Make(fyle-1,RANK_2));
265 if (rank == RANK_4) { squares.Add(square_Make(fyle-1,RANK_3)); }
266 }
267 if (fyle < H_FYLE) {
268 squares.Add(square_Make(fyle+1,RANK_2));
269 if (rank == RANK_4) { squares.Add(square_Make(fyle+1,RANK_3)); }
270 }
271 }
272
273 // Now check each square for an enemy pawn:
274 for (uint i=0; i < squares.Size(); i++) {
275 if (board[squares.Get(i)] == enemyPawn) { return false; }
276 }
277 return true;
278 }
279
280 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
281 // Engine::Score
282 // Returns a score in centipawns for the current engine position,
283 // from the perspective of the side to move.
284 int
Score(void)285 Engine::Score (void)
286 {
287 // Look for a recognized ending with an exact score:
288 int recog = Recognizer::Recognize(&Pos);
289 if (recogFlag(recog) == SCORE_EXACT) { return recogScore(recog); }
290
291 return Score (-Infinity, Infinity);
292 }
293
294 static uint nScoreCalls = 0;
295 static uint nScoreFull = 0;
296
297 inline int
ScoreWhiteMaterial(void)298 Engine::ScoreWhiteMaterial (void)
299 {
300 const byte* pieceCount = Pos.GetMaterial();
301 return pieceCount[WQ] * QueenValue + pieceCount[WR] * RookValue
302 + pieceCount[WB] * BishopValue + pieceCount[WN] * KnightValue
303 + pieceCount[WP] * PawnValue;
304 }
305
306 inline int
ScoreBlackMaterial(void)307 Engine::ScoreBlackMaterial (void)
308 {
309 const byte* pieceCount = Pos.GetMaterial();
310 return pieceCount[BQ] * QueenValue + pieceCount[BR] * RookValue
311 + pieceCount[BB] * BishopValue + pieceCount[BN] * KnightValue
312 + pieceCount[BP] * PawnValue;
313 }
314
315 int
ScoreMaterial(void)316 Engine::ScoreMaterial (void)
317 {
318 int score = ScoreWhiteMaterial() - ScoreBlackMaterial();
319 return (Pos.GetToMove() == WHITE) ? score : -score;
320 }
321
322 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
323 // Engine::Score
324 // Returns a score in centipawns for the current engine position,
325 // from the perspective of the side to move.
326 // Alpha and beta cutoff scores are specified for performance. If
327 // simple material counting produces a score much lower than alpha
328 // or much greater than beta, the score is returned without
329 // slower square-based evaluation.
330 int
Score(int alpha,int beta)331 Engine::Score (int alpha, int beta)
332 {
333 colorT toMove = Pos.GetToMove();
334 const byte * pieceCount = Pos.GetMaterial();
335 const pieceT * board = Pos.GetBoard();
336 int materialScore[2] = {0, 0};
337 int allscore[2] = {0, 0}; // Scoring in all positions
338 int endscore[2] = {0, 0}; // Scoring in endgames
339 int midscore[2] = {0, 0}; // Scoring in middlegames
340 int nNonPawns[2] = {0, 0}; // Non-pawns on each side, including kings
341
342 nScoreCalls++;
343
344 nNonPawns[WHITE] = Pos.NumNonPawns(WHITE);
345 nNonPawns[BLACK] = Pos.NumNonPawns(BLACK);
346
347 // First compute material scores:
348 allscore[WHITE] = materialScore[WHITE] = ScoreWhiteMaterial();
349 allscore[BLACK] = materialScore[BLACK] = ScoreBlackMaterial();
350
351 int pieceMaterial = (materialScore[WHITE] - pieceCount[WP] * PawnValue)
352 + (materialScore[BLACK] - pieceCount[BP] * PawnValue);
353 bool inEndgame = false;
354 bool inMiddlegame = false;
355 if (pieceMaterial <= EndgameValue) { inEndgame = true; }
356 if (pieceMaterial >= MiddlegameValue) { inMiddlegame = true; }
357 bool inPawnEndgame = Pos.InPawnEnding();
358
359 // Look for a bad trade: minor piece for pawns; Q for R+Pawns; etc.
360 // But only do this if both sides have pawns.
361 if (pieceCount[WP] > 0 && pieceCount[BP] > 0) {
362 uint wminors = pieceCount[WB] + pieceCount[WN];
363 uint bminors = pieceCount[BB] + pieceCount[BN];
364 uint wmajors = pieceCount[WR] + (2 * pieceCount[WQ]);
365 uint bmajors = pieceCount[BR] + (2 * pieceCount[BQ]);
366 if (wmajors == bmajors) {
367 if (wminors < bminors) { allscore[WHITE] -= BadPieceTrade; }
368 if (wminors > bminors) { allscore[BLACK] -= BadPieceTrade; }
369 } else if (wminors == bminors) {
370 if (wmajors < bmajors) { allscore[WHITE] -= BadPieceTrade; }
371 if (wmajors > bmajors) { allscore[BLACK] -= BadPieceTrade; }
372 }
373 }
374
375 // Add the Bishop-pair bonus now, because it is fast and easy:
376 if (pieceCount[WB] >= 2) { allscore[WHITE] += BishopPair; }
377 if (pieceCount[BB] >= 2) { allscore[BLACK] += BishopPair; }
378
379 // If there are no pawns, a material advantage of only one minor
380 // piece is worth very little so reduce the material score.
381 if (pieceCount[WP] + pieceCount[BP] == 0) {
382 int materialDiff = materialScore[WHITE] - materialScore[BLACK];
383 if (materialDiff < 0) { materialDiff = -materialDiff; }
384 if (materialDiff == BishopValue || materialDiff == KnightValue) {
385 allscore[WHITE] /= 4;
386 allscore[BLACK] /= 4;
387 }
388 }
389
390 // Look for a trapped bishop on a7/h7/a2/h2:
391 if (Pos.RankCount (WB, RANK_7) > 0) {
392 if (board[A7] == WB && board[B6] == BP) { allscore[WHITE] -= BishopTrapped; }
393 if (board[H7] == WB && board[G6] == BP) { allscore[WHITE] -= BishopTrapped; }
394 }
395 if (Pos.RankCount (BB, RANK_2) > 0) {
396 if (board[A2] == BB && board[B3] == WP) { allscore[BLACK] -= BishopTrapped; }
397 if (board[H2] == BB && board[G6] == WP) { allscore[BLACK] -= BishopTrapped; }
398 }
399
400 // Check for a score much worse than alpha or better than beta
401 // which can be returned immediately on the assumption that
402 // a full evaluation could not get inside the alpha-beta range.
403 // If we are in a pawn ending, a much larger margin is used since
404 // huge bonuses can be added for passed pawns in such endgames.
405 int lazyMargin = LazyEvalMargin;
406 if (inEndgame) { lazyMargin = LazyEvalEndingMargin; }
407 if (inPawnEndgame) { lazyMargin = LazyEvalPawnEndingMargin; }
408
409 int fastScore = allscore[WHITE] - allscore[BLACK];
410 if (toMove == BLACK) { fastScore = -fastScore; }
411 if (fastScore - lazyMargin > beta) { return fastScore; }
412 if (fastScore + lazyMargin < alpha) { return fastScore; }
413
414 // Get the pawn structure score next, because it is usually fast:
415 pawnTableEntryT pawnEntry;
416 ScorePawnStructure (&pawnEntry);
417
418 // Penalise d-file and e-file pawns blocked on their home squares:
419 if (board[D2] == WP && board[D3] != EMPTY) { allscore[WHITE] -= BlockedHomePawn; }
420 if (board[E2] == WP && board[E3] != EMPTY) { allscore[WHITE] -= BlockedHomePawn; }
421 if (board[D7] == BP && board[D6] != EMPTY) { allscore[BLACK] -= BlockedHomePawn; }
422 if (board[E7] == BP && board[E6] != EMPTY) { allscore[BLACK] -= BlockedHomePawn; }
423
424 // Incentive for side ahead in material to trade nonpawn pieces and
425 // for side behind in material to avoid trades:
426 if (materialScore[WHITE] > materialScore[BLACK]) {
427 int bonus = (5 - nNonPawns[BLACK]) * 5;
428 allscore[WHITE] += bonus;
429 } else if (materialScore[BLACK] > materialScore[WHITE]) {
430 int bonus = (5 - nNonPawns[WHITE]) * 5;
431 allscore[BLACK] += bonus;
432 }
433
434 // Check again for a score outside the alpha-beta range, using a
435 // smaller fixed margin of error since the pawn structure score
436 // has been added:
437 fastScore = (allscore[WHITE] - allscore[BLACK]) + pawnEntry.score;
438 if (toMove == BLACK) { fastScore = -fastScore; }
439 if (fastScore > beta + 200) { return fastScore; }
440 if (fastScore < alpha - 200) { return fastScore; }
441
442 nScoreFull++;
443
444 // Now refine the score with piece-square bonuses:
445
446 squareT wk = Pos.GetKingSquare(WHITE);
447 squareT bk = Pos.GetKingSquare(BLACK);
448 fyleT wkFyle = square_Fyle(wk);
449 fyleT bkFyle = square_Fyle(bk);
450
451 // Check if each side should be storming the enemy king:
452 if (!inEndgame) {
453 if (wkFyle <= C_FYLE && bkFyle >= F_FYLE) {
454 midscore[WHITE] += pawnEntry.wLongbShortScore;
455 } else if (wkFyle >= F_FYLE && bkFyle <= C_FYLE) {
456 midscore[WHITE] += pawnEntry.wShortbLongScore;
457 }
458 }
459
460 // Iterate over the piece for each color:
461
462 for (colorT c = WHITE; c <= BLACK; c++) {
463 colorT enemy = color_Flip(c);
464 // squareT ownKing = Pos.GetKingSquare(c);
465 squareT enemyKing = Pos.GetKingSquare(enemy);
466 uint npieces = Pos.GetCount(c);
467 squareT * sqlist = Pos.GetList(c);
468 int mscore = 0; // Middlegame score adjustments
469 int escore = 0; // Endgame score adjustments
470 int ascore = 0; // All-position adjustments (middle and endgame)
471
472 for (uint i = 0; i < npieces; i++) {
473 squareT sq = sqlist[i];
474 pieceT p = board[sq];
475 pieceT ptype = piece_Type(p);
476 ASSERT (p != EMPTY && piece_Color(p) == c);
477 squareT bonusSq = (c == WHITE) ? square_FlipRank(sq) : sq;
478 uint rank = RANK_1 + RANK_8 - square_Rank(bonusSq);
479
480 // Piece-specific bonuses. The use of if-else instead of
481 // a switch statement was observed to be faster since
482 // the most common piece types are handled first.
483
484 if (ptype == PAWN) {
485 // Most pawn-specific bonuses are in ScorePawnStructure().
486
487 // Kings should be close to pawns in endgames:
488 // if (!inMiddlegame) {
489 // escore += 3 * square_Distance (sq, enemyKing)
490 // - 2 * square_Distance (sq, ownKing);
491 // }
492 } else if (ptype == ROOK) {
493 ascore += RookFile[square_Fyle(sq)];
494 if (rank == RANK_7) {
495 ascore += RookOnSeventh;
496 // Even bigger bonus if rook traps king on 8th rank:
497 bool kingOn8th = (p == WR) ? (bk >= A8) : (wk <= H1);
498 if (kingOn8th) { ascore += RookOnSeventh; }
499 }
500 if (!inEndgame) {
501 mscore += RookKingDist[square_Distance(sq, enemyKing)];
502 }
503 if (!inMiddlegame) {
504 uint mobility = Pos.Mobility (ROOK, c, sq);
505 escore += RookEndgameMobility [mobility];
506 }
507 } else if (ptype == KING) {
508 if (Pos.GetCount(c) == 1) {
509 // Forcing a lone king to a corner:
510 ascore += 5 * KingEndgameSquare[bonusSq] - 150;
511 } else {
512 mscore += KingSquare[bonusSq];
513 escore += KingEndgameSquare[bonusSq];
514 }
515 } else if (ptype == BISHOP) {
516 ascore += BishopSquare[bonusSq];
517 ascore += BishopMobility [Pos.Mobility (BISHOP, c, sq)];
518 // Middlegame bonus for diagonal close to enemy king:
519 if (! inEndgame) {
520 mscore += BishopKingDist[square_Distance(sq, enemyKing)];
521 // Reward a bishop attacking the enemy king vicinity:
522 int leftdiff = (int)square_LeftDiag(sq)
523 - (int)square_LeftDiag(enemyKing);
524 int rightdiff = (int)square_RightDiag(sq)
525 - (int)square_RightDiag(enemyKing);
526 if ((leftdiff >= -2 && leftdiff <= 2)
527 || (rightdiff >= -2 && rightdiff <= 2)) {
528 mscore += BishopEyesKing;
529 }
530 }
531 } else if (ptype == KNIGHT) {
532 ascore += KnightSquare[bonusSq];
533 if (!inEndgame) {
534 mscore += KnightKingDist[square_Distance(sq, enemyKing)];
535 // Bonus for a useful outpost:
536 if (rank >= RANK_4 && !square_IsEdgeSquare(sq)
537 && isOutpost(board, sq, c)) {
538 mscore += KnightOutpost;
539 }
540 }
541 if (!inMiddlegame) {
542 // Penalty for knight in an endgame with enemy
543 // pawns on both wings.
544 pieceT enemyPawn = piece_Make (enemy, PAWN);
545 uint qsidePawns = Pos.FyleCount(enemyPawn, A_FYLE)
546 + Pos.FyleCount(enemyPawn, B_FYLE)
547 + Pos.FyleCount(enemyPawn, C_FYLE);
548 uint ksidePawns = Pos.FyleCount(enemyPawn, F_FYLE)
549 + Pos.FyleCount(enemyPawn, G_FYLE)
550 + Pos.FyleCount(enemyPawn, H_FYLE);
551 if (ksidePawns > 0 && qsidePawns > 0) {
552 escore -= KnightBadEndgame;
553 }
554 }
555 } else /* (ptype == QUEEN) */ {
556 ASSERT (ptype == QUEEN);
557 ascore += QueenSquare[bonusSq];
558 ascore += QueenKingDist[square_Distance(sq, enemyKing)];
559 }
560 }
561 allscore[c] += ascore;
562 midscore[c] += mscore;
563 endscore[c] += escore;
564 }
565
566 // Now reward rooks on open files or behind passed pawns:
567 byte passedPawnFyles =
568 pawnEntry.fyleHasPassers[WHITE] | pawnEntry.fyleHasPassers[BLACK];
569 for (colorT color = WHITE; color <= BLACK; color++) {
570 pieceT rook = piece_Make (color, ROOK);
571 if (pieceCount[rook] == 0) { continue; }
572 colorT enemy = color_Flip (color);
573 pieceT ownPawn = piece_Make (color, PAWN);
574 pieceT enemyPawn = piece_Make (enemy, PAWN);
575 fyleT enemyKingFyle = square_Fyle (Pos.GetKingSquare(enemy));
576 int bonus = 0;
577
578 for (fyleT fyle = A_FYLE; fyle <= H_FYLE; fyle++) {
579 uint nRooks = Pos.FyleCount (rook, fyle);
580 if (nRooks == 0) { continue; }
581 if (nRooks > 1) { bonus += DoubledRooks; }
582 uint passedPawnsOnFyle = passedPawnFyles & (1 << fyle);
583 if (passedPawnsOnFyle != 0) {
584 // Rook is on same file as a passed pawn.
585 // TODO: make bonus bigger when rook is *behind* the pawn.
586 bonus += RookPasserFile;
587 } else if (Pos.FyleCount (ownPawn, fyle) == 0) {
588 // Rook on open or half-open file:
589 if (Pos.FyleCount (enemyPawn, fyle) == 0) {
590 bonus += RookOpenFile;
591 } else {
592 bonus += RookHalfOpenFile;
593 }
594 // If this open/half-open file leads to a square adjacent
595 // to the enemy king, give a further bonus:
596 int fdiff = (int)fyle - (int)enemyKingFyle;
597 if (fdiff >= -1 && fdiff < 1) { bonus += RookEyesKing; }
598 }
599 }
600 allscore[color] += bonus;
601 }
602
603 // King safety:
604 if (! inEndgame) {
605 if (pieceCount[BQ] > 0) {
606 if (Pos.GetCastling(WHITE,KSIDE)) { midscore[WHITE] += CanCastle; }
607 if (Pos.GetCastling(WHITE,QSIDE)) { midscore[WHITE] += CanCastle; }
608 }
609 if (pieceCount[WQ] > 0) {
610 if (Pos.GetCastling(BLACK,KSIDE)) { midscore[BLACK] += CanCastle; }
611 if (Pos.GetCastling(BLACK,QSIDE)) { midscore[BLACK] += CanCastle; }
612 }
613 // Bonus for pawn cover in front of a castled king. Actually we
614 // also include bishops because they are important for defence.
615 if (square_Rank(wk) == RANK_1 && wk != D1 && wk != E1) {
616 uint nCoverPawns = 0;
617 pieceT p = board[square_Move (wk, UP_LEFT)];
618 if (p == WP || p == WB) { nCoverPawns++; }
619 p = board[square_Move (wk, UP)];
620 if (p == WP || p == WB) { nCoverPawns++; }
621 p = board[square_Move (wk, UP_RIGHT)];
622 if (p == WP || p == WB) { nCoverPawns++; }
623 midscore[WHITE] += CoverPawn * nCoverPawns;
624 if ((wk == F1 || wk == G1)
625 && (board[G1] == WR || board[H1] == WR || board[H2] == WR)) {
626 midscore[WHITE] -= KingTrapsRook;
627 }
628 if ((wk == C1 || wk == B1)
629 && (board[B1] == WR || board[A1] == WR || board[A2] == WR)) {
630 midscore[WHITE] -= KingTrapsRook;
631 }
632 }
633 if (square_Rank(bk) == RANK_8 && bk != D8 && bk != E8) {
634 uint nCoverPawns = 0;
635 pieceT p = board[square_Move (bk, DOWN_LEFT)];
636 if (p == BP || p == BB) { nCoverPawns++; }
637 p = board[square_Move (bk, DOWN)];
638 if (p == BP || p == BB) { nCoverPawns++; }
639 p = board[square_Move (bk, DOWN_RIGHT)];
640 if (p == BP || p == BB) { nCoverPawns++; }
641 midscore[BLACK] += CoverPawn * nCoverPawns;
642 if ((bk == F8 || bk == G8)
643 && (board[G8] == BR || board[H8] == BR || board[H7] == BR)) {
644 midscore[BLACK] -= KingTrapsRook;
645 }
646 if ((bk == C8 || bk == B8)
647 && (board[B8] == BR || board[A8] == BR || board[A7] == BR)) {
648 midscore[BLACK] -= KingTrapsRook;
649 }
650 }
651
652 // Pawn centre:
653 if ((board[D4] == WP || board[D5] == WP)
654 && (board[E4] == WP || board[E5] == WP)) {
655 midscore[WHITE] += CentralPawnPair;
656 }
657 if ((board[D4] == BP || board[D5] == BP)
658 && (board[E4] == BP || board[E5] == BP)) {
659 midscore[BLACK] += CentralPawnPair;
660 }
661
662 // Minor pieces developed:
663 if (board[B1] != WN) { midscore[WHITE] += Development; }
664 if (board[C1] != WB) { midscore[WHITE] += Development; }
665 if (board[F1] != WB) { midscore[WHITE] += Development; }
666 if (board[G1] != WN) { midscore[WHITE] += Development; }
667 if (board[B8] != BN) { midscore[BLACK] += Development; }
668 if (board[C8] != BB) { midscore[BLACK] += Development; }
669 if (board[F8] != BB) { midscore[BLACK] += Development; }
670 if (board[G8] != BN) { midscore[BLACK] += Development; }
671 }
672
673 // Work out the middlegame and endgame scores including pawn structure
674 // evaluation, with a larger pawn structure weight in endgames:
675 int baseScore = allscore[WHITE] - allscore[BLACK];
676 int mgScore = baseScore + midscore[WHITE] - midscore[BLACK];
677 int egScore = baseScore + endscore[WHITE] - endscore[BLACK];
678 mgScore += pawnEntry.score;
679 egScore += (pawnEntry.score * 5) / 4;
680
681 // Scale down the endgame score for bishops of opposite colors, if both
682 // sides have the same non-pawn material:
683 if (pieceCount[WB] == 1 && pieceCount[BB] == 1) {
684 if (Pos.SquareColorCount(WB,WHITE) != Pos.SquareColorCount(BB,WHITE)) {
685 if (pieceCount[WQ] == pieceCount[BQ]
686 && pieceCount[WR] == pieceCount[BR]
687 && pieceCount[WN] == pieceCount[BN]) {
688 egScore = egScore * 5 / 8;
689 }
690 }
691 }
692
693 // Negate scores for Black to move:
694 if (toMove == BLACK) {
695 mgScore = -mgScore;
696 egScore = -egScore;
697 }
698
699 // Determine the final score from the middlegame and endgame scores:
700 int finalScore = 0;
701 if (inMiddlegame) {
702 finalScore = mgScore; // Use the middlegame score only.
703 } else if (inEndgame) {
704 finalScore = egScore; // Use the endgame score only.
705 } else {
706 // The final score is a weighted mean of the two scores:
707 int midpart = (pieceMaterial - EndgameValue) * mgScore;
708 int endpart = (MiddlegameValue - pieceMaterial) * egScore;
709 finalScore = (endpart + midpart) / (MiddlegameValue - EndgameValue);
710 }
711 return finalScore;
712 }
713
714 static uint nPawnHashProbes = 0;
715 static uint nPawnHashHits = 0;
716
717 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
718 // Engine::ScorePawnStructure
719 // Fill in the provided pawnTableEntryT structure with pawn structure
720 // scoring information, using the pawn hash table wherever possible.
721 void
ScorePawnStructure(pawnTableEntryT * pawnEntry)722 Engine::ScorePawnStructure (pawnTableEntryT * pawnEntry)
723 {
724 nPawnHashProbes++;
725 uint pawnhash = Pos.PawnHashValue();
726 // We only use 32-bit hash values, so without further safety checks
727 // the rate of false hits in the pawn hash table could be high.
728 // To reduce the chance of false hits, we compute an extra signature.
729 uint sig = (Pos.SquareColorCount(WP,WHITE) << 12)
730 | (Pos.SquareColorCount(BP,BLACK) << 8)
731 | (Pos.PieceCount(WP) << 4) | Pos.PieceCount(BP);
732 pawnEntry->pawnhash = pawnhash;
733 pawnEntry->sig = sig;
734 pawnEntry->fyleHasPassers[WHITE] = 0;
735 pawnEntry->fyleHasPassers[BLACK] = 0;
736
737 bool inPawnEndgame = (Pos.NumNonPawns(WHITE) == 1
738 && Pos.NumNonPawns(BLACK) == 1);
739 pawnTableEntryT * hashEntry = NULL;
740
741 // Check for a pawn hash table hit, but not in pawn endings:
742 if (!inPawnEndgame) {
743 uint hashSlot = pawnhash % PawnTableSize;
744 hashEntry = &(PawnTable[hashSlot]);
745 if (pawnhash == hashEntry->pawnhash && sig == hashEntry->sig) {
746 nPawnHashHits++;
747 *pawnEntry = *hashEntry;
748 return;
749 }
750 }
751
752 // The pawnFiles array contains the number of pawns of each color on
753 // each file. Indexes 1-8 are used while 0 and 9 are empty dummy files
754 // added so that even the a and h files have two adjacent files, making
755 // isolated/passed pawn calculation easier.
756 uint pawnFiles[2][10] = { {0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
757 {0, 0, 0, 0, 0, 0, 0, 0, 0, 0} };
758 // firstRank stores the rank of the leading pawn on each file.
759 uint firstRank[2][10] = { {0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
760 {0, 0, 0, 0, 0, 0, 0, 0, 0, 0} };
761 // lastRank stores the rank of the rearmost pawn on each file.
762 uint lastRank[2][10] = { {7, 7, 7, 7, 7, 7, 7, 7, 7, 7},
763 {7, 7, 7, 7, 7, 7, 7, 7, 7, 7} };
764
765 int pawnScore[2] = {0, 0};
766 int longVsShortScore[2] = {0, 0}; // Pawn storm bonuses, O-O-O vs O-O
767 int shortVsLongScore[2] = {0, 0}; // Pawn storm bonuses, O-O vs O-O-O
768 rankT bestRacingPawn[2] = {RANK_1, RANK_1};
769
770 for (fyleT f = A_FYLE; f <= H_FYLE; f++) {
771 pawnFiles[WHITE][f+1] = Pos.FyleCount (WP, f);
772 pawnFiles[BLACK][f+1] = Pos.FyleCount (BP, f);
773 }
774
775 for (colorT c = WHITE; c <= BLACK; c++) {
776 pieceT pawn = piece_Make (c, PAWN);
777 uint npawns = Pos.PieceCount(pawn);
778 SquareList sqlist;
779 Pos.GetSquares (pawn, &sqlist);
780 for (uint i = 0; i < npawns; i++) {
781 squareT sq = sqlist.Get(i);
782 squareT wsq = (c == WHITE) ? sq : square_FlipRank(sq);
783 squareT bonusSq = square_FlipRank(wsq);
784 pawnScore[c] += PawnSquare[bonusSq];
785 longVsShortScore[c] += PawnStorm[bonusSq];
786 shortVsLongScore[c] += PawnStorm[square_FlipFyle(bonusSq)];
787 uint fyle = square_Fyle(wsq) + 1;
788 uint rank = square_Rank(wsq);
789 if (rank > firstRank[c][fyle]) {
790 firstRank[c][fyle] = rank;
791 }
792 if (rank < lastRank[c][fyle]) {
793 lastRank[c][fyle] = rank;
794 }
795 }
796 }
797
798 byte fyleHasPassers[2] = {0, 0};
799
800 for (colorT color = WHITE; color <= BLACK; color++) {
801 if (Pos.PieceCount(piece_Make(color,PAWN)) == 0) { continue; }
802 colorT enemy = color_Flip(color);
803
804 for (uint fyle=1; fyle <= 8; fyle++) {
805 uint pawnCount = pawnFiles[color][fyle];
806 if (pawnCount == 0) { continue; }
807 uint pawnRank = firstRank[color][fyle];
808
809 // Doubled pawn penalty:
810 if (pawnCount > 1) {
811 pawnScore[color] -= DoubledPawn * pawnCount;
812 }
813
814 // Isolated pawn penalty:
815 bool isolated = false;
816 if (pawnFiles[color][fyle-1] == 0
817 && pawnFiles[color][fyle+1] == 0) {
818 isolated = true;
819 pawnScore[color] -= IsolatedPawn * pawnCount;
820 // Extra penalty for isolated on half-open file:
821 if (pawnFiles[enemy][fyle] == 0) {
822 pawnScore[color] -= IsolatedPawn * pawnCount / 2;
823 }
824 } else if (lastRank[color][fyle-1] > lastRank[color][fyle]
825 && lastRank[color][fyle+1] > lastRank[color][fyle]) {
826 // Not isolated, but backward:
827 pawnScore[color] -= BackwardPawn;
828 // Extra penalty for backward on half-open file:
829 if (pawnFiles[enemy][fyle] == 0) {
830 pawnScore[color] -= BackwardPawn;
831 }
832 }
833
834 // Passed pawn bonus:
835 if (pawnRank >= 7 - lastRank[enemy][fyle]
836 && pawnRank >= 7 - lastRank[enemy][fyle-1]
837 && pawnRank >= 7 - lastRank[enemy][fyle+1]) {
838 int bonus = PassedPawnRank[pawnRank];
839 // Smaller bonus for rook-file or isolated passed pawns:
840 if (fyle == 1 || fyle == 8 || isolated) {
841 bonus = bonus * 3 / 4;
842 }
843 // Bigger bonus for a passed pawn protected by another pawn:
844 if (!isolated) {
845 if (pawnRank == firstRank[color][fyle-1] + 1
846 || pawnRank == firstRank[color][fyle+1] + 1) {
847 bonus = (bonus * 3) / 2;
848 }
849 }
850 pawnScore[color] += bonus;
851 // Update the passed-pawn-files bitmap:
852 fyleHasPassers[color] |= (1 << (fyle-1));
853
854 // Give a big bonus for a connected passed pawn on
855 // the 6th or 7th rank.
856 if (pawnRank >= RANK_6 && pawnFiles[color][fyle-1] > 0
857 && firstRank[color][fyle-1] >= RANK_6) {
858 // pawnScore[color] += some_bonus...;
859 }
860
861 // Check for passed pawn races in pawn endgames:
862 if (inPawnEndgame) {
863 // Check if the enemy king is outside the square:
864 squareT kingSq = Pos.GetKingSquare(color_Flip(color));
865 squareT pawnSq = square_Make(fyle-1, pawnRank);
866 squareT promoSq = square_Make(fyle-1, RANK_8);
867 if (color == BLACK) {
868 pawnSq = square_FlipRank(pawnSq);
869 promoSq = square_FlipRank(promoSq);
870 }
871 uint kingDist = square_Distance(kingSq, promoSq);
872 uint pawnDist = square_Distance(pawnSq, promoSq);
873 if (color != Pos.GetToMove()) { pawnDist++; }
874 if (pawnDist < kingDist) {
875 bestRacingPawn[color] = pawnRank;
876 }
877 }
878 }
879 }
880 }
881
882 int score = pawnScore[WHITE] - pawnScore[BLACK];
883 pawnEntry->score = score;
884 pawnEntry->fyleHasPassers[WHITE] = fyleHasPassers[WHITE];
885 pawnEntry->fyleHasPassers[BLACK] = fyleHasPassers[BLACK];
886 pawnEntry->wLongbShortScore = longVsShortScore[WHITE] - shortVsLongScore[BLACK];
887 pawnEntry->wShortbLongScore = shortVsLongScore[WHITE] - longVsShortScore[BLACK];
888
889 // If not a pawn endgame, store the score in the pawn hash table:
890 if (!inPawnEndgame) {
891 *hashEntry = *pawnEntry;
892 return;
893 }
894
895 // This is a pawn endgame, so we cannot store the score in the
896 // pawn hash table since we include king positions as a factor.
897 // If one side has a pawn that clearly queens before the best
898 // enemy pawn in a race (where kings cannot catch the pawns),
899 // give a huge bonus since it almost certainly wins:
900
901 if (bestRacingPawn[WHITE] > bestRacingPawn[BLACK] + 1) {
902 pawnEntry->score += RookValue;
903 } else if (bestRacingPawn[BLACK] > bestRacingPawn[WHITE] + 1) {
904 pawnEntry->score -= RookValue;
905 }
906 }
907
908 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
909 // Engine::IsMatingScore
910 // Returns true if the score indicates the side to move will checkmate.
911 inline bool
IsMatingScore(int score)912 Engine::IsMatingScore (int score)
913 {
914 return (score > (Infinity - (int)ENGINE_MAX_PLY));
915 }
916
917 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
918 // Engine::IsGettingMatedScore
919 // Returns true if the score indicates the side to move will be checkmated.
920 inline bool
IsGettingMatedScore(int score)921 Engine::IsGettingMatedScore (int score)
922 {
923 return (score < (-Infinity + (int)ENGINE_MAX_PLY));
924 }
925
926 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
927 // Engine::PlayMove
928 // Play the specified move, not in a search.
929 void
PlayMove(simpleMoveT * sm)930 Engine::PlayMove (simpleMoveT * sm) {
931 PushRepeat(&RootPos);
932 RootPos.DoSimpleMove(sm);
933 Pos.DoSimpleMove(sm);
934 #ifdef WINCE
935 simpleMoveT * newMove = (simpleMoveT *) my_Tcl_Alloc(sizeof(simpleMoveT));
936 #else
937 simpleMoveT * newMove = new simpleMoveT;
938 #endif
939 *newMove = *sm;
940 GameMoves[NumGameMoves] = newMove;
941 NumGameMoves++;
942 // Change the transposition table sequence number:
943 TranTableSequence++;
944 }
945
946 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
947 // Engine::RetractMove
948 // Take back a move played in the game.
949 void
RetractMove(void)950 Engine::RetractMove (void)
951 {
952 if (NumGameMoves == 0) { return; }
953 PopRepeat();
954 NumGameMoves--;
955 RootPos.UndoSimpleMove(GameMoves[NumGameMoves]);
956 Pos.UndoSimpleMove(GameMoves[NumGameMoves]);
957 #ifdef WINCE
958 my_Tcl_Free((char *)GameMoves);
959 #else
960 delete GameMoves[NumGameMoves];
961 #endif
962 TranTableSequence--;
963 }
964
965 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
966 // Engine::DoMove
967 // Make the specified move in a search.
968 inline void
DoMove(simpleMoveT * sm)969 Engine::DoMove (simpleMoveT * sm) {
970 PushRepeat(&Pos);
971 Pos.DoSimpleMove(sm);
972 Ply++;
973 }
974
975 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
976 // Engine::UndoMove
977 // Take back the specified move in a search.
978 inline void
UndoMove(simpleMoveT * sm)979 Engine::UndoMove (simpleMoveT * sm) {
980 PopRepeat();
981 Pos.UndoSimpleMove(sm);
982 Ply--;
983 }
984
985 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
986 // Engine::PushRepeat
987 // Remember the current position on the repetition stack.
988 inline void
PushRepeat(Position * pos)989 Engine::PushRepeat (Position * pos)
990 {
991 repeatT * rep = &(RepStack[RepStackSize]);
992 rep->hash = pos->HashValue();
993 rep->pawnhash = pos->PawnHashValue();
994 rep->npieces = pos->TotalMaterial();
995 rep->stm = pos->GetToMove();
996 RepStackSize++;
997 }
998
999 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1000 // Engine::PopRepeat
1001 // Pops the last entry off the repetition stack.
1002 inline void
PopRepeat(void)1003 Engine::PopRepeat (void)
1004 {
1005 ASSERT (RepStackSize > 0);
1006 RepStackSize--;
1007 }
1008
1009 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1010 // Engine::NoMatingMaterial
1011 // Returns true if the position is a certain draw through neither
1012 // side having mating material.
1013 bool
NoMatingMaterial(void)1014 Engine::NoMatingMaterial (void)
1015 {
1016 uint npieces = Pos.TotalMaterial();
1017
1018 // Check for K vs K, K+N vs K, and K+B vs K:
1019 if (npieces <= 2) { return true; }
1020 if (npieces == 3) {
1021 const byte* material = Pos.GetMaterial();
1022 if (material[WB] == 1 || material[WN] == 1) { return true; }
1023 if (material[BB] == 1 || material[BN] == 1) { return true; }
1024 }
1025 return false;
1026 }
1027
1028 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1029 // Engine::FiftyMoveDraw
1030 // Returns true if a draw has been reached through fifty full
1031 // moves since the last capture or pawn move.
1032 bool
FiftyMoveDraw(void)1033 Engine::FiftyMoveDraw (void)
1034 {
1035 if (RepStackSize < 100) { return false; }
1036
1037 uint pawnhash = Pos.PawnHashValue();
1038 uint npieces = Pos.TotalMaterial();
1039
1040 // Go back through the stack of hash values:
1041 uint plycount = 0;
1042 for (uint i = RepStackSize; i > 0; i--) {
1043 repeatT * rep = &(RepStack[i-1]);
1044 // Stop at an irreversible move:
1045 if (npieces != rep->npieces) { break; }
1046 if (pawnhash != rep->pawnhash) { break; }
1047 plycount++;
1048 }
1049 if (plycount >= 100) { return true; }
1050 return false;
1051 }
1052
1053 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1054 // Engine::RepeatedPosition
1055 // Returns the number if times the current position has been reached,
1056 // with the same side to move, castling and en passant settings.
1057 // The current occurrence is included in the returned count.
1058 uint
RepeatedPosition(void)1059 Engine::RepeatedPosition (void)
1060 {
1061 uint hash = Pos.HashValue();
1062 uint pawnhash = Pos.PawnHashValue();
1063 uint npieces = Pos.TotalMaterial();
1064 colorT stm = Pos.GetToMove();
1065
1066 // Go back through the stack of hash values detecting repetition:
1067 uint ntimes = 1;
1068 for (uint i = RepStackSize; i > 0; i--) {
1069 repeatT * rep = &(RepStack[i-1]);
1070 // Stop at an irreversible move:
1071 if (npieces != rep->npieces) { break; }
1072 if (pawnhash != rep->pawnhash) { break; }
1073 // Look for repetition:
1074 if (hash == rep->hash && stm == rep->stm) { ntimes++; }
1075 }
1076 return ntimes;
1077 }
1078
1079 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1080 // Engine::SetHashTableKilobytes
1081 // Set the transposition table size in kilobytes.
1082 void
SetHashTableKilobytes(uint size)1083 Engine::SetHashTableKilobytes (uint size)
1084 {
1085 // Compute the number of entries, which must be even:
1086 uint bytes = size * 1024;
1087 if(TranTableSize != bytes / sizeof(transTableEntryT))
1088 {
1089 TranTableSize = bytes / sizeof(transTableEntryT);
1090 if ((TranTableSize % 2) == 1) { TranTableSize--; }
1091 #ifdef WINCE
1092 if (TranTable != NULL) { my_Tcl_Free((char *) TranTable); }
1093 TranTable = (transTableEntryT*)my_Tcl_Alloc(sizeof ( transTableEntryT [TranTableSize]));
1094 #else
1095 if (TranTable != NULL) { delete[] TranTable; }
1096 TranTable = new transTableEntryT [TranTableSize];
1097 #endif
1098 }
1099 ClearHashTable();
1100 }
1101
1102 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1103 // Engine::SetPawnTableKilobytes
1104 // Set the pawn structure hash table size in kilobytes.
1105 void
SetPawnTableKilobytes(uint size)1106 Engine::SetPawnTableKilobytes (uint size)
1107 {
1108 // Compute the number of entries:
1109 uint bytes = size * 1024;
1110 if(PawnTableSize != bytes / sizeof(pawnTableEntryT))
1111 {
1112 PawnTableSize = bytes / sizeof(pawnTableEntryT);
1113 #ifdef WINCE
1114 if (PawnTable != NULL) { my_Tcl_Free((char *) PawnTable); }
1115 PawnTable = (pawnTableEntryT*)my_Tcl_Alloc(sizeof (pawnTableEntryT [PawnTableSize]) );
1116 #else
1117 if (PawnTable != NULL) { delete[] PawnTable; }
1118 PawnTable = new pawnTableEntryT [PawnTableSize];
1119 #endif
1120 }
1121 ClearPawnTable();
1122 }
1123
1124 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1125 // Engine::ClearHashTable
1126 // Clear the transposition table.
1127 void
ClearHashTable(void)1128 Engine::ClearHashTable (void)
1129 {
1130 for (uint i = 0; i < TranTableSize; i++) {
1131 TranTable[i].flags = SCORE_NONE;
1132 }
1133 }
1134
1135
1136 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1137 // Engine::ClearPawnTable
1138 // Clear the pawn structure hash table.
1139 void
ClearPawnTable(void)1140 Engine::ClearPawnTable (void)
1141 {
1142 for (uint i = 0; i < PawnTableSize; i++) {
1143 PawnTable[i].pawnhash = 0;
1144 }
1145 }
1146
1147 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1148 // tte_Get/Set functions
1149 // Helpers for packing/extracting transposition table entry fields.
1150
tte_SetFlags(transTableEntryT * tte,scoreFlagT sflag,colorT stm,byte castling,bool isOnlyMove)1151 inline void tte_SetFlags (transTableEntryT * tte, scoreFlagT sflag,
1152 colorT stm, byte castling, bool isOnlyMove)
1153 { tte->flags = (castling << 4) | (stm << 3) | (isOnlyMove ? 4 : 0) | sflag; }
1154
tte_ScoreFlag(transTableEntryT * tte)1155 inline scoreFlagT tte_ScoreFlag (transTableEntryT * tte)
1156 { return (tte->flags & 7); }
1157
tte_SideToMove(transTableEntryT * tte)1158 inline colorT tte_SideToMove (transTableEntryT * tte)
1159 { return ((tte->flags >> 3) & 1); }
1160
tte_Castling(transTableEntryT * tte)1161 inline byte tte_Castling (transTableEntryT * tte)
1162 { return (tte->flags >> 4); }
1163
tte_IsOnlyMove(transTableEntryT * tte)1164 inline bool tte_IsOnlyMove (transTableEntryT * tte)
1165 { return (((tte->flags >> 2) & 1) == 1); }
1166
tte_SetBestMove(transTableEntryT * tte,simpleMoveT * bestMove)1167 inline void tte_SetBestMove (transTableEntryT * tte, simpleMoveT * bestMove)
1168 {
1169 ASSERT (bestMove->from <= H8 && bestMove->to <= H8);
1170 ushort bm = bestMove->from;
1171 bm <<= 6; bm |= bestMove->to;
1172 bm <<= 4; bm |= bestMove->promote;
1173 tte->bestMove = bm;
1174 }
1175
tte_GetBestMove(transTableEntryT * tte,simpleMoveT * bestMove)1176 inline void tte_GetBestMove (transTableEntryT * tte, simpleMoveT * bestMove)
1177 {
1178 ushort bm = tte->bestMove;
1179 bestMove->promote = bm & 15; bm >>= 4;
1180 bestMove->to = bm & 63; bm >>= 6;
1181 bestMove->from = bm & 63;
1182 }
1183
1184 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1185 // Engine::StoreHash
1186 // Store the score for the current position in the transposition table.
1187 void
StoreHash(int depth,scoreFlagT ttFlag,int score,simpleMoveT * bestMove,bool isOnlyMove)1188 Engine::StoreHash (int depth, scoreFlagT ttFlag, int score,
1189 simpleMoveT * bestMove, bool isOnlyMove)
1190 {
1191 if (TranTableSize == 0) { return; }
1192 ASSERT (ttFlag <= SCORE_UPPER);
1193
1194 uint hash = Pos.HashValue();
1195 uint pawnhash = Pos.PawnHashValue();
1196 colorT stm = Pos.GetToMove();
1197 if (stm == BLACK) { hash = ~hash; }
1198
1199 // Find the least useful (lowest depth) of two entries to replace
1200 // but replace the previous entry for this position if it exists
1201 // and use an empty hash table entry if possible:
1202
1203 uint ttSlot = (hash % TranTableSize) & 0xFFFFFFFEU;
1204 ASSERT (ttSlot < TranTableSize - 1);
1205 transTableEntryT * ttEntry1 = &(TranTable[ttSlot]);
1206 transTableEntryT * ttEntry2 = &(TranTable[ttSlot + 1]);
1207 bool replacingSameEntry = false;
1208
1209 transTableEntryT * ttEntry;
1210 if (ttEntry1->hash == hash && ttEntry1->pawnhash == pawnhash) {
1211 ttEntry = ttEntry1; // Replace this existing entry.
1212 replacingSameEntry = true;
1213 } else if (ttEntry2->hash == hash && ttEntry2->pawnhash == pawnhash) {
1214 ttEntry = ttEntry2; // Replace this existing entry.
1215 replacingSameEntry = true;
1216 } else if (tte_ScoreFlag(ttEntry1) == SCORE_NONE) {
1217 ttEntry = ttEntry1; // Use this empty entry.
1218 } else if (tte_ScoreFlag(ttEntry2) == SCORE_NONE) {
1219 ttEntry = ttEntry2; // Use this empty entry.
1220 } else {
1221 // Replace the entry with the shallower depth, unless the deeper
1222 // entry has an old sequence number:
1223 transTableEntryT * ttDeeper = ttEntry1;
1224 transTableEntryT * ttShallower = ttEntry2;
1225 if (ttEntry1->depth < ttEntry2->depth) {
1226 ttDeeper = ttEntry2;
1227 ttShallower = ttEntry1;
1228 }
1229 if (ttShallower->sequence != TranTableSequence) {
1230 ttEntry = ttShallower; // Replace this old entry
1231 } else if (ttDeeper->sequence != TranTableSequence) {
1232 ttEntry = ttDeeper; // Replace this old entry
1233 } else {
1234 ttEntry = ttShallower; // Replace this shallow entry
1235 }
1236 }
1237
1238 if (replacingSameEntry) {
1239 if (depth < ttEntry->depth) {
1240 // Do not overwrite an existing better entry for the same
1241 // position; but if there was no move, add one:
1242 if (ttEntry->bestMove == 0 && bestMove != NULL) {
1243 tte_SetBestMove (ttEntry, bestMove);
1244 }
1245 return;
1246 }
1247 if (depth == ttEntry->depth) {
1248 // Do not replace an exact score entry of the same depth for
1249 // the same position with an inexact entry:
1250 if (tte_ScoreFlag(ttEntry) == SCORE_EXACT && ttFlag != SCORE_EXACT) {
1251 return;
1252 }
1253 }
1254 }
1255
1256 // Convert mating scores to include the current Ply count:
1257 if (IsMatingScore(score)) { score += Ply; }
1258 if (IsGettingMatedScore(score)) { score -= Ply; }
1259
1260 // Fill in the hash entry fields:
1261 ttEntry->hash = hash;
1262 ttEntry->pawnhash = pawnhash;
1263 ttEntry->depth = depth;
1264 ttEntry->score = score;
1265 tte_SetFlags (ttEntry, ttFlag, stm, Pos.GetCastlingFlags(), isOnlyMove);
1266 ttEntry->sequence = TranTableSequence;
1267 ttEntry->bestMove = 0;
1268 if (bestMove != NULL) {
1269 ASSERT (bestMove->movingPiece != EMPTY);
1270 ASSERT (piece_Color(bestMove->movingPiece) == stm);
1271 ASSERT (bestMove->from <= H8);
1272 tte_SetBestMove (ttEntry, bestMove);
1273 }
1274 ttEntry->enpassant = Pos.GetEPTarget();
1275 }
1276
1277 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1278 // Engine::ProbeHash
1279 // Probe the transposition table for the current position.
1280 //
1281 scoreFlagT
ProbeHash(int depth,int * score,simpleMoveT * bestMove,bool * isOnlyMove)1282 Engine::ProbeHash (int depth, int * score, simpleMoveT * bestMove, bool * isOnlyMove)
1283 {
1284 // Clear the best move:
1285 if (bestMove != NULL) { bestMove->from = bestMove->to = NULL_SQUARE; }
1286
1287 if (TranTableSize == 0) { return SCORE_NONE; }
1288
1289 uint hash = Pos.HashValue();
1290 colorT stm = Pos.GetToMove();
1291 if (stm == BLACK) { hash = ~hash; }
1292
1293 // Examine the corresponding pair of table entries:
1294 uint ttSlot = (hash % TranTableSize) & 0xFFFFFFFEU;
1295 ASSERT (ttSlot+1 < TranTableSize);
1296 transTableEntryT * ttEntry = &(TranTable[ttSlot]);
1297 if (ttEntry->hash != hash) { ttEntry++; }
1298 if (ttEntry->hash != hash) { return SCORE_NONE; }
1299 if (tte_ScoreFlag(ttEntry) == SCORE_NONE) { return SCORE_NONE; }
1300 uint pawnhash = Pos.PawnHashValue();
1301 if (ttEntry->pawnhash != pawnhash) { return SCORE_NONE; }
1302 if (tte_SideToMove(ttEntry) != stm) { return SCORE_NONE; }
1303 if (tte_Castling(ttEntry) != Pos.GetCastlingFlags()) { return SCORE_NONE; }
1304 if (ttEntry->enpassant != Pos.GetEPTarget()) { return SCORE_NONE; }
1305
1306 // If a hash move is stored, we return it even if the depth is not
1307 // sufficient, because it will be useful for move ordering anyway.
1308 if (bestMove != NULL && ttEntry->bestMove != 0) {
1309 tte_GetBestMove (ttEntry, bestMove);
1310 const pieceT* board = Pos.GetBoard();
1311 bestMove->movingPiece = board[bestMove->from];
1312 }
1313 if (isOnlyMove != NULL) {
1314 *isOnlyMove = tte_IsOnlyMove (ttEntry);
1315 }
1316 // Only return an exact or bounded score if the stored depth is at
1317 // least as large as the requested depth:
1318 if (ttEntry->depth < depth) { return SCORE_NONE; }
1319 if (score != NULL) {
1320 *score = ttEntry->score;
1321 // Convert mating scores to exclude the current Ply count:
1322 if (IsMatingScore(*score)) { *score -= Ply; }
1323 if (IsGettingMatedScore(*score)) { *score += Ply; }
1324 }
1325 return tte_ScoreFlag(ttEntry);
1326 }
1327
1328 static uint nFailHigh = 0;
1329 static uint nFailHighFirstMove = 0;
1330
1331 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1332 // Engine::SetPosition
1333 // Set the current position. If the new position parameter
1334 // is NULL, the standard starting position is used.
1335 void
SetPosition(Position * newpos)1336 Engine::SetPosition (Position * newpos)
1337 {
1338 // Delete old game moves:
1339 for (uint i=0; i < NumGameMoves; i++) {
1340 #ifdef WINCE
1341 my_Tcl_Free((char *) GameMoves[i]);
1342 #else
1343 delete GameMoves[i];
1344 #endif
1345 }
1346 NumGameMoves = 0;
1347
1348 // Set the position:
1349 if (newpos == NULL) {
1350 RootPos.StdStart();
1351 Pos.StdStart();
1352 } else {
1353 RootPos.CopyFrom (newpos);
1354 Pos.CopyFrom (newpos);
1355 }
1356
1357 // Clear the repetition stack:
1358 RepStackSize = 0;
1359
1360 // Clear the PV:
1361 PV[0].length = 0;
1362
1363 // Change the transposition table sequence number so existing
1364 // entries can be detected as old ones:
1365 TranTableSequence++;
1366 }
1367
1368 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1369 // Engine::Think
1370 // Initiate a search from the current position. If the supplied
1371 // move list is NULL, generate and examine all legal moves at the
1372 // root position. However, if the move list is not NULL, it
1373 // contains a subset of the legal moves to be analyzed.
1374 //
1375 // Returns the score (in centipawns, for the side to move) and
1376 // reorders the move list (if supplied) so the best move is at
1377 // the start of the list.
1378 int
Think(MoveList * mlist)1379 Engine::Think (MoveList * mlist)
1380 {
1381 Elapsed.Reset();
1382 NodeCount = 0;
1383 QNodeCount = 0;
1384 Ply = 0;
1385 IsOutOfTime = false;
1386 EasyMove = false;
1387 HardMove = false;
1388 InNullMove = 0;
1389 SetPVLength();
1390
1391 ClearKillerMoves();
1392 ClearHistoryValues();
1393
1394 // If no legal move list was specified, generate and search all moves:
1395 MoveList tmpMoveList;
1396 if (mlist == NULL) {
1397 mlist = &tmpMoveList;
1398 Pos.GenerateMoves(mlist);
1399 }
1400
1401 // No legal moves? Return 0 for stalemate, -Infinity for checkmate.
1402 if (mlist->Size() == 0) {
1403 return (Pos.IsKingInCheck() ? -Infinity : 0);
1404 }
1405
1406 // Sort the root move list by quiescent evaluation to get a
1407 // reasonably good initial move order:
1408 for (uint i=0; i < mlist->Size(); i++) {
1409 simpleMoveT * sm = mlist->Get(i);
1410 DoMove(sm);
1411 sm->score = -Quiesce (-Infinity, Infinity);
1412 UndoMove(sm);
1413 }
1414 std::sort(mlist->begin(), mlist->end());
1415
1416 // Check for an easy move, one that scores more than two pawns
1417 // better than any alternative:
1418 if (mlist->Size() > 1) {
1419 int margin = mlist->Get(0)->score - mlist->Get(1)->score;
1420 if (margin > (2 * PawnValue)) {
1421 // Output ("Easy move: margin = %d\n", margin);
1422 EasyMove = true;
1423 }
1424 }
1425
1426 int bestScore = -Infinity;
1427
1428 // Do iterative deepening starting at depth 1, until out of
1429 // time or the maximum depth is reached:
1430 for (uint depth = 1; depth <= MaxDepth; depth++) {
1431
1432 HardMove = false;
1433
1434 // If we have searched at least a few ply, and there is less
1435 // than 30% of the recommended search time remaining, then
1436 // continuing the search is unlikely to be useful since it
1437 // will probably spend all remaining time on the first move:
1438 if (depth > MinDepthCheckTime) { // was 4. or will think too long when trying to check if a move is obvious
1439 double used = (double)Elapsed.MilliSecs() / (double)SearchTime;
1440 if (used > 0.7) { break; }
1441 }
1442
1443 // Set up the alpha-beta range. For all but the first depth,
1444 // use a small aspiration window around the previous score
1445 // since we do not expect the score to change much:
1446 int alpha = -Infinity - 1;
1447 int beta = Infinity + 1;
1448 if (depth > 1) {
1449 alpha = bestScore - AspirationWindow;
1450 beta = bestScore + AspirationWindow;
1451 }
1452
1453 int score = SearchRoot (depth, alpha, beta, mlist);
1454 if (OutOfTime()) { break; }
1455 if (score >= beta) {
1456 // Aspiration window fail-high:
1457 PrintPV (depth, score, "++");
1458 alpha = score - 1;
1459 beta = Infinity + 1;
1460 score = SearchRoot (depth, alpha, beta, mlist);
1461 } else if (score <= alpha) {
1462 // Aspiration window fail-low:
1463 PrintPV (depth, score, "--");
1464 EasyMove = false;
1465 HardMove = true;
1466 alpha = -Infinity - 1;
1467 beta = score + 1;
1468 score = SearchRoot (depth, alpha, beta, mlist);
1469 }
1470 if (OutOfTime()) { break; }
1471 // If the 2nd search failed, try again with an infinite window.
1472 // This is rare, but can happen with hashing/null-move effects.
1473 if (score < alpha || score > beta) {
1474 alpha = -Infinity;
1475 beta = Infinity;
1476 EasyMove = false;
1477 HardMove = true;
1478 score = SearchRoot (depth, alpha, beta, mlist);
1479 }
1480
1481 if (OutOfTime()) { break; }
1482 bestScore = score;
1483 PrintPV (depth, bestScore, ">>>");
1484
1485 // Stop if Only a few moves OR checkmate has been found - but not too soon:
1486 if (mlist->Size() <= depth || (depth >= 5 && IsMatingScore(bestScore))) { break; }
1487
1488 // Make sure the first move in the list remains there by
1489 // giving it a huge node count for its move ordering score:
1490 mlist->Get(0)->score = 1 << 30;
1491
1492 // Sort the move list based on node counts from this iteration:
1493 std::sort(mlist->begin(), mlist->end());
1494 }
1495
1496 return bestScore;
1497 }
1498
1499 int
SearchRoot(int depth,int alpha,int beta,MoveList * mlist)1500 Engine::SearchRoot (int depth, int alpha, int beta, MoveList * mlist)
1501 {
1502 ASSERT (depth >= 1);
1503
1504 // If no legal move list was specified, generate and search all moves:
1505 MoveList tmpMoveList;
1506 if (mlist == NULL) {
1507 mlist = &tmpMoveList;
1508 Pos.GenerateMoves(mlist);
1509 }
1510
1511 // No legal moves to search? Just return an equal score for
1512 // stalemate or -Infinity for checkmate.
1513 if (mlist->Size() == 0) {
1514 return (Pos.IsKingInCheck() ? -Infinity : 0);
1515 }
1516
1517 bool isOnlyMove = (mlist->Size() == 1);
1518 int bestScore = -Infinity - 1;
1519
1520 for (uint movenum=0; movenum < mlist->Size(); movenum++) {
1521 simpleMoveT * sm = mlist->Get(movenum);
1522 uint oldNodeCount = NodeCount;
1523 // Make this move and search it:
1524 DoMove (sm);
1525 InCheck[Ply] = Pos.IsKingInCheck (sm);
1526 #define PVS_SEARCH
1527 #ifdef PVS_SEARCH
1528 int score = alpha;
1529 if (movenum == 0) {
1530 score = -Search (depth - 1, -beta, -alpha, true);
1531 } else {
1532 // Do a minimal window search first, to try and quickly
1533 // identify the common case of a move not being good
1534 // enough to improve alpha:
1535 score = -Search (depth - 1, -alpha - 1, -alpha, true);
1536 if (score > alpha && score < beta) {
1537 // This move is good enough to search with the proper
1538 // window; use the score it returned as the lower bound:
1539 score = -Search (depth - 1, -beta, -score, true);
1540 }
1541 }
1542 #else
1543 int score = -Search (depth - 1, -beta, -alpha, true);
1544 #endif
1545 UndoMove (sm);
1546 if (OutOfTime()) { break; }
1547
1548 // Set the move ordering score of this move to be the number of
1549 // nodes spent on it, so interesting moves of this iteration are
1550 // searched first at the next iteration depth:
1551 sm->score = NodeCount - oldNodeCount;
1552
1553 // If this is the first move searched at this depth or
1554 // a new best move, update the best score and promote
1555 // the move to be first in the list:
1556 if (movenum == 0 || score > bestScore) {
1557 bestScore = score;
1558 alpha = score;
1559 UpdatePV (sm);
1560 PrintPV (depth, bestScore);
1561 StoreHash (depth, SCORE_EXACT, score, sm, isOnlyMove);
1562 std::rotate(mlist->begin(), mlist->begin() + movenum,
1563 mlist->begin() + movenum + 1);
1564 if (movenum > 0) { EasyMove = false; }
1565 }
1566 }
1567 return bestScore;
1568 }
1569
1570 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1571 // Engine::Search
1572 // Internal Search routine, used at every depth except
1573 // the root position.
1574 int
Search(int depth,int alpha,int beta,bool tryNullMove)1575 Engine::Search (int depth, int alpha, int beta, bool tryNullMove)
1576 {
1577 SetPVLength();
1578
1579 // If there is no remaining depth, return a quiescent evaluation:
1580 if (depth <= 0) { return Quiesce (alpha, beta); }
1581
1582 // Check that the absolute depth limit is not exceeded:
1583 if (Ply >= ENGINE_MAX_PLY - 1) { return alpha; }
1584
1585 // Check for a drawn position (no mating material, repetition, etc):
1586 if (NoMatingMaterial()) { return 0; }
1587 if (FiftyMoveDraw()) { return 0; }
1588 uint repeats = RepeatedPosition();
1589 if (repeats >= 3 || (repeats == 2 && Ply > 2)) { return 0; }
1590
1591 colorT toMove = Pos.GetToMove();
1592 NodeCount++;
1593
1594 // Stop now if we ran out of time:
1595 if (OutOfTime()) { return alpha; }
1596
1597 // Check for a recognized endgame score:
1598 if (Pos.TotalMaterial() <= Recognizer::MaxPieces()) {
1599 int recog = Recognizer::Recognize(&Pos);
1600 int rscore = recogScore(recog);
1601 scoreFlagT rflag = recogFlag(recog);
1602
1603 if (rflag == SCORE_EXACT) {
1604 return rscore;
1605 } else if (rflag == SCORE_LOWER) {
1606 if (rscore >= beta) { return rscore; }
1607 if (rscore < alpha) { alpha = rscore; }
1608 } else if (rflag == SCORE_UPPER) {
1609 if (rscore <= alpha) { return rscore; }
1610 if (rscore > beta) { beta = rscore; }
1611 }
1612 }
1613
1614 // Probe the hash table:
1615 int hashscore = alpha;
1616 simpleMoveT hashmove;
1617 bool isOnlyMove = 0;
1618 scoreFlagT hashflag = ProbeHash (depth, &hashscore, &hashmove, &isOnlyMove);
1619
1620 switch (hashflag) {
1621 case SCORE_NONE:
1622 break;
1623 case SCORE_LOWER:
1624 if (hashscore >= beta) { return hashscore; }
1625 if (hashscore > alpha) { alpha = hashscore; }
1626 break;
1627 case SCORE_UPPER:
1628 if (hashscore <= alpha) { return hashscore; }
1629 if (hashscore < beta) { beta = hashscore; }
1630 break;
1631 case SCORE_EXACT:
1632 if (hashscore > alpha && hashscore < beta) {
1633 UpdatePV (&hashmove);
1634 }
1635 return hashscore;
1636 }
1637
1638 int baseExtensions = 0;
1639 bool inCheck = InCheck[Ply];
1640
1641 // Null move pruning:
1642 // If the side to move has at least a few pieces (to reduce the risk
1643 // of zugzwang) and is not in check, and a null move was not made to
1644 // reach this point in the search, try making a null move now. The
1645 // idea is to pass on our move and see (with a shallow search) if
1646 // if the enemy has any move that can score better than the beta
1647 // cutoff. If they have no such move, it means our position is good
1648 // enough to cut off the search without even considering our own
1649 // possible moves.
1650
1651 if (inCheck || depth < 2 || Pos.NumNonPawns(toMove) < 3) {
1652 tryNullMove = false;
1653 }
1654
1655 if (tryNullMove) {
1656 Pos.SetToMove (color_Flip(toMove));
1657 squareT oldEPTarget = Pos.GetEPTarget();
1658 Pos.SetEPTarget (NULL_SQUARE);
1659 // We keep track of whether we are in a null move search or
1660 // not, to avoid updating the PV.
1661 InNullMove++;
1662 // Do an R=2 or R=3 nullmove search, depending on remaining depth:
1663 int nulldepth = depth - NullMoveReduction;
1664 if (depth > 6) {
1665 nulldepth--; // An R=3 null move search.
1666 }
1667 int nullscore = -Search (nulldepth - 1, -beta, -beta + 1, false);
1668 InNullMove--;
1669 Pos.SetEPTarget (oldEPTarget);
1670 Pos.SetToMove (toMove);
1671
1672 // If the null-move score is better than beta, cut the search:
1673 if (nullscore >= beta) {
1674 return beta;
1675 }
1676
1677 // If the null-move score indicates that making a null move
1678 // would lead to us getting mated, extend the search another
1679 // ply to try and avoid the mate threats:
1680 if (IsGettingMatedScore (nullscore)) { baseExtensions++; }
1681 }
1682
1683 // In-check extension: search one ply deeper if we are in check.
1684 if (inCheck) { baseExtensions++; }
1685
1686 // Now we want to generate all legal moves and order them. But if
1687 // we got a move from the hash table, it is worth trying that move
1688 // first, and only generating and scoring the rest of the moves if
1689 // the hash move does not cause a beta cutoff.
1690 // Note that we already know whether the side to move is in check,
1691 // so we pass this information to GenerateMoves to speed it up.
1692
1693 MoveList mlist;
1694 bool gotHashMove;
1695 if (Pos.IsLegalMove (&hashmove)) {
1696 gotHashMove = true;
1697 // For now, we only add the hash move to the move list.
1698 mlist.push_back(hashmove);
1699 mlist.Get(0)->score = ENGINE_HASH_SCORE;
1700 } else {
1701 // No hash table move, so generate and score all the moves now.
1702 gotHashMove = false;
1703 Pos.GenerateMoves (&mlist, EMPTY, GEN_ALL_MOVES, InCheck[Ply]);
1704 ScoreMoves (&mlist);
1705 isOnlyMove = (mlist.Size() == 1);
1706 }
1707
1708 // If there is only one legal move, extend the search:
1709 if (isOnlyMove) { baseExtensions++; }
1710
1711 // Remember the original alpha score:
1712 int oldAlpha = alpha;
1713 int bestMoveIndex = -1;
1714
1715 // Search each move:
1716 for (uint movenum = 0; movenum < mlist.Size(); movenum++) {
1717 // Find the highest-scoring remaining move:
1718 MoveList::iterator sm = std::min_element(mlist.begin() + movenum, mlist.end());
1719 std::iter_swap(mlist.begin() + movenum, sm);
1720
1721 // Move-specific extensions:
1722 int extensions = baseExtensions;
1723
1724 // If moving a pawn to the 7th or 8th rank, extend the search:
1725 if (piece_Type(sm->movingPiece) == PAWN) {
1726 rankT rank = square_Rank(sm->to);
1727 if (rank <= RANK_2 || rank >= RANK_7) { extensions++; }
1728 }
1729
1730 // Reduce extensions if the search is deep:
1731 if (extensions > 0 && (int)Ply >= depth + depth) { extensions /= 2; }
1732
1733 // Limit extensions to one ply (only if deep enough?):
1734 if (extensions > 1 /*&& (int)Ply >= depth*/) { extensions = 1; }
1735
1736 // Make this move and remember if it gives check:
1737 DoMove (sm);
1738 InCheck[Ply] = Pos.IsKingInCheck (sm);
1739
1740 // Simple futility pruning. Note that pruning with depth of two
1741 // remaining is risky, but seems to work well enough in practise.
1742 // We only prune when:
1743 // (1) there are no extensions,
1744 // (2) we are at ply 3 or deeper,
1745 // (3) the move made does not give check,
1746 // (4) the score does not indicate mate,
1747 // (5) the move is not the only legal move, and
1748 // (6) we are not in a pawn ending.
1749
1750 if (Pruning && extensions == 0 && Ply > 2 && depth <= 2
1751 && !InCheck[Ply] && !IsMatingScore (alpha) && !isOnlyMove
1752 && Pos.NumNonPawns(WHITE) > 1 && Pos.NumNonPawns(BLACK) > 1) {
1753 int mscore = -ScoreMaterial();
1754 bool futile = false;
1755 if (depth == 1) {
1756 // Futility pruning, when 2 pawns below alpha:
1757 futile = ((mscore + (PawnValue * 2)) < alpha);
1758 } else if (depth == 2) {
1759 // Extended futility pruning, when a rook below alpha:
1760 futile = ((mscore + RookValue) < alpha);
1761 }
1762 // Skip this move if it is futile:
1763 if (futile) {
1764 UndoMove(sm);
1765 continue;
1766 }
1767 }
1768
1769 #define PVS_SEARCH
1770 #ifdef PVS_SEARCH
1771 // We do a normal search for the first move, but for all other
1772 // moves we try a minimal window search first to save time:
1773 int score = alpha;
1774 if (movenum == 0) {
1775 score = -Search (depth + extensions - 1, -beta, -alpha, true);
1776 } else {
1777 score = -Search (depth + extensions - 1, -alpha - 1, -alpha, true);
1778 if (score > alpha && score < beta) {
1779 // This move is good enough to search with the proper
1780 // window; use the score it returned as the lower bound:
1781 score = -Search (depth + extensions - 1, -beta, -score, true);
1782 }
1783 }
1784 #else
1785 // No PVS, just do a regular search at every move:
1786 int score = -Search (depth + extensions - 1, -beta, -alpha, true);
1787 #endif
1788 UndoMove (sm);
1789
1790 // If this move scored at least as good as beta, we have
1791 // "failed high" so there is no need to continue searching
1792 // for an even better move:
1793 if (score >= beta) {
1794 IncHistoryValue (sm, depth * depth);
1795 AddKillerMove (sm);
1796 StoreHash (depth, SCORE_LOWER, score, sm, isOnlyMove);
1797 // Fail-high-first-move stats:
1798 nFailHigh++;
1799 if (movenum == 0) { nFailHighFirstMove++; }
1800 return beta;
1801 }
1802
1803 // If this move is better than the alpha score, it is a new
1804 // best move at this point in the search tree. Update the PV
1805 // (and boost the history value of the move a little? - no):
1806 if (score > alpha) {
1807 alpha = score;
1808 bestMoveIndex = movenum;
1809 UpdatePV (sm);
1810 // IncHistoryValue (sm, depth);
1811 }
1812
1813 // All done with that move. If it was the first move in the list and
1814 // it was the move from the hashtable, then the remaining moves have
1815 // not been generated and scored for move ordering. We do that now,
1816 // ensuring that the hash table move we just examined is moved to
1817 // the start of the list so it does not get searched again.
1818 if (movenum == 0 && gotHashMove && !isOnlyMove) {
1819 mlist.Clear();
1820 Pos.GenerateMoves (&mlist, EMPTY, GEN_ALL_MOVES, InCheck[Ply]);
1821 ScoreMoves (&mlist);
1822 MoveList::iterator hm = std::find(mlist.begin(), mlist.end(), cmpMove(hashmove));
1823 if (hm != mlist.end()) {
1824 std::iter_swap(mlist.begin(), hm);
1825 } else {
1826 // The hash table move was legal, but not found in the
1827 // move list -- Bizarre!
1828 Output ("# Yikes! Hash table move not in move list! Bug?\n");
1829 }
1830 }
1831 }
1832
1833 if (mlist.Size() == 0) {
1834 // No legal moves? Must be checkmate or stalemate:
1835 return (InCheck[Ply] ? (-Infinity + Ply) : 0);
1836 }
1837
1838 // If alpha did not get improved, we "failed low"; every move
1839 // scored worse than our lower bound.
1840 // Store alpha in the transposition table as an upper bound on
1841 // the true score of this position, with no best move.
1842 if (alpha == oldAlpha) {
1843 ASSERT (bestMoveIndex < 0);
1844 StoreHash (depth, SCORE_UPPER, alpha, NULL, isOnlyMove);
1845 } else {
1846 // Update the transposition table with the best move:
1847 ASSERT (bestMoveIndex >= 0);
1848 simpleMoveT * bestMove = mlist.Get(bestMoveIndex);
1849 IncHistoryValue (bestMove, depth * depth);
1850 // Should we also add this as a killer move? Possibly not,
1851 // since it was not good enough to cause a beta cutoff.
1852 // It seems to make little difference.
1853 AddKillerMove (bestMove);
1854 StoreHash (depth, SCORE_EXACT, alpha, bestMove, isOnlyMove);
1855 }
1856 return alpha;
1857 }
1858
1859 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1860 // Engine::Quiesce
1861 // Search only captures until a stable position is reached
1862 // that can be evaluated.
1863 int
Quiesce(int alpha,int beta)1864 Engine::Quiesce (int alpha, int beta)
1865 {
1866 NodeCount++;
1867 QNodeCount++;
1868
1869 // Check that the absolute depth limit is not exceeded:
1870 if (Ply >= ENGINE_MAX_PLY - 1) { return alpha; }
1871 SetPVLength();
1872
1873 // Stop now if we are out of time:
1874 if (OutOfTime()) { return alpha; }
1875
1876 // Check for a recognized endgame score:
1877 if (Pos.TotalMaterial() <= Recognizer::MaxPieces()) {
1878 int recog = Recognizer::Recognize(&Pos);
1879 int rscore = recogScore(recog);
1880 scoreFlagT rflag = recogFlag(recog);
1881
1882 if (rflag == SCORE_EXACT) {
1883 return rscore;
1884 } else if (rflag == SCORE_LOWER) {
1885 if (rscore >= beta) { return rscore; }
1886 if (rscore < alpha) { alpha = rscore; }
1887 } else if (rflag == SCORE_UPPER) {
1888 if (rscore <= alpha) { return rscore; }
1889 if (rscore > beta) { beta = rscore; }
1890 }
1891 }
1892
1893 // Find the static evaluation of this position, to either cause
1894 // a beta cutoff or improve the alpha score:
1895 int staticScore = Score (alpha, beta);
1896 if (staticScore >= beta) { return beta; }
1897 if (staticScore > alpha) { alpha = staticScore; }
1898
1899 // Check for a static score so far below alpha that no capture
1900 // is going to be good enough anyway:
1901 int margin = PawnValue;
1902 if (staticScore + QueenValue + margin < alpha) { return alpha; }
1903
1904 // Generate and score the list of captures:
1905 MoveList mlist;
1906 Pos.GenerateMoves (&mlist, GEN_CAPTURES);
1907 for (uint m=0; m < mlist.Size(); m++) {
1908 simpleMoveT * sm = mlist.Get(m);
1909 sm->score = SEE (sm->from, sm->to);
1910 }
1911
1912 // Iterate through each quiescent move to find a beta cutoff or
1913 // improve the alpha score:
1914
1915 for (uint i = 0; i < mlist.Size(); i++) {
1916 // Find the highest-scoring remaining move, make it and search:
1917 MoveList::iterator sm = std::min_element(mlist.begin() + i, mlist.end());
1918 std::iter_swap(mlist.begin() + i, sm);
1919 pieceT promote = piece_Type(sm->promote);
1920
1921 // Skip underpromotions:
1922 if (promote != EMPTY && promote != QUEEN) { continue; }
1923
1924 // Stop if the capture gain is negative or is so small that it
1925 // will (very likely) not improve alpha:
1926 if (sm->score < 0) { break; }
1927 if ((sm->score + staticScore + margin) < alpha) { break; }
1928
1929 // Make the move and evaluate it:
1930 DoMove (sm);
1931 int score = -Quiesce (-beta, -alpha);
1932 UndoMove (sm);
1933
1934 // Check for a score so good it causes a beta cutoff:
1935 if (score >= beta) { return score; }
1936
1937 // See if we have a new best move:
1938 if (score > alpha) {
1939 alpha = score;
1940 UpdatePV (sm);
1941 }
1942 }
1943 return alpha;
1944 }
1945
1946 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1947 // Engine::SEE
1948 // Static Exchange Evaluator.
1949 // Evaluates the approximate material result of moving the piece
1950 // from the from square (which must not be empty) to the target
1951 // square (which may be empty or may hold an enemy piece).
1952 int
SEE(squareT from,squareT target)1953 Engine::SEE (squareT from, squareT target)
1954 {
1955 const pieceT * board = Pos.GetBoard();
1956 SquareList attackers[2];
1957 pieceT mover = piece_Type(board[from]);
1958 ASSERT (mover != EMPTY);
1959 colorT stm = piece_Color_NotEmpty(board[from]);
1960
1961 #define SEE_ADD(c,sq) attackers[(c)].Add(sq)
1962
1963 // Currently the SEE method is only called for legal moves, so if
1964 // the moving piece is a king then it clearly cannot be captured.
1965 // If potentially illegal king moves are to be passed to this
1966 // method, the following optimisation should be removed.
1967 if (mover == KING) { return PieceValue(board[target]); }
1968
1969 // Find the estimated result assuming one recapture:
1970 int fastResult = PieceValue(board[target]) - PieceValue(mover);
1971
1972 // We can do quick estimation for a big gain, but have to be
1973 // careful since move ordering is very sensitive to positive SEE
1974 // scores. Only return a fast estimate for PxQ, NxQ, BxQ and PxR:
1975 if (fastResult > KnightValue && mover != ROOK) { return fastResult; }
1976
1977 // Add attacking pawns to the attackers list:
1978 squareT pawnSq = square_Move (target, DOWN_LEFT);
1979 if (board[pawnSq] == WP && pawnSq != from) { SEE_ADD (WHITE, pawnSq); }
1980 pawnSq = square_Move (target, DOWN_RIGHT);
1981 if (board[pawnSq] == WP && pawnSq != from) { SEE_ADD (WHITE, pawnSq); }
1982 pawnSq = square_Move (target, UP_LEFT);
1983 if (board[pawnSq] == BP && pawnSq != from) { SEE_ADD (BLACK, pawnSq); }
1984 pawnSq = square_Move (target, UP_RIGHT);
1985 if (board[pawnSq] == BP && pawnSq != from) { SEE_ADD (BLACK, pawnSq); }
1986
1987 // Quick estimation for a nonpawn capturing a lesser-valued piece (or
1988 // moving to an empty square) which is defended by an enemy pawn.
1989 if (fastResult < -PawnValue && attackers[color_Flip(stm)].Size() > 0) {
1990 return fastResult;
1991 }
1992
1993 // Add attacking knights. Only bother searching for them if there
1994 // are any knights on the appropriate square color.
1995 colorT knightSquareColor = color_Flip(square_Color(target));
1996 uint nEligibleKnights = Pos.SquareColorCount(WN, knightSquareColor)
1997 + Pos.SquareColorCount(BN, knightSquareColor);
1998 if (nEligibleKnights > 0) {
1999 const squareT * nextKnightSq = knightAttacks[target];
2000 while (true) {
2001 squareT dest = *nextKnightSq;
2002 if (dest == NULL_SQUARE) { break; }
2003 nextKnightSq++;
2004 pieceT p = board[dest];
2005 if (piece_Type(p) != KNIGHT) { continue; }
2006 if (dest == from) { continue; }
2007 // Quick estimate when this recapture ensures a negative result:
2008 colorT knightColor = piece_Color_NotEmpty(p);
2009 if (fastResult < -KnightValue && knightColor != stm) {
2010 return fastResult + KnightValue / 2;
2011 }
2012 SEE_ADD (knightColor, dest);
2013 }
2014 }
2015
2016 // Add the first sliding attackers in each direction. Others
2017 // may appear later as appropriate, when the piece in front
2018 // of them takes part in the capture sequence.
2019
2020 // First make an array containing all the directions that contain
2021 // potential sliding attackers, to avoid searching useless directions.
2022 rankT rank = square_Rank(target);
2023 fyleT fyle = square_Fyle(target);
2024 leftDiagT ul = square_LeftDiag(target);
2025 rightDiagT ur = square_RightDiag(target);
2026 uint rankCount = Pos.RankCount(WQ,rank) + Pos.RankCount(BQ,rank)
2027 + Pos.RankCount(WR,rank) + Pos.RankCount(BR,rank);
2028 uint fyleCount = Pos.FyleCount(WQ,fyle) + Pos.FyleCount(BQ,fyle)
2029 + Pos.FyleCount(WR,fyle) + Pos.FyleCount(BR,fyle);
2030 uint upLeftCount = Pos.LeftDiagCount(WQ,ul) + Pos.LeftDiagCount(BQ,ul)
2031 + Pos.LeftDiagCount(WB,ul) + Pos.LeftDiagCount(BB,ul);
2032 uint upRightCount = Pos.RightDiagCount(WQ,ur) + Pos.RightDiagCount(BQ,ur)
2033 + Pos.RightDiagCount(WB,ur) + Pos.RightDiagCount(BB,ur);
2034
2035 // If the moving piece is a slider, it is worth removing it from the
2036 // rank/file/diagonal counts because we will avoid searching two
2037 // directions if it is the only slider on its rank/file/diagonal.
2038 if (piece_IsSlider(mover)) {
2039 if (square_Rank(from) == square_Rank(target)) {
2040 rankCount--;
2041 } else if (square_Fyle(from) == square_Fyle(target)) {
2042 fyleCount--;
2043 } else if (square_LeftDiag(from) == square_LeftDiag(target)) {
2044 upLeftCount--;
2045 } else {
2046 ASSERT (square_RightDiag(from) == square_RightDiag(target));
2047 upRightCount--;
2048 }
2049 }
2050
2051 // Build the list of directions with potential sliding capturers:
2052 uint nDirs = 0;
2053 directionT sliderDir[8];
2054 if (rankCount > 0) {
2055 sliderDir[nDirs++] = LEFT;
2056 sliderDir[nDirs++] = RIGHT;
2057 }
2058 if (fyleCount > 0) {
2059 sliderDir[nDirs++] = UP;
2060 sliderDir[nDirs++] = DOWN;
2061 }
2062 if (upLeftCount > 0) {
2063 sliderDir[nDirs++] = UP_LEFT;
2064 sliderDir[nDirs++] = DOWN_RIGHT;
2065 }
2066 if (upRightCount > 0) {
2067 sliderDir[nDirs++] = UP_RIGHT;
2068 sliderDir[nDirs++] = DOWN_LEFT;
2069 }
2070
2071 // Iterate over each direction, looking for an attacking slider:
2072
2073 for (uint dirIndex = 0; dirIndex < nDirs; dirIndex++) {
2074 directionT dir = sliderDir[dirIndex];
2075 squareT dest = target;
2076 squareT last = square_Last (target, dir);
2077 int delta = direction_Delta (dir);
2078 uint distance = 0;
2079
2080 while (dest != last) {
2081 dest += delta;
2082 distance++;
2083 pieceT p = board[dest];
2084 if (p == EMPTY) { continue; }
2085 if (dest == from) { continue; }
2086 pieceT ptype = piece_Type(p);
2087 if (ptype == PAWN) {
2088 // Look through this pawn if it was also a capturer.
2089 if (distance != 1) { break; }
2090 if (p == WP) {
2091 if (dir == DOWN_LEFT || dir == DOWN_RIGHT) { continue; }
2092 } else {
2093 if (dir == UP_LEFT || dir == UP_RIGHT) { continue; }
2094 }
2095 break;
2096 }
2097 if (! piece_IsSlider(ptype)) { break; }
2098 if (ptype == ROOK && direction_IsDiagonal(dir)) { break; }
2099 if (ptype == BISHOP && !direction_IsDiagonal(dir)) { break; }
2100 colorT c = piece_Color_NotEmpty(p);
2101
2102 // Quick estimate when this recapture ensures a negative result:
2103 if (fastResult < -BishopValue && ptype == BISHOP) {
2104 if (c != stm) { return fastResult + BishopValue / 2; }
2105 } else if (fastResult < -RookValue && ptype == ROOK) {
2106 if (c != stm) { return fastResult + RookValue / 2; }
2107 }
2108
2109 // OK, we have a sliding attacker. Add it:
2110 SEE_ADD (c, dest);
2111 break;
2112 }
2113 }
2114
2115 // Add one capturing king if the other king cannot capture:
2116 squareT wk = Pos.GetKingSquare (WHITE);
2117 squareT bk = Pos.GetKingSquare (BLACK);
2118 if (wk != from && bk != from) {
2119 bool wkAttacks = square_Adjacent (target, wk);
2120 bool bkAttacks = square_Adjacent (target, bk);
2121 if (wkAttacks && !bkAttacks) {
2122 SEE_ADD (WHITE, wk);
2123 } else if (bkAttacks && !wkAttacks) {
2124 SEE_ADD (BLACK, bk);
2125 }
2126 }
2127
2128 // Now go through the attack lists (which may get hidden sliders added
2129 // as sliding pieces make captures) finding the best capture sequence.
2130
2131 bool targetIsPromoSquare = (target <= H1 || target >= A8);
2132 int swaplist[32];
2133 uint nswaps = 1;
2134 swaplist[0] = PieceValue (board[target]);
2135 int attackedVal = PieceValue (mover);
2136
2137 // Adjust the swap value for a promotion:
2138 if (targetIsPromoSquare && attackedVal == PawnValue) {
2139 swaplist[0] += QueenValue - PawnValue;
2140 attackedVal = QueenValue;
2141 }
2142
2143 // Add as many captures to the sequence as possible, using
2144 // lowest-valued pieces first:
2145 while (true) {
2146 // Switch to the other side:
2147 stm = color_Flip(stm);
2148 SquareList * attackList = &(attackers[stm]);
2149 uint attackCount = attackList->Size();
2150
2151 // Has this side run out of pieces to capture with?
2152 if (attackCount == 0) { break; }
2153
2154 // Find the best (lowest-valued) piece to capture with:
2155 uint bestIndex = 0;
2156 squareT attackSquare = attackList->Get(0);
2157 int attackValue = PieceValue(board[attackSquare]);
2158 for (uint i = 1; i < attackCount; i++) {
2159 if (attackValue == PawnValue) { break; }
2160 squareT newSquare = attackList->Get(i);
2161 int newValue = PieceValue(board[newSquare]);
2162 if (newValue < attackValue) {
2163 attackSquare = newSquare;
2164 attackValue = newValue;
2165 bestIndex = i;
2166 }
2167 }
2168 pieceT attackPiece = piece_Type(board[attackSquare]);
2169
2170 // Update the swap list:
2171 swaplist[nswaps] = -swaplist[nswaps-1] + attackedVal;
2172 nswaps++;
2173 attackedVal = attackValue;
2174
2175 // Fudge the value for a promotion, turning the pawn into a queen:
2176 if (targetIsPromoSquare && attackValue == PawnValue) {
2177 swaplist[nswaps-1] += QueenValue - PawnValue;
2178 attackedVal = QueenValue;
2179 }
2180
2181 // Remove the chosen attacker from the list:
2182 attackList->Remove(bestIndex);
2183
2184 // If the attacker is a slider, look for another slider behind it:
2185 if (piece_IsSlider (attackPiece)) {
2186 directionT dir = sqDir[target][attackSquare];
2187 ASSERT (dir != NULL_DIR);
2188 squareT dest = attackSquare;
2189 squareT last = square_Last (dest, dir);
2190 int delta = direction_Delta (dir);
2191
2192 while (dest != last) {
2193 dest += delta;
2194 pieceT p = board[dest];
2195 if (p == EMPTY) { continue; }
2196 pieceT pt = piece_Type(p);
2197 if (! piece_IsSlider(pt)) { break; }
2198 if (pt == ROOK && direction_IsDiagonal(dir)) { break; }
2199 if (pt == BISHOP && !direction_IsDiagonal(dir)) { break; }
2200 // OK, we have another sliding attacker. Add it:
2201 SEE_ADD (piece_Color_NotEmpty(p), dest);
2202 break;
2203 }
2204 }
2205 }
2206
2207 // Finally, go backwards through the swap list and determine when one
2208 // side would stop because further exchanges would be useless:
2209 nswaps--;
2210 while (nswaps > 0) {
2211 uint prev = nswaps - 1;
2212 if (swaplist[nswaps] > -swaplist[prev]) {
2213 swaplist[prev] = -swaplist[nswaps];
2214 }
2215 nswaps--;
2216 }
2217 return swaplist[0];
2218 }
2219
2220 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2221 // Engine::ScoreMoves
2222 // Gives each move in the specified move list a score for move
2223 // ordering. Captures are scored using static exchange evaluation
2224 // while non-capture scores are based on killer move and history
2225 // heuristic information. Promotions are treated as captures.
2226 // The ordering has four basic categories:
2227 // (1) Non-losing captures (ordered by SEE value, score >= EMH * 2);
2228 // (2) Non-capture killer moves (EMH <= score < 2 * EMH);
2229 // (3) Other non-captures (by history heuristic, 0 <= score < EMH);
2230 // (4) Losing captures (ordered by SEE value, score < 0).
2231 // where EMH = ENGINE_MAX_HISTORY is the history value threshold.
2232 void
ScoreMoves(MoveList * mlist)2233 Engine::ScoreMoves (MoveList * mlist)
2234 {
2235 for (uint i = 0; i < mlist->Size(); i++) {
2236 simpleMoveT * sm = mlist->Get(i);
2237 if (sm->capturedPiece != EMPTY || sm->promote != EMPTY) {
2238 int see = SEE (sm->from, sm->to);
2239 if (see >= 0) {
2240 sm->score = ENGINE_MAX_HISTORY * 2 + see;
2241 } else {
2242 sm->score = see;
2243 }
2244 } else {
2245 // Non-capture; just use the history/killer value for this move.
2246 sm->score = GetHistoryValue (sm);
2247 if (IsKillerMove (sm)) {
2248 sm->score += ENGINE_MAX_HISTORY;
2249 }
2250 }
2251 }
2252 }
2253
2254 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2255 // Engine::Output
2256 // Prints a formatted string (as passed to printf) to standard output
2257 // and the the log file if one is being used.
2258 void
Output(const char * format,...)2259 Engine::Output (const char * format, ...)
2260 {
2261 va_list ap;
2262 va_start (ap, format);
2263 vprintf (format, ap);
2264 if (LogFile != NULL) {
2265 vfprintf (LogFile, format, ap);
2266 }
2267 va_end (ap);
2268 }
2269 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2270 // Engine::PrintPV
2271 // Print the current depth, score and principal variation.
2272 void
PrintPV(uint depth,int score,const char * note)2273 Engine::PrintPV (uint depth, int score, const char * note)
2274 {
2275 if (! PostInfo) { return; }
2276 uint ms = Elapsed.MilliSecs();
2277 if (XBoardMode && ms < 50 && Ply < 6) { return; }
2278
2279 if (XBoardMode) {
2280 Output (" %2u %6d %5u %9u ", depth, score, ms / 10, NodeCount);
2281 } else {
2282 Output (" %2u %-3s %+6d %5u %9u ", depth, note, score, ms, NodeCount);
2283 }
2284
2285 principalVarT * pv = &(PV[0]);
2286 uint i;
2287
2288 if (Pos.GetToMove() == BLACK) {
2289 Output ("%u...", Pos.GetFullMoveCount());
2290 }
2291
2292 // Make and print each PV move:
2293 for (i = 0; i < pv->length; i++) {
2294 simpleMoveT * sm = &(pv->move[i]);
2295
2296 // Check for legality, to protect against hash table
2297 // false hits and bugs in PV updating:
2298 if (! Pos.IsLegalMove (sm)) {
2299 Output (" <illegal>");
2300 break;
2301 }
2302 if (i > 0) { Output (" "); }
2303 if (Pos.GetToMove() == WHITE) {
2304 Output ("%u.", Pos.GetFullMoveCount());
2305 }
2306 char s[10];
2307 Pos.MakeSANString (sm, s, SAN_MATETEST);
2308 Output ("%s", s);
2309 Pos.DoSimpleMove (sm);
2310 }
2311 Output ("\n");
2312 // Undo each PV move that was made:
2313 for (; i > 0; i--) {
2314 Pos.UndoSimpleMove (&(pv->move[i-1]));
2315 }
2316 }
2317
2318 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2319 // Engine::OutOfTime
2320 // Returns true if the search time limit has been reached.
2321 // "Out Of Time" is also the name of a great R.E.M. album. :-)
2322 bool
OutOfTime()2323 Engine::OutOfTime ()
2324 {
2325 if (IsOutOfTime) { return true; }
2326
2327 // Only check the time approximately every 1000 nodes for speed:
2328 if ((NodeCount & 1023) != 0) { return false; }
2329
2330 int ms = Elapsed.MilliSecs();
2331
2332 if (EasyMove) {
2333 IsOutOfTime = (ms > MinSearchTime);
2334 } else if (HardMove) {
2335 IsOutOfTime = (ms > MaxSearchTime);
2336 } else {
2337 IsOutOfTime = (ms > SearchTime);
2338 }
2339
2340 if (!IsOutOfTime && CallbackFunction != NULL) {
2341 IsOutOfTime = CallbackFunction (this, CallbackData);
2342 }
2343
2344 return IsOutOfTime;
2345 }
2346 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2347 // Engine::PerfTest
2348 // Returns the number of leaf node moves when generating, making and
2349 // unmaking every move to the specified depth from the current position.
2350 uint
PerfTest(uint depth)2351 Engine::PerfTest (uint depth)
2352 {
2353 if (depth <= 0) { return 1; }
2354 MoveList mlist;
2355 Pos.GenerateMoves (&mlist);
2356 uint nmoves = 0;
2357 for (uint i = 0; i < mlist.Size(); i++) {
2358 simpleMoveT * sm = mlist.Get(i);
2359 Pos.DoSimpleMove (sm);
2360 nmoves += PerfTest (depth-1);
2361 Pos.UndoSimpleMove (sm);
2362 }
2363 return nmoves;
2364 }
2365
2366 //////////////////////////////////////////////////////////////////////
2367 // EOF: engine.cpp
2368 //////////////////////////////////////////////////////////////////////
2369
2370