mirror of
https://gitlab.com/manzerbredes/chessarbiter.git
synced 2025-04-06 10:06:26 +02:00
Improve FEN parser
This commit is contained in:
parent
41107b6890
commit
851f31e7ae
3 changed files with 112 additions and 46 deletions
46
src/Fen.cpp
46
src/Fen.cpp
|
@ -5,19 +5,30 @@ namespace chessarbiter {
|
||||||
std::string FENParser::normalize_rank(std::string fen_rank) {
|
std::string FENParser::normalize_rank(std::string fen_rank) {
|
||||||
std::string normalized;
|
std::string normalized;
|
||||||
for (char &c : fen_rank) {
|
for (char &c : fen_rank) {
|
||||||
if (IS_DIGIT(c)) {
|
if (FEN__IS_DIGIT(c)) {
|
||||||
for (char i = 0; i < (c - '0'); i++) {
|
for (char i = 0; i < (c - '0'); i++) {
|
||||||
normalized += ' ';
|
normalized += ' ';
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// Check validity
|
||||||
|
char c2 = std::tolower(c);
|
||||||
|
if (!(c2 == 'p' || c2 == 'r' || c2 == 'n' || c2 == 'b' || c2 == 'q' ||
|
||||||
|
c2 == 'k' || c2 == ' ')) {
|
||||||
|
throw InvalidFEN();
|
||||||
|
}
|
||||||
|
// Add
|
||||||
normalized += c;
|
normalized += c;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Check validity
|
||||||
|
if (normalized.size() != 8) {
|
||||||
|
throw InvalidFEN();
|
||||||
|
}
|
||||||
return (normalized);
|
return (normalized);
|
||||||
}
|
}
|
||||||
|
|
||||||
char FENParser::NextToken(std::string fen, char loc) {
|
char FENParser::NextToken(std::string fen, char loc) {
|
||||||
while (loc < fen.size() && IS_BLANK(fen[loc])) {
|
while (loc < fen.size() && FEN__IS_BLANK(fen[loc])) {
|
||||||
loc++;
|
loc++;
|
||||||
}
|
}
|
||||||
return (loc);
|
return (loc);
|
||||||
|
@ -99,6 +110,7 @@ FEN FENParser::Parse(std::string fen) {
|
||||||
// Parse board
|
// Parse board
|
||||||
char loc = 0;
|
char loc = 0;
|
||||||
for (char rank = 0; rank < 8; rank++) {
|
for (char rank = 0; rank < 8; rank++) {
|
||||||
|
FEN__CHECK_LOC();
|
||||||
char newloc = NextRank(fen, loc);
|
char newloc = NextRank(fen, loc);
|
||||||
parsed.board += normalize_rank(fen.substr(loc, newloc - loc));
|
parsed.board += normalize_rank(fen.substr(loc, newloc - loc));
|
||||||
loc = newloc + 1;
|
loc = newloc + 1;
|
||||||
|
@ -106,15 +118,20 @@ FEN FENParser::Parse(std::string fen) {
|
||||||
|
|
||||||
// Parse player to move
|
// Parse player to move
|
||||||
loc = NextToken(fen, loc);
|
loc = NextToken(fen, loc);
|
||||||
|
if (!(fen[loc] == 'w' || fen[loc] == 'b')) {
|
||||||
|
throw InvalidFEN();
|
||||||
|
}
|
||||||
parsed.player = fen[loc] == 'b';
|
parsed.player = fen[loc] == 'b';
|
||||||
|
FEN__CHECK_LOC();
|
||||||
|
|
||||||
// Parse castling
|
// Parse castling
|
||||||
loc = NextToken(fen, loc + 1);
|
loc = NextToken(fen, loc + 1);
|
||||||
char length = 0;
|
char length = 0;
|
||||||
char cur_loc = loc;
|
char cur_loc = loc;
|
||||||
while (!IS_BLANK(fen[cur_loc])) {
|
while (!FEN__IS_BLANK(fen[cur_loc])) {
|
||||||
length++;
|
length++;
|
||||||
cur_loc++;
|
cur_loc++;
|
||||||
|
FEN__CHECK_LOC();
|
||||||
}
|
}
|
||||||
parsed.white_castle_short = false;
|
parsed.white_castle_short = false;
|
||||||
parsed.white_castle_long = false;
|
parsed.white_castle_long = false;
|
||||||
|
@ -135,6 +152,10 @@ FEN FENParser::Parse(std::string fen) {
|
||||||
case 'q':
|
case 'q':
|
||||||
parsed.black_castle_long = true;
|
parsed.black_castle_long = true;
|
||||||
break;
|
break;
|
||||||
|
case '-':
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw InvalidFEN();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -143,22 +164,37 @@ FEN FENParser::Parse(std::string fen) {
|
||||||
if (fen[loc] != '-') {
|
if (fen[loc] != '-') {
|
||||||
parsed.en_passant = fen.substr(loc, 2);
|
parsed.en_passant = fen.substr(loc, 2);
|
||||||
loc++;
|
loc++;
|
||||||
|
// Check format
|
||||||
|
char f = parsed.en_passant[0];
|
||||||
|
char r = parsed.en_passant[1];
|
||||||
|
if (!((f >= 'a' && f <= 'h') && (r >= '1' && r <= '8'))) {
|
||||||
|
throw InvalidFEN();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
loc++;
|
loc++;
|
||||||
|
FEN__CHECK_LOC();
|
||||||
|
|
||||||
// Parse half move counter
|
// Parse half move counter
|
||||||
loc = NextToken(fen, loc);
|
loc = NextToken(fen, loc);
|
||||||
std::string halfmove;
|
std::string halfmove;
|
||||||
while (!IS_BLANK(fen[loc])) {
|
while (!FEN__IS_BLANK(fen[loc])) {
|
||||||
|
if (!FEN__IS_DIGIT(fen[loc])) {
|
||||||
|
throw InvalidFEN();
|
||||||
|
}
|
||||||
halfmove += fen[loc];
|
halfmove += fen[loc];
|
||||||
loc++;
|
loc++;
|
||||||
|
FEN__CHECK_LOC();
|
||||||
}
|
}
|
||||||
parsed.halfmove = stoi(halfmove);
|
parsed.halfmove = stoi(halfmove);
|
||||||
|
|
||||||
// Parse move counter
|
// Parse move counter
|
||||||
loc = NextToken(fen, loc);
|
loc = NextToken(fen, loc);
|
||||||
std::string move;
|
std::string move;
|
||||||
while (loc < fen.size() && !IS_BLANK(fen[loc])) {
|
while (loc < fen.size() && !FEN__IS_BLANK(fen[loc])) {
|
||||||
|
if (!FEN__IS_DIGIT(fen[loc])) {
|
||||||
|
throw InvalidFEN();
|
||||||
|
}
|
||||||
|
FEN__CHECK_LOC();
|
||||||
move += fen[loc];
|
move += fen[loc];
|
||||||
loc++;
|
loc++;
|
||||||
}
|
}
|
||||||
|
|
11
src/Fen.hpp
11
src/Fen.hpp
|
@ -2,10 +2,11 @@
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#define IS_DIGIT(c) \
|
#define FEN__IS_DIGIT(c) \
|
||||||
(c == '0' || c == '1' || c == '2' || c == '3' || c == '4' || c == '5' || \
|
(c == '0' || c == '1' || c == '2' || c == '3' || c == '4' || c == '5' || \
|
||||||
c == '6' || c == '7' || c == '8' || c == '9')
|
c == '6' || c == '7' || c == '8' || c == '9')
|
||||||
#define IS_BLANK(c) (c == ' ' || c == '\n' || c == '\t' || c == '\r')
|
#define FEN__IS_BLANK(c) (c == ' ' || c == '\n' || c == '\t' || c == '\r')
|
||||||
|
#define FEN__CHECK_LOC() {if(loc>=fen.size()){throw InvalidFEN();}}
|
||||||
|
|
||||||
namespace chessarbiter {
|
namespace chessarbiter {
|
||||||
|
|
||||||
|
@ -33,9 +34,13 @@ private:
|
||||||
static char NextRank(std::string fen, char loc);
|
static char NextRank(std::string fen, char loc);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/// @brief Parse a FEN from a string
|
/// @brief Parse a FEN from a string (can throw InvalidFEN)
|
||||||
static FEN Parse(std::string);
|
static FEN Parse(std::string);
|
||||||
/// @brief Generate a fen string from the FEN object
|
/// @brief Generate a fen string from the FEN object
|
||||||
static std::string Serialize(FEN fen);
|
static std::string Serialize(FEN fen);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct InvalidFEN : public std::exception {
|
||||||
|
const char *what() const throw() { return "No piece found"; }
|
||||||
|
};
|
||||||
} // namespace chessarbiter
|
} // namespace chessarbiter
|
||||||
|
|
101
tests/fen.cpp
101
tests/fen.cpp
|
@ -3,48 +3,36 @@
|
||||||
|
|
||||||
using namespace chessarbiter;
|
using namespace chessarbiter;
|
||||||
|
|
||||||
TEST_CASE("Serializer", "[fen/serialize]") {
|
|
||||||
FEN f;
|
|
||||||
f.board = "p p p"
|
|
||||||
"p p"
|
|
||||||
" "
|
|
||||||
"QQQQQQQQ"
|
|
||||||
"kpkpkpkp"
|
|
||||||
" "
|
|
||||||
"p p r b "
|
|
||||||
" R";
|
|
||||||
REQUIRE(FENParser::Serialize(f) ==
|
|
||||||
"p4p1p/p6p/8/QQQQQQQQ/kpkpkpkp/8/p1p1r1b1/7R w KQkq - 0 1");
|
|
||||||
|
|
||||||
f.white_castle_short = false;
|
|
||||||
f.white_castle_long = false;
|
|
||||||
f.black_castle_short = false;
|
|
||||||
f.black_castle_long = false;
|
|
||||||
REQUIRE(FENParser::Serialize(f) ==
|
|
||||||
"p4p1p/p6p/8/QQQQQQQQ/kpkpkpkp/8/p1p1r1b1/7R w - - 0 1");
|
|
||||||
|
|
||||||
f.en_passant = "a3";
|
|
||||||
REQUIRE(FENParser::Serialize(f) ==
|
|
||||||
"p4p1p/p6p/8/QQQQQQQQ/kpkpkpkp/8/p1p1r1b1/7R w - a3 0 1");
|
|
||||||
|
|
||||||
f.player = true;
|
|
||||||
REQUIRE(FENParser::Serialize(f) ==
|
|
||||||
"p4p1p/p6p/8/QQQQQQQQ/kpkpkpkp/8/p1p1r1b1/7R b - a3 0 1");
|
|
||||||
|
|
||||||
f.halfmove = 5;
|
|
||||||
REQUIRE(FENParser::Serialize(f) ==
|
|
||||||
"p4p1p/p6p/8/QQQQQQQQ/kpkpkpkp/8/p1p1r1b1/7R b - a3 5 1");
|
|
||||||
|
|
||||||
f.move = 5;
|
|
||||||
REQUIRE(FENParser::Serialize(f) ==
|
|
||||||
"p4p1p/p6p/8/QQQQQQQQ/kpkpkpkp/8/p1p1r1b1/7R b - a3 5 5");
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("Parse", "[fen/parse]") {
|
TEST_CASE("Parse", "[fen/parse]") {
|
||||||
FEN f;
|
FEN f;
|
||||||
|
// Start game FEN
|
||||||
|
REQUIRE_NOTHROW(FENParser::Parse(
|
||||||
|
"rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"));
|
||||||
|
|
||||||
|
// Throw tests
|
||||||
|
REQUIRE_THROWS_AS(
|
||||||
|
FENParser::Parse(
|
||||||
|
"rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR z KQkq - 0 1"),
|
||||||
|
InvalidFEN);
|
||||||
|
REQUIRE_THROWS_AS(
|
||||||
|
FENParser::Parse(
|
||||||
|
"rnbqkbnr/ppppppp2/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"),
|
||||||
|
InvalidFEN);
|
||||||
|
REQUIRE_THROWS_AS(
|
||||||
|
FENParser::Parse(
|
||||||
|
"rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w pQkq - 0 1"),
|
||||||
|
InvalidFEN);
|
||||||
|
REQUIRE_THROWS_AS(
|
||||||
|
FENParser::Parse("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP w KQkq - 0 1"),
|
||||||
|
InvalidFEN);
|
||||||
|
REQUIRE_THROWS_AS(
|
||||||
|
FENParser::Parse(
|
||||||
|
"rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 1a 1"),
|
||||||
|
InvalidFEN);
|
||||||
|
|
||||||
|
// Tests
|
||||||
f = FENParser::Parse(
|
f = FENParser::Parse(
|
||||||
"p4p1p/p6p/8/QQQQQQQQ/kpkpkpkp/8/p1p1r1b1/7R b - a3 5 5");
|
"p4p1p/p6p/8/QQQQQQQQ/kpkpkpkp/8/p1p1r1b1/7R b - a3 5 5");
|
||||||
|
|
||||||
REQUIRE(f.board == "p p p"
|
REQUIRE(f.board == "p p p"
|
||||||
"p p"
|
"p p"
|
||||||
" "
|
" "
|
||||||
|
@ -89,3 +77,40 @@ TEST_CASE("Parse", "[fen/parse]") {
|
||||||
CHECK(f.black_castle_short == true);
|
CHECK(f.black_castle_short == true);
|
||||||
CHECK(f.black_castle_long == true);
|
CHECK(f.black_castle_long == true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Serializer", "[fen/serialize]") {
|
||||||
|
FEN f;
|
||||||
|
f.board = "p p p"
|
||||||
|
"p p"
|
||||||
|
" "
|
||||||
|
"QQQQQQQQ"
|
||||||
|
"kpkpkpkp"
|
||||||
|
" "
|
||||||
|
"p p r b "
|
||||||
|
" R";
|
||||||
|
REQUIRE(FENParser::Serialize(f) ==
|
||||||
|
"p4p1p/p6p/8/QQQQQQQQ/kpkpkpkp/8/p1p1r1b1/7R w KQkq - 0 1");
|
||||||
|
|
||||||
|
f.white_castle_short = false;
|
||||||
|
f.white_castle_long = false;
|
||||||
|
f.black_castle_short = false;
|
||||||
|
f.black_castle_long = false;
|
||||||
|
REQUIRE(FENParser::Serialize(f) ==
|
||||||
|
"p4p1p/p6p/8/QQQQQQQQ/kpkpkpkp/8/p1p1r1b1/7R w - - 0 1");
|
||||||
|
|
||||||
|
f.en_passant = "a3";
|
||||||
|
REQUIRE(FENParser::Serialize(f) ==
|
||||||
|
"p4p1p/p6p/8/QQQQQQQQ/kpkpkpkp/8/p1p1r1b1/7R w - a3 0 1");
|
||||||
|
|
||||||
|
f.player = true;
|
||||||
|
REQUIRE(FENParser::Serialize(f) ==
|
||||||
|
"p4p1p/p6p/8/QQQQQQQQ/kpkpkpkp/8/p1p1r1b1/7R b - a3 0 1");
|
||||||
|
|
||||||
|
f.halfmove = 5;
|
||||||
|
REQUIRE(FENParser::Serialize(f) ==
|
||||||
|
"p4p1p/p6p/8/QQQQQQQQ/kpkpkpkp/8/p1p1r1b1/7R b - a3 5 1");
|
||||||
|
|
||||||
|
f.move = 5;
|
||||||
|
REQUIRE(FENParser::Serialize(f) ==
|
||||||
|
"p4p1p/p6p/8/QQQQQQQQ/kpkpkpkp/8/p1p1r1b1/7R b - a3 5 5");
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue