Fix gtest dependency for Travis CI
[purplehaze.git] / src / eval.cpp
blobea779fff49953a6ec8fdaa632a9d8cabef960304
1 /* Copyright (C) 2007-2012 Vincent Ollivier
3 * Purple Haze is free software: you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation, either version 3 of the License, or
6 * (at your option) any later version.
8 * Purple Haze is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 #include <cassert>
18 #include <iomanip>
19 #include <iostream>
20 #include <sstream>
21 #include <string>
23 #include "game.h"
24 #include "eval.h"
25 #include "hashtable.h"
27 // PST[Phase][Color][PieceType][Square]
28 static int PST[2][2][NB_PIECE_TYPES][BOARD_SIZE] = { { { { 0 } } } };
30 void Game::init_eval()
32 for (const Square &s : SQUARES) {
33 for (const PieceType &t : PIECE_TYPES) {
34 int opening_score = 0;
35 int ending_score = 0;
37 switch (t) {
38 case PAWN:
39 // Develop central pawns
40 // But not side pawns
41 opening_score = PAWN_FILES_VALUES[board.file(s)];
43 // Run for promotion
44 ending_score = 10 * board.rank(s);
45 break;
46 case KNIGHT:
47 case BISHOP:
48 // Develop toward center files
49 opening_score = CENTER_BONUS[board.file(s)];
50 if (board.is_border(s)) {
51 opening_score = 2 * BORDER_MALUS;
53 // no break
54 default:
55 ending_score = CENTER_BONUS[board.file(s)];
56 ending_score += CENTER_BONUS[board.rank(s)];
57 break;
60 // Rank bonus
61 int bonus = OPENING_RANKS_BONUS[t][board.rank(s)];
62 opening_score += (opening_score * bonus) / 2;
64 PST[OPENING][WHITE][t][s] = opening_score;
65 PST[ENDING][WHITE][t][s] = ending_score;
68 // Special corrections
69 // Urge to develop light pieces during opening
70 PST[OPENING][WHITE][KNIGHT][B1] = -20;
71 PST[OPENING][WHITE][KNIGHT][G1] = -20;
72 PST[OPENING][WHITE][BISHOP][C1] = -15;
73 PST[OPENING][WHITE][BISHOP][F1] = -15;
74 // But others should stay where they are
75 PST[OPENING][WHITE][ROOK][A1] = 5;
76 PST[OPENING][WHITE][ROOK][H1] = 5;
77 PST[OPENING][WHITE][KING][E1] = 5;
78 // Fianchetto
79 PST[OPENING][WHITE][BISHOP][B2] = 3;
80 PST[OPENING][WHITE][BISHOP][G2] = 3;
81 // Protection against bishop attacks
82 PST[OPENING][WHITE][PAWN][A3] += 3;
83 PST[OPENING][WHITE][PAWN][H3] += 3;
85 // Flip scores according to black's side
86 for (const Phase &p : PHASES) {
87 for (const Square &ws : SQUARES) {
88 const Square bs = Board::flip(ws);
89 for (const PieceType &t : PIECE_TYPES) {
90 PST[p][BLACK][t][bs] = PST[p][WHITE][t][ws];
96 static const int LAZY_EVAL_MARGIN = PIECE_VALUE[ROOK];
98 int Game::eval(int alpha, int beta)
100 // Material evaluation
101 int score = material_eval();
103 // TODO Draws should be caught here
105 if (score == 0) {
106 return 0; // Draw
109 if (score > PIECE_VALUE[KING]) {
110 return INF; // Win
111 } else if (score < -PIECE_VALUE[KING]) {
112 return -INF; // Loss
115 // Lazy evaluation
116 if (score + LAZY_EVAL_MARGIN < alpha) {
117 return score;
118 } else if (score - LAZY_EVAL_MARGIN > beta) {
119 return score;
122 // TODO Positional evaluation
123 score += position_eval();
125 // TODO Mobility evaluation
126 //score += mobility_eval();
128 return score;
131 int Game::material_eval()
133 Position &pos = current_position();
135 // Lookup position in material hash table
136 bool is_empty = true;
137 auto hash_score = material_table.lookup(pos.material_hash(), &is_empty);
138 if (!is_empty) {
139 return (pos.side() == WHITE ? hash_score : -hash_score);
142 int scores[2] = { 0 };
143 int bonuses[2] = { 0 };
144 for (const Color &c : COLORS) {
145 int nb_pawns = 0;
146 int nb_minors = 0;
147 for (const PieceType &t : PIECE_TYPES) {
148 const int n = pieces.count(c, t);
149 // Standard pieces values
150 scores[c] += n * PIECE_VALUE[t];
152 // Bonus values depending on material imbalance
153 switch (t) {
154 case PAWN:
155 nb_pawns = n;
156 if (n == 0) {
157 bonuses[c] += NO_PAWNS_MALUS;
159 break;
160 case KNIGHT:
161 nb_minors = n;
162 if (n > 1) {
163 bonuses[c] += REDUNDANCY_MALUS;
166 // Value adjusted by the number of pawns on the board
167 bonuses[c] += n * PAWNS_ADJUSTEMENT[KNIGHT][nb_pawns];
168 break;
169 case BISHOP:
170 nb_minors += n;
171 // Bishop bonus pair (from +40 to +64): less than half a pawn
172 // when most or all the pawns are on the board, and more than
173 // half a pawn when half or more of the pawns are gone.
174 // (Kaufman 1999)
176 // No bonus for two bishops controlling the same color
177 // No bonus for more than two bishops
178 if (n == 2 && has_bishop_pair(c, pieces)) {
179 bonuses[c] += BISHOP_PAIR_BONUS + 3 * 8 - nb_pawns;
182 // Value adjusted by the number of pawns on the board
183 bonuses[c] += n * PAWNS_ADJUSTEMENT[BISHOP][nb_pawns];
184 break;
185 case ROOK:
186 // Principle of the redundancy (Kaufman 1999)
187 if (n > 1) {
188 bonuses[c] += REDUNDANCY_MALUS;
191 // Value adjusted by the number of pawns on the board
192 bonuses[c] += n * PAWNS_ADJUSTEMENT[ROOK][nb_pawns];
193 break;
194 case QUEEN:
195 if (nb_minors > 1) {
196 // With two or more minor pieces, the queen equals two
197 // rooks. (Kaufman 1999)
198 bonuses[c] += 2 * PIECE_VALUE[ROOK] - PIECE_VALUE[QUEEN];
200 // Value adjusted by the number of pawns on the board
201 bonuses[c] += n * PAWNS_ADJUSTEMENT[QUEEN][nb_pawns];
202 break;
203 default:
204 break;
209 // Draw by insufficient material detection
210 bool is_draw = false;
211 const int K = PIECE_VALUE[KING];
212 const int P = PIECE_VALUE[PAWN];
213 const int N = PIECE_VALUE[KNIGHT];
214 const int B = PIECE_VALUE[BISHOP];
215 for (const Color &c : COLORS) {
216 is_draw = true;
217 // FIDE rules for draw
218 if (scores[c] == K) {
219 if (scores[!c] == K) {
220 break;
221 } else if (scores[!c] == K + B) {
222 break;
223 } else if (scores[!c] == K + N) {
224 break;
225 } else if (scores[!c] == K + N + N) {
226 break;
229 // TODO is this duplicate with MALUS_NO_PAWNS?
230 if (!pieces.count(!c, PAWN) && scores[!c] < K + 4 * P) {
231 break;
234 is_draw = false; // no break happened
237 const Color c = pos.side();
238 int score = 0;
239 if (!is_draw) {
240 score = scores[c] - scores[!c];
241 score += bonuses[c] - bonuses[!c];
244 // Save score to material hash table
245 hash_score = (c == WHITE ? score : -score);
246 material_table.save(pos.material_hash(), hash_score);
248 return score;
251 static int castling_score(const Position &pos, Color c)
253 int score = 0;
254 if (pos.has_castled(c)) {
255 score += CASTLE_BONUS;
256 } else {
257 for (const PieceType &t : SIDE_TYPES) { // for QUEEN and KING side
258 if (!pos.can_castle(c, t)) {
259 score += BREAKING_CASTLE_MALUS;
263 return score;
266 int Game::position_eval()
268 int phase = 0;
269 int pos_scores[2][2] = { { 0 } };
270 int pawns_files[2][8] = { { 0 } };
271 const Position &pos = current_position();
272 for (const Color &c : COLORS) {
273 for (const PieceType &t : PIECE_TYPES) {
274 const int n = pieces.count(c, t);
275 phase += n * PHASE_COEF[t];
276 for (int i = 0; i < n; ++i) {
277 const Square s = pieces.position(c, t, i);
278 pos_scores[OPENING][c] += PST[OPENING][c][t][s];
279 pos_scores[ENDING][c] += PST[ENDING][c][t][s];
280 if (t == PAWN) {
281 pawns_files[c][board.file(s)]++;
286 int pawns_score = 0;
287 for (int i = 0; i < 8; ++i) {
288 pawns_score += MULTI_PAWNS_MALUS[pawns_files[c][i]];
290 pos_scores[OPENING][c] += pawns_score;
292 // Rooks' files bonus
293 int rooks_score = 0;
294 for (int i = 0, n = pieces.count(c, ROOK); i < n; ++i) {
295 const Square s = pieces.position(c, ROOK, i);
296 if (!pawns_files[!c][board.file(s)]) {
297 if (!pawns_files[c][board.file(s)]) {
298 rooks_score += OPEN_FILE_BONUS;
299 } else {
300 rooks_score += HALF_OPEN_FILE_BONUS;
304 pos_scores[OPENING][c] += rooks_score;
306 // Castling bonus/malus
307 pos_scores[OPENING][c] += castling_score(pos, c);
311 // Retrieve opening and ending score
312 const Color c = pos.side();
313 const int opening = pos_scores[OPENING][c] - pos_scores[OPENING][!c];
314 const int ending = pos_scores[ENDING][c] - pos_scores[ENDING][!c];
316 // Tapered Eval (idea from Fruit 2.1)
317 const int max = PHASE_MAX;
318 phase = ((phase > max) ? max : ((phase < 0) ? 0 : phase));
319 return (opening * phase + ending * (max - phase)) / max;