Commit 2fc44fc8 authored by Loic Guegan's avatar Loic Guegan
Browse files

Add PGN parser unit tests

parent 37155903
Pipeline #218762629 passed with stage
in 8 minutes and 32 seconds
......@@ -56,6 +56,7 @@ file(COPY ${CMAKE_SOURCE_DIR}/assets DESTINATION ${CMAKE_BINARY_DIR}/)
set(COMPILE_TESTS ON CACHE BOOL "Should we compile unit tests ?")
if(COMPILE_TESTS)
enable_testing()
add_compile_definitions(TESTING)
add_subdirectory(${CMAKE_SOURCE_DIR}/tests/)
endif()
......
......@@ -12,10 +12,9 @@ namespace pgneditor {
class Move {
public:
std::string move;
std::string *move;
Move* next;
std::vector<Move*> variations;
Move(std::string move):move(move),next(nullptr){}
Move():move(""),next(nullptr){}
Move():move(nullptr),next(nullptr){}
};
}
......@@ -79,10 +79,10 @@ void PGNEditor::DrawLine(Move *line) {
int CurrentXBack=CurrentX;
CurrentX += state->counterW;
if (WhiteToPlay) {
DrawMove(line->move);
DrawMove(*line->move);
if(line->next!=nullptr){
CurrentX += state->colWidth;
DrawMove(line->next->move);
DrawMove(*line->next->move);
}
// Restore state
CurrentX = CurrentXBack;
......@@ -97,7 +97,7 @@ void PGNEditor::DrawLine(Move *line) {
} else {
CurrentX += state->colWidth;
DrawMove(line->move);
DrawMove(*line->move);
// Restore state
CurrentX = CurrentXBack;
CurrentY+=state->rowWidth;
......
......@@ -73,19 +73,17 @@ bool Game::Move(Coord src, Coord dst) {
}
bool Game::Move(ochess::model::Move *m){
bool ret=false;
if(m->isLongCastle || m->isShortCastle){
std::cout << "Castle" << std::endl;
bool ret=Castle(m->isLongCastle);
H.GetCurrentState()->SANMove=m->SANMove;
H.GetCurrentState()->editorMove.move=m->SANMove;
return(ret);
ret=Castle(m->isLongCastle);
}
std::cout << " dst: " << m->dst << " ispawn: " << m->isPawn << " piece: " << m->piece<< std::endl <<std::flush;
Coord src=A.FindSrc(m->dst, m->col,m->isPawn,m->piece);
bool ret=this->Move(src.GetXY(), m->dst);
H.GetCurrentState()->SANMove=m->SANMove;
H.GetCurrentState()->editorMove.move=m->SANMove;
else{
Coord src=A.FindSrc(m->dst, m->col,m->isPawn,m->piece);
ret=this->Move(src.GetXY(), m->dst);
}
//std::cout << " dst: " << m->dst << " ispawn: " << m->isPawn << " piece: " << m->piece<< std::endl <<std::flush;
if(ret)
H.ConfigureState(m);
return(ret);
}
......
......@@ -191,16 +191,15 @@ public:
}
ochess::model::Move* GetMoveLine(){
return(H.GetInitialState());
return(H.GetInitialState()->main);
}
void PlayLine(ochess::model::Move* line){
if(!Move(line)){
std::cout << "fail" << std::endl << std::flush;
std::cout << "Failed to play move in playline" << std::endl << std::flush;
}
if(line->variations.size()>0){
Previous();
for(auto move: line->variations){
std::cout << "play var!" << std::endl;
PlayLine(move);
}
Next();
......
......@@ -11,13 +11,13 @@ namespace model {
* @brief Node data structure of the game history tree.
*/
/* typedef struct GameState {
/// @brief Current node fen
std::string FEN;
/// @brief Parent node state
GameState *ParentState = NULL;
/// @brief Current node child
std::vector<GameState*> NextStates;
} GameState;
/// @brief Current node fen
std::string FEN;
/// @brief Parent node state
GameState *ParentState = NULL;
/// @brief Current node child
std::vector<GameState*> NextStates;
} GameState;
*/
/**
* @class History
......@@ -98,11 +98,32 @@ public:
* @param state FenState to synchronize with the new CurrentState
*/
void Erase(Board<PPiece> *board, FenState *state);
Move* GetInitialState(){
return(&(this->InitialState));
Move* GetInitialState() {
return (&(this->InitialState));
}
Move* GetCurrentState(){
return(this->CurrentState);
Move* GetCurrentState() {
return (this->CurrentState);
}
void ConfigureState(Move *m) {
this->CurrentState->SANMove = m->SANMove;
this->CurrentState->isCheck = m->isCheck;
this->CurrentState->Src = m->Src;
this->CurrentState->Dst = m->Dst;
this->CurrentState->dst = m->dst;
this->CurrentState->comment = m->comment;
this->CurrentState->piece = m->piece;
this->CurrentState->isPromotion = m->isPromotion;
this->CurrentState->isCheckMate = m->isCheckMate;
this->CurrentState->isPawn = m->isPawn;
this->CurrentState->isCapture = m->isCapture;
this->CurrentState->isLongCastle = m->isLongCastle;
this->CurrentState->isShortCastle = m->isShortCastle;
this->CurrentState->promoteTo = m->promoteTo;
this->CurrentState->col = m->col;
this->CurrentState->moveId = m->moveId;
memcpy(&this->CurrentState->clk,&m->clk,sizeof(char)*3);
memcpy(&this->CurrentState->egt,&m->egt,sizeof(char)*3);
memcpy(&this->CurrentState->emt,&m->emt,sizeof(char)*3);
}
};
......
......@@ -19,6 +19,7 @@ namespace ochess
isCheckMate(false), isPawn(false),
isCapture(false), isLongCastle(false), isShortCastle(false), promoteTo('?'), col('?'),
clk{0}, egt{0}, emt{0}, mct{0},piece('?'),moveId(1) {
editorMove.move=&SANMove;
}
void Move::dump()
{
......@@ -52,7 +53,6 @@ namespace ochess
}
void Move::expand(Move *m)
{
this->editorMove.move=this->SANMove;
if(this->main==nullptr){
this->main=m;
this->editorMove.next=&(m->editorMove);
......
......@@ -138,8 +138,10 @@ namespace ochess
char c = SANMove[i];
if (c == 'x')
move->isCapture = true;
if (c == '#')
if (c == '#'){
move->isCheckMate = true;
move->isCheck = true;
}
if (c == '+')
move->isCheck = true;
if (c == '=')
......@@ -295,6 +297,7 @@ namespace ochess
line->comment.push_back(read());
}
read(); // Read the }
boost::algorithm::trim(line->comment);
// Parse commands
unsigned long i;
unsigned long size = line->comment.size();
......
......@@ -6,6 +6,7 @@
#include <string.h>
#include "model/Game.hpp"
#include "model/Move.hpp"
#include <boost/algorithm/string/trim.hpp>
#define IS_SPACE(c) ((c) == ' ' || (c) == '\t' || (c) == '\n')
#define CHECK_SAN_SIZE(l) \
......@@ -46,6 +47,8 @@ namespace ochess
ochess::model::Move *parseLine(ochess::model::Move *parent);
/**
* @brief Parse a PGN comment that may contains clock informations
*
* Note that comment are trimmed (Leading and Trailling spaces are removed)
*/
void parseComment(ochess::model::Move *line);
/**
......@@ -85,6 +88,9 @@ namespace ochess
* The game will be available into @a game
*/
void parseNextGame();
bool IsOpen(){
return(file && file.is_open());
}
};
} // namespace pgn
......
......@@ -9,4 +9,15 @@ add_test(NAME Model COMMAND model_test)
add_executable(fake_engine engine/fake_engine.cpp)
add_executable(engine_test engine/engine.cpp)
target_link_libraries(engine_test LINK_PUBLIC ${Boost_LIBRARIES} libochess)
add_test(NAME Engine WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} COMMAND engine_test)
\ No newline at end of file
add_test(NAME Engine WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} COMMAND engine_test)
# Compile utils tests
file(GLOB_RECURSE UTILS_FILES ./utils/*.cpp)
file(GLOB_RECURSE PGN ./utils/*.pgn)
foreach(pgn ${PGN})
get_filename_component(basename ${pgn} NAME)
configure_file(${pgn} ${CMAKE_CURRENT_BINARY_DIR}/pgn/${basename} COPYONLY)
endforeach(pgn)
add_executable(utils_test ${UTILS_FILES})
target_link_libraries(utils_test LINK_PUBLIC ${Boost_LIBRARIES} libochess)
add_test(NAME Utils WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} COMMAND utils_test)
\ No newline at end of file
[Event "Pawn Structures: Chapter 11"]
[Site "https://lichess.org/study/EzH8y5L3/58UGS3YD"]
[Result "*"]
[UTCDate "2020.11.19"]
[UTCTime "19:34:55"]
[Variant "Standard"]
[ECO "C20"]
[Opening "King's Pawn Game: Napoleon Attack"]
[Annotator "https://lichess.org/@/manzerbredes"]
1. e4 e5 2. Qf3 Nc6 3. Bc4 a6 4. Qxf7# { 1-0 White wins by checkmate. } *
\ No newline at end of file
#define BOOST_TEST_MODULE Utils
#include <boost/test/included/unit_test.hpp>
#include "utils/pgn.hpp"
#include <iostream>
using namespace ochess;
#include <boost/algorithm/string/trim.hpp>
BOOST_AUTO_TEST_SUITE(PGN_TEST)
/**
* Count the number of child in a variation
*/
int GetVariationSize(Move *var){
if(var==nullptr)
return(0);
return(1+GetVariationSize(var->main));
}
/**
* Get the Nth child of a move line
*/
Move* GetMoveN(Move *var,int i){
if(var==nullptr)
return(nullptr);
if(i==0)
return(var);
return(GetMoveN(var->main,i-1));
}
BOOST_AUTO_TEST_CASE(test_pgn) {
pgn::PGN parser("./pgn/test.pgn");
BOOST_REQUIRE_MESSAGE(parser.IsOpen(), "Failed to open pgn file");
parser.parseNextGame();
Move *line = parser.game->GetMoveLine();
// Count moves
BOOST_REQUIRE_MESSAGE(GetVariationSize(line) == 24,
"Parsed PGN does not contains the right number of move.");
// Check random moves in the main line
BOOST_CHECK(line->SANMove == "e4");
BOOST_CHECK(line->main->SANMove == "c5");
BOOST_CHECK(GetMoveN(line,11)->SANMove == "d6");
BOOST_CHECK(GetMoveN(line,12)->SANMove == "Bc4");
BOOST_CHECK(GetMoveN(line,5)->SANMove == "Nd5");
BOOST_CHECK(GetMoveN(line,7)->SANMove == "cxd4");
BOOST_CHECK(GetMoveN(line,23)->SANMove == "Be7");
BOOST_CHECK(GetMoveN(line,22)->SANMove == "O-O");
BOOST_CHECK(GetMoveN(line,22)->isShortCastle);
BOOST_CHECK(!GetMoveN(line,22)->isLongCastle);
// Check variations
Move *v1,*v2,*v3;
Move* current = line;
int countMainLineMove = 1;
while (current->main != nullptr) {
switch (countMainLineMove) {
case 3:
BOOST_REQUIRE(current->variations.size() == 1);
BOOST_CHECK(current->variations[0]->SANMove=="d5");
v1=current->variations[0];
break;
case 6:
BOOST_REQUIRE(current->variations.size() == 1);
BOOST_CHECK(current->variations[0]->SANMove=="Nf3");
v2=current->variations[0];
break;
case 10:
BOOST_REQUIRE(current->variations.size() == 1);
BOOST_CHECK(current->variations[0]->SANMove=="Bc4");
v3=current->variations[0];
break;
}
countMainLineMove++;
current = current->main;
}
// Check Comment
BOOST_CHECK(line->comment.find("Sources:\n- https://fr.wikipe")!=std::string::npos);
BOOST_CHECK(v1->main->main->comment.find("In the Barmen")!=std::string::npos);
// Check variations
BOOST_CHECK(GetVariationSize(v1)==17);
BOOST_CHECK(GetVariationSize(v2)==19);
BOOST_CHECK(GetVariationSize(v3)==1);
BOOST_REQUIRE(GetMoveN(v1,5)->variations.size() ==1);
Move* vv1=GetMoveN(v1,5)->variations[0];
BOOST_REQUIRE(GetVariationSize(vv1->variations[0])==7);
BOOST_CHECK(vv1->SANMove=="e6");
BOOST_REQUIRE(vv1->variations.size()==1);
BOOST_REQUIRE(vv1->main->comment=="Preparing Bc4");
BOOST_REQUIRE(vv1->main->variations.size()==1);
BOOST_REQUIRE(vv1->variations[0]->SANMove=="Be2");
// Check for checks
BOOST_CHECK(GetMoveN(vv1->main->variations[0],4)->isCheck);
BOOST_CHECK(!GetMoveN(vv1->main->variations[0],4)->isCheckMate);
pgn::PGN parser2("./pgn/checkmate.pgn");
BOOST_REQUIRE_MESSAGE(parser2.IsOpen(), "Failed to open pgn file");
parser2.parseNextGame();
line = parser2.game->GetMoveLine();
BOOST_REQUIRE_MESSAGE(GetVariationSize(line) == 7,
"Parsed PGN does not contains the right number of move.");
BOOST_CHECK(GetMoveN(line,6)->isCheckMate);
BOOST_CHECK(GetMoveN(line,6)->isCheck);
}
BOOST_AUTO_TEST_SUITE_END()
[Event "Sicilian: Alapine"]
[Site "https://lichess.org/study/68xEhXW1/aA76MUje"]
[Result "*"]
[UTCDate "2020.10.04"]
[UTCTime "08:57:57"]
[Variant "Standard"]
[ECO "B22"]
[Opening "Sicilian Defense: Alapin Variation, Smith-Morra Declined"]
[Annotator "https://lichess.org/@/manzerbredes"]
1. e4 { Sources:
- https://fr.wikipedia.org/wiki/Variante_Alapine
- https://www.youtube.com/watch?v=VGP0qWscORM } 1... c5 2. c3 { Whites idea: Play d4 and on ..cxd5 there is cxd4
Blacks idea:
1) Threatening Nf6 and then ...Nxe4 thus forcing whites to play e5 (see Alekhine defense to known about blacks plan: they will try to attack d5 because of whites over expansion and blacks leading development)
2) e6 to prepare for d5
3) d5 then after exd5 ...Qxd5 white can't won a tempo on the queen since Nc3 is not possible
4) e5 to prevent d4 } 2... Nf6 (2... d5 3. exd5 Qxd5 { In the Barmen defense you always wait for blacks to capture on d4 } 4. d4 Nf6 5. Nf3 Bg4 (5... e6 6. Na3 { Preparing Bc4 } (6. Be2 Nc6 7. O-O cxd4 8. cxd4 Be7 9. Nc3) 6... Qd8 (6... Nc6 7. Nb5 Qd8 8. dxc5 Qxd1+ { This move is loosing for blacks } (8... Bxc5 9. Qxd8+ Kxd8) 9. Kxd1) 7. Nc2 (7. Nc4)) 6. Be2 e6 { Now this is possible (blacks light square bishop is out) } 7. h3 (7. O-O { This line is also fine }) 7... Bh5 8. O-O Nc6 9. Be3 cxd4 10. cxd4 Be7) 3. e5 Nd5 4. d4 (4. Nf3 Nc6 (4... d6 5. d4 cxd4 6. cxd4 Nc6 7. Bc4 Nb6 8. Bb5 dxe5 9. Nxe5 Bd7 10. Nxd7 Qxd7 (10... Nxd7 11. d5 { Much better for white (leading development so huge) } 11... Nce5 12. Be3 a6 13. Be2 Nf6 14. Nc3 g6 15. O-O) 11. O-O) 5. d4 cxd4 6. Bc4 (6. cxd4 d6 7. Bc4 Nb6 8. Bb5 dxe5 9. Nxe5 Bd7 10. Nxd7 Qxd7 11. Nc3 e6 12. O-O { Here whites play for the initiative beause they have an isolated queen' pawn so end game will be better for blacks }) 6... Nb6 7. Bb3 d5 8. exd6 Qxd6 9. O-O Be6 10. Bxe6 Qxe6 11. Nxd4 Nxd4 12. Qxd4 Rd8 13. Qh4) 4... cxd4 5. Nf3 Nc6 6. cxd4 (6. Bc4) 6... d6 7. Bc4 Nb6 8. Bb5 dxe5 9. Nxe5 Bd7 10. Nxd7 Qxd7 11. Nc3 e6 12. O-O Be7 *
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