Commit 7455d91a authored by Loic Guegan's avatar Loic Guegan
Browse files

Improve PGN parser

parent 7c67f121
Pipeline #211704247 passed with stage
in 7 minutes and 56 seconds
......@@ -40,8 +40,10 @@ file(GLOB_RECURSE MODEL ${SRC}/model/*.cpp)
file(GLOB_RECURSE ENGINE ${SRC}/engine/*.cpp)
# Tools Module
file(GLOB_RECURSE TOOLS ${SRC}/tools/*.cpp)
# PGN Module
file(GLOB_RECURSE PGN ${SRC}/pgn/*.cpp)
# Combine for reusability
add_library(libochess OBJECT ${MODEL} ${TOOLS} ${ENGINE})
add_library(libochess OBJECT ${MODEL} ${TOOLS} ${ENGINE} ${PGN})
# GUI Module
file(GLOB_RECURSE GUI ${SRC}/gui/*.cpp)
# Generate Executable
......
......@@ -47,7 +47,7 @@ int main(int argc, char *argv[])
(void)argc;
(void)argv;
ochess::pgn::PGN p("/home/loic/bbb.pgn");
ochess::pgn::PGN p("/home/loic/test.pgn");
return 0;
}
......
#include "Game.hpp"
namespace ochess
{
namespace pgn
{
void freeMoveLine(MoveNode *l)
{
if (l->main == nullptr && l->variations.size() == 0)
free(l);
if (l->main != nullptr)
freeMoveLine(l->main);
for (auto v : l->variations)
freeMoveLine(v);
}
//----- MoveNode -----
MoveNode *Game::getLine()
{
return (this->line);
}
MoveNode::MoveNode() : parent(nullptr),main(nullptr),isCheck(false), isPromotion(false),
isCheckMate(false), isPawn(false),
isCapture(false), isLongCastle(false), isShortCastle(false), promoteTo('?'), col('?'),
clk{0}, egt{0}, emt{0}, mct{0} {}
void MoveNode::dump()
{
cout << "isCheck:" << isCheck << " isPromotion:" << isPromotion << " isCheckMate:" << isCheckMate << " isPawn:" << isPawn << " isCapture:" << isCapture << " isLongCastle:" << isLongCastle << " isShortCastle:" << isShortCastle << " dst:" << dst << " promoteTo:" << promoteTo << " col:" << col << " clk:" << clk[0] << ":" << clk[1] << ":" << clk[2];
}
//----- Game -----
void Game::dump(MoveNode *l)
{
if (l != nullptr)
{
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);
}
}
Game::Game() : isDraw(false), isWhiteWin(false), WhiteClock{0}, BlackClock{0} {}
vector<string> Game::listTag()
{
std::vector<string> keys;
for (auto kv : tags)
keys.push_back(kv.first);
return (keys);
}
Game::~Game()
{
freeMoveLine(this->line);
}
} // namespace pgn
} // namespace ochess
\ No newline at end of file
#ifndef PGN_GAME_HPP
#define PGN_GAME_HPP
#include <unordered_map>
#include <vector>
#include <string>
#include <iostream>
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
{
class Move
/**
* @brief Node of a game Abstract Syntax Tree (AST)
*/
class MoveNode
{
public:
public:
MoveNode *parent;
MoveNode *main;
vector<MoveNode *> variations;
string SANMove;
char piece;
string dst;
string comment;
char piece;
bool isCheck;
bool isPromotion;
bool isCheckMate;
bool isPawn;
bool isCapture;
bool longCastle;
bool shortCastle;
bool isLongCastle;
bool isShortCastle;
char promoteTo;
char col;
Move *parent;
Move *main;
vector<Move*> variations;
string comment;
short clk[3];
short egt[3];
short emt[3];
short mct[3];
short moveId;
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;
}
/**
* @brief Initialize a MoveNode (set pointers values etc.)
*/
MoveNode();
void dump();
};
/// @brief Free a game AST
void freeMoveLine(MoveNode *l);
/**
* @brief Container for a parsed game.
* Most of the attribute are public to simplify access to the members.
*/
class Game
{
private:
unordered_map<string, string> tags;
Move *line;
short deep=0;
/// @brief Used by the dump function
short deep = 0;
public:
bool draw;
bool whiteWin;
void dumpLine(){
dump(line);
}
void dump(Move *l){
if(l!=nullptr){
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);
}
}
void addTag(string key, string value);
string getTag(string key);
MoveNode *line;
bool isDraw;
bool isWhiteWin;
/// @brief Cf the "WhiteClock" PGN tag
short WhiteClock[3];
/// @brief Cf the "BlackClock" PGN tag
short BlackClock[3];
unordered_map<string, string> tags;
Game();
/**
* @brief A special attention should be paied at freeing the AST memory
*/
~Game();
void dump(MoveNode *l);
vector<string> listTag();
Move *getLine();
void setLine(Move *line);
Game():draw(false),whiteWin(false){}
MoveNode *getLine();
};
void Game::addTag(string key, string value)
{
this->tags[key] = value;
}
string Game::getTag(string key){
return(tags[key]);
}
vector<string> Game::listTag()
{
std::vector<string> keys;
for (auto kv : tags)
keys.push_back(kv.first);
return (keys);
}
Move* Game::getLine(){
return(this->line);
}
void Game::setLine(Move *line){
this->line=line;
}
} // namespace pgn
} // namespace ochess
#endif
\ No newline at end of file
......@@ -7,11 +7,16 @@
#include "Game.hpp"
#define IS_SPACE(c) ((c) == ' ' || (c) == '\t' || (c) == '\n')
#define CHECK_SAN_SIZE(l) {if(SANSize < l){abort("Invalide SAN move",-SANSize);}}
#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
// TODO: Allow for clock management
using namespace std;
namespace ochess
......@@ -25,28 +30,31 @@ namespace ochess
long line;
long col;
char read();
void parse();
bool done;
void parseNextGame();
vector<Game> games;
Move *parseLine(Move *parent);
void parseComment(Move *line);
MoveNode *parseLine(MoveNode *parent);
void parseComment(MoveNode *line);
void skipSpaces();
string readWord();
void abort(string reason, short colOffset);
void parseMove(string SANMove, Move *move);
void parseMove(string SANMove, MoveNode *move);
void extractClock(string data, short *clock);
Game *current;
public:
PGN(string file_path);
~PGN();
};
void PGN::abort(string reason, short colOffset=0){
cerr << "Error Ln " << line << ",Col " << col+colOffset << ": " << reason << endl;
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)
PGN::PGN(string file_path) : line(1), done(true)
{
file.open(file_path);
parse();
parseNextGame();
}
PGN::~PGN()
......@@ -59,12 +67,14 @@ namespace ochess
char c = file.get();
if (c == EOF) // We should never reach EOF in this function
abort("Unexpected end of file");
if(c=='\n'){
if (c == '\n')
{
line++;
col=1;
}else
col = 1;
}
else
col++;
return c;
}
void PGN::skipSpaces()
......@@ -79,8 +89,13 @@ namespace ochess
word.push_back(read());
return (word);
}
void PGN::parse()
void PGN::parseNextGame()
{
if (NEXTCHAR() == EOF)
{
done = true;
return;
}
current = new Game();
while (NEXTCHAR() != EOF)
{
......@@ -107,39 +122,63 @@ namespace ochess
}
c = read();
}
current->addTag(tagKey, tagValue);
current->tags[tagKey]=tagValue;
if (tagKey == "WhiteClock")
extractClock(tagValue, current->WhiteClock);
else if (tagKey == "BlackClock")
extractClock(tagValue, current->BlackClock);
}
else if (NEXTCHAR() == '1')
{
//g->setLine(parseLine(nullptr));
//for (auto kv : g->listTag())
// cout << kv << " ----- " << g->getTag(kv) << endl;
current->dump(parseLine(nullptr));
exit(0);
current = new Game(); // Now we start parsing a new game
current->line=parseLine(nullptr);
current->dump(current->line);
break; // First game parsed to leave
}
if (NEXTCHAR() != EOF)
read();
}
free(current); // Since their is no more game
}
void PGN::extractClock(string data, short *clock)
{
string values[3];
short curValue = 0;
unsigned long i = 0;
while (i < data.size())
{
char c = data[i];
if (c >= '0' && c <= '9')
values[curValue].push_back(c);
else if (c == ':')
curValue++;
i++;
}
if (curValue == 2)
{
clock[0] = stoi(values[0]);
clock[1] = stoi(values[1]);
clock[2] = stoi(values[2]);
}
}
void PGN::parseMove(string SANMove, Move *move)
void PGN::parseMove(string SANMove, MoveNode *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;
if (SANMove == "O-O")
move->isShortCastle = true;
else if (SANMove == "O-O-O")
{
move->isLongCastle = true;
}
else
{
CHECK_SAN_SIZE(2);
// Setup bool
for (auto c : SANMove)
for (short i = 0; i < (short)SANMove.size(); i++)
{
char c = SANMove[i];
if (c == 'x')
move->isCapture = true;
if (c == '#')
......@@ -147,7 +186,11 @@ namespace ochess
if (c == '+')
move->isCheck = true;
if (c == '=')
{
move->isPromotion = true;
CHECK_SAN_SIZE(i + 1);
move->promoteTo = SANMove[i + 1];
}
}
if (SANMove[0] >= 'A' && SANMove[0] <= 'Z')
move->piece = SANMove[0];
......@@ -171,38 +214,43 @@ namespace ochess
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
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->col = SANMove[1];
move->dst.push_back(SANMove[3]);
move->dst.push_back(SANMove[4]);
}
else{
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'){
else
{
if (SANMove[2] >= '0' && SANMove[2] <= '9')
{
CHECK_SAN_SIZE(3);
move->dst.push_back(SANMove[1]);
move->dst.push_back(SANMove[2]);
move->dst.push_back(SANMove[2]);
}
else{ // If not this mean that there is a column symbol
else
{ // If not this mean that there is a column symbol
CHECK_SAN_SIZE(4);
move->col=SANMove[1];
move->col = SANMove[1];
move->dst.push_back(SANMove[2]);
move->dst.push_back(SANMove[3]);
move->dst.push_back(SANMove[3]);
}
}
}
}
}
Move *PGN::parseLine(Move *parent)
MoveNode *PGN::parseLine(MoveNode *parent)
{
Move *root = new Move();
Move *l = root;
MoveNode *root = new MoveNode();
MoveNode *l = root;
l->parent = parent;
while (NEXTCHAR() != EOF && NEXTCHAR() != '*' && NEXTCHAR() != ')')
{
......@@ -244,7 +292,7 @@ namespace ochess
}
// Create next Move
Move *newMove = new Move();
MoveNode *newMove = new MoveNode();
newMove->parent = l;
newMove->moveId = l->moveId; // By default same of id of the parent
l->main = newMove;
......@@ -254,20 +302,20 @@ namespace ochess
// 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
current->isWhiteWin = true;
current->isDraw = false; // It is important since variation can modify the game results
break;
}
else if (move == "0-1")
{
current->whiteWin = false;
current->draw = false;
current->isWhiteWin = false;
current->isDraw = false;
break;
}
else if (move == "1/2-1/2")
{
current->whiteWin = false;
current->draw = true;
current->isWhiteWin = false;
current->isDraw = true;
break;
}
}
......@@ -282,7 +330,7 @@ namespace ochess
return (root);
}
void PGN::parseComment(Move *line)
void PGN::parseComment(MoveNode *line)
{
read(); // Read the {
while (NEXTCHAR() != '}')
......@@ -290,6 +338,46 @@ namespace ochess
line->comment.push_back(read());
}
read(); // Read the }
// Parse commands
unsigned long i;
unsigned long size = line->comment.size();
vector<unsigned long int> cmds;
for (i = 0; i < size; i++)
{
if ((i + 2) < size)
{
char c = line->comment[i];
char nextChar = line->comment[i + 1];
if (c == '[' && nextChar == '%')
cmds.push_back(i + 2);
}
}
// Read cmds
for (auto i : cmds)
{
if ((i + 4) < size)
{
string clkCmd = line->comment.substr(i, 3);
if (clkCmd == "clk" || clkCmd == "egt" || clkCmd == "emt" || clkCmd == "mct")
{
string clockData;
unsigned long j = i + 4;
while (j < size && line->comment[j] != ']')
{
clockData.push_back(line->comment[j]);
j++;
}
if (clkCmd == "clk")
extractClock(clockData, line->clk);
else if (clkCmd == "egt")
extractClock(clockData, line->egt);
else if (clkCmd == "emt")
extractClock(clockData, line->emt);
else if (clkCmd == "mct")
extractClock(clockData, line->mct);
}
}
}
}
} // namespace pgn
......
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