Subversion Repositories Games.Chess Giants

Rev

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
}