Comment extraire les coups d'une bibliothèque
au format PgnScanner ?

Mise-à-jour : 01/06/2006

 

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
		=> 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
		=> 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...