01c873959eac591a5dfe83729fbef1a122339dde
[stockfish] / src / san.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 <iomanip>
28 #include <string>
29 #include <sstream>
30
31 #include "movepick.h"
32 #include "san.h"
33
34
35 ////
36 //// Local definitions
37 ////
38
39 namespace {
40
41   /// Types
42
43   enum Ambiguity {
44     AMBIGUITY_NONE,
45     AMBIGUITY_FILE,
46     AMBIGUITY_RANK,
47     AMBIGUITY_BOTH
48   };
49
50
51   /// Functions
52
53   Ambiguity move_ambiguity(Position &pos, Move m);
54   const std::string time_string(int milliseconds);
55   const std::string score_string(Value v);
56 }
57
58
59 ////
60 //// Functions
61 ////
62
63 /// move_to_san() takes a position and a move as input, where it is assumed
64 /// that the move is a legal move from the position.  The return value is
65 /// a string containing the move in short algebraic notation.
66
67 const std::string move_to_san(Position &pos, Move m) {
68   std::string str;
69
70   assert(pos.is_ok());
71   assert(move_is_ok(m));
72
73   if(m == MOVE_NONE) {
74     str = "(none)";
75     return str;
76   }
77   else if(m == MOVE_NULL) {
78     str = "(null)";
79     return str;
80   }
81   else if(move_is_long_castle(m))
82     str = "O-O-O";
83   else if(move_is_short_castle(m))
84     str = "O-O";
85   else {
86     Square from, to;
87     Piece pc;
88
89     from = move_from(m);
90     to = move_to(m);
91     pc = pos.piece_on(move_from(m));
92
93     str = "";
94
95     if(type_of_piece(pc) == PAWN) {
96       if(pos.move_is_capture(m))
97         str += file_to_char(square_file(move_from(m)));
98     }
99     else {
100       str += piece_type_to_char(type_of_piece(pc), true);
101
102       Ambiguity amb = move_ambiguity(pos, m);
103       switch(amb) {
104
105       case AMBIGUITY_NONE:
106         break;
107
108       case AMBIGUITY_FILE:
109         str += file_to_char(square_file(from));
110         break;
111
112       case AMBIGUITY_RANK:
113         str += rank_to_char(square_rank(from));
114         break;
115
116       case AMBIGUITY_BOTH:
117         str += square_to_string(from);
118         break;
119
120       default:
121         assert(false);
122       }
123     }
124
125     if(pos.move_is_capture(m))
126       str += "x";
127
128     str += square_to_string(move_to(m));
129
130     if(move_promotion(m)) {
131       str += "=";
132       str += piece_type_to_char(move_promotion(m), true);
133     }
134   }
135
136   // Is the move check?  We don't use pos.move_is_check(m) here, because
137   // Position::move_is_check doesn't detect all checks (not castling moves,
138   // promotions and en passant captures).
139   UndoInfo u;
140   pos.do_move(m, u);
141   if(pos.is_check())
142     str += pos.is_mate()? "#" : "+";
143   pos.undo_move(m, u);
144
145   return str;
146 }
147
148
149 /// move_from_san() takes a position and a string as input, and tries to
150 /// interpret the string as a move in short algebraic notation.  On success,
151 /// the move is returned.  On failure (i.e. if the string is unparsable, or
152 /// if the move is illegal or ambiguous), MOVE_NONE is returned.
153
154 Move move_from_san(Position &pos, const std::string &movestr) {
155   assert(pos.is_ok());
156
157   MovePicker mp = MovePicker(pos, false, MOVE_NONE, MOVE_NONE, MOVE_NONE,
158                              MOVE_NONE, OnePly);
159
160   // Castling moves
161   if(movestr == "O-O-O") {
162     Move m;
163     while((m = mp.get_next_move()) != MOVE_NONE)
164       if(move_is_long_castle(m) && pos.pl_move_is_legal(m))
165         return m;
166     return MOVE_NONE;
167   }
168   else if(movestr == "O-O") {
169     Move m;
170     while((m = mp.get_next_move()) != MOVE_NONE)
171       if(move_is_short_castle(m) && pos.pl_move_is_legal(m))
172         return m;
173     return MOVE_NONE;
174   }
175
176   // Normal moves
177   const char *cstr = movestr.c_str();
178   const char *c;
179   char *cc;
180   char str[10];
181   int i;
182
183   // Initialize str[] by making a copy of movestr with the characters
184   // 'x', '=', '+' and '#' removed.
185   cc = str;
186   for(i=0, c=cstr; i<10 && *c!='\0' && *c!='\n' && *c!=' '; i++, c++)
187     if(!strchr("x=+#", *c)) {
188       *cc = strchr("nrq", *c)? toupper(*c) : *c;
189       cc++;
190     }
191   *cc = '\0';
192
193   size_t left = 0, right = strlen(str) - 1;
194   PieceType pt = NO_PIECE_TYPE, promotion;
195   Square to;
196   File fromFile = FILE_NONE;
197   Rank fromRank = RANK_NONE;
198
199   // Promotion?
200   if(strchr("BNRQ", str[right])) {
201     promotion = piece_type_from_char(str[right]);
202     right--;
203   }
204   else
205     promotion = NO_PIECE_TYPE;
206
207   // Find the moving piece:
208   if(left < right) {
209     if(strchr("BNRQK", str[left])) {
210       pt = piece_type_from_char(str[left]);
211       left++;
212     }
213     else
214       pt = PAWN;
215   }
216
217   // Find the to square:
218   if(left < right) {
219     if(str[right] < '1' || str[right] > '8' ||
220        str[right-1] < 'a' || str[right-1] > 'h')
221       return MOVE_NONE;
222     to = make_square(file_from_char(str[right-1]), rank_from_char(str[right]));
223     right -= 2;
224   }
225   else
226     return MOVE_NONE;
227
228   // Find the file and/or rank of the from square:
229   if(left <= right) {
230     if(strchr("abcdefgh", str[left])) {
231       fromFile = file_from_char(str[left]);
232       left++;
233     }
234     if(strchr("12345678", str[left]))
235       fromRank = rank_from_char(str[left]);
236   }
237
238   // Look for a matching move:
239   Move m, move = MOVE_NONE;
240   int matches = 0;
241
242   while((m = mp.get_next_move()) != MOVE_NONE) {
243     bool match = true;
244     if(pos.type_of_piece_on(move_from(m)) != pt)
245       match = false;
246     else if(move_to(m) != to)
247       match = false;
248     else if(move_promotion(m) != promotion)
249       match = false;
250     else if(fromFile != FILE_NONE && fromFile != square_file(move_from(m)))
251       match = false;
252     else if(fromRank != RANK_NONE && fromRank != square_rank(move_from(m)))
253       match = false;
254     if(match) {
255       move = m;
256       matches++;
257     }
258   }
259
260   if(matches == 1)
261     return move;
262   else
263     return MOVE_NONE;
264 }
265
266
267 /// line_to_san() takes a position and a line (an array of moves representing
268 /// a sequence of legal moves from the position) as input, and returns a
269 /// string containing the line in short algebraic notation.  If the boolean
270 /// parameter 'breakLines' is true, line breaks are inserted, with a line
271 /// length of 80 characters.  After a line break, 'startColumn' spaces are
272 /// inserted at the beginning of the new line.
273
274 const std::string line_to_san(const Position &pos, Move line[], int startColumn,
275                               bool breakLines) {
276   Position p = Position(pos);
277   UndoInfo u;
278   std::stringstream s;
279   std::string moveStr;
280   size_t length, maxLength;
281
282   length = 0;
283   maxLength = 80 - startColumn;
284
285   for(int i = 0; line[i] != MOVE_NONE; i++) {
286     moveStr = move_to_san(p, line[i]);
287     length += moveStr.length() + 1;
288     if(breakLines && length > maxLength) {
289       s << "\n";
290       for(int j = 0; j < startColumn; j++)
291         s << " ";
292       length = moveStr.length() + 1;
293     }
294     s << moveStr << " ";
295
296     if(line[i] == MOVE_NULL)
297       p.do_null_move(u);
298     else
299       p.do_move(line[i], u);
300   }
301
302   return s.str();
303 }
304
305
306 /// pretty_pv() creates a human-readable string from a position and a PV.
307 /// It is used to write search information to the log file (which is created
308 /// when the UCI parameter "Use Search Log" is "true").
309
310 const std::string pretty_pv(const Position &pos, int time, int depth,
311                             uint64_t nodes, Value score, Move pv[]) {
312   std::stringstream s;
313
314   // Depth
315   s << std::setw(2) << std::setfill(' ') << depth << "  ";
316
317   // Score
318   s << std::setw(8) << score_string(score);
319
320   // Time
321   s << std::setw(8) << std::setfill(' ') << time_string(time) << " ";
322
323   // Nodes
324   if(nodes < 1000000ULL)
325     s << std::setw(8) << std::setfill(' ') << nodes << " ";
326   else if(nodes < 1000000000ULL)
327     s << std::setw(7) << std::setfill(' ') << nodes/1000ULL << 'k' << " ";
328   else
329     s << std::setw(7) << std::setfill(' ') << nodes/1000000ULL << 'M' << " ";
330
331   // PV
332   s << line_to_san(pos, pv, 30, true);
333
334   return s.str();
335 }
336
337
338 namespace {
339
340   Ambiguity move_ambiguity(Position &pos, Move m) {
341     Square from, to;
342     Piece pc;
343
344     from = move_from(m);
345     to = move_to(m);
346     pc = pos.piece_on(from);
347
348     // King moves are never ambiguous, because there is never two kings of
349     // the same color.
350     if(type_of_piece(pc) == KING)
351       return AMBIGUITY_NONE;
352
353     MovePicker mp = MovePicker(pos, false, MOVE_NONE, MOVE_NONE, MOVE_NONE,
354                                MOVE_NONE, OnePly);
355     Move mv, moveList[8];
356     int i, j, n;
357
358     n = 0;
359     while((mv = mp.get_next_move()) != MOVE_NONE)
360       if(move_to(mv) == to && pos.piece_on(move_from(mv)) == pc
361          && pos.pl_move_is_legal(mv))
362         moveList[n++] = mv;
363     if(n == 1)
364       return AMBIGUITY_NONE;
365
366     j = 0;
367     for(i = 0; i < n; i++)
368       if(square_file(move_from(moveList[i])) == square_file(from))
369         j++;
370     if(j == 1)
371       return AMBIGUITY_FILE;
372
373     j = 0;
374     for(i = 0; i < n; i++)
375       if(square_rank(move_from(moveList[i])) == square_rank(from))
376         j++;
377     if(j == 1)
378       return AMBIGUITY_RANK;
379
380     return AMBIGUITY_BOTH;
381   }
382
383
384   const std::string time_string(int milliseconds) {
385     std::stringstream s;
386
387     int hours = milliseconds / (1000 * 60 * 60);
388     int minutes = (milliseconds - hours*1000*60*60) / (60*1000);
389     int seconds = (milliseconds - hours*1000*60*60 - minutes*60*1000) / 1000;
390
391     if(hours)
392       s << hours << ':';
393     s << std::setw(2) << std::setfill('0') << minutes << ':';
394     s << std::setw(2) << std::setfill('0') << seconds;
395
396     return s.str();
397   }
398
399
400   const std::string score_string(Value v) {
401     std::stringstream s;
402
403     if(abs(v) >= VALUE_MATE - 200) {
404       if(v < 0)
405         s << "-#" << (VALUE_MATE + v) / 2;
406       else
407         s << "#" << (VALUE_MATE - v + 1) / 2;
408     }
409     else {
410       float floatScore = float(v) / float(PawnValueMidgame);
411       if(v >= 0)
412         s << '+';
413       s << std::setprecision(2) << std::fixed << floatScore;
414     }
415     return s.str();
416   }
417
418 }