Moteur : plateau, fin de partie, Match ID et saisie demi-coup par demi-coup
Ce dont le domaine d'une partie a besoin, sans réimplémenter de règle : décodage d'un Position ID en plateau et position vue de l'adversaire, résultat d'une partie finie (gammon, règle Jacoby), codage d'un Match ID, vérification de demi-coups saisis un par un (la légalité vient de GenerateMoves de gnubg), demi-coups de chaque coup légal, droit de doubler et recommandations de double et de prise. Nouvelles commandes d'engine-cli position, halfmoves et matchid, avec leurs tests natifs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
03b26ccba7
commit
6af82f73b0
+202
-4
@@ -13,6 +13,15 @@
|
||||
* 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) :
|
||||
@@ -45,6 +54,9 @@
|
||||
* 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 ou reference"
|
||||
|
||||
/* Coups gardés dans le classement de la commande reference (comme hint). */
|
||||
#define REFERENCE_MAX_MOVES 10
|
||||
|
||||
@@ -113,10 +125,24 @@ print_probabilities(const float p[ENGINE_NUM_PROBABILITIES])
|
||||
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\"", m->notation, m->position_id);
|
||||
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);
|
||||
@@ -254,11 +280,177 @@ run_position_command(const options *opt)
|
||||
print_probabilities(c.probabilities);
|
||||
printf(", ");
|
||||
print_cube_fields(&c);
|
||||
printf("}\n");
|
||||
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).
|
||||
@@ -359,7 +551,7 @@ parse_options(int argc, char **argv, options *opt)
|
||||
}
|
||||
}
|
||||
if (!opt->command)
|
||||
return "commande attendue : bestmove, moves, legal, cube, eval ou reference";
|
||||
return "commande attendue : " COMMANDS;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -393,8 +585,14 @@ main(int argc, char **argv)
|
||||
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
|
||||
result = usage_error("commande inconnue : bestmove, moves, legal, cube, eval ou reference");
|
||||
result = usage_error("commande inconnue : " COMMANDS);
|
||||
|
||||
#ifndef __EMSCRIPTEN__
|
||||
engine_shutdown();
|
||||
|
||||
Reference in New Issue
Block a user