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
 * textio.cpp
21
 *
22
 *  Created on: Feb 25, 2012
23
 *      Author: petero
24
 */
25
 
26
#include "textio.hpp"
27
#include "moveGen.hpp"
28
#include <cassert>
29
 
30
const std::string TextIO::startPosFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
31
 
32
 
33
Position
34
TextIO::readFEN(const std::string& fen) {
35
    Position pos;
36
 
37
    // Piece placement
38
    int row = 7;
39
    int col = 0;
40
    size_t i;
41
    for (i = 0; i < fen.length(); i++) {
42
        char c = fen[i];
43
        if (c == ' ')
44
            break;
45
        switch (c) {
46
            case '1': col += 1; break;
47
            case '2': col += 2; break;
48
            case '3': col += 3; break;
49
            case '4': col += 4; break;
50
            case '5': col += 5; break;
51
            case '6': col += 6; break;
52
            case '7': col += 7; break;
53
            case '8': col += 8; break;
54
            case '/':
55
                row--; col = 0;
56
                if (row < 0) throw ChessParseError("Too many rows");
57
                break;
58
            case 'P': safeSetPiece(pos, col, row, Piece::WPAWN);   col++; break;
59
            case 'N': safeSetPiece(pos, col, row, Piece::WKNIGHT); col++; break;
60
            case 'B': safeSetPiece(pos, col, row, Piece::WBISHOP); col++; break;
61
            case 'R': safeSetPiece(pos, col, row, Piece::WROOK);   col++; break;
62
            case 'Q': safeSetPiece(pos, col, row, Piece::WQUEEN);  col++; break;
63
            case 'K': safeSetPiece(pos, col, row, Piece::WKING);   col++; break;
64
            case 'p': safeSetPiece(pos, col, row, Piece::BPAWN);   col++; break;
65
            case 'n': safeSetPiece(pos, col, row, Piece::BKNIGHT); col++; break;
66
            case 'b': safeSetPiece(pos, col, row, Piece::BBISHOP); col++; break;
67
            case 'r': safeSetPiece(pos, col, row, Piece::BROOK);   col++; break;
68
            case 'q': safeSetPiece(pos, col, row, Piece::BQUEEN);  col++; break;
69
            case 'k': safeSetPiece(pos, col, row, Piece::BKING);   col++; break;
70
            default: throw ChessParseError("Invalid piece");
71
        }
72
    }
73
    while (i < fen.length() && fen[i] == ' ')
74
        i++;
75
    if (i >= fen.length())
76
        throw ChessParseError("Invalid side");
77
    pos.setWhiteMove(fen[i++] == 'w');
78
 
79
    // Castling rights
80
    int castleMask = 0;
81
    while (i < fen.length() && fen[i] == ' ')
82
        i++;
83
    for ( ; i < fen.length(); i++) {
84
        char c = fen[i];
85
        if (c == ' ')
86
            break;
87
        switch (c) {
88
        case 'K': castleMask |= (1 << Position::H1_CASTLE); break;
89
        case 'Q': castleMask |= (1 << Position::A1_CASTLE); break;
90
        case 'k': castleMask |= (1 << Position::H8_CASTLE); break;
91
        case 'q': castleMask |= (1 << Position::A8_CASTLE); break;
92
        case '-': break;
93
        default: throw ChessParseError("Invalid castling flags");
94
        }
95
    }
96
    pos.setCastleMask(castleMask);
97
 
98
    while (i < fen.length() && fen[i] == ' ')
99
        i++;
100
 
101
    if (i < fen.length()) {
102
        // En passant target square
103
        if (fen[i] != '-') {
104
            if (i >= fen.length() - 1)
105
                throw ChessParseError("Invalid en passant square");
106
            pos.setEpSquare(getSquare(fen.substr(i, 2)));
107
        }
108
        while (i < fen.length() && fen[i] != ' ')
109
            i++;
110
    }
111
 
112
    while (i < fen.length() && fen[i] == ' ')
113
        i++;
114
    if (i < fen.length()) {
115
        int i0 = i;
116
        while (i < fen.length() && fen[i] != ' ')
117
            i++;
118
        int halfMoveClock;
119
        if (str2Num(fen.substr(i0, i - i0), halfMoveClock))
120
            pos.setHalfMoveClock(halfMoveClock);
121
    }
122
    while (i < fen.length() && fen[i] == ' ')
123
        i++;
124
    if (i < fen.length()) {
125
        int i0 = i;
126
        while (i < fen.length() && fen[i] != ' ')
127
            i++;
128
        int fullMoveCounter;
129
        if (str2Num(fen.substr(i0, i - i0), fullMoveCounter))
130
            pos.setFullMoveCounter(fullMoveCounter);
131
    }
132
 
133
    // Each side must have exactly one king
134
    int wKings = 0;
135
    int bKings = 0;
136
    for (int x = 0; x < 8; x++) {
137
        for (int y = 0; y < 8; y++) {
138
            int p = pos.getPiece(Position::getSquare(x, y));
139
            if (p == Piece::WKING)
140
                wKings++;
141
            else if (p == Piece::BKING)
142
                bKings++;
143
        }
144
    }
145
    if (wKings != 1)
146
        throw ChessParseError("White must have exactly one king");
147
    if (bKings != 1)
148
        throw ChessParseError("Black must have exactly one king");
149
 
150
    // Make sure king can not be captured
151
    Position pos2(pos);
152
    pos2.setWhiteMove(!pos.isWhiteMove());
153
    if (MoveGen::inCheck(pos2))
154
        throw ChessParseError("King capture possible");
155
 
156
    fixupEPSquare(pos);
157
    return pos;
158
}
159
 
