From e069a29f99dfb6e1139f47f3bb291902752d1283 Mon Sep 17 00:00:00 2001
From: Loic Guegan <manzerbredes@mailbox.org>
Date: Tue, 10 Jan 2023 11:14:25 +0100
Subject: [PATCH] Debug animations, LiveEngine and improve arrow/squares
 highlights

---
 TODO.md                                       |  2 +-
 src/MainWindow.cpp                            |  4 +-
 src/game_tab/left_panel/GameTabLeftPanel.cpp  | 39 ++++----
 src/game_tab/left_panel/GameTabLeftPanel.hpp  |  2 +-
 src/game_tab/left_panel/board/BoardCanvas.cpp | 88 ++++++++-----------
 src/game_tab/left_panel/board/BoardCanvas.hpp | 20 ++++-
 src/game_tab/right_panel/LiveEngineDialog.cpp | 34 ++++---
 7 files changed, 95 insertions(+), 94 deletions(-)

diff --git a/TODO.md b/TODO.md
index 4496664..bb40991 100644
--- a/TODO.md
+++ b/TODO.md
@@ -2,7 +2,7 @@
 
 ## Before releasing v0.1.0
  - [ ] Implement pawn promotions in BoardCanvas
- - [ ] Debug animations (have a more reliable approach (can segfault when clicking on variations in the editor))
+ - [x] Debug animations (have a more reliable approach (can segfault when clicking on variations in the editor))
  - [x] In BoardCanvas search for a workaround of the dynamic allocation of adata.buffer (on canvas resize)
  - [x] Bind the chess game editor settings to EditorPrefs.hpp
  - [x] Ask before closing MainWindow/Tabs if anything is not saved
diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp
index 6202889..7a8c27f 100644
--- a/src/MainWindow.cpp
+++ b/src/MainWindow.cpp
@@ -59,8 +59,8 @@ MainWindow::MainWindow()
 
 
   // Temporary TO REMOVE JUST FOR TESTS:
