You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
+#include <vector>
+#include <string>
+#include <sstream>
+#include <cctype>
+#include <iostream>
+#include <math.h>
+
+#include "types.h"
+#include "variant.h"
+
namespace PSQT {
- void init(const Variant* v);
+void init(const Variant* v);
}
enum Notation {
{
Square s = pop_lsb(&b);
if ( pos.pseudo_legal(make_move(s, to))
- && pos.legal(make_move(s, to))
- && !(is_shogi(n) && pos.unpromoted_piece_on(s) != pos.unpromoted_piece_on(from)))
+ && pos.legal(make_move(s, to))
+ && !(is_shogi(n) && pos.unpromoted_piece_on(s) != pos.unpromoted_piece_on(from)))
others |= s;
}
// 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
return true;
}
+
+namespace fen {
+
+enum FenValidation : int {
+ FEN_MISSING_SPACE_DELIM = -12,
+ FEN_INVALID_NB_PARTS = -11,
+ FEN_INVALID_CHAR = -10,
+ FEN_TOUCHING_KINGS = -9,
+ FEN_INVALID_BOARD_GEOMETRY = -8,
+ FEN_INVALID_POCKET_INFO = -7,
+ FEN_INVALID_SIDE_TO_MOVE = -6,
+ FEN_INVALID_CASTLING_INFO = -5,
+ FEN_INVALID_EN_PASSANT_SQ = -4,
+ FEN_INVALID_NUMBER_OF_KINGS = -3,
+ FEN_INVALID_HALF_MOVE_COUNTER = -2,
+ FEN_INVALID_MOVE_COUNTER = -1,
+ FEN_EMPTY = 0,
+ FEN_OK = 1
+};
+enum Validation : int {
+ NOK,
+ OK
+};
+
+struct CharSquare {
+ int rowIdx;
+ int fileIdx;
+ CharSquare() : rowIdx(-1), fileIdx(-1) {}
+ CharSquare(int rowIdx, int fileIdx) : rowIdx(rowIdx), fileIdx(fileIdx) {}
+};
+
+bool operator==(const CharSquare& s1, const CharSquare& s2) {
+ return s1.rowIdx == s2.rowIdx && s1.fileIdx == s2.fileIdx;
+}
+
+bool operator!=(const CharSquare& s1, const CharSquare& s2) {
+ return !(s1 == s2);
+}
+
+int non_root_euclidian_distance(const CharSquare& s1, const CharSquare& s2) {
+ return pow(s1.rowIdx - s2.rowIdx, 2) + pow(s1.fileIdx - s2.fileIdx, 2);
+}
+
+class CharBoard {
+private:
+ int nbRanks;
+ int nbFiles;
+ std::vector<char> board; // fill an array where the pieces are for later geometry checks
+public:
+ CharBoard(int nbRanks, int nbFiles) : nbRanks(nbRanks), nbFiles(nbFiles) {
+ assert(nbFiles > 0 && nbRanks > 0);
+ board = std::vector<char>(nbRanks * nbFiles, ' ');
+ }
+ void set_piece(int rankIdx, int fileIdx, char c) {
+ board[rankIdx * nbFiles + fileIdx] = c;
+ }
+ char get_piece(int rowIdx, int fileIdx) const {
+ return board[rowIdx * nbFiles + fileIdx];
+ }
+ int get_nb_ranks() const {
+ return nbRanks;
+ }
+ int get_nb_files() const {
+ return nbFiles;
+ }
+ /// 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) {
+ s.rowIdx = r;
+ s.fileIdx = c;
+ return s;
+ }
+ }
+ }
+ 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) {
+ squares.emplace_back(CharSquare(r, c));
+ }
+ }
+ }
+ return squares;
+ }
+ /// Checks if a given character is on a given rank index
+ bool is_piece_on_rank(char piece, int rowIdx) const {
+ for (int f = 0; f < nbFiles; ++f)
+ if (get_piece(rowIdx, f) == piece)
+ return true;
+ return false;
+ }
+ friend std::ostream& operator<<(std::ostream& os, const CharBoard& board);
+};
+
+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) {
+ 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) {
+ std::cerr << "Invalid piece character: '" << c << "'." << std::endl;
+ return NOK;
+ }
+ }
+ return OK;
+}
+
+std::vector<std::string> get_fen_parts(const std::string& fullFen, char delim) {
+ std::vector<std::string> fenParts;
+ std::string curPart;
+ std::stringstream ss(fullFen);
+ while (std::getline(ss, curPart, delim))
+ fenParts.emplace_back(curPart);
+ return fenParts;
+}
+
+/// fills the character board according to a given FEN string
+Validation fill_char_board(CharBoard& board, const std::string& fenBoard, const std::string& validSpecialCharacters, const Variant* v) {
+ int rankIdx = 0;
+ int fileIdx = 0;
+
+ char prevChar = '?';
+ for (char c : fenBoard) {
+ if (c == ' ' || c == '[')
+ break;
+ 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 == '/') {
+ ++rankIdx;
+ if (fileIdx != board.get_nb_files()) {
+ std::cerr << "curRankWidth != nbFiles: " << fileIdx << " != " << board.get_nb_files() << std::endl;
+ return NOK;
+ }
+ if (rankIdx == board.get_nb_ranks())
+ break;
+ fileIdx = 0;
+ }
+ 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;
+ }
+ board.set_piece(v->maxRank-rankIdx, fileIdx, c); // we mirror the rank index because the black pieces are given first in the FEN
+ ++fileIdx;
+ }
+ prevChar = c;
+ }
+
+ 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()) {
+ std::cerr << "Invalid number of ranks. Expected: " << board.get_nb_ranks() << " Actual: " << rankIdx+1 << 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)) {
+ std::cerr << "Invalid castling specification: '" << c << "'." << std::endl;
+ return NOK;
+ }
+ else if (isupper(c))
+ castlingInfoSplitted[WHITE] += tolower(c);
+ else
+ castlingInfoSplitted[BLACK] += c;
+ }
+ }
+ return OK;
+}
+
+std::string color_to_string(Color c) {
+ switch (c) {
+ case WHITE:
+ return "WHITE";
+ case BLACK:
+ return "BLACK";
+ case COLOR_NB:
+ return "COLOR_NB";
+ default:
+ return "INVALID_COLOR";
+ }
+}
+
+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) {
+ case KING_SIDE:
+ return "KING_SIDE";
+ case QUEEN_SIDE:
+ return "QUEENS_SIDE";
+ case WHITE_OO:
+ return "WHITE_OO";
+ case WHITE_OOO:
+ return "WHITE_OOO";
+ case BLACK_OO:
+ return "BLACK_OO";
+ case BLACK_OOO:
+ return "BLACK_OOO";
+ case WHITE_CASTLING:
+ return "WHITE_CASTLING";
+ case BLACK_CASTLING:
+ return "BLACK_CASTLING";
+ case ANY_CASTLING:
+ return "ANY_CASTLING";
+ case CASTLING_RIGHT_NB:
+ return "CASTLING_RIGHT_NB";
+ default:
+ return "INVALID_CASTLING_RIGHTS";
+ }
+}
+
+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 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) {
+
+ for (Color c : {WHITE, BLACK}) {
+ if (castlingInfoSplitted[c].size() == 0)
+ continue;
+ 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}) {
+ 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) {
+ 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) {
+
+ char stopChar;
+ int offset = 0;
+ if (std::count(fenBoard.begin(), fenBoard.end(), '/') == nbRanks) {
+ // look for last '/'
+ stopChar = '/';
+ }
+ else {
+ // pocket is defined as [ and ]
+ stopChar = '[';
+ offset = 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) {
+ const char c = *it;
+ if (c == stopChar)
+ return OK;
+ if (c != '-') {
+ if (v->pieceToChar.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;
+ }
+ }
+ }
+ 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]));
+
+ if (nbWhiteKings != 1) {
+ std::cerr << "Invalid number of white kings. Expected: 1. Given: " << nbWhiteKings << std::endl;
+ return NOK;
+ }
+ if (nbBlackKings != 1) {
+ std::cerr << "Invalid number of black kings. Expected: 1. 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) {
+ 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;
+ 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;
+ 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;
+}
+
+FenValidation validate_fen(const std::string& fen, const Variant* v) {
+
+ const std::string validSpecialCharacters = "/+~[]-";
+ // 0) Layout
+ // check for empty fen
+ 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
+ if (fenParts.size() != nbFenParts) {
+ std::cerr << "Invalid number of fen parts. Expected: " << nbFenParts << " 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) {
+ return FEN_INVALID_CHAR;
+ }
+
+ // check for number of ranks
+ const int nbRanks = v->maxRank + 1;
+ // check for number of files
+ const int nbFiles = v->maxFile + 1;
+ CharBoard board(nbRanks, nbFiles); // create a 2D character board for later geometry checks
+
+ if (fill_char_board(board, fenParts[0], validSpecialCharacters, v) == NOK)
+ 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)
+ 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
+ // (variants like giveaway use the COMMONER piece type instead)
+ if (check_number_of_kings(fenParts[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
+ 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]));
+ 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') {
+ std::cerr << "Invalid side to move specification: '" << fenParts[1][0] << "'." << std::endl;
+ return FEN_INVALID_SIDE_TO_MOVE;
+ }
+
+ // 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;
+ }
+
+ // 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)
+
+ // 6) Part
+ // check half move counter
+ for (char c : fenParts[nbFenParts-2])
+ if (!isdigit(c)) {
+ std::cerr << "Invalid half move counter: '" << c << "'." << std::endl;
+ return FEN_INVALID_HALF_MOVE_COUNTER;
+ }
+
+ // 7) Part
+ // check move counter
+ for (char c : fenParts[nbFenParts-1])
+ if (!isdigit(c)) {
+ std::cerr << "Invalid move counter: '" << c << "'." << std::endl;
+ return FEN_INVALID_MOVE_COUNTER;
+ }
+
+ return FEN_OK;
+}
+}
Bitbases::init();
}
+#define DELIM " "
+
+inline void save_pop_back(std::string& s) {
+ if (s.size() != 0) {
+ s.pop_back();
+ }
+}
+
+const Variant* get_variant(const std::string& uciVariant) {
+ if (uciVariant.size() == 0)
+ return variants.find("chess")->second;
+ return variants.find(uciVariant)->second;
+}
class Board {
// note: we can't use references for strings here due to conversion to JavaScript
}
std::string legal_moves() {
- std::string moves = "";
- bool first = true;
+ std::string moves;
for (const ExtMove& move : MoveList<LEGAL>(this->pos)) {
- if (first) {
- moves = UCI::move(this->pos, move);
- first = false;
- }
- else
- moves += " " + UCI::move(this->pos, move);
+ moves += UCI::move(this->pos, move);
+ moves += DELIM;
}
+ save_pop_back(moves);
return moves;
}
std::string legal_moves_san() {
- std::string movesSan = "";
- bool first = true;
+ std::string movesSan;
for (const ExtMove& move : MoveList<LEGAL>(this->pos)) {
- if (first) {
- movesSan = move_to_san(this->pos, move, NOTATION_SAN);
- first = false;
- }
- else
- movesSan += " " + move_to_san(this->pos, move, NOTATION_SAN);
+ movesSan += move_to_san(this->pos, move, NOTATION_SAN);
+ movesSan += DELIM;
}
+ save_pop_back(movesSan);
return movesSan;
}
if (moveNumbers) {
variationSan = std::to_string(fullmove_number());
if (pos.side_to_move() == WHITE)
- variationSan += ". ";
+ variationSan += ". ";
else
- variationSan += "...";
+ variationSan += "...";
}
variationSan += move_to_san(this->pos, moves.back(), Notation(notation));
}
else {
- if (moveNumbers && pos.side_to_move() == WHITE)
- variationSan += " " + std::to_string(fullmove_number()) + ".";
- variationSan += " " + move_to_san(this->pos, moves.back(), Notation(notation));
+ if (moveNumbers && pos.side_to_move() == WHITE) {
+ variationSan += DELIM;
+ variationSan += std::to_string(fullmove_number());
+ variationSan += ".";
+ }
+ variationSan += DELIM;
+ variationSan += move_to_san(this->pos, moves.back(), Notation(notation));
}
states->emplace_back();
pos.do_move(moves.back(), states->back());
}
std::string move_stack() const {
- if (moveStack.size() == 0) {
- return "";
- }
- std::string moves = UCI::move(pos, moveStack[0]);
- for(auto it = std::begin(moveStack)+1; it != std::end(moveStack); ++it) {
- moves += " " + UCI::move(pos, *it);
+ std::string moves;
+ for(auto it = std::begin(moveStack); it != std::end(moveStack); ++it) {
+ moves += UCI::move(pos, *it);
+ moves += DELIM;
}
+ save_pop_back(moves);
return moves;
}
void push_moves(std::string uciMoves) {
- std::stringstream ss(uciMoves);
- std::string uciMove;
- while (std::getline(ss, uciMove, ' ')) {
- push(uciMove);
- }
+ std::stringstream ss(uciMoves);
+ std::string uciMove;
+ while (std::getline(ss, uciMove, ' ')) {
+ push(uciMove);
+ }
}
void push_san_moves(std::string sanMoves) {
- return push_san_moves(sanMoves, NOTATION_SAN);
+ return push_san_moves(sanMoves, NOTATION_SAN);
}
void push_san_moves(std::string sanMoves, Notation notation) {
- std::stringstream ss(sanMoves);
- std::string sanMove;
- while (std::getline(ss, sanMove, ' ')) {
- push_san(sanMove, notation);
+ std::stringstream ss(sanMoves);
+ std::string sanMove;
+ while (std::getline(ss, sanMove, ' '))
+ push_san(sanMove, notation);
+ }
+
+ std::string pocket(bool color) {
+ const Color c = Color(!color);
+ std::string pocket;
+ for (PieceType pt = KING; pt >= PAWN; --pt) {
+ for (int i = 0; i < pos.count_in_hand(c, pt); ++i) {
+ // only create BLACK pieces in order to convert to lower case
+ pocket += std::string(1, pos.piece_to_char()[make_piece(BLACK, pt)]);
}
+ }
+ return pocket;
}
// TODO: return board in ascii notation
initialize_stockfish();
Board::sfInitialized = true;
}
- if (uciVariant == "")
- uciVariant = "chess";
- this->v = variants.find(uciVariant)->second;
+ v = get_variant(uciVariant);
this->resetStates();
if (fen == "")
fen = v->startFen;
bool Board::sfInitialized = false;
namespace ffish {
+ // returns the version of the Fairy-Stockfish binary
+ std::string info() {
+ return engine_info();
+ }
-// returns the version of the Fairy-Stockfish binary
-std::string info() {
- return engine_info();
-}
-
-template <typename T>
-void set_option(std::string name, T value) {
- Options[name] = value;
- Board::sfInitialized = false;
-}
+ template <typename T>
+ void set_option(std::string name, T value) {
+ Options[name] = value;
+ Board::sfInitialized = false;
+ }
-std::string available_variants() {
- bool first = true;
- std::string availableVariants = "";
+ std::string available_variants() {
+ std::string availableVariants;
for (std::string variant : variants.get_keys()) {
- if (first) {
- first = false;
- availableVariants = variant;
- }
- else
- availableVariants += " " + variant;
+ availableVariants += variant;
+ availableVariants += DELIM;
}
+ save_pop_back(availableVariants);
return availableVariants;
-}
+ }
-void load_variant_config(std::string variantInitContent) {
+ void load_variant_config(std::string variantInitContent) {
std::stringstream ss(variantInitContent);
if (!Board::sfInitialized)
- initialize_stockfish();
+ initialize_stockfish();
variants.parse_istream<false>(ss);
Options["UCI_Variant"].set_combo(variants.get_keys());
Board::sfInitialized = true;
-}
+ }
+
+ std::string starting_fen(std::string uciVariant) {
+ const Variant* v = get_variant(uciVariant);
+ return v->startFen;
+ }
+
+ int validate_fen(std::string fen, std::string uciVariant) {
+ const Variant* v = get_variant(uciVariant);
+ return fen::validate_fen(fen, v);
+ }
+
+ int validate_fen(std::string fen) {
+ return validate_fen(fen, "chess");
+ }
}
class Game {
- private:
- std::unordered_map<std::string, std::string> header;
- std::unique_ptr<Board> board;
- std::string variant = "chess";
- std::string fen = ""; // start pos
- bool is960 = false;
- bool parsedGame = false;
- public:
- std::string header_keys() {
- std::string keys = "";
- bool first = true;
- for (auto it = header.begin(); it != header.end(); ++it) {
- if (first) {
- keys = it->first;
- first = false;
- }
- else
- keys += " " + it->first;
- }
- return keys;
- }
+private:
+ std::unordered_map<std::string, std::string> header;
+ std::unique_ptr<Board> board;
+ std::string variant = "chess";
+ std::string fen = ""; // start pos
+ bool is960 = false;
+ bool parsedGame = false;
+public:
+ std::string header_keys() {
+ std::string keys;
+ for (auto it = header.begin(); it != header.end(); ++it) {
+ keys += it->first;
+ keys += DELIM;
+ }
+ save_pop_back(keys);
+ return keys;
+ }
- std::string headers(std::string item) {
- auto it = header.find(item);
- if (it == header.end())
- return "";
- return it->second;
- }
+ std::string headers(std::string item) {
+ auto it = header.find(item);
+ if (it == header.end())
+ return "";
+ return it->second;
+ }
- std::string mainline_moves() {
- if (!parsedGame)
- return "";
- return board->move_stack();
- }
+ std::string mainline_moves() {
+ if (!parsedGame)
+ return "";
+ return board->move_stack();
+ }
- friend Game read_game_pgn(std::string);
+ friend Game read_game_pgn(std::string);
};
Game read_game_pgn(std::string pgn) {
- Game game;
- size_t lineStart = 0;
- bool headersParsed = false;
-
- while(true) {
- size_t lineEnd = pgn.find('\n', lineStart);
-
- if (lineEnd == std::string::npos)
- lineEnd = pgn.size();
-
- if (!headersParsed && pgn[lineStart] == '[') {
- // parse header
- // look for item
- size_t headerKeyStart = lineStart+1;
- size_t headerKeyEnd = pgn.find(' ', lineStart);
- size_t headerItemStart = headerKeyEnd+2;
- size_t headerItemEnd = pgn.find(']', headerKeyEnd)-1;
-
- // put item into list
- game.header[pgn.substr(headerKeyStart, headerKeyEnd-headerKeyStart)] = pgn.substr(headerItemStart, headerItemEnd-headerItemStart);
- }
- else {
- if (!headersParsed) {
- headersParsed = true;
- auto it = game.header.find("Variant");
- if (it != game.header.end()) {
- game.variant = it->second;
- std::transform(game.variant.begin(), game.variant.end(), game.variant.begin(),
- [](unsigned char c){ return std::tolower(c); });
- game.is960 = it->second.find("960") != std::string::npos;
- }
-
- it = game.header.find("FEN");
- if (it != game.header.end())
- game.fen = it->second;
-
- game.board = std::make_unique<Board>(game.variant, game.fen, game.is960);
- game.parsedGame = true;
- }
-
- // game line
- size_t curIdx = lineStart;
- while (curIdx <= lineEnd) {
- if (pgn[curIdx] == '*')
- return game;
-
- while (pgn[curIdx] == '{') {
- // skip comment
- curIdx = pgn.find('}', curIdx);
- if (curIdx == std::string::npos) {
- std::cerr << "Missing '}' for move comment while reading pgn." << std::endl;
- return game;
- }
- curIdx += 2;
- }
- while (pgn[curIdx] == '(') {
- // skip comment
- curIdx = pgn.find(')', curIdx);
- if (curIdx == std::string::npos) {
- std::cerr << "Missing ')' for move comment while reading pgn." << std::endl;
- return game;
- }
- curIdx += 2;
- }
-
- if (pgn[curIdx] >= '0' && pgn[curIdx] <= '9') {
- // we are at a move number -> look for next point
- curIdx = pgn.find('.', curIdx);
- if (curIdx == std::string::npos)
- break;
- ++curIdx;
- // increment if we're at a space
- while (curIdx < pgn.size() && pgn[curIdx] == ' ')
- ++curIdx;
- // increment if we're at a point
- while (curIdx < pgn.size() && pgn[curIdx] == '.')
- ++curIdx;
- }
- // extract sanMove
- size_t sanMoveEnd = std::min(pgn.find(' ', curIdx), lineEnd);
- if (sanMoveEnd > curIdx) {
- std::string sanMove = pgn.substr(curIdx, sanMoveEnd-curIdx);
- // clean possible ? and ! from string
- size_t annotationChar1 = sanMove.find('?');
- size_t annotationChar2 = sanMove.find('!');
- if (annotationChar1 != std::string::npos || annotationChar2 != std::string::npos)
- sanMove = sanMove.substr(0, std::min(annotationChar1, annotationChar2));
- game.board->push_san(sanMove);
- }
- curIdx = sanMoveEnd+1;
- }
+ Game game;
+ size_t lineStart = 0;
+ bool headersParsed = false;
+
+ while(true) {
+ size_t lineEnd = pgn.find('\n', lineStart);
+
+ if (lineEnd == std::string::npos)
+ lineEnd = pgn.size();
+
+ if (!headersParsed && pgn[lineStart] == '[') {
+ // parse header
+ // look for item
+ size_t headerKeyStart = lineStart+1;
+ size_t headerKeyEnd = pgn.find(' ', lineStart);
+ size_t headerItemStart = headerKeyEnd+2;
+ size_t headerItemEnd = pgn.find(']', headerKeyEnd)-1;
+
+ // put item into list
+ game.header[pgn.substr(headerKeyStart, headerKeyEnd-headerKeyStart)] = pgn.substr(headerItemStart, headerItemEnd-headerItemStart);
+ }
+ else {
+ if (!headersParsed) {
+ headersParsed = true;
+ auto it = game.header.find("Variant");
+ if (it != game.header.end()) {
+ game.variant = it->second;
+ std::transform(game.variant.begin(), game.variant.end(), game.variant.begin(),
+ [](unsigned char c){ return std::tolower(c); });
+ game.is960 = it->second.find("960") != std::string::npos;
}
- lineStart = lineEnd+1;
- if (lineStart >= pgn.size())
+ it = game.header.find("FEN");
+ if (it != game.header.end())
+ game.fen = it->second;
+
+ game.board = std::make_unique<Board>(game.variant, game.fen, game.is960);
+ game.parsedGame = true;
+ }
+
+ // game line
+ size_t curIdx = lineStart;
+ while (curIdx <= lineEnd) {
+ if (pgn[curIdx] == '*')
+ return game;
+
+ while (pgn[curIdx] == '{') {
+ // skip comment
+ curIdx = pgn.find('}', curIdx);
+ if (curIdx == std::string::npos) {
+ std::cerr << "Missing '}' for move comment while reading pgn." << std::endl;
+ return game;
+ }
+ curIdx += 2;
+ }
+ while (pgn[curIdx] == '(') {
+ // skip comment
+ curIdx = pgn.find(')', curIdx);
+ if (curIdx == std::string::npos) {
+ std::cerr << "Missing ')' for move comment while reading pgn." << std::endl;
return game;
+ }
+ curIdx += 2;
+ }
+
+ if (pgn[curIdx] >= '0' && pgn[curIdx] <= '9') {
+ // we are at a move number -> look for next point
+ curIdx = pgn.find('.', curIdx);
+ if (curIdx == std::string::npos)
+ break;
+ ++curIdx;
+ // increment if we're at a space
+ while (curIdx < pgn.size() && pgn[curIdx] == ' ')
+ ++curIdx;
+ // increment if we're at a point
+ while (curIdx < pgn.size() && pgn[curIdx] == '.')
+ ++curIdx;
+ }
+ // extract sanMove
+ size_t sanMoveEnd = std::min(pgn.find(' ', curIdx), lineEnd);
+ if (sanMoveEnd > curIdx) {
+ std::string sanMove = pgn.substr(curIdx, sanMoveEnd-curIdx);
+ // clean possible ? and ! from string
+ size_t annotationChar1 = sanMove.find('?');
+ size_t annotationChar2 = sanMove.find('!');
+ if (annotationChar1 != std::string::npos || annotationChar2 != std::string::npos)
+ sanMove = sanMove.substr(0, std::min(annotationChar1, annotationChar2));
+ game.board->push_san(sanMove);
+ }
+ curIdx = sanMoveEnd+1;
+ }
}
+ lineStart = lineEnd+1;
+
+ if (lineStart >= pgn.size())
return game;
+ }
+ return game;
}
.function("moveStack", &Board::move_stack)
.function("pushMoves", &Board::push_moves)
.function("pushSanMoves", select_overload<void(std::string)>(&Board::push_san_moves))
- .function("pushSanMoves", select_overload<void(std::string, Notation)>(&Board::push_san_moves));
+ .function("pushSanMoves", select_overload<void(std::string, Notation)>(&Board::push_san_moves))
+ .function("pocket", &Board::pocket);
class_<Game>("Game")
.function("headerKeys", &Game::header_keys)
.function("headers", &Game::headers)
function("readGamePGN", &read_game_pgn);
function("variants", &ffish::available_variants);
function("loadVariantConfig", &ffish::load_variant_config);
+ function("startingFen", &ffish::starting_fen);
+ function("validateFen", select_overload<int(std::string)>(&ffish::validate_fen));
+ function("validateFen", select_overload<int(std::string, std::string)>(&ffish::validate_fen));
// TODO: enable to string conversion method
// .class_function("getStringFromInstance", &Board::get_string_from_instance);
}
v->startFen = "pppppppp/pppppppp/8/8/8/8/PPPPPPPP/PPPPPPPP w 0 1";
v->promotionPieceTypes = {};
v->firstRankDoubleSteps = false;
+ v->doubleStep = false;
v->castling = false;
v->stalemateValue = -VALUE_MATE;
v->flagPiece = BREAKTHROUGH_PIECE;
# Hybrid variant of makruk and crazyhouse
[makrukhouse:makruk]
+startFen = rnsmksnr/8/pppppppp/8/8/PPPPPPPP/8/RNSKMSNR[] w - - 0 1
pieceDrops = true
capturesToHand = true
# Hybrid variant of xiangqi and crazyhouse
[xiangqihouse:xiangqi]
+startFen = rnbakabnr/9/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/9/RNBAKABNR[] w - - 0 1
pieceDrops = true
capturesToHand = true
dropChecks = false
# Hybrid variant of janggi and crazyhouse
[janggihouse:janggi]
+startFen = rnba1abnr/4k4/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/4K4/RNBA1ABNR[] w - - 0 1
pieceDrops = true
capturesToHand = true
variantTemplate = shogi
pieceToCharTable = PNBR.F.....++++.+Kpnbr.f.....++++.+k
pocketSize = 8
-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
## Custom variants
Fairy-Stockfish also allows defining custom variants by loading a configuration file.
-See e.g. the confiugration for connect4, tictactoe or janggihouse in [variants.ini](https://github.com/ianfab/Fairy-Stockfish/blob/master/src/variants.ini).
+
+See e.g. the configuration for **connect4**, **tictactoe** or **janggihouse** in [variants.ini](https://github.com/ianfab/Fairy-Stockfish/blob/master/src/variants.ini).
```javascript
fs = require('fs');
let configFilePath = './variants.ini';
}
```
-Set a custom fen position:
+Set a custom fen position including fen valdiation:
```javascript
-board.setFen("rnb1kbnr/ppp1pppp/8/3q4/8/8/PPPP1PPP/RNBQKBNR w KQkq - 0 3");
+fen = "rnb1kbnr/ppp1pppp/8/3q4/8/8/PPPP1PPP/RNBQKBNR w KQkq - 0 3";
+if (ffish.valdiateFen(fen) == 1) { // ffish.valdiateFen(fen) can return different error codes, it returns 1 for FEN_OK
+ board.setFen(fen);
+}
+else {
+ console.error(`Fen couldn't be parsed.`);
+}
```
Alternatively, you can initialize a board with a custom FEN directly:
let legalMovesSan = board.legalMovesSan().split(" ");
for (var i = 0; i < legalMovesSan.length; i++) {
- console.log(`${i}: ${legalMoves[i]}, ${legalMoves})
+ console.log(`${i}: ${legalMoves[i]}, ${legalMovesSan[i]}`)
}
```
-Unfortunately, it is impossible for Emscripten to call the destructors on C++ object.
+Unfortunately, it is impossible for Emscripten to call the destructor on C++ objects.
Therefore, you need to call `.delete()` to free the heap memory of an object.
```javascript
board.delete();
console.log(game.mainlineMoves())
let board = new ffish.Board(game.headers("Variant").toLowerCase());
- board.pushMoves(game.mainlineMoves());
+ for (let idx = 0; idx < mainlineMoves.length; ++idx) {
+ board.push(mainlineMoves[idx]);
+ }
+ // or use board.pushMoves(game.mainlineMoves()); to push all moves at once
let finalFen = board.fen();
board.delete();
```
```bash
emcc -O3 --bind -DLARGEBOARDS -DPRECOMPUTED_MAGICS -DNNUE_EMBEDDING_OFF -DNO_THREADS \
- -s TOTAL_MEMORY=67108864 -s ALLOW_MEMORY_GROWTH=1 -s WASM_MEM_MAX=2147483648 \
- -s ASSERTIONS=0 -s SAFE_HEAP=0 \
+ -s TOTAL_MEMORY=32MB -s ALLOW_MEMORY_GROWTH=1 -s WASM_MEM_MAX=1GB \
+ -s ASSERTIONS=0 -s SAFE_HEAP=0 -std=c++17 -Wall \
-DNO_THREADS -DLARGEBOARDS -DPRECOMPUTED_MAGICS \
ffishjs.cpp \
benchmark.cpp \
```
```bash
emcc -O3 --bind -DLARGEBOARDS -DPRECOMPUTED_MAGICS -DNNUE_EMBEDDING_OFF -DNO_THREADS \
- -s TOTAL_MEMORY=67108864 -s ALLOW_MEMORY_GROWTH=1 -s WASM_MEM_MAX=2147483648 \
- -s ASSERTIONS=0 -s SAFE_HEAP=0 \
+ -s TOTAL_MEMORY=32MB -s ALLOW_MEMORY_GROWTH=1 -s WASM_MEM_MAX=1GB \
+ -s ASSERTIONS=0 -s SAFE_HEAP=0 -std=c++17 -Wall \
-s ENVIRONMENT='web,worker' -s EXPORT_ES6=1 -s MODULARIZE=1 -s USE_ES6_IMPORT_META=0 \
ffishjs.cpp \
benchmark.cpp \
{
"name": "ffish",
- "version": "0.4.2",
+ "version": "0.4.4",
"description": "A high performance WebAssembly chess variant library based on Fairy-Stockfish",
"main": "ffish.js",
"scripts": {
pgnDir = __dirname + '/../pgn/';
srcDir = __dirname + '/../../src/';
ffish = require('./ffish.js');
+ WHITE = true;
+ BLACK = false;
ffish['onRuntimeInitialized'] = () => {
resolve();
}
});
});
+describe('board.pocket(turn)', function () {
+ it("it returns the pocket for the given player as a string with no delimeter. All pieces are returned in lower case.", () => {
+ let board = new ffish.Board("crazyhouse", "rnb1kbnr/ppp1pppp/8/8/8/5q2/PPPP1PPP/RNBQKB1R/Pnp w KQkq - 0 4");
+ chai.expect(board.pocket(WHITE)).to.equal("p");
+ chai.expect(board.pocket(BLACK)).to.equal("np");
+ board.delete();
+ let board2 = new ffish.Board("crazyhouse", "rnb1kbnr/ppp1pppp/8/8/8/5q2/PPPP1PPP/RNBQKB1R[Pnp] w KQkq - 0 4");
+ chai.expect(board2.pocket(WHITE)).to.equal("p");
+ chai.expect(board2.pocket(BLACK)).to.equal("np");
+ board2.delete();
+ });
+});
+
describe('ffish.info()', function () {
it("it returns the version of the Fairy-Stockfish binary", () => {
chai.expect(ffish.info()).to.be.a('string');
});
});
+describe('ffish.startingFen(uciVariant)', function () {
+ it("it returns the starting fen for the given uci-variant.", () => {
+ chai.expect(ffish.startingFen("chess")).to.equal("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1");
+ });
+});
+
+describe('ffish.validateFen(fen)', function () {
+ it("it validates a given chess fen and returns +1 if fen is valid. Otherwise an error code will be returned.", () => {
+ chai.expect(ffish.validateFen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1")).to.equal(1);
+ chai.expect(ffish.validateFen("6k1/R7/2p4p/2P2p1P/PPb2Bp1/2P1K1P1/5r2/8 b - - 4 39")).to.equal(1);
+ });
+});
+
+describe('ffish.validateFen(fen, uciVariant)', function () {
+ it("it validates a given fen and returns +1 if fen is valid. Otherwise an error code will be returned.", () => {
+ // check if starting fens are valid for all variants
+ const variants = ffish.variants().split(" ")
+ for (let idx = 0; idx < variants.length; ++idx) {
+ const startFen = ffish.startingFen(variants[idx]);
+ chai.expect(ffish.validateFen(startFen, variants[idx])).to.equal(1);
+ }
+ // alternative pocket piece formulation
+ chai.expect(ffish.validateFen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR/RB w KQkq - 3+3 0 1", "3check-crazyhouse")).to.equal(1);
+ chai.expect(ffish.validateFen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR/ w KQkq - 3+3 0 1", "3check-crazyhouse")).to.equal(1);
+
+ // error id checks
+ chai.expect(ffish.validateFen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR[]wKQkq-3+301", "3check-crazyhouse")).to.equal(-12);
+ chai.expect(ffish.validateFen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR[] KQkq - 3+3 0 1", "3check-crazyhouse")).to.equal(-11);
+ chai.expect(ffish.validateFen("rnbqkbnr/ppppXppp/8/8/8/8/PPPPPPPP/RNBQKBNR[] w KQkq - 3+3 0 1", "3check-crazyhouse")).to.equal(-10);
+ chai.expect(ffish.validateFen("rnbqkbnr/pppppKpp/8/8/8/8/PPPPPPPP/RNBQ1BNR[] w KQkq - 3+3 0 1", "3check-crazyhouse")).to.equal(-9);
+ chai.expect(ffish.validateFen("rnbqkbnr/ppppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR[] w KQkq - 3+3 0 1", "3check-crazyhouse")).to.equal(-8);
+ chai.expect(ffish.validateFen("rnbqkbnr/pppppppp/8/8/8/PPPPPPPP/RNBQKBNR[] w KQkq - 3+3 0 1", "3check-crazyhouse")).to.equal(-8);
+ chai.expect(ffish.validateFen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR[77] w KQkq - 3+3 0 1", "3check-crazyhouse")).to.equal(-7);
+ chai.expect(ffish.validateFen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR[] o KQkq - 3+3 0 1", "3check-crazyhouse")).to.equal(-6);
+ chai.expect(ffish.validateFen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR[] w K6kq - 3+3 0 1", "3check-crazyhouse")).to.equal(-5);
+ chai.expect(ffish.validateFen("rnbq1bnr/pppkpppp/8/8/8/8/PPPPPPPP/RNBQKBNR[] w KQkq - 3+3 0 1", "3check-crazyhouse")).to.equal(-5);
+ chai.expect(ffish.validateFen("rnbqkbn1/pppppppr/8/8/8/8/PPPPPPPP/RNBQKBNR[] w KQkq - 3+3 0 1", "3check-crazyhouse")).to.equal(-5);
+ chai.expect(ffish.validateFen("rnbkqbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR/RB w KQkq - 3+3 0 1", "3check-crazyhouse")).to.equal(-5);
+ chai.expect(ffish.validateFen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR[] w KQkq ss 3+3 0 1", "3check-crazyhouse")).to.equal(-4);
+ chai.expect(ffish.validateFen("rnbqkknr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR[] w KQkq - 3+3 0 1", "3check-crazyhouse")).to.equal(-3);
+ chai.expect(ffish.validateFen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR[] w KQkq - 3+3 x 1", "3check-crazyhouse")).to.equal(-2);
+ chai.expect(ffish.validateFen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR[] w KQkq - 3+3 0 -13", "3check-crazyhouse")).to.equal(-1);
+ chai.expect(ffish.validateFen("", "chess")).to.equal(0);
+ });
+});
+
describe('ffish.readGamePGN(pgn)', function () {
it("it reads a pgn string and returns a game object", () => {
fs = require('fs');
}
let game = ffish.readGamePGN(data);
- let board = new ffish.Board(game.headers("Variant").toLowerCase());
- board.pushMoves(game.mainlineMoves());
+ const variant = game.headers("Variant").toLowerCase();
+ let board = new ffish.Board(variant);
+ const mainlineMoves = game.mainlineMoves().split(" ");
+ for (let idx2 = 0; idx2 < mainlineMoves.length; ++idx2) {
+ board.push(mainlineMoves[idx2]);
+ chai.expect(ffish.validateFen(board.fen(), variant)).to.equal(1);
+ }
chai.expect(board.fen()).to.equal(expectedFens[idx]);
board.delete();
game.delete();