160
 
161
void
162
TextIO::fixupEPSquare(Position& pos) {
163
    int epSquare = pos.getEpSquare();
164
    if (epSquare >= 0) {
165
        MoveList moves;
166
        MoveGen::pseudoLegalMoves(pos, moves);
167
        MoveGen::removeIllegal(pos, moves);
168
        bool epValid = false;
169
        for (int mi = 0; mi < moves.size; mi++) {
170
            const Move& m = moves[mi];
171
            if (m.to() == epSquare) {
172
                if (pos.getPiece(m.from()) == (pos.isWhiteMove() ? Piece::WPAWN : Piece::BPAWN)) {
173
                    epValid = true;
174
                    break;
175
                }
176
            }
177
        }
178
        if (!epValid)
179
            pos.setEpSquare(-1);
180
    }
181
}
182
 
183
 
184
std::string
185
TextIO::toFEN(const Position& pos) {
186
    std::string ret;
187
    // Piece placement
188
    for (int r = 7; r >=0; r--) {
189
        int numEmpty = 0;
190
        for (int c = 0; c < 8; c++) {
191
            int p = pos.getPiece(Position::getSquare(c, r));
192
            if (p == Piece::EMPTY) {
193
                numEmpty++;
194
            } else {
195
                if (numEmpty > 0) {
196
                    ret += (char)('0' + numEmpty);
197
                    numEmpty = 0;
198
                }
199
                switch (p) {
200
                    case Piece::WKING:   ret += 'K'; break;
201
                    case Piece::WQUEEN:  ret += 'Q'; break;
202
                    case Piece::WROOK:   ret += 'R'; break;
203
                    case Piece::WBISHOP: ret += 'B'; break;
204
                    case Piece::WKNIGHT: ret += 'N'; break;
205
                    case Piece::WPAWN:   ret += 'P'; break;
206
                    case Piece::BKING:   ret += 'k'; break;
207
                    case Piece::BQUEEN:  ret += 'q'; break;
208
                    case Piece::BROOK:   ret += 'r'; break;
209
                    case Piece::BBISHOP: ret += 'b'; break;
210
                    case Piece::BKNIGHT: ret += 'n'; break;
211
                    case Piece::BPAWN:   ret += 'p'; break;
212
                    default: assert(false); break;
213
                }
214
            }
215
        }
216
        if (numEmpty > 0)
217
            ret += (char)('0' + numEmpty);
218
        if (r > 0)
219
            ret += '/';
220
    }
221
    ret += (pos.isWhiteMove() ? " w " : " b ");
222
 
223
    // Castling rights
224
    bool anyCastle = false;
225
    if (pos.h1Castle()) {
226
        ret += 'K';
227
        anyCastle = true;
228
    }
229
    if (pos.a1Castle()) {
230
        ret += 'Q';
231
        anyCastle = true;
232
    }
233
    if (pos.h8Castle()) {
234
        ret += 'k';
235
        anyCastle = true;
236
    }
237
    if (pos.a8Castle()) {
238
        ret += 'q';
239
        anyCastle = true;
240
    }
241
    if (!anyCastle) {
242
        ret += '-';
243
    }
244
 
245
    // En passant target square
246
    {
247
        ret += ' ';
248
        if (pos.getEpSquare() >= 0) {
249
            int x = Position::getX(pos.getEpSquare());
250
            int y = Position::getY(pos.getEpSquare());
251
            ret += ((char)(x + 'a'));
252
            ret += ((char)(y + '1'));
253
        } else {
254
            ret += '-';
255
        }
256
    }
257
 
258
    // Move counters
259
    ret += ' ';
260
    ret += num2Str(pos.getHalfMoveClock());
261
    ret += ' ';
262
    ret += num2Str(pos.getFullMoveCounter());
263
 
264
    return ret;
265
}
266
 
