Commit 7c67f121 authored by Loic Guegan's avatar Loic Guegan
Browse files

Update PGN parser

parent 01e25a03
Pipeline #211515979 passed with stage
in 7 minutes and 27 seconds
......@@ -47,7 +47,7 @@ int main(int argc, char *argv[])
(void)argc;
(void)argv;
ochess::pgn::PGN p("/home/loic/test.pgn");
ochess::pgn::PGN p("/home/loic/bbb.pgn");
return 0;
}
......
......@@ -6,34 +6,78 @@
using namespace std;
// TODO: Change move to a struct and store the move state in
// a string (each char represent a attribute of move) for performances reasons
// Then the user can use this string as parameters of a class constructor that parse it
namespace ochess
{
namespace pgn
{
typedef struct Line
class Move
{
Line *parent;
Line *main;
vector<Line*> variations;
char move[10];
public:
string SANMove;
char piece;
string dst;
bool isCheck;
bool isPromotion;
bool isCheckMate;
bool isPawn;
bool isCapture;
bool longCastle;
bool shortCastle;
char promoteTo;
char col;
Move *parent;
Move *main;
vector<Move*> variations;
string comment;
short moveId;
} Line;
Move():isCheck(false),isPromotion(false),
isCheckMate(false),isPawn(false),
isCapture(false),longCastle(false),shortCastle(false),col('?'){}
void dump(){
cout << "isCheck:"<<isCheck<<
" isPromotion:" << isPromotion <<
" isCheckMate:" << isCheckMate <<
" isPawn:" << isPawn <<
" isCapture:" << isCapture<<
" longCastle:" << longCastle<<
" shortCastle:" << shortCastle<<
" dst:" << dst <<
" col:" << col;
}
};
class Game
{
private:
unordered_map<string, string> tags;
Line *line;
Move *line;
short deep=0;
public:
bool draw;
bool whiteWin;
void dumpLine(){
dump(line);
}
void dump(Line *l){
void dump(Move *l){
if(l!=nullptr){
cout << "Move id:" << l->move << endl;
for(int i=0;i<deep;i++)
cout << " ";
cout << "|" << l->SANMove << " ";
l->dump();
cout << endl;
for(auto var: l->variations){
deep++;
dump(var);
deep--;
}
dump(l->main);
}
}
......@@ -41,8 +85,9 @@ namespace ochess
string getTag(string key);
vector<string> listTag();
Line *getLine();
void setLine(Line *line);
Move *getLine();
void setLine(Move *line);
Game():draw(false),whiteWin(false){}
};
void Game::addTag(string key, string value)
......@@ -61,10 +106,10 @@ namespace ochess
return (keys);
}
Line* Game::getLine(){
Move* Game::getLine(){
return(this->line);
}
void Game::setLine(Line *line){
void Game::setLine(Move *line){
this->line=line;
}
......
......@@ -6,7 +6,12 @@
#include <string.h>
#include "Game.hpp"
#define IS_SPACE(c) ((c) == ' ' || (c) == '\t')
#define IS_SPACE(c) ((c) == ' ' || (c) == '\t' || (c) == '\n')
#define CHECK_SAN_SIZE(l) {if(SANSize < l){abort("Invalide SAN move",-SANSize);}}
#define NEXTCHAR() (file.peek())
// TODO: in parseMove handle promotion piece
// TODO: Not parse in constructor and allow to parse game by game (not all the PGN) for very large files
using namespace std;
namespace ochess
......@@ -17,21 +22,28 @@ namespace ochess
{
private:
ifstream file;
long line;
long col;
char read();
char nextChar();
void parse();
vector<Game> games;
Line *parseLine(Line *parent);
void parseComment(Line *line);
Move *parseLine(Move *parent);
void parseComment(Move *line);
void skipSpaces();
string readWord();
void abort(string reason, short colOffset);
void parseMove(string SANMove, Move *move);
Game *current;
public:
PGN(string file_path);
~PGN();
};
PGN::PGN(string file_path)
void PGN::abort(string reason, short colOffset=0){
cerr << "Error Ln " << line << ",Col " << col+colOffset << ": " << reason << endl;
exit(1);
}
PGN::PGN(string file_path):line(1)
{
file.open(file_path);
parse();
......@@ -45,36 +57,35 @@ namespace ochess
char PGN::read()
{
char c = file.get();
if (c == EOF)
{ // We should never reach EOF in this function
cout << "Unexpected end of file" << endl;
exit(0);
}
if (c == EOF) // We should never reach EOF in this function
abort("Unexpected end of file");
if(c=='\n'){
line++;
col=1;
}else
col++;
return c;
}
char PGN::nextChar()
{
return file.peek();
}
void PGN::skipSpaces()
{
while (IS_SPACE(nextChar()))
while (IS_SPACE(NEXTCHAR()))
read();
}
string PGN::readWord()
{
string word;
while (!IS_SPACE(nextChar()))
while (!IS_SPACE(NEXTCHAR()))
word.push_back(read());
return (word);
}
void PGN::parse()
{
Game *g = new Game();
while (nextChar() != EOF)
current = new Game();
while (NEXTCHAR() != EOF)
{
if (nextChar() == '[')
if (NEXTCHAR() == '[')
{
read(); // Read the [
char c = read();
......@@ -84,7 +95,7 @@ namespace ochess
{
if (!valueStart)
{
if (c == ' ')
if (IS_SPACE(c))
valueStart = true;
else
tagKey.push_back(c);
......@@ -96,39 +107,114 @@ namespace ochess
}
c = read();
}
g->addTag(tagKey, tagValue);
current->addTag(tagKey, tagValue);
}
else if (nextChar() == '1')
else if (NEXTCHAR() == '1')
{
//g->setLine(parseLine(nullptr));
//for (auto kv : g->listTag())
// cout << kv << " ----- " << g->getTag(kv) << endl;
g->dump(parseLine(nullptr));
current->dump(parseLine(nullptr));
exit(0);
g = new Game(); // Now we start parsing a new game
current = new Game(); // Now we start parsing a new game
}
if (nextChar() != EOF)
if (NEXTCHAR() != EOF)
read();
}
free(g); // Since their is no more game
free(current); // Since their is no more game
}
Line *PGN::parseLine(Line *parent)
void PGN::parseMove(string SANMove, Move *move)
{
move->SANMove = SANMove;
short SANSize = SANMove.size(); // Used by CHECK_SAN_SIZE directive
if(SANMove=="O-O")
move->shortCastle=true;
else if (SANMove=="O-O-O"){
move->longCastle=true;
}
else
{
CHECK_SAN_SIZE(2);
// Setup bool
for (auto c : SANMove)
{
if (c == 'x')
move->isCapture = true;
if (c == '#')
move->isCheckMate = true;
if (c == '+')
move->isCheck = true;
if (c == '=')
move->isPromotion = true;
}
if (SANMove[0] >= 'A' && SANMove[0] <= 'Z')
move->piece = SANMove[0];
else
move->isPawn = true;
// Parse remaining
if (move->isPawn && move->isCapture)
{
CHECK_SAN_SIZE(4);
move->col = SANMove[0];
move->dst.push_back(SANMove[2]);
move->dst.push_back(SANMove[3]);
}
else if (move->isPawn)
{
move->dst.push_back(SANMove[0]);
move->dst.push_back(SANMove[1]);
}
else
{ // It is a piece :)
move->piece = SANMove[0];
if (move->isCapture)
{
if(SANMove[1]!='x'){ // if not x it is the char of a column after the piece name
CHECK_SAN_SIZE(5);
move->col=SANMove[1];
move->dst.push_back(SANMove[3]);
move->dst.push_back(SANMove[4]);
}
else{
CHECK_SAN_SIZE(4);
move->dst.push_back(SANMove[2]);
move->dst.push_back(SANMove[3]);
}
}
else{
if(SANMove[2] >='0' && SANMove[2] <='9'){
CHECK_SAN_SIZE(3);
move->dst.push_back(SANMove[1]);
move->dst.push_back(SANMove[2]);
}
else{ // If not this mean that there is a column symbol
CHECK_SAN_SIZE(4);
move->col=SANMove[1];
move->dst.push_back(SANMove[2]);
move->dst.push_back(SANMove[3]);
}
}
}
}
}
Move *PGN::parseLine(Move *parent)
{
Line *root = new Line;
Line *l = root;
Move *root = new Move();
Move *l = root;
l->parent = parent;
while (nextChar() != EOF && nextChar() != '*' && nextChar() != ')')
while (NEXTCHAR() != EOF && NEXTCHAR() != '*' && NEXTCHAR() != ')')
{
skipSpaces(); // Goto next char
// Read move number
if (nextChar() >= '0' && nextChar() <= '9')
if (NEXTCHAR() >= '0' && NEXTCHAR() <= '9')
{
string moveId;
while (nextChar() != '.')
while (NEXTCHAR() != '.')
moveId.push_back(read());
l->moveId = stoi(moveId);
while (nextChar() == '.')
while (NEXTCHAR() == '.')
{ // Read the mandatory .
read();
}
......@@ -137,22 +223,20 @@ namespace ochess
// Read the move
string move;
while(!IS_SPACE(nextChar()) && nextChar()!=')')
while (!IS_SPACE(NEXTCHAR()) && NEXTCHAR() != ')')
move.push_back(read());
strcpy(l->move, move.c_str()); // Can leads to segfault if l.move is not large enough
parseMove(move, l);
skipSpaces();
// cout << l->moveId<< endl;
// cout << l->move<< endl;
// Check if there is a comment
if (nextChar() == '{')
if (NEXTCHAR() == '{')
{
parseComment(l);
skipSpaces();
}
// Read all variations
while (nextChar() == '(')
while (NEXTCHAR() == '(')
{
read(); // Skip parenthesis for the parseLine function
l->variations.push_back(parseLine(l));
......@@ -160,27 +244,48 @@ namespace ochess
}
// Create next Move
Line *newMove = new Line;
Move *newMove = new Move();
newMove->parent = l;
newMove->moveId = l->moveId; // By default same of id of the parent
l->main = newMove;
l = newMove;
// Just in case even if variations modify the game result (recursive call)
// the main line will still be set at the end (but that should not append according to PGN specification)
if (move == "1-0")
{
current->whiteWin = true;
current->draw = false; // It is important since variation can modify the game results
break;
}
else if (move == "0-1")
{
current->whiteWin = false;
current->draw = false;
break;
}
else if (move == "1/2-1/2")
{
current->whiteWin = false;
current->draw = true;
break;
}
}
if (root != l)
{// Last created move is not usefull anymore
{ // Last created move is not usefull anymore
l->parent->main = nullptr;
free(l);
}
skipSpaces();
if (nextChar() == ')') // Read the last parenthesis of a variation
if (NEXTCHAR() == ')') // Read the last parenthesis of a variation
read();
return (root);
} // namespace pgn
void PGN::parseComment(Line *line)
}
void PGN::parseComment(Move *line)
{
read(); // Read the {
while (nextChar() != '}')
while (NEXTCHAR() != '}')
{
line->comment.push_back(read());
}
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment