/* * 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_ERROR_LEVEL, /* niveau de jeu inconnu */ ENGINE_ERROR_MOVE, /* coup joué absent des coups légaux */ ENGINE_ERROR_CUBE_ACTION /* décision de videau inconnue */ } engine_status; #define ENGINE_MAX_PLY 4 /* * Niveaux de jeu de l'ordinateur : les réglages prédéfinis de gnubg, dans * l'ordre de son tableau aecSettings (engine/gnubg/eval.c, l. 305-328, noms * de aszSettings). Les quatre premiers évaluent à 0 pli et ajoutent un * bruit (rNoise, écart type en équité) qui fait commettre des erreurs ; ce * bruit est « déterministe » (fDeterministic) : il est tiré d'un hachage MD5 * de la position (Noise, eval.c l. 2241), donc la même position donne * toujours le même coup. Les suivants sont sans bruit, de 0 à 4 plis, avec * le filtre de coups « Normal » (aiSettingsMoveFilter, eval.c l. 331). * * Les anciennes versions de gnubg nommaient « expert » le niveau 0 pli et * « world class » (champion du monde) le niveau 2 plis. */ typedef enum { ENGINE_LEVEL_BEGINNER = 0, /* « beginner » : 0 pli, bruit 0,060 */ ENGINE_LEVEL_CASUAL, /* « casual play » : 0 pli, bruit 0,050 */ ENGINE_LEVEL_INTERMEDIATE, /* « intermediate » : 0 pli, bruit 0,040 */ ENGINE_LEVEL_ADVANCED, /* « advanced » : 0 pli, bruit 0,015 */ ENGINE_LEVEL_0PLY, /* « 0ply » (ancien « expert ») */ ENGINE_LEVEL_1PLY, /* « 1ply » */ ENGINE_LEVEL_2PLY, /* « 2ply » (ancien « world class ») */ ENGINE_LEVEL_3PLY, /* « 3ply » */ ENGINE_LEVEL_4PLY, /* « 4ply » */ ENGINE_LEVEL_COUNT } engine_level; /* Réglages d'un niveau, tels que gnubg les définit. */ typedef struct { const char *name; /* identifiant : « beginner », « casual », * « intermediate », « advanced », « 0ply »… « 4ply » */ int plies; /* profondeur, en plis de gnubg */ float noise; /* écart type du bruit (0 : aucun) */ int deterministic; /* bruit tiré de la position, reproductible */ int prune; /* élagage des réponses (gnubg : à partir d'1 pli) */ } engine_level_settings; /* * Appréciation d'un coup ou d'une décision de videau : seuils de gnubg sur * l'équité perdue, normalisée pour un videau de 1 (arSkillLevel dans * gnubg.c l. 280-285 et Skill dans analysis.c l. 288). Une perte * strictement plus grande que le seuil fait passer à la catégorie. */ #define ENGINE_SKILL_DOUBTFUL_THRESHOLD 0.03f /* douteux */ #define ENGINE_SKILL_BAD_THRESHOLD 0.06f /* mauvais */ #define ENGINE_SKILL_VERY_BAD_THRESHOLD 0.12f /* très mauvais */ typedef enum { ENGINE_SKILL_NONE = 0, /* rien à signaler */ ENGINE_SKILL_DOUBTFUL, /* douteux */ ENGINE_SKILL_BAD, /* mauvais */ ENGINE_SKILL_VERY_BAD /* très mauvais */ } engine_skill; /* * Chance d'un jet : seuils de gnubg sur l'équité gagnée ou perdue par * rapport à la moyenne des jets (arLuckLevel dans gnubg.c l. 274-279 et Luck * dans analysis.c l. 272). Un écart strictement plus grand que le seuil * fait passer à la catégorie. */ #define ENGINE_LUCK_THRESHOLD 0.3f /* chanceux, malchanceux */ #define ENGINE_LUCK_VERY_THRESHOLD 0.6f /* très chanceux, très malchanceux */ typedef enum { ENGINE_LUCK_VERY_UNLUCKY = 0, /* très malchanceux */ ENGINE_LUCK_UNLUCKY, /* malchanceux */ ENGINE_LUCK_NONE, /* ordinaire */ ENGINE_LUCK_LUCKY, /* chanceux */ ENGINE_LUCK_VERY_LUCKY /* très chanceux */ } engine_luck_rating; /* Décision de videau prise par un joueur. Les deux premières sont celles * du joueur au trait, les deux dernières sa réponse par l'adversaire. */ typedef enum { ENGINE_CUBE_ACTION_NO_DOUBLE = 0, ENGINE_CUBE_ACTION_DOUBLE, ENGINE_CUBE_ACTION_TAKE, ENGINE_CUBE_ACTION_PASS } engine_cube_action; /* 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 }; /* * Demi-coup : un pion déplacé d'une case avec un seul dé. Les cases sont * numérotées du point de vue du joueur qui joue, comme dans la notation de * gnubg : 1 à 24, 25 pour la barre, 0 pour la sortie. */ #define ENGINE_BAR 25 #define ENGINE_OFF 0 typedef struct { int from; int to; } engine_half_move; /* Un coup complet utilise au plus quatre dés (un double). */ #define ENGINE_MAX_HALF_MOVES 4 /* 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 */ engine_half_move half_moves[ENGINE_MAX_HALF_MOVES]; int half_move_count; /* demi-coups du coup, dans un ordre jouable */ 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 */ float optimal; /* équité après les meilleures décisions des deux * joueurs */ const char *decision; /* nom de la décision dans l'énumération * cubedecision de gnubg : « NODOUBLE_TAKE », * « DOUBLE_TAKE », « DOUBLE_PASS », * « TOOGOOD_PASS », « NOT_AVAILABLE »… */ const char *decision_text; /* libellé anglais de gnubg, ex. « Double, take » */ int available; /* le joueur au trait a le droit de doubler */ int should_double; /* gnubg recommande de doubler : décision * DOUBLE_TAKE, DOUBLE_PASS, DOUBLE_BEAVER, * REDOUBLE_TAKE ou REDOUBLE_PASS (un double * « facultatif » n'est pas recommandé) */ int should_take; /* s'il double, l'adversaire doit prendre : * prendre lui coûte moins que passer * (double_take <= double_pass) */ } engine_cube; /* 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 * possible, rien d'autre n'est rempli) */ int rank; /* rang du coup joué dans le classement, à * partir de 1 */ engine_move played; /* le coup joué, évalué */ engine_move best; /* le meilleur coup, évalué */ 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 */ } engine_move_analysis; /* Analyse d'une décision de videau, comme l'analyse de partie de gnubg. */ typedef struct { int analysed; /* gnubg analyse cette décision ; sinon (videau * inaccessible, position de départ…), perte * nulle et cube non rempli */ engine_cube cube; /* la décision de videau du joueur au trait */ float equity_loss; /* équité perdue par celui qui décide, du point * de vue de celui-ci, normalisée pour un videau * de 1 : 0 ou négative */ engine_skill skill; /* appréciation de gnubg pour cette perte */ } engine_cube_analysis; /* Chance d'un jet, comme l'analyse de partie de gnubg. */ typedef struct { float luck; /* équité gagnée (positive) ou perdue par ce jet * par rapport à la moyenne des jets, du point * de vue du joueur au trait */ engine_luck_rating rating; } engine_luck; /* Un camp sur le plateau, vu de lui-même (sa case 1 est la plus proche de * la sortie). */ typedef struct { int points[24]; /* pions sur les cases 1 à 24 (indices 0 à 23) */ int bar; /* pions sur la barre */ int off; /* pions sortis */ int pip_count; /* compte de points */ } engine_side; /* Plateau décodé d'un Position ID. */ typedef struct { engine_side on_roll; /* le joueur au trait */ engine_side opponent; /* son adversaire */ char swapped_position_id[16]; /* la même position vue de l'adversaire */ } engine_board; /* Fin de partie. */ typedef struct { int over; /* la partie est finie */ int winner; /* 0 : le joueur au trait, 1 : son adversaire */ int kind; /* 1 : simple, 2 : gammon, 3 : backgammon */ int points; /* points gagnés : kind fois la valeur du videau, * mais 1 fois en partie libre avec la règle * Jacoby quand le videau n'a pas bougé */ } engine_result; /* Où en est un coup saisi demi-coup par demi-coup. */ typedef struct { int valid; /* les demi-coups commencent un coup légal */ int complete; /* ils forment à eux seuls un coup légal */ char position_id[16]; /* si valid : position après ces demi-coups, vue * du joueur qui joue ; sinon chaîne vide */ } engine_half_moves_check; /* 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); /* * Code un Match ID (12 caractères et le zéro final), sans dés ni double en * cours. cube_value : puissance de 2 de 1 à 4096 ; cube_owner : -1, 0 ou 1. */ engine_status engine_encode_match(const engine_match *match, char match_id[13]); /* Décode un Position ID en plateau. */ engine_status engine_decode_position(const char *position_id, engine_board *board); /* La partie est-elle finie dans cette position, et combien rapporte-t-elle ? */ engine_status engine_game_result(const char *position_id, const char *match_id, engine_result *result); /* * Vérifie des demi-coups saisis un par un (count de 0 à * ENGINE_MAX_HALF_MOVES), avec le jet die1, die2 : chacun doit être permis * par les règles, et l'ensemble doit pouvoir se compléter en un des coups * légaux d'engine_legal_moves. Des demi-coups refusés ne sont pas une erreur : * check->valid vaut alors 0. */ engine_status engine_check_half_moves(const char *position_id, int die1, int die2, const engine_half_move *half_moves, int count, engine_half_moves_check *check); /* * 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, exactement * comme la commande « hint » de gnubg (filtre de coups « Normal »). Les coups * écartés par les filtres gardent l'évaluation du niveau inférieur (voir * leur champ ply) et sont classés après ceux évalués plus profondément. */ 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); /* ------------------------------------------------------ Niveaux de jeu */ /* Réglages d'un niveau (engine_level). */ engine_status engine_level_settings_of(int level, engine_level_settings *settings); /* Le niveau qui porte ce nom, ou ENGINE_ERROR_LEVEL. */ engine_status engine_level_from_name(const char *name, int *level); /* * Coups légaux classés comme les classe l'ordinateur de gnubg quand il joue * à ce niveau (ComputerTurn dans play.c : FindnSaveBestMoves sans analyse * ni seuil, filtre « Normal », puis RefreshMoveList) ; il joue le premier. * Les équités incluent le bruit du niveau. */ engine_status engine_level_best_moves(const char *position_id, const char *match_id, int die1, int die2, int level, engine_move *moves, int max_moves, int *count); /* Décision de videau évaluée avec les réglages de ce niveau, bruit compris. */ engine_status engine_level_cube_decision(const char *position_id, const char *match_id, int level, engine_cube *cube); /* ------------------------------------------------------------- Analyse */ /* Appréciation de gnubg pour une perte d'équité (0 ou négative). */ engine_skill engine_skill_of(float equity_loss); /* Catégorie de chance de gnubg pour une chance de jet. */ 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 * 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. */ 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); /* * Analyse une décision de videau au niveau ply, comme l'analyse de partie de * gnubg (AnalyzeMove, cas MOVE_NORMAL, MOVE_DOUBLE, MOVE_TAKE et * MOVE_DROP). La position et le Match ID sont ceux du joueur au trait, qui * double ou non ; pour TAKE et PASS, la perte est celle de son adversaire. */ engine_status engine_analyse_cube(const char *position_id, const char *match_id, int ply, engine_cube_action action, engine_cube_analysis *analysis); /* * Chance du jet die1, die2 pour le joueur au trait, comme l'analyse de * partie de gnubg (LuckAnalysis dans analysis.c : meilleur coup de chaque * jet à 0 pli, avec videau ; à la position de départ, le premier jet ne * peut pas être un double). */ engine_status engine_luck_of(const char *position_id, const char *match_id, int die1, int die2, engine_luck *luck); #ifdef __cplusplus } #endif #endif /* USTAGAMMON_ENGINE_H */