267
std::string
268
TextIO::moveToUCIString(const Move& m) {
269
    std::string ret = squareToString(m.from());
270
    ret += squareToString(m.to());
271
    switch (m.promoteTo()) {
272
        case Piece::WQUEEN:
273
        case Piece::BQUEEN:
274
            ret += "q";
275
            break;
276
        case Piece::WROOK:
277
        case Piece::BROOK:
278
            ret += "r";
279
            break;
280
        case Piece::WBISHOP:
281
        case Piece::BBISHOP:
282
            ret += "b";
283
            break;
284
        case Piece::WKNIGHT:
285
        case Piece::BKNIGHT:
286
            ret += "n";
287
            break;
288
        default:
289
            break;
290
    }
291
    return ret;
292
}
293
 
294
Move
295
TextIO::uciStringToMove(const std::string& move) {
296
    Move m;
297
    if ((move.length() < 4) || (move.length() > 5))
298
        return m;
299
    int fromSq = TextIO::getSquare(move.substr(0, 2));
300
    int toSq   = TextIO::getSquare(move.substr(2, 2));
301
    if ((fromSq < 0) || (toSq < 0)) {
302
        return m;
303
    }
304
    char prom = ' ';
305
    bool white = true;
306
    if (move.length() == 5) {
307
        prom = move[4];
308
        if (Position::getY(toSq) == 7) {
309
            white = true;
310
        } else if (Position::getY(toSq) == 0) {
311
            white = false;
312
        } else {
313
            return m;
314
        }
315
    }
316
    int promoteTo;
317
    switch (prom) {
318
        case ' ':
319
            promoteTo = Piece::EMPTY;
320
            break;
321
        case 'q':
322
            promoteTo = white ? Piece::WQUEEN : Piece::BQUEEN;
323
            break;
324
        case 'r':
325
            promoteTo = white ? Piece::WROOK : Piece::BROOK;
326
            break;
327
        case 'b':
328
            promoteTo = white ? Piece::WBISHOP : Piece::BBISHOP;
329
            break;
330
        case 'n':
331
            promoteTo = white ? Piece::WKNIGHT : Piece::BKNIGHT;
332
            break;
333
        default:
334
            return m;
335
    }
336
    return Move(fromSq, toSq, promoteTo);
337
}
338
 
