StockFish UML

Diagramme de classes du code source C++ de SF

Pour appréhender l’architecture du code source C++ du logiciel SF, on peut établir une carte de ses classes principales : c’est le diagramme de classes de l’Unified Modeling Language (UML).

Le procédé s’inscrit dans une activité de rétroingénierie logicielle afin de comprendre le code source pour pouvoir le modifier.

Le diagramme de classes de SF a été simplifié pour ne mentionner que les interfaces publiques. Une première tentative de regroupement de classes en famille est proposée.

Type en langage C

Score défini ligne 260 du fichier entête de déclaration (header) types.h (ouvrir dans CodeBlocks Workspace > StockFish > Headers > Source > src > types.h) est un type de nombre pour évaluer la position échiquéenne.

SF range dans une variable de type Score à la fois la note en finale (LSB 16-bit) et la note en milieu de partie (MSB 16-bit). Autrement dit une variable s de type Score est implémentée sous la forme duale d’un integer 32-bit comprenant deux notes en finale et après l’ouverture dénotant ainsi la prise en compte par SF du changement de notation selon la phase de la partie d’Echecs.

La fonction qui suit en ligne 266 retourne le calcul de la valeur d’une variable de type Score  :
inline Score make_score(int mg, int eg) { return Score((mg << 16) + eg); }

A partir de deux notes mg (middle game) et eg (end game), la fonction make_score() calcule la valeur combinée en retournant une variable de type Score.

Retenir que le type de la fonction précède le nom de la fonction. De même le paramètre mg a pour type int (integer) précisé juste avant.

Entre accolades {...} se trouve le corps de la fonction qui retourne le résultat d’une addition en prenant soin de décaler mg (middle game) de 16 bits vers la gauche c-a-d vers MSB grâce à l’opérateur de décalage bit à bit "<<".

Du C au C++

Les débutants liront le tutoriel : Programmez avec le langage C++. Des objets... pour quoi faire ?

Au niveau du nom de la fonction make_score(), il s’agit de programmation procédurale classique du langage C suivant l’ordre occidental de lecture de la gauche vers la droite du langage naturel de l’anglais : le verbe make est avant le complément d’objet direct score dans le nom de la fonction make_score() avec en son milieu l’optionnel séparateur underscore "_" dans l’alphabet des identifieurs.

Etant donné que le nommage de la fonction est libre, dans un code source où le type Score ne serait pas défini juste au-dessus de la fonction make_score() mais par exemple dans un autre header, il peut être difficile de savoir que la fonction make_score() est effectivement relative au calcul d’un Score en majuscule.

  • Pour résoudre ce problème de portée, on aimerait bien encapsuler la fonction de calcul de Score dans la définition du type Score.
  • De plus ceux qui utilisent des variables de type Score n’ont pas besoin de savoir les détails d’implémentation comme la note de middle game en poids fort (MSB). Ils veulent juste savoir qu’elle est la méthode pour calculer un Score sans connaître le codage interne.

En programmation orientée objet (POO), le type Score serait alors promu en classe C++. On constaterait une inversion syntaxique Score::Make() avec le séparateur de classe "::" au lieu de l’underscore "_". Le type Score en majuscule serait devant le verbe Make. On parlerait alors de la méthode Make exposée par l’objet de type Score garantissant par contrat que l’interface Make est bien relatif à l’objet de classe Score. C’est contraire à l’ordre occidental de lecture. Il faut l’interpréter comme : avec un objet de classe Score, on peut faire un Make().

Dans l’implémentation SF, c’est probablement pour des raisons de performance (noter le préfixe inline typique des petites fonctions où le passage de paramètres coûterait plus cher en temps d’exécution qu’une répétition du corps de la fonction à chaque appel) que le type Score n’a pas été promu en classe C++.

Classe échiquéenne en C++

Les débutants continueront le tutoriel : Programmez avec le langage C++. Créer une classe.

En POO, la classe C++ est le type d’un objet qui peut exposer plusieurs méthodes publiques tout en cachant l’implémentation qui ne doit intéresser que le développeur de la classe, les utilisateurs de la classe n’ayant accès qu’aux méthodes publiques. Cela garantit que l’objet de la classe ne peut être modifié qu’avec les méthodes qu’il expose et non pas par des fonctions externes sauvages qui dépendraient de l’implémentation physique. Si l’on change l’implémentation physique d’une classe pour corriger un bug ou optimiser la performance, cela ne doit pas avoir d’impact sur les développements qui utilisent la classe car ils sont basés sur les interfaces publiques qui doivent masquer l’implémentation physique de la classe.

Prenons l’objet la_position_courante dans la partie de jeu d’Echecs. Elle n’existe qu’à partir du moment où on a instancié son type, c-a-d que l’on a déclaré cet objet avec comme type son nom de classe Position. Attention en C++ et en C, la casse (majuscule / minuscule) est significative.

On se reportera à la page HTML Help sur la classe Position dans StockFish.chm qui fournit une présentation standardisée, simplifiée et avec graphique de la classe.
Ensuite on ouvrira dans CodeBlocks Workspace > StockFish > Headers > Source > src > position.h ayant l’extension de fichier .h comme Header.

