Improve openings database code

This commit is contained in:
Loic Guegan 2023-01-16 14:55:48 +01:00
parent b7667d1d40
commit 3b11b9d4f3
19 changed files with 3709 additions and 28331 deletions

106
src/Openings.cpp Normal file
View file

@ -0,0 +1,106 @@
#include "Openings.hpp"
#include <sstream>
/**
* @brief Count the number of similar moves in between two lines
*
* @param m1
* @param m2
* @return int
*/
inline int CompareMainlines(const pgnp::HalfMove *m1,const pgnp::HalfMove *m2){
int count=0;
const pgnp::HalfMove *n1=m1;
const pgnp::HalfMove *n2=m2;
while(n1 && n2 != nullptr){
if(n1->move == n2->move)
count++;
else
break;
n1=n1->MainLine;
n2=n2->MainLine;
}
return count;
}
Openings::Openings() {
std::string tsv(a_tsv);
LoadVolume(tsv,&A);
tsv=std::string(b_tsv);
LoadVolume(tsv,&B);
tsv=std::string(c_tsv);
LoadVolume(tsv,&C);
tsv=std::string(d_tsv);
LoadVolume(tsv,&D);
tsv=std::string(e_tsv);
LoadVolume(tsv,&E);
}
void Openings::SearchOpening(const pgnp::HalfMove *moves,std::string &name, std::string &eco){
// Now goto the right volume
std::vector<Volume*> vols={&A,&B,&C,&D,&E};
if(moves->GetLength()>=2){
if(moves->move == "e4"){
if(moves->MainLine->move == "e6"|| moves->MainLine->move == "e5")
vols={&C};
else
vols={&B};
}
// TODO: Improve volumes filtering
}
// Then search for opening name:
int matches=0;
for(Volume *v: vols){
for(auto &entry: *v){
std::string &eco_=std::get<0>(entry);
std::string &name_=std::get<1>(entry);
std::string &pgn_=std::get<2>(entry);
pgnp::HalfMove *moves_=new pgnp::HalfMove();
pgnp::ParseSANMoves(pgn_,moves_);
int nmatches=CompareMainlines(moves,moves_);
if(nmatches>matches && nmatches == moves_->GetLength()){
name=name_;
eco=eco_;
matches=nmatches;
if(moves->GetLength()==moves_->GetLength()){
delete moves_;
return;
}
}
delete moves_;
}
}
}
void Openings::GuessOpening(const std::string &SANMoves, std::string &name, std::string &eco){
pgnp::HalfMove *m=new pgnp::HalfMove();
pgnp::ParseSANMoves(SANMoves,m);
SearchOpening(m,name,eco);
delete m;
}
void Openings::GuessOpening(const pgnp::HalfMove *moves, std::string &name, std::string &eco){
SearchOpening(moves,name,eco);
}
void Openings::LoadVolume(const std::string &tsv, Volume *vol){
std::istringstream f(tsv);
std::string line;
while (std::getline(f, line)) {
int eco=-1,name=-1,pgn=-1;
for(int i=0;i<line.size();i++){
if(line[i]=='\t'){
if(eco == -1)
eco=i-1;
else if(name==-1)
name=i-1;
else
pgn=i;
}
}
vol->push_back(std::make_tuple(
line.substr(0,eco+1),
line.substr(eco+2,name-eco-1),
line.substr(name+2,pgn-name)));
}
}

21
src/Openings.hpp Normal file
View file

@ -0,0 +1,21 @@
#pragma once
#include "binres/openings.hpp"
#include "pgnp.hpp"
/**
* @brief Guess the opening using the Lichess Opening Database
* See: https://github.com/lichess-org/chess-openings
*/
class Openings {
typedef std::vector<std::string> MoveList;
typedef std::vector<std::tuple<std::string,std::string,std::string>> Volume;
Volume A,B,C,D,E;
void SearchOpening(const pgnp::HalfMove *moves,std::string &name, std::string &eco);
void LoadVolume(const std::string &tsv, Volume *vol);
public:
void GuessOpening(const std::string &SANMoves, std::string &name, std::string &eco);
void GuessOpening(const pgnp::HalfMove *moves, std::string &name, std::string &eco);
Openings();
};

View file

@ -23,4 +23,5 @@ wxBitmap LoadPNG(std::string icon) {
return (wxBITMAP_PNG(mat));
}
return (wxNullBitmap);
}
}

View file

@ -10,4 +10,5 @@
#include "mat_png.hpp"
wxBitmap LoadPNG(std::string icon, wxSize size);
wxBitmap LoadPNG(std::string icon);
wxBitmap LoadPNG(std::string icon);

File diff suppressed because it is too large Load diff

View file