339
static bool
340
isCapture(const Position& pos, const Move& move) {
341
    if (pos.getPiece(move.to()) != Piece::EMPTY)
342
        return true;
343
    int p = pos.getPiece(move.from());
344
    return (p == (pos.isWhiteMove() ? Piece::WPAWN : Piece::BPAWN)) &&
345
           (move.to() == pos.getEpSquare());
346
}
347
 
348
static std::string
349
pieceToChar(int p) {
350
    switch (p) {
351
        case Piece::WQUEEN:  case Piece::BQUEEN:  return "Q";
352
        case Piece::WROOK:   case Piece::BROOK:   return "R";
353
        case Piece::WBISHOP: case Piece::BBISHOP: return "B";
354
        case Piece::WKNIGHT: case Piece::BKNIGHT: return "N";
355
        case Piece::WKING:   case Piece::BKING:   return "K";
356
    }
357
    return "";
358
}
359
 
360
static std::string
361
moveToString(Position& pos, const Move& move, bool longForm, const MoveList& moves) {
362
    std::string ret;
363
    int wKingOrigPos = Position::getSquare(4, 0);
364
    int bKingOrigPos = Position::getSquare(4, 7);
365
    if (move.from() == wKingOrigPos && pos.getPiece(wKingOrigPos) == Piece::WKING) {
366
        // Check white castle
367
        if (move.to() == Position::getSquare(6, 0))
368
            ret += "O-O";
369
        else if (move.to() == Position::getSquare(2, 0))
370
            ret += "O-O-O";
371
    } else if (move.from() == bKingOrigPos && pos.getPiece(bKingOrigPos) == Piece::BKING) {
372
        // Check black castle
373
        if (move.to() == Position::getSquare(6, 7))
374
            ret += "O-O";
375
        else if (move.to() == Position::getSquare(2, 7))
376
            ret += "O-O-O";
377
    }
378
    if (ret.length() == 0) {
379
        int p = pos.getPiece(move.from());
380
        ret += pieceToChar(p);
381
        int x1 = Position::getX(move.from());
382
        int y1 = Position::getY(move.from());
383
        int x2 = Position::getX(move.to());
384
        int y2 = Position::getY(move.to());
385
        if (longForm) {
386
            ret += (char)(x1 + 'a');
387
            ret += (char)(y1 + '1');
388
            ret += isCapture(pos, move) ? 'x' : '-';
389
        } else {
390
            if (p == (pos.isWhiteMove() ? Piece::WPAWN : Piece::BPAWN)) {
391
                if (isCapture(pos, move))
392
                    ret += (char)(x1 + 'a');
393
            } else {
394
                int numSameTarget = 0;
395
                int numSameFile = 0;
396
                int numSameRow = 0;
397
                for (int mi = 0; mi < moves.size; mi++) {
398
                    const Move& m = moves[mi];
399
                    if (m.isEmpty())
400
                        break;
401
                    if ((pos.getPiece(m.from()) == p) && (m.to() == move.to())) {
402
                        numSameTarget++;
403
                        if (Position::getX(m.from()) == x1)
404
                            numSameFile++;
405
                        if (Position::getY(m.from()) == y1)
406
                            numSameRow++;
407
                    }
408
                }
409
                if (numSameTarget < 2) {
410
                    // No file/row info needed
411
                } else if (numSameFile < 2) {
412
                    ret += (char)(x1 + 'a');   // Only file info needed
413
                } else if (numSameRow < 2) {
414
                    ret += (char)(y1 + '1');   // Only row info needed
415
                } else {
416
                    ret += (char) (x1 + 'a');   // File and row info needed
417
                    ret += (char) (y1 + '1');
418
                }
419
            }
420
            if (isCapture(pos, move))
421
                ret += 'x';
422
        }
423
        ret += (char)(x2 + 'a');
424
        ret += (char)(y2 + '1');
425
        if (move.promoteTo() != Piece::EMPTY)
426
            ret += pieceToChar(move.promoteTo());
427
    }
428
    UndoInfo ui;
429
    if (MoveGen::givesCheck(pos, move)) {
430
        pos.makeMove(move, ui);
431
        MoveList nextMoves;
432
        MoveGen::pseudoLegalMoves(pos, nextMoves);
433
        MoveGen::removeIllegal(pos, nextMoves);
434
        if (nextMoves.size == 0)
435
            ret += '#';
436
        else
437
            ret += '+';
438
        pos.unMakeMove(move, ui);
439
    }
440
 
441
    return ret;
442
}
443
 
