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