#include "variant.h"
-namespace PSQT {
-void init(const Variant* v);
-}
-
enum Notation {
NOTATION_DEFAULT,
// https://en.wikipedia.org/wiki/Algebraic_notation_(chess)
return NOTATION_SAN;
}
+namespace SAN {
+
enum Disambiguation {
NO_DISAMBIGUATION,
FILE_DISAMBIGUATION,
}
std::string file(const Position& pos, Square s, Notation n) {
- switch (n) {
+ switch (n)
+ {
case NOTATION_SHOGI_HOSKING:
case NOTATION_SHOGI_HODGES:
case NOTATION_SHOGI_HODGES_NUMBER:
}
std::string rank(const Position& pos, Square s, Notation n) {
- switch (n) {
+ switch (n)
+ {
case NOTATION_SHOGI_HOSKING:
case NOTATION_SHOGI_HODGES_NUMBER:
return std::to_string(pos.max_rank() - rank_of(s) + 1);
}
std::string square(const Position& pos, Square s, Notation n) {
- switch (n) {
+ switch (n)
+ {
case NOTATION_JANGGI:
return rank(pos, s, n) + file(pos, s, n);
default:
return san;
}
-bool hasInsufficientMaterial(Color c, const Position& pos) {
+} // namespace SAN
+
+bool has_insufficient_material(Color c, const Position& pos) {
// Other win rules
if ( pos.captures_to_hand()
- || pos.count_in_hand(c, ALL_PIECES)
- || pos.extinction_value() != VALUE_NONE
- || (pos.capture_the_flag_piece() && pos.count(c, pos.capture_the_flag_piece())))
+ || pos.count_in_hand(c, ALL_PIECES)
+ || pos.extinction_value() != VALUE_NONE
+ || (pos.capture_the_flag_piece() && pos.count(c, pos.capture_the_flag_piece())))
return false;
// Restricted pieces
for (PieceType pt : { BISHOP, FERS, FERS_ALFIL, ALFIL, ELEPHANT })
colorbound |= pos.pieces(pt) & ~restricted;
unbound = pos.pieces() ^ restricted ^ colorbound;
- if ((colorbound & pos.pieces(c)) && (((DarkSquares & colorbound) && (~DarkSquares & colorbound)) || unbound))
+ if ((colorbound & pos.pieces(c)) && (((DarkSquares & colorbound) && (~DarkSquares & colorbound)) || unbound || pos.stalemate_value() != VALUE_DRAW || pos.check_counting()))
return false;
// Unbound pieces require one helper piece of either color
- if ((pos.pieces(c) & unbound) && (popcount(pos.pieces() ^ restricted) >= 2 || pos.stalemate_value() != VALUE_DRAW))
+ if ((pos.pieces(c) & unbound) && (popcount(pos.pieces() ^ restricted) >= 2 || pos.stalemate_value() != VALUE_DRAW || pos.check_counting()))
return false;
return true;
}
-namespace fen {
+namespace FEN {
enum FenValidation : int {
- FEN_MISSING_SPACE_DELIM = -12,
+ FEN_INVALID_COUNTING_RULE = -14,
+ FEN_INVALID_CHECK_COUNT = -13,
FEN_INVALID_NB_PARTS = -11,
FEN_INVALID_CHAR = -10,
FEN_TOUCHING_KINGS = -9,
/// Returns the square of a given character
CharSquare get_square_for_piece(char piece) const {
CharSquare s;
- for (int r = 0; r < nbRanks; ++r) {
- for (int c = 0; c < nbFiles; ++c) {
- if (get_piece(r, c) == piece) {
+ for (int r = 0; r < nbRanks; ++r)
+ {
+ for (int c = 0; c < nbFiles; ++c)
+ {
+ if (get_piece(r, c) == piece)
+ {
s.rowIdx = r;
s.fileIdx = c;
return s;
/// Returns all square positions for a given piece
std::vector<CharSquare> get_squares_for_piece(char piece) const {
std::vector<CharSquare> squares;
- for (int r = 0; r < nbRanks; ++r) {
- for (int c = 0; c < nbFiles; ++c) {
- if (get_piece(r, c) == piece) {
+ for (int r = 0; r < nbRanks; ++r)
+ for (int c = 0; c < nbFiles; ++c)
+ if (get_piece(r, c) == piece)
squares.emplace_back(CharSquare(r, c));
- }
- }
- }
return squares;
}
/// Checks if a given character is on a given rank index
};
std::ostream& operator<<(std::ostream& os, const CharBoard& board) {
- for (int r = 0; r < board.nbRanks; ++r) {
- for (int c = 0; c < board.nbFiles; ++c) {
+ for (int r = 0; r < board.nbRanks; ++r)
+ {
+ for (int c = 0; c < board.nbFiles; ++c)
os << "[" << board.get_piece(r, c) << "] ";
- }
os << std::endl;
}
return os;
}
Validation check_for_valid_characters(const std::string& firstFenPart, const std::string& validSpecialCharacters, const Variant* v) {
- for (char c : firstFenPart) {
- if (!isdigit(c) && v->pieceToChar.find(c) == std::string::npos && validSpecialCharacters.find(c) == std::string::npos) {
+ for (char c : firstFenPart)
+ {
+ if (!isdigit(c) && v->pieceToChar.find(c) == std::string::npos && v->pieceToCharSynonyms.find(c) == std::string::npos && validSpecialCharacters.find(c) == std::string::npos)
+ {
std::cerr << "Invalid piece character: '" << c << "'." << std::endl;
return NOK;
}
int fileIdx = 0;
char prevChar = '?';
- for (char c : fenBoard) {
+ for (char c : fenBoard)
+ {
if (c == ' ' || c == '[')
break;
- if (isdigit(c)) {
+ if (isdigit(c))
+ {
fileIdx += c - '0';
// if we have multiple digits attached we can add multiples of 9 to compute the resulting number (e.g. -> 21 = 2 + 2 * 9 + 1)
if (isdigit(prevChar))
fileIdx += 9 * (prevChar - '0');
}
- else if (c == '/') {
+ else if (c == '/')
+ {
++rankIdx;
- if (fileIdx != board.get_nb_files()) {
+ if (fileIdx != board.get_nb_files())
+ {
std::cerr << "curRankWidth != nbFiles: " << fileIdx << " != " << board.get_nb_files() << std::endl;
return NOK;
}
break;
fileIdx = 0;
}
- else if (validSpecialCharacters.find(c) == std::string::npos) { // normal piece
- if (fileIdx == board.get_nb_files()) {
+ else if (validSpecialCharacters.find(c) == std::string::npos)
+ { // normal piece
+ if (fileIdx == board.get_nb_files())
+ {
std::cerr << "File index: " << fileIdx << " for piece '" << c << "' exceeds maximum of allowed number of files: " << board.get_nb_files() << "." << std::endl;
return NOK;
}
prevChar = c;
}
- if (v->pieceDrops) { // pockets can either be defined by [] or /
- if (rankIdx+1 != board.get_nb_ranks() && rankIdx != board.get_nb_ranks()) {
+ if (v->pieceDrops)
+ { // pockets can either be defined by [] or /
+ if (rankIdx+1 != board.get_nb_ranks() && rankIdx != board.get_nb_ranks())
+ {
std::cerr << "Invalid number of ranks. Expected: " << board.get_nb_ranks() << " Actual: " << rankIdx+1 << std::endl;
return NOK;
}
}
- else {
- if (rankIdx+1 != board.get_nb_ranks()) {
+ else
+ {
+ if (rankIdx+1 != board.get_nb_ranks())
+ {
std::cerr << "Invalid number of ranks. Expected: " << board.get_nb_ranks() << " Actual: " << rankIdx+1 << std::endl;
return NOK;
}
return OK;
}
+Validation check_touching_kings(const CharBoard& board, const std::array<CharSquare, 2>& kingPositions) {
+ if (non_root_euclidian_distance(kingPositions[WHITE], kingPositions[BLACK]) <= 2)
+ {
+ std::cerr << "King pieces are next to each other." << std::endl;
+ std::cerr << board << std::endl;
+ return NOK;
+ }
+ return OK;
+}
+
Validation fill_castling_info_splitted(const std::string& castlingInfo, std::array<std::string, 2>& castlingInfoSplitted) {
- for (char c : castlingInfo) {
- if (c != '-') {
- if (!isalpha(c)) {
+ for (char c : castlingInfo)
+ {
+ if (c != '-')
+ {
+ if (!isalpha(c))
+ {
std::cerr << "Invalid castling specification: '" << c << "'." << std::endl;
return NOK;
}
}
std::string color_to_string(Color c) {
- switch (c) {
+ switch (c)
+ {
case WHITE:
return "WHITE";
case BLACK:
}
}
-Validation check_960_castling(const std::array<std::string, 2>& castlingInfoSplitted, const CharBoard& board, const std::array<CharSquare, 2>& kingPositionsStart) {
-
- for (Color color : {WHITE, BLACK}) {
- for (char charPiece : {'K', 'R'}) {
- if (castlingInfoSplitted[color].size() == 0)
- continue;
- const Rank rank = Rank(kingPositionsStart[color].rowIdx);
- if (color == BLACK)
- charPiece = tolower(charPiece);
- if (!board.is_piece_on_rank(charPiece, rank)) {
- std::cerr << "The " << color_to_string(color) << " king and rook must be on rank " << rank << " if castling is enabled for " << color_to_string(color) << "." << std::endl;
- return NOK;
- }
- }
- }
- return OK;
-}
-
std::string castling_rights_to_string(CastlingRights castlingRights) {
- switch (castlingRights) {
+ switch (castlingRights)
+ {
case KING_SIDE:
return "KING_SIDE";
case QUEEN_SIDE:
}
}
-Validation check_touching_kings(const CharBoard& board, const std::array<CharSquare, 2>& kingPositions) {
- if (non_root_euclidian_distance(kingPositions[WHITE], kingPositions[BLACK]) <= 2) {
- std::cerr << "King pieces are next to each other." << std::endl;
- std::cerr << board << std::endl;
- return NOK;
+Validation check_castling_rank(const std::array<std::string, 2>& castlingInfoSplitted, const CharBoard& board, const Variant* v) {
+
+ for (Color c : {WHITE, BLACK})
+ {
+ for (char charPiece : {v->pieceToChar[make_piece(c, v->castlingKingPiece)],
+ v->pieceToChar[make_piece(c, v->castlingRookPiece)]})
+ {
+ if (castlingInfoSplitted[c].size() == 0)
+ continue;
+ const Rank castlingRank = relative_rank(c, v->castlingRank, v->maxRank);
+ if (!board.is_piece_on_rank(charPiece, castlingRank))
+ {
+ std::cerr << "The " << color_to_string(c) << " king and rook must be on rank " << castlingRank << " if castling is enabled for " << color_to_string(c) << "." << std::endl;
+ return NOK;
+ }
+ }
}
return OK;
}
Validation check_standard_castling(std::array<std::string, 2>& castlingInfoSplitted, const CharBoard& board,
const std::array<CharSquare, 2>& kingPositions, const std::array<CharSquare, 2>& kingPositionsStart,
- const std::array<std::vector<CharSquare>, 2>& rookPositionsStart) {
+ const std::array<std::vector<CharSquare>, 2>& rookPositionsStart, const Variant* v) {
- for (Color c : {WHITE, BLACK}) {
+ for (Color c : {WHITE, BLACK})
+ {
if (castlingInfoSplitted[c].size() == 0)
continue;
- if (kingPositions[c] != kingPositionsStart[c]) {
+ if (kingPositions[c] != kingPositionsStart[c])
+ {
std::cerr << "The " << color_to_string(c) << " KING has moved. Castling is no longer valid for " << color_to_string(c) << "." << std::endl;
return NOK;
}
- for (CastlingRights castling: {KING_SIDE, QUEEN_SIDE}) {
+ for (CastlingRights castling: {KING_SIDE, QUEEN_SIDE})
+ {
CharSquare rookStartingSquare = castling == QUEEN_SIDE ? rookPositionsStart[c][0] : rookPositionsStart[c][1];
char targetChar = castling == QUEEN_SIDE ? 'q' : 'k';
- char rookChar = 'R'; // we don't use v->pieceToChar[ROOK]; here because in the newzealand_variant the ROOK is replaced by ROOKNI
- if (c == BLACK)
- rookChar = tolower(rookChar);
- if (castlingInfoSplitted[c].find(targetChar) != std::string::npos) {
- if (board.get_piece(rookStartingSquare.rowIdx, rookStartingSquare.fileIdx) != rookChar) {
+ char rookChar = v->pieceToChar[make_piece(c, v->castlingRookPiece)];
+ if (castlingInfoSplitted[c].find(targetChar) != std::string::npos)
+ {
+ if (board.get_piece(rookStartingSquare.rowIdx, rookStartingSquare.fileIdx) != rookChar)
+ {
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;
return NOK;
return OK;
}
-Validation check_pocket_info(const std::string& fenBoard, int nbRanks, const Variant* v, std::array<std::string, 2>& pockets) {
+Validation check_pocket_info(const std::string& fenBoard, int nbRanks, const Variant* v, std::string& pocket) {
char stopChar;
int offset = 0;
- if (std::count(fenBoard.begin(), fenBoard.end(), '/') == nbRanks) {
+ if (std::count(fenBoard.begin(), fenBoard.end(), '/') == nbRanks)
+ {
// look for last '/'
stopChar = '/';
}
- else {
+ else
+ {
// pocket is defined as [ and ]
stopChar = '[';
offset = 1;
- if (*(fenBoard.end()-1) != ']') {
+ if (*(fenBoard.end()-1) != ']')
+ {
std::cerr << "Pocket specification does not end with ']'." << std::endl;
return NOK;
}
}
// look for last '/'
- for (auto it = fenBoard.rbegin()+offset; it != fenBoard.rend(); ++it) {
+ for (auto it = fenBoard.rbegin()+offset; it != fenBoard.rend(); ++it)
+ {
const char c = *it;
if (c == stopChar)
return OK;
- if (c != '-') {
- if (v->pieceToChar.find(c) == std::string::npos) {
+ if (c != '-')
+ {
+ if (v->pieceToChar.find(c) == std::string::npos && v->pieceToCharSynonyms.find(c) == std::string::npos)
+ {
std::cerr << "Invalid pocket piece: '" << c << "'." << std::endl;
return NOK;
}
- else {
- if (isupper(c))
- pockets[WHITE] += tolower(c);
- else
- pockets[BLACK] += c;
- }
+ else
+ pocket += c;
}
}
std::cerr << "Pocket piece closing character '" << stopChar << "' was not found." << std::endl;
return NOK;
}
-Validation check_number_of_kings(const std::string& fenBoard, const Variant* v) {
- int nbWhiteKings = std::count(fenBoard.begin(), fenBoard.end(), toupper(v->pieceToChar[KING]));
- int nbBlackKings = std::count(fenBoard.begin(), fenBoard.end(), tolower(v->pieceToChar[KING]));
+int piece_count(const std::string& fenBoard, Color c, PieceType pt, const Variant* v) {
+ return std::count(fenBoard.begin(), fenBoard.end(), v->pieceToChar[make_piece(c, pt)]);
+}
+
+Validation check_number_of_kings(const std::string& fenBoard, const std::string& startFenBoard, const Variant* v) {
+ int nbWhiteKings = piece_count(fenBoard, WHITE, KING, v);
+ int nbBlackKings = piece_count(fenBoard, BLACK, KING, v);
+ int nbWhiteKingsStart = piece_count(startFenBoard, WHITE, KING, v);
+ int nbBlackKingsStart = piece_count(startFenBoard, BLACK, KING, v);
- if (nbWhiteKings != 1) {
- std::cerr << "Invalid number of white kings. Expected: 1. Given: " << nbWhiteKings << std::endl;
+ if (nbWhiteKings != nbWhiteKingsStart)
+ {
+ std::cerr << "Invalid number of white kings. Expected: " << nbWhiteKingsStart << ". Given: " << nbWhiteKings << std::endl;
return NOK;
}
- if (nbBlackKings != 1) {
- std::cerr << "Invalid number of black kings. Expected: 1. Given: " << nbBlackKings << std::endl;
+ if (nbBlackKings != nbBlackKingsStart)
+ {
+ std::cerr << "Invalid number of black kings. Expected: " << nbBlackKingsStart << ". Given: " << nbBlackKings << std::endl;
return NOK;
}
return OK;
}
+
Validation check_en_passant_square(const std::string& enPassantInfo) {
- const char firstChar = enPassantInfo[0];
- if (firstChar != '-') {
- if (enPassantInfo.size() != 2) {
+ if (enPassantInfo.size() != 1 || enPassantInfo[0] != '-')
+ {
+ if (enPassantInfo.size() != 2)
+ {
std::cerr << "Invalid en-passant square '" << enPassantInfo << "'. Expects 2 characters. Actual: " << enPassantInfo.size() << " character(s)." << std::endl;
return NOK;
}
- if (isdigit(firstChar)) {
- std::cerr << "Invalid en-passant square '" << enPassantInfo << "'. Expects 1st character to be a digit." << std::endl;
+ if (!isalpha(enPassantInfo[0]))
+ {
+ std::cerr << "Invalid en-passant square '" << enPassantInfo << "'. Expects 1st character to be a letter." << std::endl;
return NOK;
}
- const char secondChar = enPassantInfo[1];
- if (!isdigit(secondChar)) {
- std::cerr << "Invalid en-passant square '" << enPassantInfo << "'. Expects 2nd character to be a non-digit." << std::endl;
+ if (!isdigit(enPassantInfo[1]))
+ {
+ std::cerr << "Invalid en-passant square '" << enPassantInfo << "'. Expects 2nd character to be a digit." << std::endl;
return NOK;
}
}
return OK;
}
-bool no_king_piece_in_pockets(const std::array<std::string, 2>& pockets) {
- return pockets[WHITE].find('k') == std::string::npos && pockets[BLACK].find('k') == std::string::npos;
+
+Validation check_check_count(const std::string& checkCountInfo) {
+ if (checkCountInfo.size() != 3)
+ {
+ std::cerr << "Invalid check count '" << checkCountInfo << "'. Expects 3 characters. Actual: " << checkCountInfo.size() << " character(s)." << std::endl;
+ return NOK;
+ }
+ if (!isdigit(checkCountInfo[0]))
+ {
+ std::cerr << "Invalid check count '" << checkCountInfo << "'. Expects 1st character to be a digit." << std::endl;
+ return NOK;
+ }
+ if (!isdigit(checkCountInfo[2])) {
+ std::cerr << "Invalid check count '" << checkCountInfo << "'. Expects 3rd character to be a digit." << std::endl;
+ return NOK;
+ }
+ return OK;
}
-Validation check_digit_field(const std::string& field)
-{
- if (field.size() == 1 && field[0] == '-') {
+
+Validation check_digit_field(const std::string& field) {
+ if (field.size() == 1 && field[0] == '-')
return OK;
- }
for (char c : field)
- if (!isdigit(c)) {
+ if (!isdigit(c))
return NOK;
- }
return OK;
}
-FenValidation validate_fen(const std::string& fen, const Variant* v) {
+FenValidation validate_fen(const std::string& fen, const Variant* v, bool chess960 = false) {
const std::string validSpecialCharacters = "/+~[]-";
// 0) Layout
// check for empty fen
- if (fen.size() == 0) {
+ if (fen.size() == 0)
+ {
std::cerr << "Fen is empty." << std::endl;
return FEN_EMPTY;
}
- // check for space
- if (fen.find(' ') == std::string::npos) {
- std::cerr << "Fen misses space as delimiter." << std::endl;
- return FEN_MISSING_SPACE_DELIM;
- }
-
std::vector<std::string> fenParts = get_fen_parts(fen, ' ');
std::vector<std::string> starFenParts = get_fen_parts(v->startFen, ' ');
- const unsigned int nbFenParts = starFenParts.size();
- // check for number of parts (also up to two additional "-" for non-existing no-progress counter or castling rights)
- const unsigned int maxNumberFenParts = 7U;
- const unsigned int topThreshold = std::min(nbFenParts + 2, maxNumberFenParts);
- if (fenParts.size() < nbFenParts || fenParts.size() > topThreshold) {
- std::cerr << "Invalid number of fen parts. Expected: >= " << nbFenParts << " and <= " << topThreshold
+ // check for number of parts
+ const unsigned int maxNumberFenParts = 6 + v->checkCounting;
+ if (fenParts.size() < 1 || fenParts.size() > maxNumberFenParts)
+ {
+ std::cerr << "Invalid number of fen parts. Expected: >= 1 and <= " << maxNumberFenParts
<< " Actual: " << fenParts.size() << std::endl;
return FEN_INVALID_NB_PARTS;
}
// 1) Part
// check for valid characters
- if (check_for_valid_characters(fenParts[0], validSpecialCharacters, v) == NOK) {
+ if (check_for_valid_characters(fenParts[0], validSpecialCharacters, v) == NOK)
return FEN_INVALID_CHAR;
- }
// check for number of ranks
const int nbRanks = v->maxRank + 1;
return FEN_INVALID_BOARD_GEOMETRY;
// check for pocket
- std::array<std::string, 2> pockets;
- if (v->pieceDrops) {
- if (check_pocket_info(fenParts[0], nbRanks, v, pockets) == NOK)
+ std::string pocket = "";
+ if (v->pieceDrops || v->seirawanGating || v->arrowGating)
+ {
+ if (check_pocket_info(fenParts[0], nbRanks, v, pocket) == NOK)
return FEN_INVALID_POCKET_INFO;
}
- // check for number of kings (skip all extinction variants for this check (e.g. horde is a sepcial case where only one side has a royal king))
- if (v->pieceTypes.find(KING) != v->pieceTypes.end() && v->extinctionPieceTypes.size() == 0) {
- // we have a royal king in this variant, ensure that each side has exactly one king
+ // check for number of kings
+ if (v->pieceTypes.find(KING) != v->pieceTypes.end())
+ {
+ // we have a royal king in this variant,
+ // ensure that each side has exactly as many kings as in the starting position
// (variants like giveaway use the COMMONER piece type instead)
- if (check_number_of_kings(fenParts[0], v) == NOK)
+ if (check_number_of_kings(fenParts[0], starFenParts[0], v) == NOK)
return FEN_INVALID_NUMBER_OF_KINGS;
- // if kings are still in pockets skip this check (e.g. placement_variant)
- if (no_king_piece_in_pockets(pockets)) {
- // check if kings are touching
+ // check for touching kings if there are exactly two royal kings on the board (excluding pocket)
+ if ( v->kingType == KING
+ && piece_count(fenParts[0], WHITE, KING, v) - piece_count(pocket, WHITE, KING, v) == 1
+ && piece_count(fenParts[0], BLACK, KING, v) - piece_count(pocket, BLACK, KING, v) == 1)
+ {
std::array<CharSquare, 2> kingPositions;
- // check if kings are touching
- kingPositions[WHITE] = board.get_square_for_piece(toupper(v->pieceToChar[KING]));
- kingPositions[BLACK] = board.get_square_for_piece(tolower(v->pieceToChar[KING]));
+ kingPositions[WHITE] = board.get_square_for_piece(v->pieceToChar[make_piece(WHITE, KING)]);
+ kingPositions[BLACK] = board.get_square_for_piece(v->pieceToChar[make_piece(BLACK, KING)]);
if (check_touching_kings(board, kingPositions) == NOK)
return FEN_TOUCHING_KINGS;
-
- // 3) Part
- // check castling rights
- if (v->castling) {
- std::array<std::string, 2> castlingInfoSplitted;
- if (fill_castling_info_splitted(fenParts[2], castlingInfoSplitted) == NOK)
- return FEN_INVALID_CASTLING_INFO;
-
- if (castlingInfoSplitted[WHITE].size() != 0 || castlingInfoSplitted[BLACK].size() != 0) {
-
- CharBoard startBoard(board.get_nb_ranks(), board.get_nb_files());
- fill_char_board(startBoard, v->startFen, validSpecialCharacters, v);
- std::array<CharSquare, 2> kingPositionsStart;
- kingPositionsStart[WHITE] = startBoard.get_square_for_piece(toupper(v->pieceToChar[KING]));
- kingPositionsStart[BLACK] = startBoard.get_square_for_piece(tolower(v->pieceToChar[KING]));
-
- if (v->chess960) {
- if (check_960_castling(castlingInfoSplitted, board, kingPositionsStart) == NOK)
- return FEN_INVALID_CASTLING_INFO;
- }
- else {
- std::array<std::vector<CharSquare>, 2> rookPositionsStart;
- // we don't use v->pieceToChar[ROOK]; here because in the newzealand_variant the ROOK is replaced by ROOKNI
- rookPositionsStart[WHITE] = startBoard.get_squares_for_piece('R');
- rookPositionsStart[BLACK] = startBoard.get_squares_for_piece('r');
-
- if (check_standard_castling(castlingInfoSplitted, board, kingPositions, kingPositionsStart, rookPositionsStart) == NOK)
- return FEN_INVALID_CASTLING_INFO;
- }
- }
-
- }
}
}
// 2) Part
// check side to move char
- if (fenParts[1][0] != 'w' && fenParts[1][0] != 'b') {
+ if (fenParts.size() >= 2 && fenParts[1][0] != 'w' && fenParts[1][0] != 'b')
+ {
std::cerr << "Invalid side to move specification: '" << fenParts[1][0] << "'." << std::endl;
return FEN_INVALID_SIDE_TO_MOVE;
}
+ // Castling and en passant can be skipped
+ bool skipCastlingAndEp = fenParts.size() >= 4 && fenParts.size() <= 5 && isdigit(fenParts[2][0]);
+
+ // 3) Part
+ // check castling rights
+ if (fenParts.size() >= 3 && !skipCastlingAndEp && v->castling)
+ {
+ std::array<std::string, 2> castlingInfoSplitted;
+ if (fill_castling_info_splitted(fenParts[2], castlingInfoSplitted) == NOK)
+ return FEN_INVALID_CASTLING_INFO;
+
+ if (castlingInfoSplitted[WHITE].size() != 0 || castlingInfoSplitted[BLACK].size() != 0)
+ {
+ std::array<CharSquare, 2> 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]));
+
+ CharBoard startBoard(board.get_nb_ranks(), board.get_nb_files());
+ fill_char_board(startBoard, v->startFen, validSpecialCharacters, v);
+
+ // skip check for gating variants to avoid confusion with gating squares
+ if (!v->gating && check_castling_rank(castlingInfoSplitted, board, v) == NOK)
+ return FEN_INVALID_CASTLING_INFO;
+
+ // only check exact squares if starting position of castling pieces is known
+ if (!v->chess960 && !v->castlingDroppedPiece && !chess960)
+ {
+ std::array<CharSquare, 2> 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)]);
+ std::array<std::vector<CharSquare>, 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)]);
+
+ if (check_standard_castling(castlingInfoSplitted, board, kingPositions, kingPositionsStart, rookPositionsStart, v) == NOK)
+ return FEN_INVALID_CASTLING_INFO;
+ }
+ }
+ }
+
// 4) Part
// check en-passant square
- if (v->doubleStep && v->pieceTypes.find(PAWN) != v->pieceTypes.end()) {
- if (check_en_passant_square(fenParts[3]) == NOK)
- return FEN_INVALID_EN_PASSANT_SQ;
+ if (fenParts.size() >= 4 && !skipCastlingAndEp)
+ {
+ if (v->doubleStep && v->pieceTypes.find(PAWN) != v->pieceTypes.end())
+ {
+ if (check_en_passant_square(fenParts[3]) == NOK)
+ return FEN_INVALID_EN_PASSANT_SQ;
+ }
+ else if (v->countingRule && !check_digit_field(fenParts[3]))
+ return FEN_INVALID_COUNTING_RULE;
}
// 5) Part
- // checkCounting is skipped because if only one check is required to win it must not be part of the FEN (e.g. karouk_variant)
+ // check check count
+ unsigned int optionalFields = 2 * !skipCastlingAndEp;
+ if (fenParts.size() >= 3 + optionalFields && v->checkCounting && fenParts.size() % 2)
+ {
+ if (check_check_count(fenParts[2 + optionalFields]) == NOK)
+ return FEN_INVALID_CHECK_COUNT;
+ optionalFields++;
+ }
// 6) Part
// check half move counter
- if (!check_digit_field(fenParts[fenParts.size()-2])) {
+ if (fenParts.size() >= 3 + optionalFields && !check_digit_field(fenParts[fenParts.size()-2]))
+ {
std::cerr << "Invalid half move counter: '" << fenParts[fenParts.size()-2] << "'." << std::endl;
return FEN_INVALID_HALF_MOVE_COUNTER;
}
// 7) Part
// check move counter
- if (!check_digit_field(fenParts[fenParts.size()-1])) {
+ if (fenParts.size() >= 4 + optionalFields && !check_digit_field(fenParts[fenParts.size()-1]))
+ {
std::cerr << "Invalid move counter: '" << fenParts[fenParts.size()-1] << "'." << std::endl;
return FEN_INVALID_MOVE_COUNTER;
}
return FEN_OK;
}
-}
+} // namespace FEN
GRAND = "r8r/1nbqkcabn1/pppppppppp/10/10/10/10/PPPPPPPPPP/1NBQKCABN1/R8R w - - 0 1"
GRANDHOUSE = "r8r/1nbqkcabn1/pppppppppp/10/10/10/10/PPPPPPPPPP/1NBQKCABN1/R8R[] w - - 0 1"
XIANGQI = "rnbakabnr/9/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/9/RNBAKABNR w - - 0 1"
-SHOGUN = "rnb+fkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNB+FKBNR w KQkq - 0 1"
+SHOGUN = "rnb+fkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNB+FKBNR[] w KQkq - 0 1"
JANGGI = "rnba1abnr/4k4/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/4K4/RNBA1ABNR w - - 0 1"
# Shogun chess
[shogun:crazyhouse]
-startFen = rnb+fkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNB+FKBNR w KQkq - 0 1
+startFen = rnb+fkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNB+FKBNR[] w KQkq - 0 1
commoner = c
centaur = g
archbishop = a
variant_positions = {
"chess": {
+ "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1": (False, False), # startpos
+ "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq -": (False, False), # startpos
+ "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR": (False, False), # startpos
+ "rnbqkbnr/ppp2ppp/4p3/3pP3/8/8/PPPP1PPP/RNBQKBNR w KQkq d6 0 3": (False, False),
"k7/8/8/8/8/8/8/K7 w - - 0 1": (True, True), # K vs K
"k7/b7/8/8/8/8/8/K7 w - - 0 1": (True, True), # K vs KB
"k7/n7/8/8/8/8/8/K7 w - - 0 1": (True, True), # K vs KN
"kb6/8/8/8/8/8/8/KB7 w - - 0 1": (False, False), # KB vs KB opp color
"8/8/8/8/8/6KN/8/6nk w - - 0 1": (False, False), # KN vs KN
},
+ "atomic": {
+ "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1": (False, False), # startpos
+ },
+ "3check": {
+ "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 3+3 0 1": (False, False), # startpos
+ "k7/n7/8/8/8/8/8/K7 w - - 1+2 0 1": (True, False), # K vs KN
+ "k7/b7/8/8/8/8/8/K7 w - - 3+1 0 1": (True, False), # K vs KB
+ },
+ "horde": {
+ "rnbqkbnr/pppppppp/8/1PP2PP1/PPPPPPPP/PPPPPPPP/PPPPPPPP/PPPPPPPP w kq - 0 1": (False, False), # startpos
+ },
+ "racingkings": {
+ "8/8/8/8/8/8/krbnNBRK/qrbnNBRQ w - - 0 1": (False, False), # startpos
+ "8/8/8/8/8/8/K6k/8 w - - 0 1": (False, False), # KvK
+ },
+ "placement": {
+ "8/pppppppp/8/8/8/8/PPPPPPPP/8[KQRRBBNNkqrrbbnn] w - - 0 1": (False, False), # startpos
+ "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR[] w KQkq - 0 1": (False, False), # chess startpos
+ "k7/8/8/8/8/8/8/K7[] w - - 0 1": (True, True), # K vs K
+ },
+ "newzealand": {
+ "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1": (False, False), # startpos
+ },
"seirawan": {
+ "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR[HEhe] w KQBCDFGkqbcdfg - 0 1": (False, False), # startpos
"k7/8/8/8/8/8/8/K7[] w - - 0 1": (True, True), # K vs K
"k7/8/8/8/8/8/8/KH6[] w - - 0 1": (False, True), # KH vs K
"k7/8/8/8/8/8/8/4K3[E] w E - 0 1": (False, True), # KE vs K
},
+ "cambodian": {
+ "rnsmksnr/8/pppppppp/8/8/PPPPPPPP/8/RNSKMSNR w DEde 0 0 1": (False, False), # startpos
+ "1ns1ksn1/r6r/pppmpppp/3p4/8/PPPPPPPP/RK2N2R/1NS1MS2 w Ee - 6 5": (False, False),
+ },
"sittuyin": {
- "8/8/4pppp/pppp4/4PPPP/PPPP4/8/8[KFRRSSNNkfrrssnn] w - - 0 1": (False, False), # starting position
+ "8/8/4pppp/pppp4/4PPPP/PPPP4/8/8[KFRRSSNNkfrrssnn] w - - 0 1": (False, False), # startpos
"k7/8/8/8/8/8/8/K7[] w - - 0 1": (True, True), # K vs K
"k6P/8/8/8/8/8/8/K7[] w - - 0 1": (True, True), # KP vs K
"k6P/8/8/8/8/8/8/K6p[] w - - 0 1": (False, False), # KP vs KP
"k7/8/8/8/8/8/8/KS6[] w - - 0 1": (False, True), # KS vs K
},
"xiangqi": {
- "rnbakabnr/9/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/9/RNBAKABNR w - - 0 1": (False, False), # starting position
+ "rnbakabnr/9/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/9/RNBAKABNR w - - 0 1": (False, False), # startpos
"5k3/4a4/3CN4/9/1PP5p/9/8P/4C4/4A4/2B1K4 w - - 0 46": (False, False), # issue #53
"4k4/9/9/9/9/9/9/9/9/4K4 w - - 0 1": (True, True), # K vs K
"4k4/9/9/4p4/9/9/9/9/9/4KR3 w - - 0 1": (False, False), # KR vs KP
"4k4/9/9/9/9/9/9/9/4A4/4KC3 w - - 0 1": (False, True), # KCA vs K
},
"janggi": {
- JANGGI: (False, False), # starting position
+ JANGGI: (False, False), # startpos
+ "rhea1aehr/4k4/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/4K4/RHEA1AEHR w - - 0 1": (False, False), # startpos
"5k3/4a4/3CN4/9/1PP5p/9/8P/4C4/4A4/2B1K4 w - - 0 46": (False, False), # issue #53
"4k4/9/9/9/9/4B4/4B4/9/9/4K4 w - - 0 1": (False, True), # KEE vs K
"4k4/9/9/9/9/9/9/9/4A4/4KC3 w - - 0 1": (False, True), # KCA vs K
},
}
+invalid_variant_positions = {
+ "chess": (
+ "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 a", # invalid full move
+ "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - b 1", # invalid half move
+ "rnbqkbnr/ppp2ppp/4p3/3pP3/8/8/PPPP1PPP/RNBQKBNR w KQkq -6 0 3", # invalid en passant
+ "rnbqkbnr/ppp2ppp/4p3/3pP3/8/8/PPPP1PPP/RNBQKBNR w KQkq .6 0 3", # invalid en passant
+ "rnbqkbnr/ppp2ppp/4p3/3pP3/8/8/PPPP1PPP/RNBQKBNR w KQkq d- 0 3", # invalid en passant
+ "rnbqkbnr/ppp2ppp/4p3/3pP3/8/8/PPPP1PPP/RNBQKBNR w KQkq 0 3", # invalid/missing en passant
+ "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w 123 - 0 1", # invalid castling
+ "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR g KQkq - 0 1", # invalid side to move
+ "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNH w KQkq - 0 1", # invalid piece type
+ "rnbqkbnr/pppppppp/7/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1", # invalid file count
+ "rnbqkbnr/pppppppp/9/7/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1", # invalid file count
+ "rnbqkbnr/pppppppp/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1", # invalid rank count
+ "1nbqkbn1/pppppppp/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1", # missing castling rook
+ "rnbqkbnr/pppppppp/8/8/4K3/PPPPPPPP/RNBQ1BNR w KQkq - 0 1", # king not on castling rank
+ "rnbqkbnr/pppppppp/8/8/RNBQKBNR/PPPPPPPP/8 w KQkq - 0 1", # not on castling rank
+ "8/pppppppp/rnbqkbnr/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1", # not on castling rank
+ ),
+ "atomic": (
+ "rnbqkbnr/pppppppp/8/8/8/RNBQKBNR/PPPPPPPP/8 w KQkq - 0 1", # wrong castling rank
+ ),
+ "3check": (
+ "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 3+a 0 1", # invalid check count
+ ),
+ "horde": (
+ "rnbqkbnr/pppppppp/8/1PP2PP1/PPPPPPPP/PPPPPPPP/PPPPPPPP/PPPPPPPK w kq - 0 1", # wrong king count
+ "rnbq1bnr/pppppppp/8/1PP2PP1/PPPPPPPP/PPPPPPPP/PPPPPPPP/PPPPPPPP w kq - 0 1", # wrong king count
+ ),
+ "sittuyin": (
+ "8/8/4pppp/pppp4/4PPPP/PPPP4/8/8[FRRSSNNkfrrssnn] w - - 0 1", # wrong king count
+ ),
+ "shako": {
+ "c8c/ernbqkbnre/pppppppppp/10/10/10/10/PPPPPPPPPP/C8C/ERNBQKBNRE w KQkq - 0 1", # not on castling rank
+ }
+}
+
class TestPyffish(unittest.TestCase):
def test_version(self):
result = sf.info()
self.assertTrue(result.startswith("Fairy-Stockfish"))
+ def test_variants_loaded(self):
+ variants = sf.variants()
+ self.assertTrue("shogun" in variants)
+
def test_set_option(self):
result = sf.set_option("UCI_Variant", "capablanca")
self.assertIsNone(result)
result = sf.get_fen("chess", CHESS, [])
self.assertEqual(result, CHESS)
+ # incomplete FENs
+ result = sf.get_fen("chess", "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR", [])
+ self.assertEqual(result, "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w - - 0 1")
+ result = sf.get_fen("chess", "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq -", [])
+ self.assertEqual(result, "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1")
+ result = sf.get_fen("chess", "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w 1 2", [])
+ self.assertEqual(result, "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w - - 1 2")
+
+ # alternative piece symbols
+ result = sf.get_fen("janggi", "rhea1aehr/4k4/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/4K4/RHEA1AEHR w - - 0 1", [])
+ self.assertEqual(result, JANGGI)
+
result = sf.get_fen("capablanca", CAPA, [])
self.assertEqual(result, CAPA)
self.assertEqual(result, expected_result, "{}: {}".format(variant, fen))
def test_validate_fen(self):
+ # valid
for variant, positions in variant_positions.items():
for fen in positions:
- self.assertTrue(sf.validate_fen(fen, variant) == 1, "{}: {}".format(variant, fen))
-
+ self.assertEqual(sf.validate_fen(fen, variant), sf.FEN_OK, "{}: {}".format(variant, fen))
+ # invalid
+ for variant, positions in invalid_variant_positions.items():
+ for fen in positions:
+ self.assertNotEqual(sf.validate_fen(fen, variant), sf.FEN_OK, "{}: {}".format(variant, fen))
+ # chess960
+ self.assertEqual(sf.validate_fen(CHESS960, "chess", True), sf.FEN_OK, "{}: {}".format(variant, fen))
+ self.assertEqual(sf.validate_fen("nrbqbkrn/pppppppp/8/8/8/8/PPPPPPPP/NRBQBKRN w BGbg - 0 1", "newzealand", True), sf.FEN_OK, "{}: {}".format(variant, fen))
+ # all variants starting positions
+ for variant in sf.variants():
+ fen = sf.start_fen(variant)
+ self.assertEqual(sf.validate_fen(fen, variant), sf.FEN_OK, "{}: {}".format(variant, fen))
if __name__ == '__main__':
unittest.main(verbosity=2)