444
std::string
445
TextIO::moveToString(const Position& pos, const Move& move, bool longForm) {
446
    MoveList moves;
447
    MoveGen::pseudoLegalMoves(pos, moves);
448
    Position tmpPos(pos);
449
    MoveGen::removeIllegal(tmpPos, moves);
450
    return ::moveToString(tmpPos, move, longForm, moves);
451
}
452
 
453
namespace {
454
    struct MoveInfo {
455
        int piece = -1;             // -1 for unspecified
456
        int fromX = -1, fromY = -1; // -1 for unspecified
457
        int toX = -1, toY = -1;     // -1 for unspecified
458
        int promPiece = -1;         // -1 for unspecified
459
    };
460
}
461
 
462
Move
463
TextIO::stringToMove(Position& pos, const std::string& strMoveIn) {
464
    std::string strMove;
465
    for (size_t i = 0; i < strMoveIn.length(); i++) {
466
        switch (strMoveIn[i]) {
467
        case '=':
468
        case '+':
469
        case '#':
470
            break;
471
        default:
472
            strMove += strMoveIn[i];
473
            break;
474
        }
475
    }
476
 
477
    Move move;
478
    if (strMove == "--")
479
        return move;
480
 
481
    const bool wtm = pos.isWhiteMove();
482
 
483
    MoveInfo info;
484
    bool capture = false;
485
    if ((strMove == "O-O") || (strMove =="0-0") || (strMove == "o-o")) {
486
        info.piece = wtm ? Piece::WKING : Piece::BKING;
487
        info.fromX = 4;
488
        info.toX = 6;
489
        info.fromY = info.toY = wtm ? 0 : 7;
490
        info.promPiece = Piece::EMPTY;
491
    } else if ((strMove == "O-O-O") || (strMove == "0-0-0") || (strMove == "o-o-o")) {
492
        info.piece = wtm ? Piece::WKING : Piece::BKING;
493
        info.fromX = 4;
494
        info.toX = 2;
495
        info.fromY = info.toY = wtm ? 0 : 7;
496
        info.promPiece = Piece::EMPTY;
497
    } else {
498
        bool atToSq = false;
499
        for (size_t i = 0; i < strMove.length(); i++) {
500
            char c = strMove[i];
501
            if (i == 0) {
502
                int piece = charToPiece(wtm, c);
503
                if (piece >= 0) {
504
                    info.piece = piece;
505
                    continue;
506
                }
507
            }
508
            int tmpX = c - 'a';
509
            if ((tmpX >= 0) && (tmpX < 8)) {
510
                if (atToSq || (info.fromX >= 0))
511
                    info.toX = tmpX;
512
                else
513
                    info.fromX = tmpX;
514
            }
515
            int tmpY = c - '1';
516
            if ((tmpY >= 0) && (tmpY < 8)) {
517
                if (atToSq || (info.fromY >= 0))
518
                    info.toY = tmpY;
519
                else
520
                    info.fromY = tmpY;
521
            }
522
            if ((c == 'x') || (c == '-')) {
523
                atToSq = true;
524
                if (c == 'x')
525
                    capture = true;
526
            }
527
            if (i == strMove.length() - 1) {
528
                int promPiece = charToPiece(wtm, c);
529
                if (promPiece >= 0) {
530
                    info.promPiece = promPiece;
531
                }
532
            }
533
        }
534
        if ((info.fromX >= 0) && (info.toX < 0)) {
535
            info.toX = info.fromX;
536
            info.fromX = -1;
537
        }
538
        if ((info.fromY >= 0) && (info.toY < 0)) {
539
            info.toY = info.fromY;
540
            info.fromY = -1;
541
        }
542
        if (info.piece < 0) {
543
            bool haveAll = (info.fromX >= 0) && (info.fromY >= 0) &&
544
                           (info.toX >= 0) && (info.toY >= 0);
545
            if (!haveAll)
546
                info.piece = wtm ? Piece::WPAWN : Piece::BPAWN;
547
        }
548
        if (info.promPiece < 0)
549
            info.promPiece = Piece::EMPTY;
550
    }
551
 
552
    MoveList moves;
553
    MoveGen::pseudoLegalMoves(pos, moves);
554
    MoveGen::removeIllegal(pos, moves);
555
 
556
    std::vector<Move> matches;
557
    for (int i = 0; i < moves.size; i++) {
558
        const Move& m = moves[i];
559
        int p = pos.getPiece(m.from());
560
        bool match = true;
561
        if ((info.piece >= 0) && (info.piece != p))
562
            match = false;
563
        if ((info.fromX >= 0) && (info.fromX != Position::getX(m.from())))
564
            match = false;
565
        if ((info.fromY >= 0) && (info.fromY != Position::getY(m.from())))
566
            match = false;
567
        if ((info.toX >= 0) && (info.toX != Position::getX(m.to())))
568
            match = false;
569
        if ((info.toY >= 0) && (info.toY != Position::getY(m.to())))
570
            match = false;
571
        if ((info.promPiece >= 0) && (info.promPiece != m.promoteTo()))
572
            match = false;
573
        if (match)
574
            matches.push_back(m);
575
    }
576
    int nMatches = matches.size();
577
    if (nMatches == 0)
578
        return move;
579
    else if (nMatches == 1)
580
        return matches[0];
581
    if (!capture)
582
        return move;
583
    for (size_t i = 0; i < matches.size(); i++) {
584
        const Move& m = matches[i];
585
        int capt = pos.getPiece(m.to());
586
        if (capt != Piece::EMPTY) {
587
            if (move.isEmpty()) {
588
                move = m;
589
            } else {
590
                move = Move();
591
                return move;
592
            }
593
        }
594
    }
595
    return move;
596
}
597
 
