/* * 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 #include #include #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[""] (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; }