return pockets[WHITE].find('k') == std::string::npos && pockets[BLACK].find('k') == std::string::npos;
}
+Validation check_digit_field(const std::string& field)
+{
+ if (field.size() == 1 && field[0] == '-') {
+ return OK;
+ }
+ for (char c : field)
+ if (!isdigit(c)) {
+ return NOK;
+ }
+ return OK;
+}
+
+
FenValidation validate_fen(const std::string& fen, const Variant* v) {
const std::string validSpecialCharacters = "/+~[]-";
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;
+ // 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
+ << " Actual: " << fenParts.size() << std::endl;
return FEN_INVALID_NB_PARTS;
}
// 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;
- }
+ if (!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
- for (char c : fenParts[nbFenParts-1])
- if (!isdigit(c)) {
- std::cerr << "Invalid move counter: '" << c << "'." << std::endl;
- return FEN_INVALID_MOVE_COUNTER;
- }
+ if (!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;
}
}
-
return variants.find(uciVariant)->second;
}
+template <bool isUCI>
+inline bool is_move_none(Move move, const std::string& strMove, const Position& pos) {
+ if (move == MOVE_NONE) {
+ std::cerr << "The given ";
+ isUCI ? std::cerr << "uciMove" : std::cerr << "sanMove";
+ std::cerr << " '" << strMove << "' for position '" << pos.fen() << "' is invalid." << std::endl;
+ return true;
+ }
+ return false;
+}
+
class Board {
// note: we can't use references for strings here due to conversion to JavaScript
private:
return MoveList<LEGAL>(pos).size();
}
- void push(std::string uciMove) {
- do_move(UCI::to_move(this->pos, uciMove));
+ bool push(std::string uciMove) {
+ const Move move = UCI::to_move(this->pos, uciMove);
+ if (is_move_none<true>(move, uciMove, pos))
+ return false;
+ do_move(move);
+ return true;
}
bool push_san(std::string sanMove) {
break;
}
}
- if (foundMove != MOVE_NONE) {
- do_move(foundMove);
- return true;
- }
- return false;
+ if (is_move_none<false>(foundMove, sanMove, pos))
+ return false;
+ do_move(foundMove);
+ return true;
}
void pop() {
// note: const identifier for pos not possible due to move_to_san()
std::string san_move(std::string uciMove) {
- return move_to_san(this->pos, UCI::to_move(this->pos, uciMove), NOTATION_SAN);
+ return san_move(uciMove, NOTATION_SAN);
}
std::string san_move(std::string uciMove, Notation notation) {
+ const Move move = UCI::to_move(this->pos, uciMove);
+ if (is_move_none<true>(move, uciMove, pos))
+ return "";
return move_to_san(this->pos, UCI::to_move(this->pos, uciMove), notation);
}
+
std::string variation_san(std::string uciMoves) {
return variation_san(uciMoves, NOTATION_SAN, true);
}
bool first = true;
while (std::getline(ss, uciMove, ' ')) {
+ const Move move = UCI::to_move(this->pos, uciMove);
+ if (is_move_none<true>(move, uciMove, pos))
+ return "";
moves.emplace_back(UCI::to_move(this->pos, uciMove));
if (first) {
first = false;
return ss.str();
}
+ std::string variant() {
+ // Iterate through the variants map
+ for (auto it = variants.begin(); it != variants.end(); ++it)
+ if (it->second == v)
+ return it->first;
+
+ std::cerr << "Current variant is not registered." << std::endl;
+ return "unknown";
+ }
+
private:
void resetStates() {
this->states = StateListPtr(new std::deque<StateInfo>(1));
bool skip_comment(const std::string& pgn, size_t& curIdx, size_t& lineEnd) {
- if (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 false;
- }
- if (curIdx > lineEnd)
- lineEnd = pgn.find('\n', curIdx);
- }
- return true;
+ curIdx = pgn.find('}', curIdx);
+ if (curIdx == std::string::npos) {
+ std::cerr << "Missing '}' for move comment while reading pgn." << std::endl;
+ return false;
+ }
+ if (curIdx > lineEnd)
+ lineEnd = pgn.find('\n', curIdx);
+ return true;
}
Game read_game_pgn(std::string pgn) {
if (pgn[curIdx] == '*')
return game;
- if (!skip_comment(pgn, curIdx, lineEnd))
- return game;
+ if (pgn[curIdx] == '{') {
+ if (!skip_comment(pgn, curIdx, lineEnd))
+ return game;
+ ++curIdx;
+ }
// Movetext RAV (Recursive Annotation Variation)
size_t openedRAV = 0;
size_t annotationChar2 = sanMove.find('!');
if (annotationChar1 != std::string::npos || annotationChar2 != std::string::npos)
sanMove = sanMove.substr(0, std::min(annotationChar1, annotationChar2));
+ std::cout << sanMove << " ";
game.board->push_san(sanMove);
}
curIdx = sanMoveEnd+1;
.function("pushSanMoves", select_overload<void(std::string, Notation)>(&Board::push_san_moves))
.function("pocket", &Board::pocket)
.function("toString", &Board::to_string)
- .function("toVerboseString", &Board::to_verbose_string);
+ .function("toVerboseString", &Board::to_verbose_string)
+ .function("variant", &Board::variant);
class_<Game>("Game")
.function("headerKeys", &Game::header_keys)
.function("headers", &Game::headers)
ffish.variants()
```
```
-3check 5check ai-wok almost amazon antichess armageddon asean ataxx breakthrough bughouse cambodian\
-capablanca capahouse caparandom centaur chancellor chess chessgi chigorin clobber clobber10 codrus courier\
-crazyhouse dobutsu embassy euroshogi extinction fairy fischerandom gardner giveaway gorogoro gothic grand\
-hoppelpoppel horde janggi janggicasual janggimodern janggitraditional janus jesonmor judkins karouk kinglet\
-kingofthehill knightmate koedem kyotoshogi loop losalamos losers makpong makruk manchu micro mini minishogi\
-minixiangqi modern newzealand nocastle normal placement pocketknight racingkings seirawan shako shatar\
-shatranj shogi shouse sittuyin suicide supply threekings xiangqi
+3check 3check-crazyhouse 5check ai-wok almost amazon anti-losalamos antichess\
+armageddon asean ataxx atomic breakthrough bughouse cambodian capablanca\
+capahouse caparandom centaur cfour chancellor chaturanga chess chessgi chigorin\
+clobber clobber10 codrus coffeehouse courier crazyhouse dobutsu embassy euroshogi\
+extinction fairy fischerandom flipello flipersi gardner gemini giveaway gorogoro\
+gothic grand grandhouse hoppelpoppel horde indiangreat janggi janggicasual\
+janggihouse janggimodern janggitraditional janus jesonmor judkins karouk kinglet\
+kingofthehill knightmate koedem kyotoshogi loop losalamos losers makpong makruk\
+makrukhouse manchu micro mini minishogi minixiangqi modern newzealand nocastle\
+nocheckatomic normal orda pawnsonly peasant placement pocketknight racingkings\
+seirawan semitorpedo shako shatar shatranj shogi shogun shouse sittuyin suicide\
+supply threekings tictactoe upsidedown weak xiangqi xiangqihouse
```
### Board object
}
```
-Set a custom fen position including fen valdiation:
+Set a custom fen position including fen validiation:
```javascript
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
+if (ffish.validateFen(fen) == 1) { // ffish.validateFen(fen) can return different error codes, it returns 1 for FEN_OK
board.setFen(fen);
}
else {
{
"name": "ffish",
- "version": "0.5.1",
+ "version": "0.6.0",
"description": "A high performance WebAssembly chess variant library based on Fairy-Stockfish",
"main": "ffish.js",
"scripts": {
board.push("e7e5");
board.push("g1f3");
chai.expect(board.fen()).to.equal("rnbqkbnr/pppp1ppp/8/4p3/4P3/5N2/PPPP1PPP/RNBQKB1R b KQkq - 1 2");
+ chai.expect(board.push("q2q7")).to.equal(false);
+ chai.expect(board.fen()).to.equal("rnbqkbnr/pppp1ppp/8/4p3/4P3/5N2/PPPP1PPP/RNBQKB1R b KQkq - 1 2");
board.delete();
});
});
board.pushSan("e5");
board.pushSan("Nf3");
chai.expect(board.fen()).to.equal("rnbqkbnr/pppp1ppp/8/4p3/4P3/5N2/PPPP1PPP/RNBQKB1R b KQkq - 1 2");
+ chai.expect(board.pushSan("Nf3")).to.equal(false);
+ chai.expect(board.fen()).to.equal("rnbqkbnr/pppp1ppp/8/4p3/4P3/5N2/PPPP1PPP/RNBQKB1R b KQkq - 1 2");
board.delete();
});
});
board.push("e2e4")
const sanMoves = board.variationSan("e7e5 g1f3 b8c6 f1c4");
chai.expect(sanMoves).to.equal("1...e5 2. Nf3 Nc6 3. Bc4");
+ const sanMovesInvalid = board.variationSan("e7e5 g1f3 b8c6 f1c7");
+ chai.expect(sanMovesInvalid).to.equal("");
board.delete();
});
});
});
});
+describe('board.variant()', function () {
+ it("it returns the uci-variant of the board.", () => {
+ const board = new ffish.Board("chess");
+ chai.expect(board.variant()).to.equal("chess");
+ board.delete();
+ const board2 = new ffish.Board("");
+ chai.expect(board2.variant()).to.equal("chess");
+ board2.delete();
+ const board3 = new ffish.Board("standard");
+ chai.expect(board3.variant()).to.equal("chess");
+ board3.delete();
+ const board4 = new ffish.Board("Standard");
+ chai.expect(board4.variant()).to.equal("chess");
+ board4.delete();
+ const board5 = new ffish.Board("atomic");
+ chai.expect(board5.variant()).to.equal("atomic");
+ board5.delete();
+ });
+});
+
describe('ffish.info()', function () {
it("it returns the version of the Fairy-Stockfish binary", () => {
chai.expect(ffish.info()).to.be.a('string');
// 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);
+ const variant = variants[idx];
+ const startFen = ffish.startingFen(variant);
+ chai.expect(ffish.validateFen(startFen, variant)).to.equal(1);
+ // check if the FEN is still valid if board.fen() is returned
+ const board = new ffish.Board(variant);
+ chai.expect(ffish.validateFen(board.fen(), variant)).to.equal(1);
+ board.delete();
}
// 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);