@ -131,6 +131,39 @@ bool Game::Play(std::string move,char promotion) {
return (false);
}
void Game::GetOpening(std::string &name,std::string &eco){
HalfMove *m=current;
if(m == nullptr)
m=moves;
if(m!=nullptr){
// First check if opening already set
std::string cname,ceco;
m->GetOpening(cname,ceco);
if(ceco.size()>0){
name=cname;
eco=ceco;
}else {
// If not, get the current move line (or first move)
// and try to guess opening
auto line=m->GetLine(); // Vector of HalfMove
std::string pgn;
int count=1;
for(int i=0;i<line.size();i++){
if(i%2==0){
pgn+=std::to_string(count)+".";
count+=1;
}
pgn+=line[i]->move +" ";
}
// If there is a line, try to guess the opening
if(pgn.size()>0){
wxGetApp().GetBook().GuessOpening(pgn,name,eco);
m->SetOpening(name,eco);
}
}
}
}
void Game::Previous() {
if (current != nullptr) {
current = current->GetParent();

View file

@ -35,6 +35,7 @@ public:
HalfMove *GetMoves();
std::string GetFen();
std::string GetResult();
void GetOpening(std::string &name,std::string &eco);
/// @brief Play the given absolute move
bool Play(std::string move,char promotion='q');
bool IsBlackToPlay();

View file

@ -20,6 +20,38 @@ HalfMove::~HalfMove() {
}
}
void HalfMove::SetOpening(const std::string &name, const std::string &eco){
HalfMove *m=this;
while(m!=nullptr){
m->opening=name;
m->eco=eco;
if(m->parent != nullptr && m->parent->mainline==m)
m=m->parent;
else
break;
}
}
void HalfMove::GetOpening(std::string &name, std::string &eco){
name=this->opening;
eco=this->eco;
}
std::vector<HalfMove *> HalfMove::GetLine(){
std::vector<HalfMove *> line;
HalfMove *m=this;
while(m!=nullptr){
line.push_back(m);
// Check if in a variation:
if(m->parent!=nullptr && m->parent->mainline!=m)
m=m->parent->parent; // Because we are in a variation
else
m=m->parent;
}
std::reverse(line.begin(), line.end());
return line;
}
HalfMove::HalfMove(HalfMove *m){
src=m->src;
dst=m->dst;

View file

@ -20,6 +20,8 @@ class HalfMove : public cgeditor::CGEHalfMove {
void BuildAndVerify(HalfMove *m, std::string fen);
/// @brief Store the source and destination square of the current move (mainly used for pieces animation)
std::string src,dst;
/// @brief Opening reach by that move while taking into account all the parents
std::string opening, eco;
public:
HalfMove(HalfMove *m);
@ -49,9 +51,13 @@ public:
HalfMove *GetParent();
HalfMove *GetMainline();
std::vector<HalfMove *> GetVariations();
/// @brief Retrieve the list of moves from the current one to the first one
std::vector<HalfMove *> GetLine();
std::map<char, std::uint8_t> GetLineCaptures();
/// @brief The opening name of current line
void SetOpening(const std::string &name, const std::string &eco);
/// @brief Getters for name and eco
void GetOpening(std::string &name, std::string &eco);
/// @brief Set parent of the current move
void SetParent(HalfMove *m);
std::string GetFen();

View file

@ -81,7 +81,6 @@ void GameTabLeftPanel::OnPlay(wxCommandEvent &event) {
ProcessEvent(event);
}
}
Notify(true); // Redraw event is move failed! Otherwise piece not resets to it initial position after dragging
}

View file

@ -11,7 +11,7 @@ GameTabRightPanel::GameTabRightPanel(wxFrame *parent, std::shared_ptr<Game> game
tags_list->InsertColumn(1, L"Value", wxLIST_FORMAT_LEFT, 500);
tagTextCtrl->SetHint("Tag");
valueTextCtrl->SetHint("Value");
opening_label->SetHint("Current opening");
RefreshTagsList();
// Bind events
@ -42,6 +42,7 @@ GameTabRightPanel::GameTabRightPanel(wxFrame *parent, std::shared_ptr<Game> game
Bind(wxEVT_KEY_UP, [p=this](wxKeyEvent &e){e.ResumePropagation(1);e.Skip();});
ApplyPreferences();
analyze_game_button->Disable();
}
void GameTabRightPanel::OnLiveAnalysis(wxCommandEvent &event) {
@ -156,6 +157,11 @@ void GameTabRightPanel::Notify() {
if (live_engine != nullptr) {
live_engine->SetFEN(game->GetFen());
}
// Update opening name
std::string opening,eco;
game->GetOpening(opening,eco);
if(eco.size()>0)
opening_label->SetValue(eco+": "+opening);
}
void GameTabRightPanel::ApplyPreferences() {

View file

@ -434,6 +434,9 @@ TabGameRightPanel::TabGameRightPanel( wxWindow* parent, wxWindowID id, const wxP
editor_page = new wxPanel( notebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL );
editor_page_sizer = new wxBoxSizer( wxVERTICAL );
opening_label = new wxTextCtrl( editor_page, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_READONLY );
editor_page_sizer->Add( opening_label, 0, wxALL|wxEXPAND, 5 );
editor_canvas_sizer = new wxBoxSizer( wxVERTICAL );

View file

@ -284,6 +284,7 @@ class TabGameRightPanel : public wxPanel
wxNotebook* notebook;
wxPanel* editor_page;
wxBoxSizer* editor_page_sizer;
wxTextCtrl* opening_label;
wxBoxSizer* editor_canvas_sizer;
wxStaticLine* m_staticline1;
wxStaticText* comment_label;

View file

@ -19,6 +19,8 @@ std::vector<TabInfos *> MyApp::ListTabInfos() {
return (tinfos);
}
Openings& MyApp::GetBook() { return Book; }
void MyApp::NewGame(TabInfos *tabsrc,std::shared_ptr<Game> g){
MainWindow *w=((MainWindow *)this->GetTopWindow());
TabInfos *i=w->NewGame(g);

View file

@ -6,6 +6,7 @@
#endif
#include "binres/binres.hpp"
#include "Openings.hpp"
#include "gui.h"
#include <memory>
#include <wx/app.h>
@ -72,10 +73,12 @@ public:
*/
class MyApp : public wxApp {
public:
Openings Book;
virtual bool OnInit();
std::vector<TabInfos *> ListTabInfos();
void NewGame(TabInfos *tabsrc,std::shared_ptr<Game> g);
void NewGame(std::shared_ptr<Game> g);
Openings& GetBook();
};
wxDECLARE_APP(MyApp);