Merge KBPP vs KB endgame from iPhone Glaurung
[stockfish] / src / material.cpp
1 /*
2   Stockfish, a UCI chess playing engine derived from Glaurung 2.1
3   Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
4   Copyright (C) 2008 Marco Costalba
5
6   Stockfish is free software: you can redistribute it and/or modify
7   it under the terms of the GNU General Public License as published by
8   the Free Software Foundation, either version 3 of the License, or
9   (at your option) any later version.
10
11   Stockfish is distributed in the hope that it will be useful,
12   but WITHOUT ANY WARRANTY; without even the implied warranty of
13   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14   GNU General Public License for more details.
15
16   You should have received a copy of the GNU General Public License
17   along with this program.  If not, see <http://www.gnu.org/licenses/>.
18 */
19
20
21 ////
22 //// Includes
23 ////
24
25 #include <cassert>
26 #include <cstring>
27 #include <sstream>
28 #include <map>
29
30 #include "material.h"
31
32
33 ////
34 //// Local definitions
35 ////
36
37 namespace {
38
39   const Value BishopPairMidgameBonus = Value(109);
40   const Value BishopPairEndgameBonus = Value(97);
41
42   Key KNNKMaterialKey, KKNNMaterialKey;
43
44 }
45
46 ////
47 //// Classes
48 ////
49
50
51 /// See header for a class description. It is declared here to avoid
52 /// to include <map> in the header file.
53
54 class EndgameFunctions {
55
56 public:
57   EndgameFunctions();
58   EndgameEvaluationFunctionBase* getEEF(Key key) const;
59   EndgameScalingFunctionBase* getESF(Key key, Color* c) const;
60
61 private:
62   void add(const std::string& keyCode, EndgameEvaluationFunctionBase* f);
63   void add(const std::string& keyCode, Color c, EndgameScalingFunctionBase* f);
64   Key buildKey(const std::string& keyCode);
65
66   struct ScalingInfo
67   {
68       Color col;
69       EndgameScalingFunctionBase* fun;
70   };
71
72   std::map<Key, EndgameEvaluationFunctionBase*> EEFmap;
73   std::map<Key, ScalingInfo> ESFmap;
74 };
75
76
77 ////
78 //// Functions
79 ////
80
81
82 /// Constructor for the MaterialInfoTable class
83
84 MaterialInfoTable::MaterialInfoTable(unsigned int numOfEntries) {
85
86   size = numOfEntries;
87   entries = new MaterialInfo[size];
88   funcs = new EndgameFunctions();
89   if (!entries || !funcs)
90   {
91       std::cerr << "Failed to allocate " << (numOfEntries * sizeof(MaterialInfo))
92                 << " bytes for material hash table." << std::endl;
93       exit(EXIT_FAILURE);
94   }
95   clear();
96 }
97
98
99 /// Destructor for the MaterialInfoTable class
100
101 MaterialInfoTable::~MaterialInfoTable() {
102
103   delete [] entries;
104   delete funcs;
105 }
106
107
108 /// MaterialInfoTable::clear() clears a material hash table by setting
109 /// all entries to 0.
110
111 void MaterialInfoTable::clear() {
112
113   memset(entries, 0, size * sizeof(MaterialInfo));
114 }
115
116
117 /// MaterialInfoTable::get_material_info() takes a position object as input,
118 /// computes or looks up a MaterialInfo object, and returns a pointer to it.
119 /// If the material configuration is not already present in the table, it
120 /// is stored there, so we don't have to recompute everything when the
121 /// same material configuration occurs again.
122
123 MaterialInfo* MaterialInfoTable::get_material_info(const Position& pos) {
124
125   Key key = pos.get_material_key();
126   int index = key & (size - 1);
127   MaterialInfo* mi = entries + index;
128
129   // If mi->key matches the position's material hash key, it means that we
130   // have analysed this material configuration before, and we can simply
131   // return the information we found the last time instead of recomputing it.
132   if (mi->key == key)
133       return mi;
134
135   // Clear the MaterialInfo object, and set its key
136   mi->clear();
137   mi->key = key;
138
139   // A special case before looking for a specialized evaluation function
140   // KNN vs K is a draw.
141   if (key == KNNKMaterialKey || key == KKNNMaterialKey)
142   {
143       mi->factor[WHITE] = mi->factor[BLACK] = 0;
144       return mi;
145   }
146
147   // Let's look if we have a specialized evaluation function for this
148   // particular material configuration. First we look for a fixed
149   // configuration one, then a generic one if previous search failed.
150   if ((mi->evaluationFunction = funcs->getEEF(key)) != NULL)
151       return mi;
152
153   else if (   pos.non_pawn_material(BLACK) == Value(0)
154            && pos.piece_count(BLACK, PAWN) == 0
155            && pos.non_pawn_material(WHITE) >= RookValueEndgame)
156   {
157       mi->evaluationFunction = &EvaluateKXK;
158       return mi;
159   }
160   else if (   pos.non_pawn_material(WHITE) == Value(0)
161            && pos.piece_count(WHITE, PAWN) == 0
162            && pos.non_pawn_material(BLACK) >= RookValueEndgame)
163   {
164       mi->evaluationFunction = &EvaluateKKX;
165       return mi;
166   }
167   else if (   pos.pawns() == EmptyBoardBB
168            && pos.rooks() == EmptyBoardBB
169            && pos.queens() == EmptyBoardBB)
170   {
171       // Minor piece endgame with at least one minor piece per side,
172       // and no pawns.
173       assert(pos.knights(WHITE) | pos.bishops(WHITE));
174       assert(pos.knights(BLACK) | pos.bishops(BLACK));
175
176       if (   pos.piece_count(WHITE, BISHOP) + pos.piece_count(WHITE, KNIGHT) <= 2
177           && pos.piece_count(BLACK, BISHOP) + pos.piece_count(BLACK, KNIGHT) <= 2)
178       {
179           mi->evaluationFunction = &EvaluateKmmKm;
180           return mi;
181       }
182   }
183
184   // OK, we didn't find any special evaluation function for the current
185   // material configuration. Is there a suitable scaling function?
186   //
187   // The code below is rather messy, and it could easily get worse later,
188   // if we decide to add more special cases.  We face problems when there
189   // are several conflicting applicable scaling functions and we need to
190   // decide which one to use.
191   Color c;
192   EndgameScalingFunctionBase* sf;
193
194   if ((sf = funcs->getESF(key, &c)) != NULL)
195   {
196       mi->scalingFunction[c] = sf;
197       return mi;
198   }
199
200   if (   pos.non_pawn_material(WHITE) == BishopValueMidgame
201       && pos.piece_count(WHITE, BISHOP) == 1
202       && pos.piece_count(WHITE, PAWN) >= 1)
203       mi->scalingFunction[WHITE] = &ScaleKBPK;
204
205   if (   pos.non_pawn_material(BLACK) == BishopValueMidgame
206       && pos.piece_count(BLACK, BISHOP) == 1
207       && pos.piece_count(BLACK, PAWN) >= 1)
208       mi->scalingFunction[BLACK] = &ScaleKKBP;
209
210   if (   pos.piece_count(WHITE, PAWN) == 0
211       && pos.non_pawn_material(WHITE) == QueenValueMidgame
212       && pos.piece_count(WHITE, QUEEN) == 1
213       && pos.piece_count(BLACK, ROOK) == 1
214       && pos.piece_count(BLACK, PAWN) >= 1)
215       mi->scalingFunction[WHITE] = &ScaleKQKRP;
216
217   else if (   pos.piece_count(BLACK, PAWN) == 0
218            && pos.non_pawn_material(BLACK) == QueenValueMidgame
219            && pos.piece_count(BLACK, QUEEN) == 1
220            && pos.piece_count(WHITE, ROOK) == 1
221            && pos.piece_count(WHITE, PAWN) >= 1)
222       mi->scalingFunction[BLACK] = &ScaleKRPKQ;
223
224   if (pos.non_pawn_material(WHITE) + pos.non_pawn_material(BLACK) == Value(0))
225   {
226       if (pos.piece_count(BLACK, PAWN) == 0)
227       {
228           assert(pos.piece_count(WHITE, PAWN) >= 2);
229           mi->scalingFunction[WHITE] = &ScaleKPsK;
230       }
231       else if (pos.piece_count(WHITE, PAWN) == 0)
232       {
233           assert(pos.piece_count(BLACK, PAWN) >= 2);
234           mi->scalingFunction[BLACK] = &ScaleKKPs;
235       }
236       else if (pos.piece_count(WHITE, PAWN) == 1 && pos.piece_count(BLACK, PAWN) == 1)
237       {
238           mi->scalingFunction[WHITE] = &ScaleKPKPw;
239           mi->scalingFunction[BLACK] = &ScaleKPKPb;
240       }
241   }
242
243   // Compute the space weight
244   if (pos.non_pawn_material(WHITE) + pos.non_pawn_material(BLACK) >=
245       2*QueenValueMidgame + 4*RookValueMidgame + 2*KnightValueMidgame)
246   {
247       int minorPieceCount =  pos.piece_count(WHITE, KNIGHT)
248                            + pos.piece_count(BLACK, KNIGHT)
249                            + pos.piece_count(WHITE, BISHOP)
250                            + pos.piece_count(BLACK, BISHOP);
251
252       mi->spaceWeight = minorPieceCount * minorPieceCount;
253   }
254
255   // Evaluate the material balance
256
257   int sign;
258   Value egValue = Value(0);
259   Value mgValue = Value(0);
260
261   for (c = WHITE, sign = 1; c <= BLACK; c++, sign = -sign)
262   {
263     // No pawns makes it difficult to win, even with a material advantage
264     if (   pos.piece_count(c, PAWN) == 0
265         && pos.non_pawn_material(c) - pos.non_pawn_material(opposite_color(c)) <= BishopValueMidgame)
266     {
267         if (   pos.non_pawn_material(c) == pos.non_pawn_material(opposite_color(c))
268             || pos.non_pawn_material(c) < RookValueMidgame)
269             mi->factor[c] = 0;
270         else
271         {
272             switch (pos.piece_count(c, BISHOP)) {
273             case 2:
274                 mi->factor[c] = 32;
275                 break;
276             case 1:
277                 mi->factor[c] = 12;
278                 break;
279             case 0:
280                 mi->factor[c] = 6;
281                 break;
282             }
283         }
284     }
285
286     // Bishop pair
287     if (pos.piece_count(c, BISHOP) >= 2)
288     {
289         mgValue += sign * BishopPairMidgameBonus;
290         egValue += sign * BishopPairEndgameBonus;
291     }
292
293     // Knights are stronger when there are many pawns on the board.  The
294     // formula is taken from Larry Kaufman's paper "The Evaluation of Material
295     // Imbalances in Chess":
296     // http://mywebpages.comcast.net/danheisman/Articles/evaluation_of_material_imbalance.htm
297     mgValue += sign * Value(pos.piece_count(c, KNIGHT)*(pos.piece_count(c, PAWN)-5)*16);
298     egValue += sign * Value(pos.piece_count(c, KNIGHT)*(pos.piece_count(c, PAWN)-5)*16);
299
300     // Redundancy of major pieces, again based on Kaufman's paper:
301     if (pos.piece_count(c, ROOK) >= 1)
302     {
303         Value v = Value((pos.piece_count(c, ROOK) - 1) * 32 + pos.piece_count(c, QUEEN) * 16);
304         mgValue -= sign * v;
305         egValue -= sign * v;
306     }
307   }
308   mi->mgValue = int16_t(mgValue);
309   mi->egValue = int16_t(egValue);
310   return mi;
311 }
312
313
314 /// EndgameFunctions member definitions. This class is used to store the maps
315 /// of end game and scaling functions that MaterialInfoTable will query for
316 /// each key. The maps are constant and are populated only at construction,
317 /// but are per-thread instead of globals to avoid expensive locks.
318
319 EndgameFunctions::EndgameFunctions() {
320
321   KNNKMaterialKey = buildKey("KNNK");
322   KKNNMaterialKey = buildKey("KKNN");
323
324   add("KPK",   &EvaluateKPK);
325   add("KKP",   &EvaluateKKP);
326   add("KBNK",  &EvaluateKBNK);
327   add("KKBN",  &EvaluateKKBN);
328   add("KRKP",  &EvaluateKRKP);
329   add("KPKR",  &EvaluateKPKR);
330   add("KRKB",  &EvaluateKRKB);
331   add("KBKR",  &EvaluateKBKR);
332   add("KRKN",  &EvaluateKRKN);
333   add("KNKR",  &EvaluateKNKR);
334   add("KQKR",  &EvaluateKQKR);
335   add("KRKQ",  &EvaluateKRKQ);
336   add("KBBKN", &EvaluateKBBKN);
337   add("KNKBB", &EvaluateKNKBB);
338
339   add("KNPK",    WHITE, &ScaleKNPK);
340   add("KKNP",    BLACK, &ScaleKKNP);
341   add("KRPKR",   WHITE, &ScaleKRPKR);
342   add("KRKRP",   BLACK, &ScaleKRKRP);
343   add("KBPKB",   WHITE, &ScaleKBPKB);
344   add("KBKBP",   BLACK, &ScaleKBKBP);
345   add("KBPPKB",  WHITE, &ScaleKBPPKB);
346   add("KBKBPP",  BLACK, &ScaleKBKBPP);
347   add("KBPKN",   WHITE, &ScaleKBPKN);
348   add("KNKBP",   BLACK, &ScaleKNKBP);
349   add("KRPPKRP", WHITE, &ScaleKRPPKRP);
350   add("KRPKRPP", BLACK, &ScaleKRPKRPP);
351   add("KRPPKRP", WHITE, &ScaleKRPPKRP);
352   add("KRPKRPP", BLACK, &ScaleKRPKRPP);
353 }
354
355 Key EndgameFunctions::buildKey(const std::string& keyCode) {
356
357     assert(keyCode.length() > 0 && keyCode[0] == 'K');
358     assert(keyCode.length() < 8);
359
360     std::stringstream s;
361     bool upcase = false;
362
363     // Build up a fen substring with the given pieces, note
364     // that the fen string could be of an illegal position.
365     for (size_t i = 0; i < keyCode.length(); i++)
366     {
367         if (keyCode[i] == 'K')
368             upcase = !upcase;
369
370         s << char(upcase? toupper(keyCode[i]) : tolower(keyCode[i]));
371     }
372     s << 8 - keyCode.length() << "/8/8/8/8/8/8/8 w -";
373     return Position(s.str()).get_material_key();
374 }
375
376 void EndgameFunctions::add(const std::string& keyCode, EndgameEvaluationFunctionBase* f) {
377
378   EEFmap.insert(std::pair<Key, EndgameEvaluationFunctionBase*>(buildKey(keyCode), f));
379 }
380
381 void EndgameFunctions::add(const std::string& keyCode, Color c, EndgameScalingFunctionBase* f) {
382
383   ScalingInfo s = {c, f};
384   ESFmap.insert(std::pair<Key, ScalingInfo>(buildKey(keyCode), s));
385 }
386
387 EndgameEvaluationFunctionBase* EndgameFunctions::getEEF(Key key) const {
388
389   std::map<Key, EndgameEvaluationFunctionBase*>::const_iterator it(EEFmap.find(key));
390   return (it != EEFmap.end() ? it->second : NULL);
391 }
392
393 EndgameScalingFunctionBase* EndgameFunctions::getESF(Key key, Color* c) const {
394
395   std::map<Key, ScalingInfo>::const_iterator it(ESFmap.find(key));
396   if (it == ESFmap.end())
397       return NULL;
398
399   *c = it->second.col;
400   return it->second.fun;
401 }