|
1. Organisation générale du fichier de stockage Un
fichier d'ouvertures généré par PgnScanner contient 3 blocs :
|
Le bloc [zTab] |
Le bloc [moves] |
Le bloc [parameters] |
|

Ce bloc contient tous les nombres
pseudo-aléatoires générés par PgnScanner qui permettent de constituer
les clés de hachages identifiant les positions. |

Ce bloc contient tous les coups et
données de la bibliothèque d'ouverture. Chaque "enregistrement"
contient les clés de hachage identifiant chaque position associée. |

Ce bloc donne des informations
générales sur les données de la bibliothèque |
2. Format de
codage
Le paramètre "hashSz" du bloc
[parameters] donne le format de
codage des clés de hachage. Il peut être de 32 bits ou de 64 bits. En 32
bits, la bibliothèque peut stocker théoriquement 2^32 positions
distinctes. Ceci est purement théorique et il est très possible que sur
les millions de positions stockables, quelques-unes obtiennent la même clé
principale de hachage (zKey). Dans ces cas-là, la clé de contrôle (zChk)
nous signalerait que la position stockée n'est pas la bonne et celle que
nous cherchons pourrait ne pas être trouvée. C'est pourquoi, pour une
fiabilité maximale, PgnScanner permet aussi le stockage des clés en 64
bits.
Format 32 bits

Format 64 bits

