Moteur : classement de l'analyse d'un coup, comme gnubg de bureau

engine_analyse_move rend les dix premiers coups de la liste de l'analyse,
chacun au niveau où gnubg l'a évalué, et refait d'abord l'analyse de la
chance comme AnalyzeMove : son cache change les coups restés à 0 pli.
Les références d'analyse gagnent le niveau de chaque coup du classement,
comparé par compare.py.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Pierre MARTIN
2026-11-12 02:47:38 +01:00
co-authored by Claude Opus 5.5
parent c42316d9d8
commit 1d21ac15d2
23 changed files with 516 additions and 22 deletions
+31 -9
View File
@@ -512,6 +512,8 @@ engine_level_best_moves(const char *position_id, const char *match_id, int die1,
/* ------------------------------------------------- Analyse d'un coup joué */
static float luck_analysis(const char *position_id, position *pos, int die1, int die2);
engine_status
engine_analyse_move(const char *position_id, const char *match_id, int die1, int die2,
const char *played_position_id, int ply, engine_move_analysis *analysis)
@@ -546,6 +548,17 @@ engine_analyse_move(const char *position_id, const char *match_id, int die1, int
* premier.
*/
memset(analysis, 0, sizeof *analysis);
/*
* gnubg analyse la chance du jet juste avant le coup (AnalyzeMove,
* l. 679-682), à 0 pli sans élagage, et garde ces évaluations dans son
* cache, qui ne distingue pas une évaluation à 0 pli avec ou sans
* réseaux d'élagage : le tri à 0 pli de FindnSaveBestMoves les reprend
* ensuite, ce qui change l'équité des coups restés à 0 pli, et parfois
* ceux que les filtres retiennent. Faire de même, sans garder le
* résultat, donne le classement de gnubg de bureau.
*/
if (luck_analysis(position_id, &pos, die1, die2) == ERR_VAL)
return ENGINE_ERROR_INTERNAL;
if (FindnSaveBestMoves(&list, die1, die2, (ConstTanBoard) pos.board, &played_key, TRUE,
DOUBTFUL_THRESHOLD, &pos.cube, &context,
aaamfMoveFilterSettings[0]) < 0)
@@ -565,6 +578,10 @@ engine_analyse_move(const char *position_id, const char *match_id, int die1, int
status = ENGINE_OK;
break;
}
/* Le classement : le début de la même liste, dans l'ordre de gnubg. */
for (i = 0; i < list.cMoves && i < ENGINE_ANALYSIS_RANKING; i++)
fill_move(&analysis->ranking[i], &list.amMoves[i], &pos, 1);
analysis->ranking_count = (int) i;
g_free(list.amMoves);
return status;
}
@@ -1005,13 +1022,25 @@ luck_first(ConstTanBoard board, int i0, int j0, cubeinfo *cube)
return equities[i0][j0] - mean / 30.0f;
}
/* LuckAnalysis (analysis.c l. 252-268) : dés rangés du plus fort au plus
* faible ; LuckFirst à la position de départ hors doubles. */
static float
luck_analysis(const char *position_id, position *pos, int die1, int die2)
{
int high = (die1 > die2 ? die1 : die2) - 1;
int low = (die1 > die2 ? die2 : die1) - 1;
if (strcmp(position_id, START_POSITION_ID) == 0 && high != low)
return luck_first((ConstTanBoard) pos->board, high, low, &pos->cube);
return luck_normal((ConstTanBoard) pos->board, high, low, &pos->cube);
}
engine_status
engine_luck_of(const char *position_id, const char *match_id, int die1, int die2,
engine_luck *luck)
{
position pos;
engine_status status;
int high, low;
float value;
status = decode_position(position_id, match_id, &pos);
@@ -1021,14 +1050,7 @@ engine_luck_of(const char *position_id, const char *match_id, int die1, int die2
return status;
start_evaluation();
/* LuckAnalysis (analysis.c l. 252-268) : dés rangés du plus fort au plus
* faible ; LuckFirst à la position de départ hors doubles. */
high = (die1 > die2 ? die1 : die2) - 1;
low = (die1 > die2 ? die2 : die1) - 1;
if (strcmp(position_id, START_POSITION_ID) == 0 && high != low)
value = luck_first((ConstTanBoard) pos.board, high, low, &pos.cube);
else
value = luck_normal((ConstTanBoard) pos.board, high, low, &pos.cube);
value = luck_analysis(position_id, &pos, die1, die2);
if (value == ERR_VAL)
return ENGINE_ERROR_INTERNAL;
luck->luck = value;
+21 -1
View File
@@ -207,6 +207,10 @@ typedef struct {
* (double_take <= double_pass) */
} engine_cube;
/* Coups gardés dans le classement d'une analyse : les dix premiers, comme
* les positions de référence (tests/reference/README.md). */
#define ENGINE_ANALYSIS_RANKING 10
/* Analyse d'un coup joué, comme l'analyse de partie de gnubg. */
typedef struct {
int move_count; /* nombre de coups légaux (0 : aucun coup
@@ -218,6 +222,14 @@ typedef struct {
float equity_loss; /* équité du coup joué moins celle du meilleur :
* 0 ou négative */
engine_skill skill; /* appréciation de gnubg pour cette perte */
/* Le classement de l'analyse, du meilleur au moins bon : les premiers
* coups de la liste que gnubg garde pour l'analyse de partie (ce que
* montre son panneau d'analyse), chacun au niveau où gnubg l'a
* évalué. Le meilleur coup et le coup joué y ont les mêmes évaluations
* que best et played ; le coup joué n'y figure que s'il est parmi les
* premiers. */
engine_move ranking[ENGINE_ANALYSIS_RANKING];
int ranking_count;
} engine_move_analysis;
/* Analyse d'une décision de videau, comme l'analyse de partie de gnubg. */
@@ -452,7 +464,15 @@ engine_luck_rating engine_luck_rating_of(float luck);
* Analyse le coup joué par le joueur au trait avec le jet die1, die2, au
* niveau ply, comme l'analyse de partie de gnubg (AnalyzeMove dans
* analysis.c : FindnSaveBestMoves avec le coup joué, seuil « douteux »,
* filtre « Normal », sans RefreshMoveList). played_position_id est la
* filtre « Normal », sans RefreshMoveList), classement compris.
*
* Niveaux : gnubg n'évalue au niveau ply que les coups retenus par ses
* filtres ; les autres gardent l'évaluation d'un niveau inférieur (champ
* ply de chaque coup, même convention que gnubg de bureau : 0 = statique).
* Si un seul coup passe le filtre du niveau ply - 1, gnubg s'arrête là, et
* tout le classement reste à ce niveau, sauf quand le coup joué est une
* erreur (écart au meilleur supérieur au seuil « douteux ») : le meilleur
* coup et le coup joué sont alors réévalués au niveau ply. played_position_id est la
* position après le coup, vue du joueur qui vient de le jouer (champ
* position_id d'engine_move). ENGINE_ERROR_MOVE si ce n'est pas un coup
* légal ; si aucun coup n'est possible, move_count vaut 0.