-  /*BaseTab *bt = new BaseTab((wxFrame *)notebook, "/home/loic/pgn/wijk_2003_annotated.pgn");
-  this->AddPage(bt,bt);*/
+  //BaseTab *bt = new BaseTab((wxFrame *)notebook, "/home/loic/pgn/wijk_2003_annotated.pgn");
+  //this->AddPage(bt,bt);
 }
 
 void MainWindow::OnAuiNotebookPageCheck(wxAuiNotebookEvent& event){
diff --git a/src/game_tab/left_panel/GameTabLeftPanel.cpp b/src/game_tab/left_panel/GameTabLeftPanel.cpp
index 5a7877d..d034115 100644
--- a/src/game_tab/left_panel/GameTabLeftPanel.cpp
+++ b/src/game_tab/left_panel/GameTabLeftPanel.cpp
@@ -72,14 +72,15 @@ void GameTabLeftPanel::OnPlay(wxCommandEvent &event) {
 }
 
 void GameTabLeftPanel::SetEngineArrows(std::vector<std::string> arrows){
-  engine_arrows=arrows;
-  int min_size=5, max_size=80;
-  int steps=(max_size-min_size)/arrows.size();
-  int current_size=max_size;
-  for(std::string &arrow:engine_arrows){
-    wxLogDebug("%s",arrow);
-    arrow+="#000000%"+std::to_string(current_size);
-    current_size-=steps;
+  engine_arrows.clear();
+  float scale=1;
+  unsigned char color=0;
+  for(auto const &arrow:arrows){
+    std::string src=arrow.substr(0,2);
+    std::string dst=arrow.substr(2,2);
+    engine_arrows.push_back({src,dst,wxColour(color,color,color),scale});
+    scale=std::max(0.1,scale-0.25);
+    color=std::min(255,color+70);
   }
   Notify(true);
 }
@@ -94,16 +95,14 @@ void GameTabLeftPanel::Notify(bool skip_animation) {
   // Update capture and check if we should to animations during moves change:
   if (m){
     captures = m->GetLineCaptures();
-    if(!m->IsVariation()){ // Animations can lead to segfault when clicking on CGEditor move
-      if(m->HasParent(last_move)){
-        m->GetAbsoluteMove(src,dst);
-        animate=true;
-      }else if(m->HasChild(last_move)){
-        // Accessing last_move here is safe since it is still
-        // in the tree of moves (since HasChild found it so not deleted)
-        last_move->GetAbsoluteMove(dst,src);
-        animate=true;
-      }
+    if(m->HasParent(last_move) && !m->IsVariation()){
+      m->GetAbsoluteMove(src,dst);
+      animate=true;
+    }else if(m->HasChild(last_move) && !last_move->IsVariation()){ // call to IsVariation is safe because of HasChild() before! cf below
+      // Accessing last_move here is safe since it is still
+      // in the tree of moves (since HasChild found it so not deleted)
+      last_move->GetAbsoluteMove(dst,src);
+      animate=true;
     }
   } else if(game->GetNextMove()){ // First move animation
     HalfMove *next=game->GetNextMove();
@@ -128,8 +127,8 @@ void GameTabLeftPanel::Notify(bool skip_animation) {
     std::string src_hl, dst_hl;
     m->GetAbsoluteMove(src_hl,dst_hl);
     if(src_hl.size()>0){ // Just in case
-      gs.squares_hl.push_back(src_hl+"d");
-      gs.squares_hl.push_back(dst_hl+"a");
+      gs.squares_hl.push_back({src_hl,wxColour(255,190,190)});
+      gs.squares_hl.push_back({dst_hl,wxColour(255,100,100)});
     }
   }
   if(skip_animation || !animate){
diff --git a/src/game_tab/left_panel/GameTabLeftPanel.hpp b/src/game_tab/left_panel/GameTabLeftPanel.hpp
index 2dec70d..6bd6b38 100644
--- a/src/game_tab/left_panel/GameTabLeftPanel.hpp
+++ b/src/game_tab/left_panel/GameTabLeftPanel.hpp
@@ -13,7 +13,7 @@ class GameTabLeftPanel : public TabGameLeftPanel {
   BoardCanvas *board_canvas;
   bool repeat;
   HalfMove *last_move;
-  std::vector<std::string> engine_arrows;
+  std::vector<GameState::Arrow> engine_arrows;
 
 public:
   GameTabLeftPanel(wxFrame *parent, std::shared_ptr<Game> game);
diff --git a/src/game_tab/left_panel/board/BoardCanvas.cpp b/src/game_tab/left_panel/board/BoardCanvas.cpp
index 9c80196..9294006 100644
--- a/src/game_tab/left_panel/board/BoardCanvas.cpp
+++ b/src/game_tab/left_panel/board/BoardCanvas.cpp
@@ -18,9 +18,10 @@ BoardCanvas::BoardCanvas(wxFrame *parent)
   valid_drag = false;
   arrow_drag = false;
   arrows_offset = t->GetSquaresSizes()/2.5;
+  arrow_thickness = t->GetSquaresSizes()/1.8;
   // Init animation data
   adata.duration=100;
-  adata.duration_fast=100;
+  adata.duration_fast=50;
   adata.fps=60;
   adata.buffer=new wxBitmap(500,500,32);
   adata.animate=false;
@@ -230,30 +231,19 @@ void BoardCanvas::DrawBoard(wxDC &dc) {
 
       // Draw highlighted squares
       for(int i=0;i<(gs.squares_hl.size()+squares_hl.size());i++){
-          const std::string &s=i<gs.squares_hl.size() ? gs.squares_hl[i] : squares_hl[i-gs.squares_hl.size()];
-          std::uint8_t sfile = s[0]-'a';
-          std::uint8_t srank = s[1]-'1';
+          const GameState::Square &s=i<gs.squares_hl.size() ? gs.squares_hl[i] : squares_hl[i-gs.squares_hl.size()];
+          std::uint8_t sfile = s.square[0]-'a';
+          std::uint8_t srank = s.square[1]-'1';
           if (!black_side) {
             srank = 7 - srank;
             sfile = 7 - sfile;
           }
           if(srank == rank && sfile==file){
-            char type='a';
-            if(s.size()>2)
-              type=s[2];
-            // Default highlight (type='a' or something else not supported)
             dc.SetPen(wxNullPen);
-            dc.SetBrush(wxColour(255, 102, 122));
-            if(type=='b')
-              dc.SetBrush(wxColour(120, 255, 102));
-            else if(type=='c')
-              dc.SetBrush(wxColour(102, 196, 255));
-            else if(type=='d')
-              dc.SetBrush(wxColour(255, 204, 213));
-            else if(type=='e')
-              dc.SetBrush(wxColour(220, 255, 204));
-            else if(type=='f')
-              dc.SetBrush(wxColour(204, 231, 255));
+            if(s.color!=wxNullColour)
+              dc.SetBrush(s.color);
+            else
+              dc.SetBrush(wxColour(255, 102, 122));
             dc.DrawRectangle(wxRect(x,y,square_width,square_width));
           }
       }
@@ -396,12 +386,12 @@ void BoardCanvas::DrawBoard(wxDC &dc) {
                               boardY + square_width * 8 + numbers_size.y*2));
   }
   // Draw arrows
-  for(int i=0;i<(gs.arrows.size()+arrows.size());i++){
-    const std::string &arrow= i<gs.arrows.size() ? gs.arrows[i] : arrows[i-gs.arrows.size()];
-    std::uint8_t sfile = arrow[0]-'a';
-    std::uint8_t srank = arrow[1]-'1';
-    std::uint8_t dfile = arrow[2]-'a';
-    std::uint8_t drank = arrow[3]-'1';
+ for(int i=0;i<(gs.arrows.size()+arrows.size());i++){
+    const GameState::Arrow &arrow= i<gs.arrows.size() ? gs.arrows[i] : arrows[i-gs.arrows.size()];
+    std::uint8_t sfile = arrow.src[0]-'a';
+    std::uint8_t srank = arrow.src[1]-'1';
+    std::uint8_t dfile = arrow.dst[0]-'a';
+    std::uint8_t drank = arrow.dst[1]-'1';
     if (!black_side) {
       srank = 7 - srank;
       sfile = 7 - sfile;
@@ -414,37 +404,20 @@ void BoardCanvas::DrawBoard(wxDC &dc) {
     std::uint32_t dy = boardY + drank * square_width + square_width/2;
 
     // Parse arrow for metadata (maybe having a datatype is better)
-    std::uint8_t thickness=50;
-    if(arrow.size()>4){
-      std::string color="#";
-      std::string new_thickness="";
-      char key='?';
-      for(const char &c:arrow){
-        if(c=='#'||c=='%'){
-          key=c;
-          continue;
-        }else if(key=='#'){
-          color+=c;
-        } else if(key=='%'){
-          new_thickness+=c;
-        }
-      }
-      if(color.size()>1)
-        dc.SetBrush(wxColour(color));
-      if(new_thickness.size()>0)
-        thickness=(std::uint8_t)std::stoi(new_thickness);
-    } else
+    if(arrow.color!=wxNullColour)
+      dc.SetBrush(arrow.color);
+    else
       dc.SetBrush(color_arrows);
     if(((abs(drank-srank) == 2) && (abs(dfile-sfile) == 1))||
     ((abs(drank-srank) == 1) && (abs(dfile-sfile) == 2))){
       if(abs(drank-srank) == 1){
-        DrawLArrow(dc,sx,sy,dx,dy,false,thickness);
+        DrawLArrow(dc,sx,sy,dx,dy,false,arrow_thickness*arrow.scale);
       }
       else {
-        DrawLArrow(dc,sx,sy,dx,dy,true,thickness);
+        DrawLArrow(dc,sx,sy,dx,dy,true,arrow_thickness*arrow.scale);
       }
     } else {
-      DrawArrow(dc,sx,sy,dx,dy,thickness);
+      DrawArrow(dc,sx,sy,dx,dy,arrow_thickness*arrow.scale);
     }
   }
 }
@@ -519,14 +492,22 @@ void BoardCanvas::MouseEvent(wxMouseEvent &event) {
                           std::to_string(+active_square.y + 1);
         std::string dst=((char)('a' + file)) + std::to_string(rank + 1);
         if(src!=dst){
-          arrows.push_back(src+dst);
+          arrows.push_back(DEFAULT_ARROW(src,dst));
           wxLogDebug("Draw arrow %s",src+dst);
         }
         else {
-          if(std::count(squares_hl.begin(), squares_hl.end(), src)){
-            squares_hl.erase(std::remove(squares_hl.begin(), squares_hl.end(), src), squares_hl.end());
-          }else{
-            squares_hl.push_back(src+"f");
+          int id=0;
+          bool removed=false;
+          for(GameState::Square &s:squares_hl){
+            if(s.square==src){
+              squares_hl.erase(squares_hl.begin()+id);
+              removed=true;
+              break;
+            }
+            id++;
+          }
+          if(!removed){
+            squares_hl.push_back(DEFAULT_SQUARE(src));
             wxLogDebug("Highlight square %s",src);
           }
         }
@@ -548,6 +529,7 @@ void BoardCanvas::Zoom(std::int32_t zoom) {
   if(!t->Zoom(zoom))
     return;
   t_captures->ResizePieces(t->GetPiecesSizes() * CAPTURE_FACTOR);
+  arrow_thickness = t->GetSquaresSizes()/1.8;
   Refresh();
 }
 
diff --git a/src/game_tab/left_panel/board/BoardCanvas.hpp b/src/game_tab/left_panel/board/BoardCanvas.hpp
index 553994c..bc850b7 100644
--- a/src/game_tab/left_panel/board/BoardCanvas.hpp
+++ b/src/game_tab/left_panel/board/BoardCanvas.hpp
@@ -35,6 +35,8 @@ wxDECLARE_EVENT(PLAY_MOVE_EVENT, wxCommandEvent);
 
 #define CAPTURE_FACTOR 0.35
 #define SQUARE_NUM_PADDING 5
+#define DEFAULT_ARROW(SRC,DST) {(SRC),(DST),wxNullColour,1}
+#define DEFAULT_SQUARE(SQUARE) {(SQUARE),wxNullColour}
 
 typedef std::tuple<short, short, short> ClockTime;
 
@@ -61,11 +63,20 @@ typedef struct AnimState {
 } AnimState;
 
 typedef struct GameState {
+  typedef struct Arrow {
+    std::string src,dst;
+    wxColour color=wxNullColour;
+    float scale=1;
+  } Arrow;
+  typedef struct Square {
+    std::string square;
+    wxColour color=wxNullColour;
+  } Square;
   std::string white, black;
   std::string board;
   std::map<char, std::uint8_t> captures;
-  std::vector<std::string> squares_hl;
-  std::vector<std::string> arrows;
+  std::vector<Square> squares_hl;
+  std::vector<Arrow> arrows;
   bool is_black_turn;
   bool mat_black;
   bool mat_white;
@@ -78,10 +89,11 @@ class BoardCanvas : public wxPanel {
   Theme *t, *t_captures;
   wxColour color_arrows;
   int arrows_offset;
+  std::uint8_t arrow_thickness;
   std::string white_player,black_player;
   // Current highlighted squares and arrows:
-  std::vector<std::string> squares_hl;
-  std::vector<std::string> arrows;
+  std::vector<GameState::Square> squares_hl;
+  std::vector<GameState::Arrow> arrows;
 
   // Various canvas state variables
   bool black_side, is_dragging, valid_drag, arrow_drag, is_black_turn;
diff --git a/src/game_tab/right_panel/LiveEngineDialog.cpp b/src/game_tab/right_panel/LiveEngineDialog.cpp
index 0c2ecfc..2413d0b 100644
--- a/src/game_tab/right_panel/LiveEngineDialog.cpp
+++ b/src/game_tab/right_panel/LiveEngineDialog.cpp
@@ -101,23 +101,31 @@ void LiveEngineDialog::StartEngine() {
 }
 
 void LiveEngineDialog::OnTimerTick(wxTimerEvent &event) {
-  wxLogDebug("Tick!");
-  lines_list->DeleteAllItems();
+  lines_list->DeleteAllItems(); // Clear lines_list
   engine->SyncAfter(0);
   EngineEvaluation *eval=new EngineEvaluation();
-  auto const &lines=engine->GetLines();
-  eval->best_lines.resize(lines.size());
-  for (auto const &line : lines) {
-    long index = lines_list->InsertItem(0, std::to_string(line.first));
+  auto lines=engine->GetLines(); // First best lines
+
+  // First retrieve lines ids from unordered map lines
+  std::vector<int> ids;
+  for(auto const &line : lines){
+    ids.push_back(line.first);
+  }
+  std::sort(ids.begin(),ids.end());
+
+  // Now fill eval and lines_list
+  for (int &id:ids) {
+    auto const &line=lines[id];
+    long index = lines_list->InsertItem(id, std::to_string(id));
+    // Update eval that will be deplayed on the board
+    if(line.pv.size()>0)
+      eval->best_lines.push_back(line.pv[0]);
+    // Refresh lines_list
     std::string line_moves;
-    for (std::string move : line.second.pv) {
+    for (std::string move : line.pv)
       line_moves += move + " ";
-    }
-    if(line.second.pv.size()>0){
-      eval->best_lines.insert(eval->best_lines.begin()+line.first,line.second.pv[0]);
-    }
-    std::string cp_str = std::to_string(line.second.score_cp);
-    if (line.second.score_cp > 0) {
+    std::string cp_str = std::to_string(line.score_cp);
+    if (line.score_cp > 0) {
       cp_str = "+" + cp_str;
     }
     lines_list->SetItem(index, 1, cp_str);