3.
Format d'enregistrement
Afin de minimiser l'espace disque
utilisé par le fichier book.psb généré par PgnScanner, les codes de 64
bits (voir figures ci-dessus) ne sont pas enregistrés tel quel en base 10 ni même
en hexadécimal. Le paramètre numBase du bloc [parameters] donne la base
qui a été utilisée. Concrètement, il s'agit de l'ensemble des caractères
ASCII en partant de la fin. Ainsi, la base 224 (base par défaut) utilise
les caractères ASCII de 32 jusqu'à 255 comme système de numération. Il n'est pas vraiment conseillé
d'utiliser les caractères de 0 à 31 car ce sont des caractères spéciaux et
il pourraient induire des problèmes de formatage (ex: caractère Retour
Chariot) dans le fichier généré.
Disposition des enregistrements dans
le fichier
Si le format des clés est 32 bits (voir hashSz), l'enregistrement des codes en base spécifiée se fait dans l'ordre
suivant à partir de l'en-tête [moves] :
|
Ordre |
Exemple |
|
[zChk+zKey]
[stats+move]
(...) |
[moves]
GŸ9vE=9¥" => zChk+zKey
1ã => stats+move
ž?U°ÖˆÝ9 => zChk+zKey
þÜ => stats+move
(...) |
Si le format des clés est 64 bits (voir hashSz), l'enregistrement des codes en base spécifiée se fait dans l'ordre
suivant à partir de l'en-tête [moves] :
|
Ordre |
Exemple |
|
[zKey]
[zChk]
[stats+move]
(...) |
[moves]
"ŸH¾_K! => zKey
ž¸ùßÔSj! => zChk
µá => stats+move
ù¥1IòÁ! => zKey
«zÂD¯òqa! => zChk
Qµ => stats+move
(...) |
4.
Chargement de la bibliothèque
A) Ouvrir le fichier en mode texte et
réserver suffisamment de mémoire pour recevoir la totalité des coups de la
bibliothèque. Pour déterminer cette quantité de mémoire, on se basera sur
le nombre de coups stockés (paramètre "moves" du bloc [parameter]) et
d'une structure du type :
typedef unsigned __int64 bitboard;
typedef unsigned __int64 U64;
typedef unsigned __int32 U32;
typedef unsigned __int16 U16;
typedef unsigned __int8 U8; struct Trecord {
U64 zKey; //clé de hashage
U64 zChk; //hashage de controle
U8 sqFrom; //case de départ
U8 sqTo; //case de destination
U8 cmt; //commentaire sur le coup
U32 nbGame; //nombre de parties prise en compte pour le %
U8 pcnt; //pourcentage de réussite
U16 xElo; //elo moyen adverse
U16 elo;
};
B) Pour chaque enregistrement, convertir
le nombre de la base utilisée (ex: numBase = 224) vers un nombre en base
10. Cela signifie que chaque ligne du bloc [moves] devra être analysée
caractère par caractère afin d'effectuer une conversion de la base 224
vers la base 10.
Pour le calcul de conversion, vous pouvez vous inspirer du morceau de code
suivant :
//+--------------------+
//| initialise la base |
//+--------------------+
void CBook::initXBase(void) {
U8 n = 0;
xbMul[0] = 1;
for (n = 1 ; n < 64; n++) xbMul[n] = (numBase * xbMul[n-1]);
}//+-------------------------+
//| Convertit un code chai- |
//| ne en entier de 64 bits |
//+-------------------------+
U64 CBook::XBaseToI64(char *buf) { //buf[64] au minimum (nécessaire en base 2 par exemple)
U64 i = 0;
int n = 0;
int offset = 256 - numBase; //dans la table ASCII
while (buf[n] != '\0' && buf[n] != '\n') {
i += (((U8)buf[n] - offset) * xbMul[n]);
n++;
}
return i;
};
C) Pour chaque nombre converti en base
10, effectuer les décalages et masquages nécessaires afin d'extraire les
valeurs utiles (consultez les figures présentées au paragraphe "Format de
codage"). Ne pas oublier de multiplier les elo par 2.
Exemple :
//+----------------------------+
//| Décode l'entrée principale |
//+----------------------------+
void CBook::decodeKCode(U64 k1Code, U64 k2Code) {
if (bkHashSz == 64) {
recBuff.zKey = k1Code;
recBuff.zChk = k2Code;
}
else {
recBuff.zKey = (k1Code & MSK32);
recBuff.zChk = ((k1Code >> 32) & MSK32);
}
}
//+-----------------------------+
//| Décode l'entrée statistique |
//+-----------------------------+
void CBook::decodeICode(U64 iCode) {
recBuff.sqFrom = (U8)(iCode & MSK6);
recBuff.sqTo = (U8)((iCode >> 6) & MSK6);
recBuff.cmt = (U8)((iCode >> 12) & MSK3);
recBuff.nbGame = (U32)((iCode >> 15) & MSK20);
recBuff.pcnt = (U8)((iCode >> 35) & MSK7);
recBuff.xElo = (U16)(((iCode >> 42) & MSK11)*2);
recBuff.elo = (U16)(((iCode >> 53) & MSK11)*2);
}
5.
Chargement des nombres pseudo-aléatoires
Les nombres pseudo-aléatoires de 64 bits
servant à élaborer l'identifiant d'une position donnée sont stockés dans
le bloc [zTab]. Attention cependant (!) car le tout premier nombre situé
immédiatement l'en-tête [zTab] est le nombre correspondant au trait en
cours. Il faut donc l'extraire indépendamment des nombres qui suivent.
Ensuite, il s'agira de convertir chaque nombre (1 nombre par ligne) de la
base utilisée vers la base 10, puis de le stocker dans une tableau à 3
dimensions [type_de_pièce][couleur][case].
Pour [type_de_pièce] on aura les valeurs possibles
suivantes :
//------------------------
//Define for type of piece
//------------------------
#define NOPC 0
#define KING 1
#define QUEEN 2
#define ROOK 3
#define BISHOP 4
#define KNIGHT 5
#define PAWN 6
#define MAXPC 7
Pour [couleur] on aura les valeurs possibles
suivantes :
#define WHITE 1
#define BLACK 0
Pour [case] :
toutes les cases de 0 à 64
Pour effectuer le chargement, on pourra s'inspirer du
morceau de code suivant :
//+---------------------+
//| Charge les tables |
//| aléatoires internes |
//+---------------------+
int CBook::loadZTab(FILE *f) {
int t, c, sq;
char str[256];
rewind(f);
//---------------------
// lectures des nombres
//---------------------
while (fgets(str, 256, f) != NULL && strcmp(str,"[zTab]\n") != 0);
if (strcmp(str,"[zTab]\n") != 0) {
ERRMSG("> PgnScanner can't find Zobrist tables in book.psb file\n");
return 0;
}
fgets(str, 256, f);
wToMv = XBaseToI64(str);
for (t=0;t<=5;t++) {
for (c=0;c<=1;c++) {
for (sq=0;sq<=63;sq++) {
//---------------
// clé principale
//---------------
fgets(str, 256, f);
if (strlen(str) < 1) {
ERRMSG("> PgnScanner's Zobrist tables corrupted in book.psb file !\n");
return 0;
}
zkTab[t][c][sq] = XBaseToI64(str);//atoi(str);
//----------------
// clé de controle
//----------------
fgets(str, 256, f);
if (strlen(str) < 1) {
ERRMSG("> PgnScanner's Zobrist tables corrupted in book.psb file !\n");
return 0;
}
zcTab[t][c][sq] = XBaseToI64(str);
}
}
}
//----------
// fermeture
//----------
zTabLoaded = true;
return 1;
}
6. Lecture
de la bibliothèque
A. Calculer la clé principale zKey et la
clé de contrôle zChk de votre position en cours. Pour cela, parcourez
votre échiquier en cours à la recherche des pièces puis, en fonction de
votre table de Zobrist, générez votre clé principale ainsi que votre clé
de contrôle selon le principe suivant :
//+------------------------+
//| Elabore la clé princi- |
//| pale de 32 ou 64 bits |
//+------------------------+
U64 CBook::setZKey(void) {
U64 key = 0;
for (int sq = 0 ; sq < 64 ; sq ++) {
if (board.typeTab[sq] == 0) continue;
key ^= zkTab[board.typeTab[sq] - 1][(board.allPc[WHITE] & bSq[sq])?WHITE:BLACK][sq];
}
if (board.stm == WHITE) key ^= wToMv;
return key;
}
//+------------------------+
//| Elabore la clé de con- |
//| trôle de 32 ou 64 bits |
//+------------------------+
U64 CBook::setZChk(void) {
U64 key = 0;
for (int sq = 0 ; sq < 64 ; sq ++) {
if (board.typeTab[sq] == 0) continue;
key ^= zcTab[board.typeTab[sq] - 1][(board.allPc[WHITE] & bSq[sq])?WHITE:BLACK][sq];
}
if (board.stm == WHITE) key ^= wToMv;
return key;
}
B. Il ne reste plus maintenant qu'à
parcourir les clés de hachages de la bibliothèque chargés en mémoire et
les comparer
avec les clés (zKey + zChk) de la position en cours. Etant donné que les
clés de hachage de la bibliothèque ont été triées selon la clé
de hachage
principale (zKey) lors de la génération du fichier, il est possible de
trouver très rapidement les coups présents en effectuant une recherche par
dichotomie.
Pour le choix du coup, le plus pratique est probablement de créer une liste
intermédiaire avec tous les coups trouvés pour la position en cours... |