From 672610e58fe7dd7fbb159a4e37f0b8a49e51fae3 Mon Sep 17 00:00:00 2001 From: Fabian Fichter Date: Sat, 25 Mar 2023 14:35:08 +0100 Subject: [PATCH] Support multiple castling rook types (#603) Closes #52. --- src/apiutil.h | 29 +++++++++++++++++------------ src/parser.cpp | 12 ++++++++++-- src/position.cpp | 43 +++++++++++++++++++++++++------------------ src/position.h | 12 ++++++------ src/variant.cpp | 32 +++++++++++++++++++++++--------- src/variant.h | 4 ++-- src/variants.ini | 2 +- tests/perft.sh | 2 ++ 8 files changed, 86 insertions(+), 50 deletions(-) diff --git a/src/apiutil.h b/src/apiutil.h index 3cdbf4d..6f36579 100644 --- a/src/apiutil.h +++ b/src/apiutil.h @@ -495,11 +495,12 @@ public: return s; } /// Returns all square positions for a given piece - std::vector get_squares_for_piece(char piece) const { + std::vector get_squares_for_pieces(Color color, PieceSet ps, const std::string& pieceChars) const { std::vector squares; + size_t pcIdx; for (int r = 0; r < nbRanks; ++r) for (int c = 0; c < nbFiles; ++c) - if (get_piece(r, c) == piece) + if ((pcIdx = pieceChars.find(get_piece(r, c))) != std::string::npos && (ps & type_of(Piece(pcIdx))) && color_of(Piece(pcIdx)) == color) squares.emplace_back(CharSquare(r, c)); return squares; } @@ -688,7 +689,6 @@ inline Validation check_castling_rank(const std::array& castling for (Color c : {WHITE, BLACK}) { const Rank castlingRank = relative_rank(c, v->castlingRank, v->maxRank); - char rookChar = v->pieceToChar[make_piece(c, v->castlingRookPiece)]; for (char castlingFlag : castlingInfoSplitted[c]) { if (tolower(castlingFlag) == 'k' || tolower(castlingFlag) == 'q') @@ -700,8 +700,11 @@ inline Validation check_castling_rank(const std::array& castling } bool kingside = tolower(castlingFlag) == 'k'; bool castlingRook = false; + size_t pcIdx; for (int f = kingside ? board.get_nb_files() - 1 : 0; f != kingPositions[c].fileIdx; kingside ? f-- : f++) - if (board.get_piece(castlingRank, f) == rookChar) + if ( (pcIdx = v->pieceToChar.find(board.get_piece(castlingRank, f))) != std::string::npos + && (v->castlingRookPieces[c] & type_of(Piece(pcIdx))) + && color_of(Piece(pcIdx)) == c) { castlingRook = true; break; @@ -740,10 +743,12 @@ inline Validation check_standard_castling(std::array& castlingIn { CharSquare rookStartingSquare = castling == QUEEN_SIDE ? rookPositionsStart[c][0] : rookPositionsStart[c][1]; char targetChar = castling == QUEEN_SIDE ? 'q' : 'k'; - char rookChar = v->pieceToChar[make_piece(c, v->castlingRookPiece)]; + size_t pcIdx; if (castlingInfoSplitted[c].find(targetChar) != std::string::npos) { - if (board.get_piece(rookStartingSquare.rowIdx, rookStartingSquare.fileIdx) != rookChar) + if ( (pcIdx = v->pieceToChar.find(board.get_piece(rookStartingSquare.rowIdx, rookStartingSquare.fileIdx))) == std::string::npos + || !(v->castlingRookPieces[c] & type_of(Piece(pcIdx))) + || color_of(Piece(pcIdx)) != c) { std::cerr << "The " << color_to_string(c) << " ROOK on the "<< castling_rights_to_string(castling) << " has moved. " << castling_rights_to_string(castling) << " castling is no longer valid for " << color_to_string(c) << "." << std::endl; @@ -994,8 +999,8 @@ inline FenValidation validate_fen(const std::string& fen, const Variant* v, bool if (castlingInfoSplitted[WHITE].size() != 0 || castlingInfoSplitted[BLACK].size() != 0) { std::array kingPositions; - kingPositions[WHITE] = board.get_square_for_piece(toupper(v->pieceToChar[v->castlingKingPiece])); - kingPositions[BLACK] = board.get_square_for_piece(tolower(v->pieceToChar[v->castlingKingPiece])); + kingPositions[WHITE] = board.get_square_for_piece(toupper(v->pieceToChar[v->castlingKingPiece[WHITE]])); + kingPositions[BLACK] = board.get_square_for_piece(tolower(v->pieceToChar[v->castlingKingPiece[BLACK]])); CharBoard startBoard(board.get_nb_ranks(), board.get_nb_files()); fill_char_board(startBoard, v->startFen, validSpecialCharactersFirstField, v); @@ -1008,11 +1013,11 @@ inline FenValidation validate_fen(const std::string& fen, const Variant* v, bool if (!v->chess960 && !v->castlingDroppedPiece && !chess960) { std::array kingPositionsStart; - kingPositionsStart[WHITE] = startBoard.get_square_for_piece(v->pieceToChar[make_piece(WHITE, v->castlingKingPiece)]); - kingPositionsStart[BLACK] = startBoard.get_square_for_piece(v->pieceToChar[make_piece(BLACK, v->castlingKingPiece)]); + kingPositionsStart[WHITE] = startBoard.get_square_for_piece(v->pieceToChar[make_piece(WHITE, v->castlingKingPiece[WHITE])]); + kingPositionsStart[BLACK] = startBoard.get_square_for_piece(v->pieceToChar[make_piece(BLACK, v->castlingKingPiece[BLACK])]); std::array, 2> rookPositionsStart; - rookPositionsStart[WHITE] = startBoard.get_squares_for_piece(v->pieceToChar[make_piece(WHITE, v->castlingRookPiece)]); - rookPositionsStart[BLACK] = startBoard.get_squares_for_piece(v->pieceToChar[make_piece(BLACK, v->castlingRookPiece)]); + rookPositionsStart[WHITE] = startBoard.get_squares_for_pieces(WHITE, v->castlingRookPieces[WHITE], v->pieceToChar); + rookPositionsStart[BLACK] = startBoard.get_squares_for_pieces(BLACK, v->castlingRookPieces[BLACK], v->pieceToChar); if (check_standard_castling(castlingInfoSplitted, board, kingPositions, kingPositionsStart, rookPositionsStart, v) == NOK) return FEN_INVALID_CASTLING_INFO; diff --git a/src/parser.cpp b/src/parser.cpp index 6ca9385..a4142cd 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -289,6 +289,8 @@ Variant* VariantParser::parse(Variant* v) { } parse_attribute("whiteFlag", v->flagRegion[WHITE]); parse_attribute("blackFlag", v->flagRegion[BLACK]); + parse_attribute("castlingRookPiece", v->castlingRookPieces[WHITE], v->pieceToChar); + parse_attribute("castlingRookPiece", v->castlingRookPieces[BLACK], v->pieceToChar); // Parse aliases parse_attribute("pawnTypes", v->promotionPawnType[WHITE], v->pieceToChar); @@ -372,8 +374,14 @@ Variant* VariantParser::parse(Variant* v) { parse_attribute("castlingQueensideFile", v->castlingQueensideFile); parse_attribute("castlingRank", v->castlingRank); parse_attribute("castlingKingFile", v->castlingKingFile); - parse_attribute("castlingKingPiece", v->castlingKingPiece, v->pieceToChar); - parse_attribute("castlingRookPiece", v->castlingRookPiece, v->pieceToChar); + parse_attribute("castlingKingPiece", v->castlingKingPiece[WHITE], v->pieceToChar); + parse_attribute("castlingKingPiece", v->castlingKingPiece[BLACK], v->pieceToChar); + parse_attribute("castlingKingPieceWhite", v->castlingKingPiece[WHITE], v->pieceToChar); + parse_attribute("castlingKingPieceBlack", v->castlingKingPiece[BLACK], v->pieceToChar); + parse_attribute("castlingRookPieces", v->castlingRookPieces[WHITE], v->pieceToChar); + parse_attribute("castlingRookPieces", v->castlingRookPieces[BLACK], v->pieceToChar); + parse_attribute("castlingRookPiecesWhite", v->castlingRookPieces[WHITE], v->pieceToChar); + parse_attribute("castlingRookPiecesBlack", v->castlingRookPieces[BLACK], v->pieceToChar); parse_attribute("checking", v->checking); parse_attribute("dropChecks", v->dropChecks); parse_attribute("mustCapture", v->mustCapture); diff --git a/src/position.cpp b/src/position.cpp index a934c6b..7daec3a 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -345,15 +345,14 @@ Position& Position::set(const Variant* v, const string& fenStr, bool isChess960, { Square rsq; Color c = islower(token) ? BLACK : WHITE; - Piece rook = make_piece(c, castling_rook_piece()); token = char(toupper(token)); if (token == 'K') - for (rsq = make_square(FILE_MAX, castling_rank(c)); piece_on(rsq) != rook; --rsq) {} + for (rsq = make_square(FILE_MAX, castling_rank(c)); !(castling_rook_pieces(c) & type_of(piece_on(rsq))) || color_of(piece_on(rsq)) != c; --rsq) {} else if (token == 'Q') - for (rsq = make_square(FILE_A, castling_rank(c)); piece_on(rsq) != rook; ++rsq) {} + for (rsq = make_square(FILE_A, castling_rank(c)); !(castling_rook_pieces(c) & type_of(piece_on(rsq))) || color_of(piece_on(rsq)) != c; ++rsq) {} else if (token >= 'A' && token <= 'A' + max_file()) rsq = make_square(File(token - 'A'), castling_rank(c)); @@ -364,10 +363,10 @@ Position& Position::set(const Variant* v, const string& fenStr, bool isChess960, // Determine castling "king" position if (castling_enabled() && st->castlingKingSquare[c] == SQ_NONE) { - Bitboard castlingKings = pieces(c, castling_king_piece()) & rank_bb(castling_rank(c)); + Bitboard castlingKings = pieces(c, castling_king_piece(c)) & rank_bb(castling_rank(c)); // Ambiguity resolution for 960 variants with more than one "king" // e.g., EAH means that an e-file king can castle with a- and h-file rooks - st->castlingKingSquare[c] = isChess960 && piece_on(rsq) == make_piece(c, castling_king_piece()) ? rsq + st->castlingKingSquare[c] = isChess960 && piece_on(rsq) == make_piece(c, castling_king_piece(c)) ? rsq : castlingKings && (!more_than_one(castlingKings) || isChess960) ? lsb(castlingKings) : make_square(castling_king_file(), castling_rank(c)); } @@ -384,18 +383,22 @@ Position& Position::set(const Variant* v, const string& fenStr, bool isChess960, continue; } - if (castling_enabled() && piece_on(rsq) == rook) + if (castling_enabled() && (castling_rook_pieces(c) & type_of(piece_on(rsq))) && color_of(piece_on(rsq)) == c) set_castling_right(c, rsq); } // Set castling rights for 960 gating variants if (gating() && castling_enabled()) for (Color c : {WHITE, BLACK}) - if ((gates(c) & pieces(castling_king_piece())) && !castling_rights(c) && (!seirawan_gating() || count_in_hand(c, ALL_PIECES) > 0 || captures_to_hand())) + if ((gates(c) & pieces(castling_king_piece(c))) && !castling_rights(c) && (!seirawan_gating() || count_in_hand(c, ALL_PIECES) > 0 || captures_to_hand())) { - Bitboard castling_rooks = gates(c) & pieces(castling_rook_piece()); + Bitboard castling_rooks = gates(c) & pieces(c); while (castling_rooks) - set_castling_right(c, pop_lsb(castling_rooks)); + { + Square s = pop_lsb(castling_rooks); + if (castling_rook_pieces(c) & type_of(piece_on(s))) + set_castling_right(c, s); + } } // counting limit @@ -741,7 +744,7 @@ string Position::fen(bool sfen, bool showPromoted, int countStarted, std::string ss << (sideToMove == WHITE ? " w " : " b "); // Disambiguation for chess960 "king" square - if (chess960 && can_castle(WHITE_CASTLING) && popcount(pieces(WHITE, castling_king_piece()) & rank_bb(castling_rank(WHITE))) > 1) + if (chess960 && can_castle(WHITE_CASTLING) && popcount(pieces(WHITE, castling_king_piece(WHITE)) & rank_bb(castling_rank(WHITE))) > 1) ss << char('A' + castling_king_square(WHITE)); if (can_castle(WHITE_OO)) @@ -760,7 +763,7 @@ string Position::fen(bool sfen, bool showPromoted, int countStarted, std::string ss << char('A' + f); // Disambiguation for chess960 "king" square - if (chess960 && can_castle(BLACK_CASTLING) && popcount(pieces(BLACK, castling_king_piece()) & rank_bb(castling_rank(BLACK))) > 1) + if (chess960 && can_castle(BLACK_CASTLING) && popcount(pieces(BLACK, castling_king_piece(BLACK)) & rank_bb(castling_rank(BLACK))) > 1) ss << char('a' + castling_king_square(BLACK)); if (can_castle(BLACK_OO)) @@ -1511,7 +1514,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { if (type_of(m) == CASTLING) { assert(type_of(pc) != NO_PIECE_TYPE); - assert(captured == make_piece(us, castling_rook_piece())); + assert(castling_rook_pieces(us) & type_of(captured)); Square rfrom, rto; do_castling(us, from, to, rfrom, rto); @@ -1666,19 +1669,23 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { // Set castling rights for dropped king or rook if (castling_dropped_piece() && rank_of(to) == castling_rank(us)) { - if (type_of(pc) == castling_king_piece() && file_of(to) == castling_king_file()) + if (type_of(pc) == castling_king_piece(us) && file_of(to) == castling_king_file()) { st->castlingKingSquare[us] = to; - Bitboard castling_rooks = pieces(us, castling_rook_piece()) + Bitboard castling_rooks = pieces(us) & rank_bb(castling_rank(us)) & (file_bb(FILE_A) | file_bb(max_file())); while (castling_rooks) - set_castling_right(us, pop_lsb(castling_rooks)); + { + Square s = pop_lsb(castling_rooks); + if (castling_rook_pieces(us) & type_of(piece_on(s))) + set_castling_right(us, s); + } } - else if (type_of(pc) == castling_rook_piece()) + else if (castling_rook_pieces(us) & type_of(pc)) { if ( (file_of(to) == FILE_A || file_of(to) == max_file()) - && piece_on(make_square(castling_king_file(), castling_rank(us))) == make_piece(us, castling_king_piece())) + && piece_on(make_square(castling_king_file(), castling_rank(us))) == make_piece(us, castling_king_piece(us))) { st->castlingKingSquare[us] = make_square(castling_king_file(), castling_rank(us)); set_castling_right(us, to); @@ -3062,7 +3069,7 @@ bool Position::pos_is_ok() const { if (!can_castle(cr)) continue; - if ( piece_on(castlingRookSquare[cr]) != make_piece(c, castling_rook_piece()) + if ( !(castling_rook_pieces(c) & type_of(piece_on(castlingRookSquare[cr]))) || castlingRightsMask[castlingRookSquare[cr]] != cr || (count(c) && (castlingRightsMask[square(c)] & cr) != cr)) assert(0 && "pos_is_ok: Castling"); diff --git a/src/position.h b/src/position.h index 2c2c94b..2fe0b7e 100644 --- a/src/position.h +++ b/src/position.h @@ -149,8 +149,8 @@ public: File castling_queenside_file() const; Rank castling_rank(Color c) const; File castling_king_file() const; - PieceType castling_king_piece() const; - PieceType castling_rook_piece() const; + PieceType castling_king_piece(Color c) const; + PieceSet castling_rook_pieces(Color c) const; PieceType king_type() const; PieceType nnue_king() const; Square nnue_king_square(Color c) const; @@ -529,14 +529,14 @@ inline File Position::castling_king_file() const { return var->castlingKingFile; } -inline PieceType Position::castling_king_piece() const { +inline PieceType Position::castling_king_piece(Color c) const { assert(var != nullptr); - return var->castlingKingPiece; + return var->castlingKingPiece[c]; } -inline PieceType Position::castling_rook_piece() const { +inline PieceSet Position::castling_rook_pieces(Color c) const { assert(var != nullptr); - return var->castlingRookPiece; + return var->castlingRookPieces[c]; } inline PieceType Position::king_type() const { diff --git a/src/variant.cpp b/src/variant.cpp index c29ce1e..ee9f67f 100644 --- a/src/variant.cpp +++ b/src/variant.cpp @@ -316,7 +316,7 @@ namespace { v->remove_piece(KNIGHT); v->add_piece(ROOKNI, 'r'); v->add_piece(KNIROO, 'n'); - v->castlingRookPiece = ROOKNI; + v->castlingRookPieces[WHITE] = v->castlingRookPieces[BLACK] = piece_set(ROOKNI); v->promotionPieceTypes[WHITE] = piece_set(QUEEN) | ROOKNI | BISHOP | KNIROO; v->promotionPieceTypes[BLACK] = piece_set(QUEEN) | ROOKNI | BISHOP | KNIROO; return v; @@ -352,7 +352,7 @@ namespace { v->remove_piece(KNIGHT); v->startFen = "rmbqkbmr/pppppppp/8/8/8/8/PPPPPPPP/RMBQKBMR w KQkq - 0 1"; v->kingType = KNIGHT; - v->castlingKingPiece = KING; + v->castlingKingPiece[WHITE] = v->castlingKingPiece[BLACK] = KING; v->promotionPieceTypes[WHITE] = piece_set(COMMONER) | QUEEN | ROOK | BISHOP; v->promotionPieceTypes[BLACK] = piece_set(COMMONER) | QUEEN | ROOK | BISHOP; return v; @@ -377,7 +377,7 @@ namespace { v->variantTemplate = "giveaway"; v->remove_piece(KING); v->add_piece(COMMONER, 'k'); - v->castlingKingPiece = COMMONER; + v->castlingKingPiece[WHITE] = v->castlingKingPiece[BLACK] = COMMONER; v->promotionPieceTypes[WHITE] = piece_set(COMMONER) | QUEEN | ROOK | BISHOP | KNIGHT; v->promotionPieceTypes[BLACK] = piece_set(COMMONER) | QUEEN | ROOK | BISHOP | KNIGHT; v->stalemateValue = VALUE_MATE; @@ -420,7 +420,7 @@ namespace { Variant* v = chess_variant_base()->init(); v->remove_piece(KING); v->add_piece(COMMONER, 'k'); - v->castlingKingPiece = COMMONER; + v->castlingKingPiece[WHITE] = v->castlingKingPiece[BLACK] = COMMONER; v->promotionPieceTypes[WHITE] = piece_set(COMMONER) | QUEEN | ROOK | BISHOP | KNIGHT; v->promotionPieceTypes[BLACK] = piece_set(COMMONER) | QUEEN | ROOK | BISHOP | KNIGHT; v->extinctionValue = -VALUE_MATE; @@ -442,7 +442,7 @@ namespace { Variant* v = chess_variant_base()->init(); v->remove_piece(KING); v->add_piece(COMMONER, 'k'); - v->castlingKingPiece = COMMONER; + v->castlingKingPiece[WHITE] = v->castlingKingPiece[BLACK] = COMMONER; v->startFen = "knbqkbnk/pppppppp/8/8/8/8/PPPPPPPP/KNBQKBNK w - - 0 1"; v->extinctionValue = -VALUE_MATE; v->extinctionPieceTypes = piece_set(COMMONER); @@ -467,7 +467,7 @@ namespace { v->variantTemplate = "atomic"; v->remove_piece(KING); v->add_piece(COMMONER, 'k'); - v->castlingKingPiece = COMMONER; + v->castlingKingPiece[WHITE] = v->castlingKingPiece[BLACK] = COMMONER; v->extinctionValue = -VALUE_MATE; v->extinctionPieceTypes = piece_set(COMMONER); v->blastOnCapture = true; @@ -487,7 +487,7 @@ namespace { Variant* v = chess_variant_base()->init(); v->remove_piece(KING); v->add_piece(COMMONER, 'k'); - v->castlingKingPiece = COMMONER; + v->castlingKingPiece[WHITE] = v->castlingKingPiece[BLACK] = COMMONER; v->extinctionValue = -VALUE_MATE; v->extinctionPieceTypes = piece_set(COMMONER); v->duckGating = true; @@ -603,7 +603,7 @@ namespace { Variant* v = bughouse_variant()->init(); v->remove_piece(KING); v->add_piece(COMMONER, 'k'); - v->castlingKingPiece = COMMONER; + v->castlingKingPiece[WHITE] = v->castlingKingPiece[BLACK] = COMMONER; v->mustDrop = true; v->mustDropType = COMMONER; v->extinctionValue = -VALUE_MATE; @@ -975,6 +975,19 @@ namespace { v->promotionPieceTypes[BLACK] = piece_set(QUEEN) | ROOK | BISHOP; return v; } + // Perfect chess + // https://www.chessvariants.com/diffmove.dir/perfectchess.html + Variant* perfect_variant() { + Variant* v = chess_variant_base()->init(); + v->add_piece(CHANCELLOR, 'c'); + v->add_piece(ARCHBISHOP, 'm'); + v->add_piece(AMAZON, 'g'); + v->startFen = "cmqgkbnr/pppppppp/8/8/8/8/PPPPPPPP/CMQGKBNR w KQkq - 0 1"; + v->promotionPieceTypes[WHITE] = piece_set(AMAZON) | CHANCELLOR | ARCHBISHOP | QUEEN | ROOK | BISHOP | KNIGHT; + v->promotionPieceTypes[BLACK] = piece_set(AMAZON) | CHANCELLOR | ARCHBISHOP | QUEEN | ROOK | BISHOP | KNIGHT; + v->castlingRookPieces[WHITE] = v->castlingRookPieces[BLACK] |= piece_set(CHANCELLOR); + return v; + } // Spartan chess // https://www.chessvariants.com/rules/spartan-chess Variant* spartan_variant() { @@ -1167,7 +1180,7 @@ namespace { v->capturesToHand = false; v->pieceDrops = false; v->promotedPieceType[CUSTOM_PIECES] = COMMONER; - v->castlingKingPiece = COMMONER; + v->castlingKingPiece[WHITE] = v->castlingKingPiece[BLACK] = COMMONER; v->extinctionValue = -VALUE_MATE; v->extinctionPieceTypes = piece_set(COMMONER); v->extinctionPseudoRoyal = true; @@ -1791,6 +1804,7 @@ void VariantMap::init() { add("gardner", gardner_variant()); add("almost", almost_variant()); add("chigorin", chigorin_variant()); + add("perfect", perfect_variant()); add("spartan", spartan_variant()); add("shatar", shatar_variant()); add("coregal", coregal_variant()); diff --git a/src/variant.h b/src/variant.h index 3af6138..d4e2745 100644 --- a/src/variant.h +++ b/src/variant.h @@ -76,8 +76,8 @@ struct Variant { File castlingQueensideFile = FILE_C; Rank castlingRank = RANK_1; File castlingKingFile = FILE_E; - PieceType castlingKingPiece = KING; - PieceType castlingRookPiece = ROOK; + PieceType castlingKingPiece[COLOR_NB] = {KING, KING}; + PieceSet castlingRookPieces[COLOR_NB] = {piece_set(ROOK), piece_set(ROOK)}; PieceType kingType = KING; bool checking = true; bool dropChecks = true; diff --git a/src/variants.ini b/src/variants.ini index 0bd032e..d5bda0d 100644 --- a/src/variants.ini +++ b/src/variants.ini @@ -183,7 +183,7 @@ # castlingRank: relative rank of castling [Rank] (default: 1) # castlingKingFile: starting file of the castlingKingPiece if there can be more than one of that type [File] (default: e) # castlingKingPiece: first piece type that participates in castling [PieceType] (default: k) -# castlingRookPiece: second piece type that participates in castling [PieceType] (default: r) +# castlingRookPieces: second piece type that participates in castling [PieceSet] (default: r) # checking: allow checks [bool] (default: true) # dropChecks: allow checks by piece drops [bool] (default: true) # mustCapture: captures are mandatory (check evasion still takes precedence) [bool] (default: false) diff --git a/tests/perft.sh b/tests/perft.sh index e11d01e..ec08d5f 100755 --- a/tests/perft.sh +++ b/tests/perft.sh @@ -61,6 +61,8 @@ if [[ $1 == "all" || $1 == "variant" ]]; then expect perft.exp sittuyin "fen 8/6s1/5P2/3n4/pR2K2S/1P6/1k4p1/8[] w - - 1 50" 4 268869 > /dev/null expect perft.exp sittuyin "fen 1k5K/3r2P1/8/8/8/8/8/8[] w - - 0 1" 5 68662 > /dev/null expect perft.exp chigorin "fen 8/7P/2k5/8/8/5K2/p7/8 w - - 0 1" 2 120 > /dev/null + expect perft.exp perfect startpos 3 15082 > /dev/null + expect perft.exp perfect "fen c3k2r/pppppppp/8/8/8/8/PPPPPPPP/C3K2R w KQkq - 0 1" 3 17500 > /dev/null expect perft.exp spartan startpos 3 14244 > /dev/null # duple check & mate expect perft.exp spartan "fen k6k/hh2Q2h/8/8/8/8/8/4K3 w - - 0 1" 3 6130 > /dev/null -- 1.7.0.4