598
std::string
599
TextIO::asciiBoard(const Position& pos) {
600
    std::string ret;
601
    ret += "    +----+----+----+----+----+----+----+----+\n";
602
    for (int y = 7; y >= 0; y--) {
603
        ret += "    |";
604
        for (int x = 0; x < 8; x++) {
605
            ret += ' ';
606
            int p = pos.getPiece(Position::getSquare(x, y));
607
            if (p == Piece::EMPTY) {
608
                bool dark = Position::darkSquare(x, y);
609
                ret.append(dark ? ".. |" : "   |");
610
            } else {
611
                ret += Piece::isWhite(p) ? ' ' : '*';
612
                std::string pieceName = pieceToChar(p);
613
                if (pieceName.length() == 0)
614
                    pieceName = "P";
615
 
616
                ret += pieceName;
617
                ret += " |";
618
            }
619
        }
620
 
621
        ret += ("\n    +----+----+----+----+----+----+----+----+\n");
622
    }
623
 
624
    return ret;
625
}
626
 
627
std::string
628
TextIO::asciiBoard(U64 mask) {
629
    std::string ret;
630
    for (int y = 7; y >= 0; y--) {
631
        for (int x = 0; x < 8; x++) {
632
            int sq = Position::getSquare(x, y);
633
            ret += (mask & (1ULL << sq)) ? '1' : '0';
634
        }
635
        ret += '\n';
636
    }
637
    return ret;
638
}