Moteur : classement exact de hint et commande engine-cli reference

- engine_best_moves suit hint_move de gnubg : fAnalyse vrai, seuil
  « douteux », puis RefreshMoveList (classement par niveau d'évaluation
  et départage des égalités).
- Correction : en match, l'équité des coups était convertie deux fois par
  mwc2eq (ScoreMove la convertit déjà).
- engine_cube : ajout de optimal et decision_text ; decision prend le nom
  de l'énumération cubedecision de gnubg, comme les références.
- engine-cli : commande reference (un niveau au format de
  tests/reference/) et nombres écrits avec 9 chiffres significatifs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Pierre MARTIN
2026-11-02 14:18:10 +01:00
co-authored by Claude Opus 5.5
parent 856e4d64bd
commit be6a4b4446
4 changed files with 141 additions and 32 deletions
+98 -8
View File
@@ -13,6 +13,11 @@
* legal POSITION_ID MATCH_ID DÉS coups légaux, sans évaluation
* cube POSITION_ID MATCH_ID décision de videau
* eval POSITION_ID MATCH_ID évaluation de la position
* reference POSITION_ID MATCH_ID [DÉS]
* un niveau au format des positions de
* référence (tests/reference/README.md) :
* évaluation, puis classement des coups
* avec DÉS, décision de videau sans
*
* --ply : niveau de gnubg, de 0 (défaut) à 4.
* --data : dossier des données ; par défaut « data » à côté du programme.
@@ -31,6 +36,13 @@
#define EXIT_ENGINE_ERROR 1
#define EXIT_USAGE_ERROR 2
/* Format des nombres : 9 chiffres significatifs redonnent exactement le
* float32 calculé par gnubg. */
#define NUMBER "%.9g"
/* Coups gardés dans le classement de la commande reference (comme hint). */
#define REFERENCE_MAX_MOVES 10
/* Arguments de la ligne de commande, une fois lus. */
typedef struct {
int ply;
@@ -90,8 +102,8 @@ usage_error(const char *message)
static void
print_probabilities(const float p[ENGINE_NUM_PROBABILITIES])
{
printf("{\"win\": %.6f, \"win_gammon\": %.6f, \"win_backgammon\": %.6f, "
"\"lose_gammon\": %.6f, \"lose_backgammon\": %.6f}",
printf("{\"win\": " NUMBER ", \"win_gammon\": " NUMBER ", \"win_backgammon\": " NUMBER ", "
"\"lose_gammon\": " NUMBER ", \"lose_backgammon\": " NUMBER "}",
p[ENGINE_WIN], p[ENGINE_WIN_GAMMON], p[ENGINE_WIN_BACKGAMMON],
p[ENGINE_LOSE_GAMMON], p[ENGINE_LOSE_BACKGAMMON]);
}
@@ -101,7 +113,7 @@ print_move(const engine_move *m, int with_evaluation)
{
printf("{\"move\": \"%s\", \"position_id\": \"%s\"", m->notation, m->position_id);
if (with_evaluation) {
printf(", \"ply\": %d, \"equity\": %.6f, \"probabilities\": ", m->ply, m->equity);
printf(", \"ply\": %d, \"equity\": " NUMBER ", \"probabilities\": ", m->ply, m->equity);
print_probabilities(m->probabilities);
}
printf("}");
@@ -116,6 +128,25 @@ print_match(const engine_match *m)
m->player_on_roll, m->crawford ? "true" : "false", m->jacoby ? "true" : "false");
}
/* Champs d'une décision de videau, sans les accolades. */
static void
print_cube_fields(const engine_cube *c)
{
printf("\"no_double\": " NUMBER ", \"double_take\": " NUMBER ", \"double_pass\": " NUMBER
", \"optimal\": " NUMBER ", \"decision\": \"%s\", \"decision_text\": \"%s\"",
c->no_double, c->double_take, c->double_pass, c->optimal, c->decision,
c->decision_text);
}
/* Début d'un niveau de référence : {"evaluation": {...} (objet laissé ouvert). */
static void
print_reference_evaluation(const engine_evaluation *e)
{
printf("{\"evaluation\": {\"probabilities\": ");
print_probabilities(e->probabilities);
printf(", \"equity\": " NUMBER "}", e->cubeless_equity);
}
/* Lit des dés au format « 31 » ; renvoie 0 si le format est faux. */
static int
parse_dice(const char *text, int *die1, int *die2)
@@ -204,7 +235,7 @@ run_position_command(const options *opt)
print_match(&match);
printf(", \"probabilities\": ");
print_probabilities(e.probabilities);
printf(", \"cubeless_equity\": %.6f, \"cubeful_equity\": %.6f}\n",
printf(", \"cubeless_equity\": " NUMBER ", \"cubeful_equity\": " NUMBER "}\n",
e.cubeless_equity, e.cubeful_equity);
} else {
engine_cube c;
@@ -216,8 +247,65 @@ run_position_command(const options *opt)
print_match(&match);
printf(", \"probabilities\": ");
print_probabilities(c.probabilities);
printf(", \"no_double\": %.6f, \"double_take\": %.6f, \"double_pass\": %.6f, "
"\"decision\": \"%s\"}\n", c.no_double, c.double_take, c.double_pass, c.decision);
printf(", ");
print_cube_fields(&c);
printf("}\n");
}
return EXIT_SUCCESS;
}
/*
* reference : un niveau d'un fichier de résultat de tests/reference/, de
* même structure que plies["<n>"] (voir tests/reference/README.md).
*/
static int
run_reference_command(const options *opt)
{
const char *position_id = opt->args[0], *match_id = opt->args[1];
engine_evaluation e;
engine_status status;
int die1, die2, count, i;
if (opt->arg_count != 2 && opt->arg_count != 3)
return usage_error("arguments attendus : POSITION_ID MATCH_ID [DÉS]");
if (opt->arg_count == 3 && !parse_dice(opt->args[2], &die1, &die2))
return usage_error("dés attendus sous la forme 31");
status = engine_evaluate(position_id, match_id, opt->ply, &e);
if (status != ENGINE_OK)
return print_engine_error(status);
if (opt->arg_count == 3) {
engine_move *moves = malloc(sizeof *moves * ENGINE_MAX_MOVES);
if (!moves)
return print_error("out_of_memory", "mémoire insuffisante", EXIT_ENGINE_ERROR);
status = engine_best_moves(position_id, match_id, die1, die2, opt->ply,
moves, ENGINE_MAX_MOVES, &count);
if (status != ENGINE_OK) {
free(moves);
return print_engine_error(status);
}
print_reference_evaluation(&e);
printf(", \"moves\": {\"legal_move_count\": %d, \"ranking\": [", count);
for (i = 0; i < count && i < REFERENCE_MAX_MOVES; i++) {
printf("%s{\"rank\": %d, \"move\": \"%s\", \"ply\": %d, \"equity\": " NUMBER
", \"equity_diff\": " NUMBER ", \"probabilities\": ",
i ? ", " : "", i + 1, moves[i].notation, moves[i].ply, moves[i].equity,
moves[i].equity - moves[0].equity);
print_probabilities(moves[i].probabilities);
printf("}");
}
printf("]}}\n");
free(moves);
} else {
engine_cube c;
status = engine_cube_decision(position_id, match_id, opt->ply, &c);
if (status != ENGINE_OK)
return print_engine_error(status);
print_reference_evaluation(&e);
printf(", \"cube\": {");
print_cube_fields(&c);
printf("}}\n");
}
return EXIT_SUCCESS;
}
@@ -266,7 +354,7 @@ parse_options(int argc, char **argv, options *opt)
}
}
if (!opt->command)
return "commande attendue : bestmove, moves, legal, cube ou eval";
return "commande attendue : bestmove, moves, legal, cube, eval ou reference";
return NULL;
}
@@ -298,8 +386,10 @@ main(int argc, char **argv)
result = run_moves_command(&opt);
else if (strcmp(opt.command, "eval") == 0 || strcmp(opt.command, "cube") == 0)
result = run_position_command(&opt);
else if (strcmp(opt.command, "reference") == 0)
result = run_reference_command(&opt);
else
result = usage_error("commande inconnue : bestmove, moves, legal, cube ou eval");
result = usage_error("commande inconnue : bestmove, moves, legal, cube, eval ou reference");
engine_shutdown();
return result;