Ligne 92 : on trouve la définition de la classe Position et ses interfaces publiques.


class Position {
public:
  Position() {}

Les interfaces publiques sont les méthodes ou fonctions que l’objet expose comme Position::see(...) le Static Exchange Evaluator (see) qui évalue statiquement le gain ou la perte d’une série d’échanges sur une case donnée de l’échiquier.

Juste sous le mot clef public se trouve le constructeur de la classe qui porte son nom. Il est appelé à l’instanciation de l’objet c-a-d à sa création.

En ligne 188 est déclarée la partie private de la classe Position qui sert à la déclaration pour son implémentation. Il existe également des attributs qui sont des variables caractéristiques de la Position telles que sideToMove ou chess960, une variante du jeu d’Echecs où la disposition initiale des pièces est aléatoire selon les préconisations de Bobby Fischer.

Finalement on consultera l’implémentation de la classe Position dans le code source position.cpp (Workspace > StockFish > Sources > Source > src > position.cpp ou plus simplement menu "Edit" > "Swap header/source F11" à partir du header position.h) pour voir la différence entre déclaration (.h) et implémentation (.cpp).

CodeBlocks met par défaut en gris les headers dans le treeview. Cela signifie qu’il faut savoir ce que l’on fait avant de s’aventurer à modifier un header, c-a-d il faut en comprendre les conséquences. En général, au moins dans un premier temps, on ne modifiera que certaines parties des fichiers code source .cpp liées à l’implémentation des classes.

Rétroingénierie C++ avec StarUML

Pour appréhender une telle quantité de code de qualité, il faut avoir une première vue globale de l’architecture du logiciel grâce à la rétroingénierie à partir du code source. On se propose donc d’obtenir un premier diagramme de classes plus ou moins selon l’Unified Modeling Language (UML).

La recherche d’un logiciel UML gratuit avec la fonctionnalité rétroingénierie n’est pas chose aisée. StarUML peut être un premier point de départ. Le SourceForge de StarUML donne une mise à jour assez récente d’avril 2013.

La fonctionnalité rétroconception est rarement mise en avant. Ce type de logiciel s’intéresse à la démarche de modélisation UML, une activité de conception architecturale menée en amont du codage qui conduit à une génération automatique de code C++ à partir des spécifications modélisées. Ce n’est pas cette fonctionnalité que l’on mettra en œuvre.

Après le téléchargement dans StockFish\StarUML\Setup puis la première exécution depuis StockFish\StarUML\Prog, le logiciel échoue dès l’affichage du logo.
Selon la discussion sur les droits administrateurs du 10 mars 2010, il est suggéré de lancer StarUML la première fois en mode administrateur (pour la registry de Windows). Il n’est plus utile de le relancer en mode admin les autres fois.

Pour SF, on s’intéresse à l’activité inverse dite de rétroingénierie à partir d’un code source C++ pour remonter jusqu’au diagramme de classes.

StarUML menu "Tools" > "C++" > "Reverse Engineer..."
To C++ reverse engineering, C++ Profile is needed.
Do you want to include C++ Profile to the current project ? Yes.

Dlg "C++ Reverse Engineering"
Select Source Code for reverse engineering.
Browse Chess > Documents > StockFish > Source > src
Click "Add All"

Dans la fenêtre du bas présentant la liste des sources .cpp et des headers .h
Sélecter types.h (le premier header du premier chapitre ci-dessus ouvert dans CodeBlocks)
Click "Remove" (le diagramme serait trop complexe dans un premier temps avec cet header)

Sélecter psqtab.h
Click "Remove" (l'analyseur C++ de StarUml plante à la ligne 95 : Unrecoverable Parse Error)

Click "Next"

Il n’y a pas de magie. Le diagramme de classes généré est à retravailler manuellement pour obtenir une carte présentable sinon toutes les classes sont superposées par défaut et donc se cachent mutuellement. Cela explique la suppression de l’analyse de types.h qui superpose des dizaines de classes non connectées par StarUML dans le coin supérieur gauche et qu’il faut effacer ou déplacer.

Cartographie UML très classe de SF

La carte UML dans le portfolio propose une vue plus lisible (rectangle bleu à coins arrondis hors standard UML) permettant un premier regroupement par "famille" dont le nom a été choisi par association.

  • Opening : PolyglotBook, un hommage explicite à Fruit de Fabien Letouzey,
  • End game : les classes en eg pour les finales,
  • Clock : TimerThread et TimeManager pour la gestion du temps,
  • Table : TT signifie TranspositionTable pour ne pas réévaluer une position déjà analysée,
  • Console output : Log et SyncCout, la gestion de la sortie standard C++ pour le protocole UCI.

Au sommet de la carte se trouve la gestion du multithread avec la technique d’exclusion mutuelle du Mutex pour accéder à des ressources partagées dans un pool de threads. Il est peu probable que l’on s’aventure à modifier une telle architecture complexe qui fonctionne bien.

On notera une version simplifiée de la classe Position (abordée ci-dessus en chapitre 2) disposée dans une position clef.

Au cœur de la carte se trouve Search() et Eval() qui caractérisent non seulement le style de StockFish mais aussi la cible de vos améliorations et développements futurs.

Posté le 21 mai 2013 par Matt