Files
Ustagammon/engine-cli/main.c
T

791 lines
28 KiB
C

/*
* 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 | --level NIVEAU] [--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
* position POSITION_ID MATCH_ID plateau décodé, même position vue de
* l'adversaire, fin de partie éventuelle
* halfmoves POSITION_ID DÉS [DEMI-COUPS]
* vérifie des demi-coups saisis un par
* un (ex. « 8/5 6/5 », « bar/22 »,
* « 6/off »)
* matchid VIDEAU PROPRIÉTAIRE Match ID d'une partie libre avec la
* règle Jacoby, joueur 0 au trait ;
* PROPRIÉTAIRE : -1 (centre), 0 ou 1
* 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
* levels niveaux de jeu, seuils d'appréciation
* et de chance de gnubg
* analysemove POSITION_ID MATCH_ID DÉS POSITION_JOUÉE
* analyse du coup joué (POSITION_JOUÉE :
* position après le coup, vue du joueur
* qui l'a joué, comme position_id dans
* moves et legal)
* analysecube POSITION_ID MATCH_ID DÉCISION
* analyse d'une décision de videau :
* no_double ou double (joueur au trait),
* take ou pass (son adversaire)
* luck POSITION_ID MATCH_ID DÉS chance du jet pour le joueur au trait
*
* --ply : niveau de gnubg, de 0 (défaut) à 4 (meilleur coup comme « hint »,
* analyses comme l'analyse de partie de gnubg).
* --level : niveau de jeu de l'ordinateur (voir levels), pour bestmove, moves
* et cube : coups classés comme l'ordinateur de gnubg les joue, bruit compris.
* --data : dossier des données ; par défaut « data » à côté du programme.
*
* En WebAssembly (engine-wasm/), le même programme sert d'interface : le
* JavaScript appelle main pour chaque commande, sans recharger le moteur.
* engine_init ne fait rien quand le moteur est déjà prêt, et main ne l'arrête
* pas à la fin.
*
* 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
/* Format des nombres : 9 chiffres significatifs redonnent exactement le
* float32 calculé par gnubg. */
#define NUMBER "%.9g"
/* Liste des commandes, pour les messages d'erreur. */
#define COMMANDS "bestmove, moves, legal, cube, eval, position, halfmoves, matchid, reference, " \
"levels, analysemove, analysecube ou luck"
/* 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. */
#define MAX_ARGS 4
typedef struct {
int ply;
int level; /* -1 : pas de --level */
const char *data_dir;
const char *command;
const char *args[MAX_ARGS];
int arg_count;
} options;
/* Noms JSON des appréciations (engine_skill), des chances
* (engine_luck_rating) et des décisions de videau (engine_cube_action). */
static const char *const skill_names[] = { "none", "doubtful", "bad", "very_bad" };
static const char *const luck_names[] = {
"very_unlucky", "unlucky", "none", "lucky", "very_lucky"
};
static const char *const cube_action_names[] = { "no_double", "double", "take", "pass" };
/* 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";
case ENGINE_ERROR_LEVEL:
return "invalid_level";
case ENGINE_ERROR_MOVE:
return "invalid_move";
case ENGINE_ERROR_CUBE_ACTION:
return "invalid_cube_action";
}
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\": " 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]);
}
/* Demi-coups sous la forme [[8, 5], [6, 5]]. */
static void
print_half_moves(const engine_half_move *half_moves, int count)
{
int i;
printf("[");
for (i = 0; i < count; i++)
printf("%s[%d, %d]", i ? ", " : "", half_moves[i].from, half_moves[i].to);
printf("]");
}
static void
print_move(const engine_move *m, int with_evaluation)
{
printf("{\"move\": \"%s\", \"position_id\": \"%s\", \"half_moves\": ",
m->notation, m->position_id);
print_half_moves(m->half_moves, m->half_move_count);
if (with_evaluation) {
printf(", \"ply\": %d, \"equity\": " NUMBER ", \"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");
}
/* 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);
}
/* « , "ply": N » ou « , "level": "NOM" », selon les options. */
static void
print_level_or_ply(const options *opt)
{
engine_level_settings settings;
if (opt->level >= 0 && engine_level_settings_of(opt->level, &settings) == ENGINE_OK)
printf(", \"level\": \"%s\"", settings.name);
else
printf(", \"ply\": %d", opt->ply);
}
/* 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 && opt->level >= 0)
status = engine_level_best_moves(position_id, match_id, die1, die2, opt->level,
moves, ENGINE_MAX_MOVES, &count);
else 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)
print_level_or_ply(opt);
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\": " NUMBER ", \"cubeful_equity\": " NUMBER "}\n",
e.cubeless_equity, e.cubeful_equity);
} else {
engine_cube c;
if (opt->level >= 0)
status = engine_level_cube_decision(position_id, match_id, opt->level, &c);
else
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\"", position_id, match_id);
print_level_or_ply(opt);
printf(", \"match\": ");
print_match(&match);
printf(", \"probabilities\": ");
print_probabilities(c.probabilities);
printf(", ");
print_cube_fields(&c);
printf(", \"available\": %s, \"double\": %s, \"take\": %s}\n",
c.available ? "true" : "false", c.should_double ? "true" : "false",
c.should_take ? "true" : "false");
}
return EXIT_SUCCESS;
}
static void
print_side(const engine_side *side)
{
int i;
printf("{\"points\": [");
for (i = 0; i < 24; i++)
printf("%s%d", i ? ", " : "", side->points[i]);
printf("], \"bar\": %d, \"off\": %d, \"pip_count\": %d}", side->bar, side->off,
side->pip_count);
}
/* position : plateau, même position vue de l'adversaire, fin de partie. */
static int
run_board_command(const options *opt)
{
static const char *const kinds[] = { "", "single", "gammon", "backgammon" };
const char *position_id = opt->args[0], *match_id = opt->args[1];
engine_board board;
engine_result result;
engine_status status;
if (opt->arg_count != 2)
return usage_error("arguments attendus : POSITION_ID MATCH_ID");
status = engine_decode_position(position_id, &board);
if (status == ENGINE_OK)
status = engine_game_result(position_id, match_id, &result);
if (status != ENGINE_OK)
return print_engine_error(status);
printf("{\"position_id\": \"%s\", \"match_id\": \"%s\", \"on_roll\": ", position_id,
match_id);
print_side(&board.on_roll);
printf(", \"opponent\": ");
print_side(&board.opponent);
printf(", \"swapped_position_id\": \"%s\", \"result\": ", board.swapped_position_id);
if (result.over)
printf("{\"winner\": \"%s\", \"kind\": \"%s\", \"points\": %d}",
result.winner == 0 ? "on_roll" : "opponent", kinds[result.kind], result.points);
else
printf("null");
printf("}\n");
return EXIT_SUCCESS;
}
/* Lit une case d'un demi-coup : 1 à 25, « bar » (25) ou « off » (0). */
static int
parse_point(const char *text, size_t length, int *point)
{
char buffer[4];
char *end;
long value;
if (length == 0 || length >= sizeof buffer)
return 0;
memcpy(buffer, text, length);
buffer[length] = '\0';
if (strcmp(buffer, "bar") == 0) {
*point = ENGINE_BAR;
return 1;
}
if (strcmp(buffer, "off") == 0) {
*point = ENGINE_OFF;
return 1;
}
value = strtol(buffer, &end, 10);
if (*end || value < 1 || value > ENGINE_BAR)
return 0;
*point = (int) value;
return 1;
}
/* Lit des demi-coups séparés par des espaces, chacun « DÉPART/ARRIVÉE ».
* Renvoie leur nombre, ou -1 si le texte est mal formé. */
static int
parse_half_moves(const char *text, engine_half_move half_moves[ENGINE_MAX_HALF_MOVES])
{
int count = 0;
while (*text) {
const char *start, *slash;
size_t length;
while (*text == ' ')
text++;
if (!*text)
break;
start = text;
while (*text && *text != ' ')
text++;
length = (size_t) (text - start);
slash = memchr(start, '/', length);
if (!slash || count == ENGINE_MAX_HALF_MOVES
|| !parse_point(start, (size_t) (slash - start), &half_moves[count].from)
|| !parse_point(slash + 1, length - (size_t) (slash - start) - 1, &half_moves[count].to))
return -1;
count++;
}
return count;
}
/* halfmoves : vérifie des demi-coups saisis un par un. */
static int
run_half_moves_command(const options *opt)
{
const char *position_id = opt->args[0];
engine_half_move half_moves[ENGINE_MAX_HALF_MOVES];
engine_half_moves_check check;
engine_status status;
int die1, die2, count = 0;
if (opt->arg_count != 2 && opt->arg_count != 3)
return usage_error("arguments attendus : POSITION_ID DÉS [DEMI-COUPS]");
if (!parse_dice(opt->args[1], &die1, &die2))
return usage_error("dés attendus sous la forme 31");
if (opt->arg_count == 3)
count = parse_half_moves(opt->args[2], half_moves);
if (count < 0)
return usage_error("demi-coups attendus sous la forme 8/5 6/5, bar/22 ou 6/off");
status = engine_check_half_moves(position_id, die1, die2, half_moves, count, &check);
if (status != ENGINE_OK)
return print_engine_error(status);
printf("{\"position_id\": \"%s\", \"dice\": [%d, %d], \"half_moves\": ", position_id,
die1, die2);
print_half_moves(half_moves, count);
printf(", \"valid\": %s, \"complete\": %s, \"result_position_id\": ",
check.valid ? "true" : "false", check.complete ? "true" : "false");
if (check.valid)
printf("\"%s\"}\n", check.position_id);
else
printf("null}\n");
return EXIT_SUCCESS;
}
/* matchid : Match ID d'une partie libre avec la règle Jacoby, joueur 0 au
* trait. */
static int
run_match_id_command(const options *opt)
{
engine_match match = { .cube_value = 1, .cube_owner = -1, .jacoby = 1 };
char match_id[13];
char *end;
engine_status status;
if (opt->arg_count != 2)
return usage_error("arguments attendus : VIDEAU PROPRIÉTAIRE");
match.cube_value = (int) strtol(opt->args[0], &end, 10);
if (*end || end == opt->args[0])
return usage_error("VIDEAU attend un nombre");
match.cube_owner = (int) strtol(opt->args[1], &end, 10);
if (*end || end == opt->args[1])
return usage_error("PROPRIÉTAIRE attend -1, 0 ou 1");
status = engine_encode_match(&match, match_id);
if (status != ENGINE_OK)
return print_engine_error(status);
printf("{\"match_id\": \"%s\", \"match\": ", match_id);
print_match(&match);
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;
}
/* levels : niveaux de jeu, seuils d'appréciation et de chance. */
static int
run_levels_command(const options *opt)
{
engine_level_settings s;
int level;
if (opt->arg_count != 0)
return usage_error("levels n'attend pas d'argument");
printf("{\"levels\": [");
for (level = 0; level < ENGINE_LEVEL_COUNT; level++) {
engine_level_settings_of(level, &s);
printf("%s{\"name\": \"%s\", \"plies\": %d, \"noise\": " NUMBER
", \"deterministic\": %s, \"prune\": %s}", level ? ", " : "", s.name, s.plies,
s.noise, s.deterministic ? "true" : "false", s.prune ? "true" : "false");
}
printf("], \"skill_thresholds\": {\"doubtful\": " NUMBER ", \"bad\": " NUMBER
", \"very_bad\": " NUMBER "}, \"luck_thresholds\": {\"lucky\": " NUMBER
", \"very_lucky\": " NUMBER "}}\n",
ENGINE_SKILL_DOUBTFUL_THRESHOLD, ENGINE_SKILL_BAD_THRESHOLD,
ENGINE_SKILL_VERY_BAD_THRESHOLD, ENGINE_LUCK_THRESHOLD, ENGINE_LUCK_VERY_THRESHOLD);
return EXIT_SUCCESS;
}
/* analysemove : analyse du coup joué. */
static int
run_analyse_move_command(const options *opt)
{
const char *position_id = opt->args[0], *match_id = opt->args[1];
const char *played_id = opt->args[3];
engine_move_analysis a;
engine_status status;
int die1, die2;
if (opt->arg_count != 4)
return usage_error("arguments attendus : POSITION_ID MATCH_ID DÉS POSITION_JOUÉE");
if (!parse_dice(opt->args[2], &die1, &die2))
return usage_error("dés attendus sous la forme 31");
status = engine_analyse_move(position_id, match_id, die1, die2, played_id, opt->ply, &a);
if (status != ENGINE_OK)
return print_engine_error(status);
printf("{\"position_id\": \"%s\", \"match_id\": \"%s\", \"dice\": [%d, %d], \"ply\": %d, "
"\"played_position_id\": \"%s\", \"move_count\": %d",
position_id, match_id, die1, die2, opt->ply, played_id, a.move_count);
if (a.move_count == 0) {
printf(", \"rank\": null, \"played\": null, \"best\": null");
} else {
printf(", \"rank\": %d, \"played\": ", a.rank);
print_move(&a.played, 1);
printf(", \"best\": ");
print_move(&a.best, 1);
}
printf(", \"equity_loss\": " NUMBER ", \"skill\": \"%s\"}\n", a.equity_loss,
skill_names[a.skill]);
return EXIT_SUCCESS;
}
/* analysecube : analyse d'une décision de videau. */
static int
run_analyse_cube_command(const options *opt)
{
const char *position_id = opt->args[0], *match_id = opt->args[1];
engine_cube_analysis a;
engine_status status;
int action;
if (opt->arg_count != 3)
return usage_error("arguments attendus : POSITION_ID MATCH_ID DÉCISION");
for (action = 0; action < 4 && strcmp(opt->args[2], cube_action_names[action]) != 0; action++)
;
if (action == 4)
return usage_error("DÉCISION attend no_double, double, take ou pass");
status = engine_analyse_cube(position_id, match_id, opt->ply, (engine_cube_action) action, &a);
if (status != ENGINE_OK)
return print_engine_error(status);
printf("{\"position_id\": \"%s\", \"match_id\": \"%s\", \"ply\": %d, \"action\": \"%s\", "
"\"analysed\": %s, \"cube\": ", position_id, match_id, opt->ply,
cube_action_names[action], a.analysed ? "true" : "false");
if (a.analysed) {
printf("{\"probabilities\": ");
print_probabilities(a.cube.probabilities);
printf(", ");
print_cube_fields(&a.cube);
printf("}");
} else {
printf("null");
}
printf(", \"equity_loss\": " NUMBER ", \"skill\": \"%s\"}\n", a.equity_loss,
skill_names[a.skill]);
return EXIT_SUCCESS;
}
/* luck : chance du jet pour le joueur au trait. */
static int
run_luck_command(const options *opt)
{
const char *position_id = opt->args[0], *match_id = opt->args[1];
engine_luck luck;
engine_status status;
int die1, die2;
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");
status = engine_luck_of(position_id, match_id, die1, die2, &luck);
if (status != ENGINE_OK)
return print_engine_error(status);
printf("{\"position_id\": \"%s\", \"match_id\": \"%s\", \"dice\": [%d, %d], \"luck\": " NUMBER
", \"rating\": \"%s\"}\n", position_id, match_id, die1, die2, luck.luck,
luck_names[luck.rating]);
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;
int has_ply = 0;
memset(opt, 0, sizeof *opt);
opt->level = -1;
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";
has_ply = 1;
} else if (strcmp(argv[i], "--level") == 0 && i + 1 < argc) {
if (engine_level_from_name(argv[++i], &opt->level) != ENGINE_OK)
return "--level attend un niveau : beginner, casual, intermediate, advanced, "
"0ply, 1ply, 2ply, 3ply ou 4ply";
} 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 < MAX_ARGS) {
opt->args[opt->arg_count++] = argv[i];
} else {
return "trop d'arguments";
}
}
if (!opt->command)
return "commande attendue : " COMMANDS;
if (opt->level >= 0 && has_ply)
return "--ply et --level ne vont pas ensemble";
if (opt->level >= 0 && strcmp(opt->command, "bestmove") != 0
&& strcmp(opt->command, "moves") != 0 && strcmp(opt->command, "cube") != 0)
return "--level ne s'applique qu'à bestmove, moves et cube";
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 if (strcmp(opt.command, "reference") == 0)
result = run_reference_command(&opt);
else if (strcmp(opt.command, "position") == 0)
result = run_board_command(&opt);
else if (strcmp(opt.command, "halfmoves") == 0)
result = run_half_moves_command(&opt);
else if (strcmp(opt.command, "matchid") == 0)
result = run_match_id_command(&opt);
else if (strcmp(opt.command, "levels") == 0)
result = run_levels_command(&opt);
else if (strcmp(opt.command, "analysemove") == 0)
result = run_analyse_move_command(&opt);
else if (strcmp(opt.command, "analysecube") == 0)
result = run_analyse_cube_command(&opt);
else if (strcmp(opt.command, "luck") == 0)
result = run_luck_command(&opt);
else
result = usage_error("commande inconnue : " COMMANDS);
#ifndef __EMSCRIPTEN__
engine_shutdown();
#endif
return result;
}