Étape 0a.3 : moteur embarqué (cœur de gnubg), engine.h et engine-cli #7

Manually merged
pierre merged 8 commits from moteur into main 2026-11-02 18:03:48 +00:00
3 changed files with 832 additions and 0 deletions
Showing only changes of commit da5fa71ff8 - Show all commits
+306
View File
@@ -0,0 +1,306 @@
/*
* engine-cli -- interroge le moteur d'Ustagammon en ligne de commande.
*
* Copyright (C) 2026 les auteurs d'Ustagammon.
* Licence : GPLv3 ou ultérieure (voir LICENSE).
*
* Usage :
* engine-cli [--ply N] [--data DOSSIER] COMMANDE ARGUMENTS
*
* Commandes :
* bestmove POSITION_ID MATCH_ID DÉS meilleur coup (DÉS : ex. 31)
* moves POSITION_ID MATCH_ID DÉS coups légaux classés, évalués
* 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
*
* --ply : niveau de gnubg, de 0 (défaut) à 4.
* --data : dossier des données ; par défaut « data » à côté du programme.
*
* Sortie : un objet JSON sur la sortie standard. En cas d'erreur, un objet
* {"error": {"code": ..., "message": ...}} et un code de sortie non nul :
* 1 pour une erreur du moteur, 2 pour une erreur d'utilisation.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "engine.h"
#define EXIT_ENGINE_ERROR 1
#define EXIT_USAGE_ERROR 2
/* Arguments de la ligne de commande, une fois lus. */
typedef struct {
int ply;
const char *data_dir;
const char *command;
const char *args[3];
int arg_count;
} options;
/* Code d'erreur JSON pour chaque engine_status. */
static const char *
status_code(engine_status status)
{
switch (status) {
case ENGINE_OK:
return "ok";
case ENGINE_ERROR_NOT_INITIALISED:
return "not_initialised";
case ENGINE_ERROR_DATA:
return "invalid_data";
case ENGINE_ERROR_POSITION_ID:
return "invalid_position_id";
case ENGINE_ERROR_MATCH_ID:
return "invalid_match_id";
case ENGINE_ERROR_DICE:
return "invalid_dice";
case ENGINE_ERROR_PLY:
return "invalid_ply";
case ENGINE_ERROR_TOO_MANY_MOVES:
return "too_many_moves";
case ENGINE_ERROR_INTERNAL:
return "internal_error";
}
return "unknown_error";
}
/* Les messages sont des constantes sans guillemets : pas d'échappement. */
static int
print_error(const char *code, const char *message, int exit_code)
{
printf("{\"error\": {\"code\": \"%s\", \"message\": \"%s\"}}\n", code, message);
return exit_code;
}
static int
print_engine_error(engine_status status)
{
return print_error(status_code(status), engine_status_message(status), EXIT_ENGINE_ERROR);
}
static int
usage_error(const char *message)
{
return print_error("usage", message, EXIT_USAGE_ERROR);
}
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}",
p[ENGINE_WIN], p[ENGINE_WIN_GAMMON], p[ENGINE_WIN_BACKGAMMON],
p[ENGINE_LOSE_GAMMON], p[ENGINE_LOSE_BACKGAMMON]);
}
static void
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);
print_probabilities(m->probabilities);
}
printf("}");
}
static void
print_match(const engine_match *m)
{
printf("{\"match_length\": %d, \"score\": [%d, %d], \"cube_value\": %d, "
"\"cube_owner\": %d, \"player_on_roll\": %d, \"crawford\": %s, \"jacoby\": %s}",
m->match_length, m->score[0], m->score[1], m->cube_value, m->cube_owner,
m->player_on_roll, m->crawford ? "true" : "false", m->jacoby ? "true" : "false");
}
/* 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)
{
if (strlen(text) != 2 || text[0] < '0' || text[0] > '9' || text[1] < '0' || text[1] > '9')
return 0;
*die1 = text[0] - '0';
*die2 = text[1] - '0';
return 1;
}
/* bestmove, moves et legal. */
static int
run_moves_command(const options *opt)
{
const char *position_id = opt->args[0], *match_id = opt->args[1];
int best_only = strcmp(opt->command, "bestmove") == 0;
int evaluate = strcmp(opt->command, "legal") != 0;
int die1, die2, count, i;
engine_move *moves;
engine_status status;
if (opt->arg_count != 3)
return usage_error("arguments attendus : POSITION_ID MATCH_ID DÉS");
if (!parse_dice(opt->args[2], &die1, &die2))
return usage_error("dés attendus sous la forme 31");
moves = malloc(sizeof *moves * ENGINE_MAX_MOVES);
if (!moves)
return print_error("out_of_memory", "mémoire insuffisante", EXIT_ENGINE_ERROR);
if (evaluate)
status = engine_best_moves(position_id, match_id, die1, die2, opt->ply,
moves, ENGINE_MAX_MOVES, &count);
else
status = engine_legal_moves(position_id, match_id, die1, die2,
moves, ENGINE_MAX_MOVES, &count);
if (status != ENGINE_OK) {
free(moves);
return print_engine_error(status);
}
printf("{\"position_id\": \"%s\", \"match_id\": \"%s\", \"dice\": [%d, %d]",
position_id, match_id, die1, die2);
if (evaluate)
printf(", \"ply\": %d", opt->ply);
if (best_only) {
printf(", \"best_move\": ");
if (count > 0)
print_move(&moves[0], 1);
else
printf("null");
} else {
printf(", \"count\": %d, \"moves\": [", count);
for (i = 0; i < count; i++) {
printf(i ? ", " : "");
print_move(&moves[i], evaluate);
}
printf("]");
}
printf("}\n");
free(moves);
return EXIT_SUCCESS;
}
/* eval et cube. */
static int
run_position_command(const options *opt)
{
const char *position_id = opt->args[0], *match_id = opt->args[1];
engine_match match;
engine_status status;
if (opt->arg_count != 2)
return usage_error("arguments attendus : POSITION_ID MATCH_ID");
status = engine_decode_match(match_id, &match);
if (status != ENGINE_OK)
return print_engine_error(status);
if (strcmp(opt->command, "eval") == 0) {
engine_evaluation e;
status = engine_evaluate(position_id, match_id, opt->ply, &e);
if (status != ENGINE_OK)
return print_engine_error(status);
printf("{\"position_id\": \"%s\", \"match_id\": \"%s\", \"ply\": %d, \"match\": ",
position_id, match_id, opt->ply);
print_match(&match);
printf(", \"probabilities\": ");
print_probabilities(e.probabilities);
printf(", \"cubeless_equity\": %.6f, \"cubeful_equity\": %.6f}\n",
e.cubeless_equity, e.cubeful_equity);
} else {
engine_cube c;
status = engine_cube_decision(position_id, match_id, opt->ply, &c);
if (status != ENGINE_OK)
return print_engine_error(status);
printf("{\"position_id\": \"%s\", \"match_id\": \"%s\", \"ply\": %d, \"match\": ",
position_id, match_id, opt->ply);
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);
}
return EXIT_SUCCESS;
}
/* Dossier « data » à côté du programme. */
static char *
default_data_dir(const char *program)
{
const char *slash = strrchr(program, '/');
size_t dir_length = slash ? (size_t) (slash - program) : 1;
char *dir = malloc(dir_length + sizeof "/data");
if (!dir)
return NULL;
if (slash)
memcpy(dir, program, dir_length);
else
dir[0] = '.';
strcpy(dir + dir_length, "/data");
return dir;
}
/* Lit les options ; renvoie un message d'erreur, ou NULL si tout va bien. */
static const char *
parse_options(int argc, char **argv, options *opt)
{
int i;
memset(opt, 0, sizeof *opt);
for (i = 1; i < argc; i++) {
if (strcmp(argv[i], "--ply") == 0 && i + 1 < argc) {
char *end;
opt->ply = (int) strtol(argv[++i], &end, 10);
if (*end || end == argv[i])
return "--ply attend un nombre";
} else if (strcmp(argv[i], "--data") == 0 && i + 1 < argc) {
opt->data_dir = argv[++i];
} else if (argv[i][0] == '-' && argv[i][1] == '-') {
return "option inconnue ou sans valeur";
} else if (!opt->command) {
opt->command = argv[i];
} else if (opt->arg_count < 3) {
opt->args[opt->arg_count++] = argv[i];
} else {
return "trop d'arguments";
}
}
if (!opt->command)
return "commande attendue : bestmove, moves, legal, cube ou eval";
return NULL;
}
int
main(int argc, char **argv)
{
options opt;
const char *problem = parse_options(argc, argv, &opt);
char *data_dir = NULL;
engine_status status;
int result;
if (problem)
return usage_error(problem);
if (!opt.data_dir) {
data_dir = default_data_dir(argv[0]);
if (!data_dir)
return print_error("out_of_memory", "mémoire insuffisante", EXIT_ENGINE_ERROR);
opt.data_dir = data_dir;
}
status = engine_init(opt.data_dir);
free(data_dir);
if (status != ENGINE_OK)
return print_engine_error(status);
if (strcmp(opt.command, "bestmove") == 0 || strcmp(opt.command, "moves") == 0
|| strcmp(opt.command, "legal") == 0)
result = run_moves_command(&opt);
else if (strcmp(opt.command, "eval") == 0 || strcmp(opt.command, "cube") == 0)
result = run_position_command(&opt);
else
result = usage_error("commande inconnue : bestmove, moves, legal, cube ou eval");
engine_shutdown();
return result;
}
+371
View File
@@ -0,0 +1,371 @@
/*
* engine.c -- façade du moteur d'Ustagammon sur le cœur de GNU Backgammon.
*
* Copyright (C) 2026 les auteurs d'Ustagammon.
* Licence : GPLv3 ou ultérieure (voir LICENSE).
*
* Chaque fonction décode les identifiants de position, prépare le contexte
* de gnubg (cubeinfo, réglages d'évaluation) comme le fait gnubg de bureau
* pour ses commandes « hint » et « eval », puis appelle gnubg.
*/
#include <stdio.h>
#include <string.h>
#include "config.h"
#include "backgammon.h"
#include "drawboard.h"
#include "matchequity.h"
#include "matchid.h"
#include "multithread.h"
#include "positionid.h"
#include "gnubg-support.h"
#include "engine.h"
/* Nombre de castors autorisés en partie libre : valeur par défaut de gnubg. */
#define DEFAULT_BEAVERS 3
/* Fichier de table d'équité de match : celui de gnubg de bureau par défaut. */
#define MET_FILE "Kazaross-XG2.xml"
/* gnubg ne sait pas se réinitialiser après EvalShutdown : on retient l'état. */
static enum { NOT_STARTED, READY, STOPPED } engine_state = NOT_STARTED;
/* Une position décodée, prête pour gnubg. */
typedef struct {
TanBoard board;
cubeinfo cube;
engine_match match;
} position;
/* -------------------------------------------------------- Initialisation */
/* Vérifie l'en-tête de gnubg.wd, pour éviter l'exit() de EvalInitialise. */
static int
binary_weights_ok(const char *path)
{
float header[2];
FILE *f = fopen(path, "rb");
int ok;
if (!f)
return 0;
ok = fread(header, sizeof header[0], 2, f) == 2
&& header[0] == WEIGHTS_MAGIC_BINARY && header[1] == WEIGHTS_VERSION_BINARY;
fclose(f);
return ok;
}
static engine_status
load_match_equity_table(const char *data_dir)
{
char *path = g_build_filename(data_dir, MET_FILE, NULL);
int ok;
ok = g_file_test(path, G_FILE_TEST_IS_REGULAR);
if (ok) {
InitMatchEquity(path);
/* En cas d'échec, gnubg se rabat sans erreur sur la table de Zadeh. */
ok = miCurrent.szFileName && strcmp(miCurrent.szFileName, path) == 0;
}
g_free(path);
return ok ? ENGINE_OK : ENGINE_ERROR_DATA;
}
static engine_status
load_neural_nets_and_bearoff(const char *data_dir)
{
char *weights = g_build_filename(data_dir, "gnubg.wd", NULL);
int ok = binary_weights_ok(weights);
if (ok) {
EvalInitialise(NULL, weights, FALSE, NULL);
/* Bases de fin de partie : gnubg continue sans elles, pas nous. */
ok = pbc1 && !pbc1->fHeuristic && pbc2;
}
g_free(weights);
return ok ? ENGINE_OK : ENGINE_ERROR_DATA;
}
engine_status
engine_init(const char *data_dir)
{
engine_status status;
if (engine_state == READY)
return ENGINE_OK;
if (engine_state == STOPPED || !data_dir)
return ENGINE_ERROR_NOT_INITIALISED;
gnubg_support_set_data_dir(data_dir);
status = load_match_equity_table(data_dir);
if (status == ENGINE_OK)
status = load_neural_nets_and_bearoff(data_dir);
if (status != ENGINE_OK)
return status;
MT_InitThreads();
engine_state = READY;
return ENGINE_OK;
}
void
engine_shutdown(void)
{
if (engine_state != READY)
return;
MT_Close();
td.tld = NULL;
EvalShutdown();
engine_state = STOPPED;
}
const char *
engine_status_message(engine_status status)
{
switch (status) {
case ENGINE_OK:
return "succès";
case ENGINE_ERROR_NOT_INITIALISED:
return "moteur non initialisé";
case ENGINE_ERROR_DATA:
return "données du moteur absentes ou invalides";
case ENGINE_ERROR_POSITION_ID:
return "Position ID invalide";
case ENGINE_ERROR_MATCH_ID:
return "Match ID invalide";
case ENGINE_ERROR_DICE:
return "dés invalides (chacun entre 1 et 6)";
case ENGINE_ERROR_PLY:
return "niveau invalide";
case ENGINE_ERROR_TOO_MANY_MOVES:
return "tableau de coups trop petit";
case ENGINE_ERROR_INTERNAL:
return "erreur interne de gnubg";
}
return "erreur inconnue";
}
/* ------------------------------------------------------------- Décodage */
/* Vrai si id a exactement length caractères de l'alphabet base64 de gnubg. */
static int
is_base64_id(const char *id, size_t length)
{
size_t i;
if (!id || strlen(id) != length)
return 0;
for (i = 0; i < length; i++)
if (Base64((unsigned char) id[i]) == 255)
return 0;
return 1;
}
engine_status
engine_decode_match(const char *match_id, engine_match *match)
{
unsigned int dice[2];
int turn, resigned, doubled;
gamestate state;
if (!is_base64_id(match_id, L_MATCHID))
return ENGINE_ERROR_MATCH_ID;
if (MatchFromID(dice, &turn, &resigned, &doubled, &match->player_on_roll,
&match->cube_owner, &match->crawford, &match->match_length,
match->score, &match->cube_value, &match->jacoby, &state, match_id) != 0)
return ENGINE_ERROR_MATCH_ID;
return ENGINE_OK;
}
/* Décode Position ID et Match ID, et prépare le cubeinfo de gnubg. */
static engine_status
decode_position(const char *position_id, const char *match_id, position *pos)
{
engine_match *m = &pos->match;
engine_status status;
if (engine_state != READY)
return ENGINE_ERROR_NOT_INITIALISED;
if (!is_base64_id(position_id, L_POSITIONID) || !PositionFromID(pos->board, position_id))
return ENGINE_ERROR_POSITION_ID;
status = engine_decode_match(match_id, m);
if (status != ENGINE_OK)
return status;
if (SetCubeInfo(&pos->cube, m->cube_value, m->cube_owner, m->player_on_roll,
m->match_length, m->score, m->crawford, m->jacoby,
DEFAULT_BEAVERS, VARIATION_STANDARD) != 0)
return ENGINE_ERROR_MATCH_ID;
return ENGINE_OK;
}
static engine_status
check_dice(int die1, int die2)
{
if (die1 < 1 || die1 > 6 || die2 < 1 || die2 > 6)
return ENGINE_ERROR_DICE;
return ENGINE_OK;
}
/* Réglages prédéfinis de gnubg pour un niveau, sans bruit. */
static engine_status
eval_context_for_ply(int ply, evalcontext *context)
{
if (ply < 0 || ply > ENGINE_MAX_PLY)
return ENGINE_ERROR_PLY;
*context = aecSettings[SETTINGS_0PLY + ply];
return ENGINE_OK;
}
/* En match, gnubg calcule en chances de gain du match : on convertit. */
static float
normalized_equity(float score, const cubeinfo *cube)
{
return cube->nMatchTo ? mwc2eq(score, cube) : score;
}
/* ------------------------------------------------------------------ Coups */
static void
fill_move(engine_move *out, const move *m, const position *pos, int evaluated)
{
int i;
FormatMove(out->notation, (ConstTanBoard) pos->board, m->anMove);
snprintf(out->position_id, sizeof out->position_id, "%s", PositionIDFromKey(&m->key));
out->ply = evaluated ? (int) m->esMove.ec.nPlies : -1;
for (i = 0; i < ENGINE_NUM_PROBABILITIES; i++)
out->probabilities[i] = evaluated ? m->arEvalMove[i] : 0.0f;
out->equity = evaluated ? normalized_equity(m->rScore, &pos->cube) : 0.0f;
}
engine_status
engine_legal_moves(const char *position_id, const char *match_id, int die1, int die2,
engine_move *moves, int max_moves, int *count)
{
position pos;
movelist list;
engine_status status;
unsigned int i;
status = decode_position(position_id, match_id, &pos);
if (status == ENGINE_OK)
status = check_dice(die1, die2);
if (status != ENGINE_OK)
return status;
/* La liste pointe vers un tableau interne de gnubg : rien à libérer. */
GenerateMoves(&list, (ConstTanBoard) pos.board, die1, die2, FALSE);
if (list.cMoves > (unsigned int) max_moves)
return ENGINE_ERROR_TOO_MANY_MOVES;
for (i = 0; i < list.cMoves; i++)
fill_move(&moves[i], &list.amMoves[i], &pos, 0);
*count = (int) list.cMoves;
return ENGINE_OK;
}
engine_status
engine_best_moves(const char *position_id, const char *match_id, int die1, int die2, int ply,
engine_move *moves, int max_moves, int *count)
{
position pos;
movelist list;
evalcontext context;
engine_status status;
unsigned int i;
status = decode_position(position_id, match_id, &pos);
if (status == ENGINE_OK)
status = check_dice(die1, die2);
if (status == ENGINE_OK)
status = eval_context_for_ply(ply, &context);
if (status != ENGINE_OK)
return status;
/* Comme la commande « hint » de gnubg, avec les filtres « normaux ». */
if (FindnSaveBestMoves(&list, die1, die2, (ConstTanBoard) pos.board, NULL, FALSE, 0.0f,
&pos.cube, &context, aaamfMoveFilterSettings[0]) < 0)
return ENGINE_ERROR_INTERNAL;
status = ENGINE_OK;
if (list.cMoves > (unsigned int) max_moves)
status = ENGINE_ERROR_TOO_MANY_MOVES;
else {
for (i = 0; i < list.cMoves; i++)
fill_move(&moves[i], &list.amMoves[i], &pos, 1);
*count = (int) list.cMoves;
}
g_free(list.amMoves);
return status;
}
/* ------------------------------------------------- Évaluation et videau */
engine_status
engine_evaluate(const char *position_id, const char *match_id, int ply,
engine_evaluation *evaluation)
{
position pos;
evalcontext context;
float output[NUM_ROLLOUT_OUTPUTS];
engine_status status;
int i;
status = decode_position(position_id, match_id, &pos);
if (status == ENGINE_OK)
status = eval_context_for_ply(ply, &context);
if (status != ENGINE_OK)
return status;
if (GeneralEvaluationE(output, (ConstTanBoard) pos.board, &pos.cube, &context) < 0)
return ENGINE_ERROR_INTERNAL;
for (i = 0; i < ENGINE_NUM_PROBABILITIES; i++)
evaluation->probabilities[i] = output[i];
evaluation->cubeless_equity = output[OUTPUT_EQUITY];
evaluation->cubeful_equity = normalized_equity(output[OUTPUT_CUBEFUL_EQUITY], &pos.cube);
return ENGINE_OK;
}
/* Noms anglais des décisions, dans l'ordre de l'énumération cubedecision. */
static const char *const cube_decision_names[] = {
"double_take", "double_pass", "no_double_take", "too_good_take", "too_good_pass",
"double_beaver", "no_double_beaver", "redouble_take", "redouble_pass",
"no_redouble_take", "too_good_redouble_take", "too_good_redouble_pass",
"no_redouble_beaver", "no_double_dead_cube", "no_redouble_dead_cube",
"not_available", "optional_double_take", "optional_redouble_take",
"optional_double_beaver", "optional_double_pass", "optional_redouble_pass"
};
engine_status
engine_cube_decision(const char *position_id, const char *match_id, int ply, engine_cube *cube)
{
position pos;
evalcontext context;
float output[2][NUM_ROLLOUT_OUTPUTS];
float equities[4];
cubedecision decision;
engine_status status;
int i;
status = decode_position(position_id, match_id, &pos);
if (status == ENGINE_OK)
status = eval_context_for_ply(ply, &context);
if (status != ENGINE_OK)
return status;
if (GeneralCubeDecisionE(output, (ConstTanBoard) pos.board, &pos.cube, &context, NULL) < 0)
return ENGINE_ERROR_INTERNAL;
/* FindCubeDecision renvoie des équités déjà normalisées. */
decision = FindCubeDecision(equities, output, &pos.cube);
if ((size_t) decision >= sizeof cube_decision_names / sizeof cube_decision_names[0])
return ENGINE_ERROR_INTERNAL;
for (i = 0; i < ENGINE_NUM_PROBABILITIES; i++)
cube->probabilities[i] = output[0][i];
cube->no_double = equities[OUTPUT_NODOUBLE];
cube->double_take = equities[OUTPUT_TAKE];
cube->double_pass = equities[OUTPUT_DROP];
cube->decision = cube_decision_names[decision];
return ENGINE_OK;
}
+155
View File
@@ -0,0 +1,155 @@
/*
* engine.h -- interface C du moteur de backgammon d'Ustagammon.
*
* Copyright (C) 2026 les auteurs d'Ustagammon.
* Licence : GPLv3 ou ultérieure (voir LICENSE).
*
* Le moteur est le cœur de GNU Backgammon (engine/gnubg/), seule autorité
* pour les règles, les coups, le videau et les évaluations. Cette interface
* n'expose aucun type de gnubg.
*
* Positions : une position est décrite par deux identifiants de gnubg,
* - le Position ID (14 caractères), qui code le plateau du point de vue du
* joueur au trait ;
* - le Match ID (12 caractères), qui code le reste : longueur du match (0 en
* partie libre), score, videau et son propriétaire, joueur au trait,
* Crawford, Jacoby. Les dés éventuellement codés dans le Match ID sont
* ignorés : les dés sont passés à part.
*
* Niveaux (« ply ») : réglages prédéfinis de gnubg, sans bruit, de 0
* (évaluation statique du réseau) à 4. gnubg compte les plis à partir de 0.
*
* Équités : toujours du point de vue du joueur au trait, normalisées pour
* un videau de 1. En match, gnubg convertit les chances de gain du match en
* équité normalisée, comme il l'affiche par défaut.
*
* Probabilités, dans l'ordre de gnubg : gain, gain gammon, gain backgammon,
* perte gammon, perte backgammon (les gammons incluent les backgammons).
*
* Contraintes : une seule instance du moteur (état global de gnubg) et des
* appels sérialisés, jamais concurrents. Toutes les fonctions renvoient un
* code engine_status ; aucune ne termine le programme pour une erreur
* d'utilisation.
*/
#ifndef USTAGAMMON_ENGINE_H
#define USTAGAMMON_ENGINE_H
#ifdef __cplusplus
extern "C" {
#endif
typedef enum {
ENGINE_OK = 0,
ENGINE_ERROR_NOT_INITIALISED, /* engine_init n'a pas réussi */
ENGINE_ERROR_DATA, /* données absentes ou invalides */
ENGINE_ERROR_POSITION_ID, /* Position ID invalide */
ENGINE_ERROR_MATCH_ID, /* Match ID invalide */
ENGINE_ERROR_DICE, /* dés hors de 1..6 */
ENGINE_ERROR_PLY, /* niveau hors de 0..ENGINE_MAX_PLY */
ENGINE_ERROR_TOO_MANY_MOVES, /* tableau de coups trop petit */
ENGINE_ERROR_INTERNAL /* échec signalé par gnubg */
} engine_status;
#define ENGINE_MAX_PLY 4
/* Nombre maximal de coups légaux pour un jet (MAX_MOVES de gnubg). */
#define ENGINE_MAX_MOVES 3060
/* Indices du tableau probabilities. */
enum {
ENGINE_WIN = 0,
ENGINE_WIN_GAMMON,
ENGINE_WIN_BACKGAMMON,
ENGINE_LOSE_GAMMON,
ENGINE_LOSE_BACKGAMMON,
ENGINE_NUM_PROBABILITIES
};
/* Un coup, avec son évaluation quand il a été classé. */
typedef struct {
char notation[32]; /* notation de gnubg, ex. « 8/5 6/5 » */
char position_id[16]; /* Position ID après le coup, plateau vu du
* joueur qui vient de jouer */
int ply; /* niveau de l'évaluation (-1 : non évalué) */
float probabilities[ENGINE_NUM_PROBABILITIES];
float equity; /* équité de classement (avec videau) */
} engine_move;
/* Évaluation d'une position, joueur au trait avant de lancer les dés. */
typedef struct {
float probabilities[ENGINE_NUM_PROBABILITIES];
float cubeless_equity; /* équité sans videau */
float cubeful_equity; /* équité avec videau */
} engine_evaluation;
/* Décision de videau du joueur au trait, avant de lancer les dés. */
typedef struct {
float probabilities[ENGINE_NUM_PROBABILITIES];
float no_double; /* équité si pas de double */
float double_take; /* équité si double et prend */
float double_pass; /* équité si double et passe */
const char *decision; /* décision de gnubg, en anglais : « no_double_take »,
* « double_take », « double_pass »,
* « too_good_pass », « not_available »… */
} engine_cube;
/* Description lisible d'un Match ID décodé. */
typedef struct {
int match_length; /* 0 : partie libre (money game) */
int score[2];
int cube_value;
int cube_owner; /* -1 : videau au centre ; sinon 0 ou 1 */
int player_on_roll; /* 0 ou 1 */
int crawford; /* partie Crawford en cours */
int jacoby; /* règle Jacoby (partie libre) */
} engine_match;
/*
* Charge les données du moteur depuis data_dir : gnubg.wd (réseaux),
* gnubg_os0.bd et gnubg_ts0.bd (fins de partie), Kazaross-XG2.xml (table
* d'équité de match). À appeler une fois avant toute autre fonction.
*/
engine_status engine_init(const char *data_dir);
/* Libère les ressources. Le moteur ne peut pas être réinitialisé ensuite. */
void engine_shutdown(void);
/* Message d'erreur en français pour un code de retour. */
const char *engine_status_message(engine_status status);
/* Décode un Match ID. */
engine_status engine_decode_match(const char *match_id, engine_match *match);
/*
* Coups légaux pour une position et un jet (die1, die2 dans 1..6), sans
* évaluation (ply vaut -1). moves doit pouvoir contenir max_moves coups ;
* ENGINE_MAX_MOVES suffit toujours. *count reçoit le nombre de coups (0 si
* le joueur ne peut pas jouer).
*/
engine_status engine_legal_moves(const char *position_id, const char *match_id,
int die1, int die2,
engine_move *moves, int max_moves, int *count);
/*
* Coups légaux classés du meilleur au moins bon, au niveau ply. Comme dans
* gnubg, les coups écartés par les filtres gardent l'évaluation du niveau
* inférieur (voir leur champ ply).
*/
engine_status engine_best_moves(const char *position_id, const char *match_id,
int die1, int die2, int ply,
engine_move *moves, int max_moves, int *count);
/* Évaluation d'une position au niveau ply. */
engine_status engine_evaluate(const char *position_id, const char *match_id,
int ply, engine_evaluation *evaluation);
/* Décision de videau au niveau ply. */
engine_status engine_cube_decision(const char *position_id, const char *match_id,
int ply, engine_cube *cube);
#ifdef __cplusplus
}
#endif
#endif /* USTAGAMMON_ENGINE_H */