Details | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
99 | pmbaty | 1 | /* |
2 | Texel - A UCI chess engine. |
||
3 | Copyright (C) 2012-2014 Peter Ă–sterlund, peterosterlund2@gmail.com |
||
4 | |||
5 | This program is free software: you can redistribute it and/or modify |
||
6 | it under the terms of the GNU General Public License as published by |
||
7 | the Free Software Foundation, either version 3 of the License, or |
||
8 | (at your option) any later version. |
||
9 | |||
10 | This program is distributed in the hope that it will be useful, |
||
11 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
||
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||
13 | GNU General Public License for more details. |
||
14 | |||
15 | You should have received a copy of the GNU General Public License |
||
16 | along with this program. If not, see <http://www.gnu.org/licenses/>. |
||
17 | */ |
||
18 | |||
19 | /* |
||
20 | * game.cpp |
||
21 | * |
||
22 | * Created on: Feb 25, 2012 |
||
23 | * Author: petero |
||
24 | */ |
||
25 | |||
26 | #include "game.hpp" |
||
27 | #include "moveGen.hpp" |
||
28 | #include "textio.hpp" |
||
29 | #include "util/timeUtil.hpp" |
||
30 | |||
31 | #include <iostream> |
||
32 | #include <iomanip> |
||
33 | #include <cassert> |
||
34 | |||
35 | |||
36 | Game::Game(const std::shared_ptr<Player>& whitePlayer, |
||
37 | const std::shared_ptr<Player>& blackPlayer) { |
||
38 | this->whitePlayer = whitePlayer; |
||
39 | this->blackPlayer = blackPlayer; |
||
40 | handleCommand("new"); |
||
41 | } |
||
42 | |||
43 | bool |
||
44 | Game::processString(const std::string& str) { |
||
45 | if (handleCommand(str)) |
||
46 | return true; |
||
47 | if (getGameState() != ALIVE) |
||
48 | return false; |
||
49 | |||
50 | Move m = TextIO::stringToMove(pos, str); |
||
51 | if (m.isEmpty()) |
||
52 | return false; |
||
53 | |||
54 | UndoInfo ui; |
||
55 | pos.makeMove(m, ui); |
||
56 | TextIO::fixupEPSquare(pos); |
||
57 | if (currentMove < (int)moveList.size()) { |
||
58 | moveList.erase(moveList.begin() + currentMove, moveList.end()); |
||
59 | uiInfoList.erase(uiInfoList.begin() + currentMove, uiInfoList.end()); |
||
60 | drawOfferList.erase(drawOfferList.begin() + currentMove, drawOfferList.end()); |
||
61 | } |
||
62 | moveList.push_back(m); |
||
63 | uiInfoList.push_back(ui); |
||
64 | drawOfferList.push_back(pendingDrawOffer); |
||
65 | pendingDrawOffer = false; |
||
66 | currentMove++; |
||
67 | return true; |
||
68 | } |
||
69 | |||
70 | std::string |
||
71 | Game::getGameStateString() { |
||
72 | switch (getGameState()) { |
||
73 | case ALIVE: |
||
74 | return ""; |
||
75 | case WHITE_MATE: |
||
76 | return "Game over, white mates!"; |
||
77 | case BLACK_MATE: |
||
78 | return "Game over, black mates!"; |
||
79 | case WHITE_STALEMATE: |
||
80 | case BLACK_STALEMATE: |
||
81 | return "Game over, draw by stalemate!"; |
||
82 | case DRAW_REP: |
||
83 | { |
||
84 | std::string ret = "Game over, draw by repetition!"; |
||
85 | if (drawStateMoveStr.length() > 0) |
||
86 | ret += " [" + drawStateMoveStr + "]"; |
||
87 | return ret; |
||
88 | } |
||
89 | case DRAW_50: |
||
90 | { |
||
91 | std::string ret = "Game over, draw by 50 move rule!"; |
||
92 | if (drawStateMoveStr.length() > 0) |
||
93 | ret += " [" + drawStateMoveStr + "]"; |
||
94 | return ret; |
||
95 | } |
||
96 | case DRAW_NO_MATE: |
||
97 | return "Game over, draw by impossibility of mate!"; |
||
98 | case DRAW_AGREE: |
||
99 | return "Game over, draw by agreement!"; |
||
100 | case RESIGN_WHITE: |
||
101 | return "Game over, white resigns!"; |
||
102 | case RESIGN_BLACK: |
||
103 | return "Game over, black resigns!"; |
||
104 | default: |
||
105 | assert(false); |
||
106 | return ""; |
||
107 | } |
||
108 | } |
||
109 | |||
110 | Move |
||
111 | Game::getLastMove() { |
||
112 | Move m; |
||
113 | if (currentMove > 0) |
||
114 | m = moveList[currentMove - 1]; |
||
115 | return m; |
||
116 | } |
||
117 | |||
118 | Game::GameState |
||
119 | Game::getGameState() { |
||
120 | MoveList moves; |
||
121 | MoveGen::pseudoLegalMoves(pos, moves); |
||
122 | MoveGen::removeIllegal(pos, moves); |
||
123 | if (moves.size == 0) { |
||
124 | if (MoveGen::inCheck(pos)) |
||
125 | return pos.isWhiteMove() ? BLACK_MATE : WHITE_MATE; |
||
126 | else |
||
127 | return pos.isWhiteMove() ? WHITE_STALEMATE : BLACK_STALEMATE; |
||
128 | } |
||
129 | if (insufficientMaterial()) |
||
130 | return DRAW_NO_MATE; |
||
131 | if (resignState != ALIVE) |
||
132 | return resignState; |
||
133 | return drawState; |
||
134 | } |
||
135 | |||
136 | bool |
||
137 | Game::haveDrawOffer() { |
||
138 | if (currentMove > 0) |
||
139 | return drawOfferList[currentMove - 1]; |
||
140 | else |
||
141 | return false; |
||
142 | } |
||
143 | |||
144 | bool |
||
145 | Game::handleCommand(const std::string& moveStr) { |
||
146 | if (moveStr == "new") { |
||
147 | moveList.clear(); |
||
148 | uiInfoList.clear(); |
||
149 | drawOfferList.clear(); |
||
150 | currentMove = 0; |
||
151 | pendingDrawOffer = false; |
||
152 | drawState = ALIVE; |
||
153 | resignState = ALIVE; |
||
154 | pos = TextIO::readFEN(TextIO::startPosFEN); |
||
155 | whitePlayer->clearTT(); |
||
156 | blackPlayer->clearTT(); |
||
157 | activateHumanPlayer(); |
||
158 | return true; |
||
159 | } else if (moveStr == "undo") { |
||
160 | if (currentMove > 0) { |
||
161 | pos.unMakeMove(moveList[currentMove - 1], uiInfoList[currentMove - 1]); |
||
162 | currentMove--; |
||
163 | pendingDrawOffer = false; |
||
164 | drawState = ALIVE; |
||
165 | resignState = ALIVE; |
||
166 | return handleCommand("swap"); |
||
167 | } else { |
||
168 | std::cout << "Nothing to undo" << std::endl; |
||
169 | } |
||
170 | return true; |
||
171 | } else if (moveStr == "redo") { |
||
172 | if (currentMove < (int)moveList.size()) { |
||
173 | pos.makeMove(moveList[currentMove], uiInfoList[currentMove]); |
||
174 | currentMove++; |
||
175 | pendingDrawOffer = false; |
||
176 | return handleCommand("swap"); |
||
177 | } else { |
||
178 | std::cout << "Nothing to redo" << std::endl; |
||
179 | } |
||
180 | return true; |
||
181 | } else if (moveStr == "swap" || moveStr == "go") { |
||
182 | std::swap(whitePlayer, blackPlayer); |
||
183 | return true; |
||
184 | } else if (moveStr == "list") { |
||
185 | listMoves(); |
||
186 | return true; |
||
187 | } else if (startsWith(moveStr, "setpos ")) { |
||
188 | std::string fen = moveStr.substr(moveStr.find_first_of(' ') + 1); |
||
189 | try { |
||
190 | Position newPos(TextIO::readFEN(fen)); |
||
191 | handleCommand("new"); |
||
192 | pos = newPos; |
||
193 | activateHumanPlayer(); |
||
194 | } catch (const ChessParseError& ex) { |
||
195 | std::cout << "Invalid FEN: " << fen << " (" << ex.what() << ")" << std::endl; |
||
196 | } |
||
197 | return true; |
||
198 | } else if (moveStr == "getpos") { |
||
199 | std::string fen(TextIO::toFEN(pos)); |
||
200 | std::cout << fen << std::endl; |
||
201 | return true; |
||
202 | } else if (startsWith(moveStr, "draw ")) { |
||
203 | if (getGameState() == ALIVE) { |
||
204 | std::string drawCmd = moveStr.substr(moveStr.find_first_of(' ') + 1); |
||
205 | return handleDrawCmd(drawCmd); |
||
206 | } else { |
||
207 | return true; |
||
208 | } |
||
209 | } else if (moveStr == "resign") { |
||
210 | if (getGameState()== ALIVE) { |
||
211 | resignState = pos.isWhiteMove() ? RESIGN_WHITE : RESIGN_BLACK; |
||
212 | return true; |
||
213 | } else { |
||
214 | return true; |
||
215 | } |
||
216 | } else if (startsWith(moveStr, "book")) { |
||
217 | std::string bookCmd = moveStr.substr(moveStr.find_first_of(' ') + 1); |
||
218 | return handleBookCmd(bookCmd); |
||
219 | } else if (startsWith(moveStr, "time")) { |
||
220 | std::string timeStr = moveStr.substr(moveStr.find_first_of(' ') + 1); |
||
221 | int timeLimit; |
||
222 | if (!str2Num(timeStr, timeLimit)) { |
||
223 | std::cout << "Can not parse number: " << timeStr << std::endl; |
||
224 | return false; |
||
225 | } |
||
226 | whitePlayer->timeLimit(timeLimit, timeLimit); |
||
227 | blackPlayer->timeLimit(timeLimit, timeLimit); |
||
228 | return true; |
||
229 | } else if (startsWith(moveStr, "perft ")) { |
||
230 | std::string depthStr = moveStr.substr(moveStr.find_first_of(' ') + 1); |
||
231 | int depth; |
||
232 | if (!str2Num(depthStr, depth)) { |
||
233 | std::cout << "Can not parse number: " << depthStr << std::endl; |
||
234 | return false; |
||
235 | } |
||
236 | S64 t0 = currentTimeMillis(); |
||
237 | U64 nodes = perfT(pos, depth); |
||
238 | S64 t1 = currentTimeMillis(); |
||
239 | double t = (t1 - t0) * 1e-3; |
||
240 | std::stringstream ss; |
||
241 | ss.precision(3); |
||
242 | std::cout << "perft(" << depth << ") = " << nodes << ", t=" |
||
243 | << (ss << std::fixed << t).rdbuf() |
||
244 | << "s" << std::endl; |
||
245 | return true; |
||
246 | } else { |
||
247 | return false; |
||
248 | } |
||
249 | } |
||
250 | |||
251 | void |
||
252 | Game::activateHumanPlayer() { |
||
253 | if (!(pos.isWhiteMove() ? whitePlayer : blackPlayer)->isHumanPlayer()) |
||
254 | std::swap(whitePlayer, blackPlayer); |
||
255 | } |
||
256 | |||
257 | void |
||
258 | Game::getPosHistory(std::vector<std::string> ret) { |
||
259 | ret.clear(); |
||
260 | Position pos(this->pos); |
||
261 | for (int i = currentMove; i > 0; i--) |
||
262 | pos.unMakeMove(moveList[i - 1], uiInfoList[i - 1]); |
||
263 | ret.push_back(TextIO::toFEN(pos)); // Store initial FEN |
||
264 | |||
265 | std::string moves; |
||
266 | UndoInfo ui; |
||
267 | for (size_t i = 0; i < moveList.size(); i++) { |
||
268 | const Move& move = moveList[i]; |
||
269 | std::string strMove(TextIO::moveToString(pos, move, false)); |
||
270 | moves += ' '; |
||
271 | moves += strMove; |
||
272 | pos.makeMove(move, ui); |
||
273 | } |
||
274 | ret.push_back(moves); // Store move list string |
||
275 | |||
276 | int numUndo = (int)moveList.size() - currentMove; |
||
277 | ret.push_back(num2Str(numUndo)); |
||
278 | } |
||
279 | |||
280 | std::string |
||
281 | Game::getMoveListString(bool compressed) { |
||
282 | std::string ret; |
||
283 | |||
284 | // Undo all moves in move history. |
||
285 | Position pos(this->pos); |
||
286 | for (int i = currentMove; i > 0; i--) |
||
287 | pos.unMakeMove(moveList[i - 1], uiInfoList[i - 1]); |
||
288 | |||
289 | // Print all moves |
||
290 | std::string whiteMove; |
||
291 | std::string blackMove; |
||
292 | for (int i = 0; i < currentMove; i++) { |
||
293 | const Move& move = moveList[i]; |
||
294 | std::string strMove = TextIO::moveToString(pos, move, false); |
||
295 | if (drawOfferList[i]) |
||
296 | strMove += " (d)"; |
||
297 | if (pos.isWhiteMove()) { |
||
298 | whiteMove = strMove; |
||
299 | } else { |
||
300 | blackMove = strMove; |
||
301 | if (whiteMove.length() == 0) |
||
302 | whiteMove = "..."; |
||
303 | if (compressed) { |
||
304 | ret += num2Str(pos.getFullMoveCounter()) + ". " + whiteMove + |
||
305 | " " + blackMove + " "; |
||
306 | } else { |
||
307 | std::stringstream ss; |
||
308 | ss << std::setw(3) << pos.getFullMoveCounter() << ". " |
||
309 | << std::setw(10) << std::left << whiteMove << " " |
||
310 | << std::setw(10) << std::left << blackMove << std::endl; |
||
311 | ret += ss.str(); |
||
312 | } |
||
313 | whiteMove.clear(); |
||
314 | blackMove.clear(); |
||
315 | } |
||
316 | UndoInfo ui; |
||
317 | pos.makeMove(move, ui); |
||
318 | } |
||
319 | if ((whiteMove.length() > 0) || (blackMove.length() > 0)) { |
||
320 | if (whiteMove.length() == 0) |
||
321 | whiteMove = "..."; |
||
322 | if (compressed) { |
||
323 | ret += num2Str(pos.getFullMoveCounter()) + ". " + whiteMove + |
||
324 | " " + blackMove + " "; |
||
325 | } else { |
||
326 | std::stringstream ss; |
||
327 | ss << std::setw(3) << pos.getFullMoveCounter() << ". " |
||
328 | << std::setw(10) << std::left << whiteMove << " " |
||
329 | << std::setw(10) << std::left << blackMove << std::endl; |
||
330 | ret += ss.str(); |
||
331 | } |
||
332 | } |
||
333 | std::string gameResult = getPGNResultString(); |
||
334 | if (gameResult != "*") { |
||
335 | ret += gameResult; |
||
336 | if (!compressed) |
||
337 | ret += '\n'; |
||
338 | } |
||
339 | return ret; |
||
340 | } |
||
341 | |||
342 | std::string |
||
343 | Game::getPGNResultString() { |
||
344 | std::string gameResult = "*"; |
||
345 | switch (getGameState()) { |
||
346 | case ALIVE: |
||
347 | break; |
||
348 | case WHITE_MATE: |
||
349 | case RESIGN_BLACK: |
||
350 | gameResult = "1-0"; |
||
351 | break; |
||
352 | case BLACK_MATE: |
||
353 | case RESIGN_WHITE: |
||
354 | gameResult = "0-1"; |
||
355 | break; |
||
356 | case WHITE_STALEMATE: |
||
357 | case BLACK_STALEMATE: |
||
358 | case DRAW_REP: |
||
359 | case DRAW_50: |
||
360 | case DRAW_NO_MATE: |
||
361 | case DRAW_AGREE: |
||
362 | gameResult = "1/2-1/2"; |
||
363 | break; |
||
364 | } |
||
365 | return gameResult; |
||
366 | } |
||
367 | |||
368 | /** Return a list of previous positions in this game, back to the last "zeroing" move. */ |
||
369 | void |
||
370 | Game::getHistory(std::vector<Position>& posList) { |
||
371 | posList.clear(); |
||
372 | Position pos(this->pos); |
||
373 | for (int i = currentMove; i > 0; i--) { |
||
374 | if (pos.getHalfMoveClock() == 0) |
||
375 | break; |
||
376 | pos.unMakeMove(moveList[i - 1], uiInfoList[i - 1]); |
||
377 | posList.push_back(pos); |
||
378 | } |
||
379 | std::reverse(posList.begin(), posList.end()); |
||
380 | } |
||
381 | |||
382 | void |
||
383 | Game::listMoves() { |
||
384 | std::string movesStr = getMoveListString(false); |
||
385 | std::cout << movesStr; |
||
386 | } |
||
387 | |||
388 | bool |
||
389 | Game::handleDrawCmd(std::string drawCmd) { |
||
390 | if (startsWith(drawCmd, "rep") || startsWith(drawCmd, "50")) { |
||
391 | bool rep = startsWith(drawCmd, "rep"); |
||
392 | Move m; |
||
393 | size_t idx = drawCmd.find_first_of(' '); |
||
394 | std::string ms; |
||
395 | if (idx != drawCmd.npos) { |
||
396 | ms = drawCmd.substr(idx + 1); |
||
397 | if (ms.length() > 0) |
||
398 | m = TextIO::stringToMove(pos, ms); |
||
399 | } |
||
400 | bool valid; |
||
401 | if (rep) { |
||
402 | valid = false; |
||
403 | std::vector<Position> oldPositions; |
||
404 | if (!m.isEmpty()) { |
||
405 | UndoInfo ui; |
||
406 | Position tmpPos(pos); |
||
407 | tmpPos.makeMove(m, ui); |
||
408 | oldPositions.push_back(tmpPos); |
||
409 | } |
||
410 | oldPositions.push_back(pos); |
||
411 | Position tmpPos(pos); |
||
412 | for (int i = currentMove - 1; i >= 0; i--) { |
||
413 | tmpPos.unMakeMove(moveList[i], uiInfoList[i]); |
||
414 | oldPositions.push_back(tmpPos); |
||
415 | } |
||
416 | int repetitions = 0; |
||
417 | Position firstPos = oldPositions[0]; |
||
418 | for (size_t i = 0; i < oldPositions.size(); i++) { |
||
419 | if (oldPositions[i].drawRuleEquals(firstPos)) |
||
420 | repetitions++; |
||
421 | } |
||
422 | if (repetitions >= 3) { |
||
423 | valid = true; |
||
424 | } |
||
425 | } else { |
||
426 | Position tmpPos(pos); |
||
427 | if (!m.isEmpty()) { |
||
428 | UndoInfo ui; |
||
429 | tmpPos.makeMove(m, ui); |
||
430 | } |
||
431 | valid = tmpPos.getHalfMoveClock() >= 100; |
||
432 | } |
||
433 | if (valid) { |
||
434 | drawState = rep ? DRAW_REP : DRAW_50; |
||
435 | drawStateMoveStr.clear(); |
||
436 | if (!m.isEmpty()) |
||
437 | drawStateMoveStr = TextIO::moveToString(pos, m, false); |
||
438 | } else { |
||
439 | pendingDrawOffer = true; |
||
440 | if (!m.isEmpty()) |
||
441 | processString(ms); |
||
442 | } |
||
443 | return true; |
||
444 | } else if (startsWith(drawCmd, "offer ")) { |
||
445 | pendingDrawOffer = true; |
||
446 | size_t idx = drawCmd.find_first_of(' '); |
||
447 | if (idx != drawCmd.npos) { |
||
448 | std::string ms = drawCmd.substr(idx + 1); |
||
449 | if (!TextIO::stringToMove(pos, ms).isEmpty()) |
||
450 | processString(ms); |
||
451 | } |
||
452 | return true; |
||
453 | } else if (drawCmd == "accept") { |
||
454 | if (haveDrawOffer()) |
||
455 | drawState = DRAW_AGREE; |
||
456 | return true; |
||
457 | } else { |
||
458 | return false; |
||
459 | } |
||
460 | } |
||
461 | |||
462 | bool |
||
463 | Game::handleBookCmd(const std::string& bookCmd) { |
||
464 | if (bookCmd == "off") { |
||
465 | whitePlayer->useBook(false); |
||
466 | blackPlayer->useBook(false); |
||
467 | return true; |
||
468 | } else if (bookCmd == "on") { |
||
469 | whitePlayer->useBook(true); |
||
470 | whitePlayer->useBook(true); |
||
471 | return true; |
||
472 | } |
||
473 | return false; |
||
474 | } |
||
475 | |||
476 | bool |
||
477 | Game::insufficientMaterial() { |
||
478 | if (pos.pieceTypeBB(Piece::WQUEEN) != 0) return false; |
||
479 | if (pos.pieceTypeBB(Piece::WROOK) != 0) return false; |
||
480 | if (pos.pieceTypeBB(Piece::WPAWN) != 0) return false; |
||
481 | if (pos.pieceTypeBB(Piece::BQUEEN) != 0) return false; |
||
482 | if (pos.pieceTypeBB(Piece::BROOK) != 0) return false; |
||
483 | if (pos.pieceTypeBB(Piece::BPAWN) != 0) return false; |
||
484 | int wb = BitBoard::bitCount(pos.pieceTypeBB(Piece::WBISHOP)); |
||
485 | int wn = BitBoard::bitCount(pos.pieceTypeBB(Piece::WKNIGHT)); |
||
486 | int bb = BitBoard::bitCount(pos.pieceTypeBB(Piece::BBISHOP)); |
||
487 | int bn = BitBoard::bitCount(pos.pieceTypeBB(Piece::BKNIGHT)); |
||
488 | if (wb + wn + bb + bn <= 1) |
||
489 | return true; // King + bishop/knight vs king is draw |
||
490 | if (wn + bn == 0) { |
||
491 | // Only bishops. If they are all on the same color, the position is a draw. |
||
492 | U64 bMask = pos.pieceTypeBB(Piece::WBISHOP) | pos.pieceTypeBB(Piece::BBISHOP); |
||
493 | if (((bMask & BitBoard::maskDarkSq) == 0) || |
||
494 | ((bMask & BitBoard::maskLightSq) == 0)) |
||
495 | return true; |
||
496 | } |
||
497 | return false; |
||
498 | } |
||
499 | |||
500 | U64 |
||
501 | Game::perfT(Position& pos, int depth) { |
||
502 | if (depth == 0) |
||
503 | return 1; |
||
504 | U64 nodes = 0; |
||
505 | MoveList moves; |
||
506 | MoveGen::pseudoLegalMoves(pos, moves); |
||
507 | MoveGen::removeIllegal(pos, moves); |
||
508 | if (depth == 1) |
||
509 | return moves.size; |
||
510 | UndoInfo ui; |
||
511 | for (int mi = 0; mi < moves.size; mi++) { |
||
512 | const Move& m = moves[mi]; |
||
513 | pos.makeMove(m, ui); |
||
514 | nodes += perfT(pos, depth - 1); |
||
515 | pos.unMakeMove(m, ui); |
||
516 | } |
||
517 | return nodes; |
||
518 | } |