Étape 0d.1 : domaine « partie » en TypeScript, scénario exécuté ; ADR 0009 accepté #11

Merged
pierre merged 11 commits from plateau into main 2026-11-02 20:14:20 +00:00
4 changed files with 644 additions and 8 deletions
Showing only changes of commit 6af82f73b0 - Show all commits
+202 -4
View File
@@ -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();
+277 -4
View File
@@ -182,6 +182,38 @@ engine_decode_match(const char *match_id, engine_match *match)
return ENGINE_OK;
}
/* Code un Match ID : voir engine.h. */
engine_status
engine_encode_match(const engine_match *match, char match_id[13])
{
const unsigned int no_dice[2] = { 0, 0 };
int value = match->cube_value, log2 = 0;
while (value > 1 && value % 2 == 0) {
value /= 2;
log2++;
}
if (value != 1 || log2 > 12 || match->cube_owner < -1 || match->cube_owner > 1
|| match->player_on_roll < 0 || match->player_on_roll > 1 || match->match_length < 0)
return ENGINE_ERROR_MATCH_ID;
snprintf(match_id, 13, "%s",
MatchID(no_dice, match->player_on_roll, 0, 0, match->player_on_roll,
match->cube_owner, match->crawford, match->match_length, match->score,
match->cube_value, match->jacoby, GAME_PLAYING));
return ENGINE_OK;
}
/* Décode un Position ID en plateau de gnubg. */
static engine_status
decode_board(const char *position_id, TanBoard board)
{
if (engine_state != READY)
return ENGINE_ERROR_NOT_INITIALISED;
if (!is_base64_id(position_id, L_POSITIONID) || !PositionFromID(board, position_id))
return ENGINE_ERROR_POSITION_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)
@@ -189,10 +221,9 @@ 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 = decode_board(position_id, pos->board);
if (status != ENGINE_OK)
return status;
status = engine_decode_match(match_id, m);
if (status != ENGINE_OK)
return status;
@@ -237,6 +268,13 @@ fill_move(engine_move *out, const move *m, const position *pos, int evaluated)
FormatMove(out->notation, (ConstTanBoard) pos->board, m->anMove);
snprintf(out->position_id, sizeof out->position_id, "%s", PositionIDFromKey(&m->key));
/* gnubg numérote les cases de 0 à 23, la barre 24 et la sortie -1. */
out->half_move_count = 0;
for (i = 0; i < 2 * ENGINE_MAX_HALF_MOVES && m->anMove[i] >= 0; i += 2) {
out->half_moves[i / 2].from = m->anMove[i] + 1;
out->half_moves[i / 2].to = m->anMove[i + 1] + 1;
out->half_move_count++;
}
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;
@@ -381,5 +419,240 @@ engine_cube_decision(const char *position_id, const char *match_id, int ply, eng
cube->optimal = equities[OUTPUT_OPTIMAL];
cube->decision = cube_decision_names[decision];
cube->decision_text = GetCubeRecommendation(decision);
GetDPEq(&cube->available, NULL, &pos.cube);
cube->should_double = decision == DOUBLE_TAKE || decision == DOUBLE_PASS
|| decision == DOUBLE_BEAVER || decision == REDOUBLE_TAKE || decision == REDOUBLE_PASS;
cube->should_take = cube->double_take <= cube->double_pass;
return ENGINE_OK;
}
/* ------------------------------------------------- Plateau et fin de partie */
static void
fill_side(engine_side *side, const unsigned int checkers[25])
{
int i, on_board = 0;
side->pip_count = 0;
for (i = 0; i < 25; i++) {
on_board += (int) checkers[i];
side->pip_count += (int) checkers[i] * (i + 1);
}
for (i = 0; i < 24; i++)
side->points[i] = (int) checkers[i];
side->bar = (int) checkers[24];
side->off = anChequers[VARIATION_STANDARD] - on_board;
}
engine_status
engine_decode_position(const char *position_id, engine_board *board)
{
TanBoard b;
engine_status status = decode_board(position_id, b);
if (status != ENGINE_OK)
return status;
/* Dans gnubg, b[1] est le joueur au trait et b[0] son adversaire. */
fill_side(&board->on_roll, b[1]);
fill_side(&board->opponent, b[0]);
SwapSides(b);
snprintf(board->swapped_position_id, sizeof board->swapped_position_id, "%s", PositionID((ConstTanBoard) b));
return ENGINE_OK;
}
engine_status
engine_game_result(const char *position_id, const char *match_id, engine_result *result)
{
TanBoard b;
engine_match match;
engine_status status = decode_board(position_id, b);
int i;
if (status == ENGINE_OK)
status = engine_decode_match(match_id, &match);
if (status != ENGINE_OK)
return status;
memset(result, 0, sizeof *result);
result->kind = GameStatus((ConstTanBoard) b, VARIATION_STANDARD);
if (result->kind == 0)
return ENGINE_OK;
result->over = 1;
/* Le gagnant est celui qui n'a plus aucune dame sur le plateau. */
for (i = 0; i < 25 && b[1][i] == 0; i++)
;
result->winner = i == 25 ? 0 : 1;
/* Règle Jacoby : gammons et backgammons ne comptent qu'une fois le
* videau tourné, c'est-à-dire quand il n'est plus au centre. */
if (match.match_length == 0 && match.jacoby && match.cube_owner == -1)
result->points = match.cube_value;
else
result->points = result->kind * match.cube_value;
return ENGINE_OK;
}
/* ------------------------------------------ Saisie demi-coup par demi-coup */
/*
* La légalité de chaque demi-coup vient de gnubg : GenerateMoves avec un seul
* dé (le second à 0) donne exactement les demi-coups permis avec ce dé (barre
* d'abord, case libre, règles de sortie). Celle du coup entier aussi : les
* demi-coups doivent pouvoir se compléter en une position de la liste des
* coups légaux, qui impose de jouer le plus de dés possible.
*/
/* Un demi-coup permis et la position où il mène. */
typedef struct {
int from, to; /* numérotation de gnubg : 0 à 24, -1 sortie */
positionkey key;
} step;
/* Positions d'arrivée des coups légaux. */
typedef struct {
positionkey *keys;
int count;
} legal_results;
/* Les demi-coups permis avec un dé. Ils sont copiés : GenerateMoves réutilise
* un tableau interne, que l'appel suivant écraserait. Il y en a au plus un
* par case de départ. */
static int
single_steps(ConstTanBoard board, int die, step steps[25])
{
movelist list;
unsigned int i;
GenerateMoves(&list, board, die, 0, FALSE);
for (i = 0; i < list.cMoves && i < 25; i++) {
steps[i].from = list.amMoves[i].anMove[0];
steps[i].to = list.amMoves[i].anMove[1];
steps[i].key = list.amMoves[i].key;
}
return (int) i;
}
static int
is_legal_result(ConstTanBoard board, const legal_results *legal)
{
positionkey key;
int i;
PositionKey(board, &key);
for (i = 0; i < legal->count; i++)
if (EqualKeys(key, legal->keys[i]))
return 1;
return 0;
}
/* Les dés sans celui d'indice skip. */
static void
remove_die(const int dice[], int count, int skip, int rest[])
{
int i, j = 0;
for (i = 0; i < count; i++)
if (i != skip)
rest[j++] = dice[i];
}
/* Vrai si board peut encore mener à un coup légal avec les dés restants. */
static int
can_complete(ConstTanBoard board, const int dice[], int count, const legal_results *legal)
{
step steps[25];
int rest[ENGINE_MAX_HALF_MOVES];
TanBoard next;
int i, j, n;
if (is_legal_result(board, legal))
return 1;
for (i = 0; i < count; i++) {
if (i > 0 && dice[i] == dice[0])
continue; /* même valeur déjà essayée (double) */
remove_die(dice, count, i, rest);
n = single_steps(board, dice[i], steps);
for (j = 0; j < n; j++) {
PositionFromKey(next, &steps[j].key);
if (can_complete((ConstTanBoard) next, rest, count - 1, legal))
return 1;
}
}
return 0;
}
/* Joue les demi-coups ; en cas de succès, result reçoit la position atteinte.
* Un même demi-coup peut correspondre à plusieurs dés (une sortie avec un dé
* plus fort que nécessaire) : chacun est essayé. */
static int
play_half_moves(ConstTanBoard board, const int dice[], int count,
const engine_half_move *half_moves, int remaining,
const legal_results *legal, TanBoard result)
{
step steps[25];
int rest[ENGINE_MAX_HALF_MOVES];
TanBoard next;
int i, j, n;
if (remaining == 0) {
if (!can_complete(board, dice, count, legal))
return 0;
memcpy(result, board, sizeof(TanBoard));
return 1;
}
for (i = 0; i < count; i++) {
if (i > 0 && dice[i] == dice[0])
continue; /* même valeur déjà essayée (double) */
remove_die(dice, count, i, rest);
n = single_steps(board, dice[i], steps);
for (j = 0; j < n; j++) {
if (steps[j].from != half_moves->from - 1 || steps[j].to != half_moves->to - 1)
continue;
PositionFromKey(next, &steps[j].key);
if (play_half_moves((ConstTanBoard) next, rest, count - 1, half_moves + 1,
remaining - 1, legal, result))
return 1;
}
}
return 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)
{
TanBoard board, result;
movelist list;
legal_results legal;
int dice[ENGINE_MAX_HALF_MOVES] = { die1, die2, die1, die2 };
int dice_count = die1 == die2 ? 4 : 2;
engine_status status;
unsigned int i;
status = decode_board(position_id, board);
if (status == ENGINE_OK)
status = check_dice(die1, die2);
if (status != ENGINE_OK)
return status;
memset(check, 0, sizeof *check);
if (count < 0 || count > dice_count)
return ENGINE_OK;
GenerateMoves(&list, (ConstTanBoard) board, die1, die2, FALSE);
if (list.cMoves == 0)
return ENGINE_OK; /* aucun coup possible : rien à saisir */
legal.count = (int) list.cMoves;
legal.keys = g_malloc(sizeof *legal.keys * list.cMoves);
for (i = 0; i < list.cMoves; i++)
legal.keys[i] = list.amMoves[i].key;
if (play_half_moves((ConstTanBoard) board, dice, dice_count, half_moves, count, &legal, result)) {
check->valid = 1;
check->complete = is_legal_result((ConstTanBoard) result, &legal);
snprintf(check->position_id, sizeof check->position_id, "%s",
PositionID((ConstTanBoard) result));
}
g_free(legal.keys);
return ENGINE_OK;
}
+84
View File
@@ -66,11 +66,29 @@ enum {
ENGINE_NUM_PROBABILITIES
};
/*
* Demi-coup : une dame déplacée 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) */
@@ -96,8 +114,50 @@ typedef struct {
* « 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;
/* 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]; /* dames sur les cases 1 à 24 (indices 0 à 23) */
int bar; /* dames sur la barre */
int off; /* dames sorties */
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) */
@@ -125,6 +185,30 @@ 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 ;
+81
View File
@@ -138,6 +138,87 @@ attendre_erreur usage bestmove "$depart" "$libre_j0"
attendre_erreur usage inconnue "$depart" "$libre_j0"
attendre_erreur invalid_data --data /dossier/absent bestmove "$depart" "$libre_j0" 31
# 6. Ce dont a besoin le domaine d'une partie (étape 0d.1).
# Les coups légaux donnent leurs demi-coups, dans un ordre jouable.
grep -qF '"move": "8/5 6/5", "position_id": "4HPwATCwZ/ABMA", "half_moves": [[8, 5], [6, 5]]' \
<<<"$($cli legal "$depart" "$libre_j0" 31)" || echouer "legal : demi-coups de 8/5 6/5 absents"
# Match ID : codage, et décodage identique.
[ "$(valeur match_id "$($cli matchid 1 -1)")" = "$libre_j0" ] || echouer "matchid 1 -1 : $libre_j0 attendu"
videau4="$(valeur match_id "$($cli matchid 4 1)")"
sortie="$($cli eval "$depart" "$videau4")"
[ "$(valeur cube_value "$sortie")" = 4 ] && [ "$(valeur cube_owner "$sortie")" = 1 ] \
|| echouer "matchid 4 1 : videau mal codé ($sortie)"
attendre_erreur invalid_match_id matchid 3 0
attendre_erreur invalid_match_id matchid 2 5
# Plateau : position de départ, symétrique, sans résultat.
sortie="$($cli position "$depart" "$libre_j0")"
grep -qF '"on_roll": {"points": [0, 0, 0, 0, 0, 5, 0, 3, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2], "bar": 0, "off": 0, "pip_count": 167}' <<<"$sortie" \
|| echouer "position de départ mal décodée : $sortie"
[ "$(valeur swapped_position_id "$sortie")" = "$depart" ] || echouer "départ : la position vue de l'adversaire diffère"
grep -qF '"result": null' <<<"$sortie" || echouer "départ : la partie ne devrait pas être finie"
attendre_erreur invalid_position_id position 'ceci-nest-pas-un-id' "$libre_j0"
# Fin de partie : le joueur au trait a tout sorti, l'adversaire aucune dame
# (gammon). Règle Jacoby : 1 point tant que le videau est au centre ; sinon
# 2 fois le videau.
fini=/38AAAAAAAAAAA
grep -qF '"result": {"winner": "on_roll", "kind": "gammon", "points": 1}' \
<<<"$($cli position "$fini" "$libre_j0")" || echouer "gammon avec Jacoby, videau au centre : 1 point attendu"
grep -qF '"result": {"winner": "on_roll", "kind": "gammon", "points": 8}' \
<<<"$($cli position "$fini" "$videau4")" || echouer "gammon, videau à 4 : 8 points attendus"
vu_du_perdant="$(valeur swapped_position_id "$($cli position "$fini" "$libre_j0")")"
grep -qF '"winner": "opponent"' <<<"$($cli position "$vu_du_perdant" "$libre_j0")" \
|| echouer "fin de partie vue du perdant : l'adversaire devrait gagner"
# Demi-coups : « valid complete position_id » pour une saisie.
demi_coups() {
local sortie valid complete
sortie="$($cli halfmoves "$@")"
valid="$(grep -oE '"valid": (true|false)' <<<"$sortie" | cut -d' ' -f2)"
complete="$(grep -oE '"complete": (true|false)' <<<"$sortie" | cut -d' ' -f2)"
echo "$valid $complete $(valeur result_position_id "$sortie")"
}
attendre_demi_coups() {
local attendu="$1"
shift
local obtenu
obtenu="$(demi_coups "$@")"
[ "$obtenu" = "$attendu" ] || echouer "halfmoves $* : « $obtenu » au lieu de « $attendu »"
}
attendre_demi_coups "true false 4HPwATDQZ/ABMA" "$depart" 31 "8/5"
attendre_demi_coups "true true 4HPwATCwZ/ABMA" "$depart" 31 "8/5 6/5"
attendre_demi_coups "true true 4HPwATCwZ/ABMA" "$depart" 31 "6/5 8/5"
attendre_demi_coups "true false $depart" "$depart" 31
# Case 12 tenue par l'adversaire ; 8/4 demande deux dés.
attendre_demi_coups "false false " "$depart" 31 "13/12"
attendre_demi_coups "false false " "$depart" 31 "8/4"
# Le même pion avec les deux dés.
attendre_demi_coups "true true 4HPwATDgc/ABIQ" "$depart" 31 "24/21 21/20"
# Sortie : la dame la plus éloignée d'abord avec un dé trop fort.
attendre_demi_coups "true true /38AAAAAAAAAAA" "$course" 66 "6/off 5/off"
attendre_demi_coups "false false " "$course" 66 "5/off"
attendre_demi_coups "true false /38AAAAgAAAAAA" "$course" 65 "5/off"
# Course, jet 21 : il faut jouer les deux dés.
attendre_demi_coups "false false " "$course" 21 "6/3"
attendre_demi_coups "true true /38AAAAkAAAAAA" "$course" 21 "6/5 5/3"
# Aucun coup possible : rien n'est valide.
attendre_demi_coups "false false " 27YBBwAcz8EDQA 66
attendre_erreur usage halfmoves "$depart" 31 "8-5"
attendre_erreur invalid_dice halfmoves "$depart" 71 "8/5"
# Videau : droit de doubler et recommandations.
sortie="$($cli cube "$depart" "$libre_j0")"
grep -qF '"available": true, "double": false, "take": true' <<<"$sortie" \
|| echouer "videau au départ : disponible, pas de double, prendre attendus"
grep -qF '"available": true, "double": true, "take": false' <<<"$($cli cube "$course" "$libre_j0")" \
|| echouer "videau en course gagnée : double et passe attendus"
# Videau chez l'adversaire : le joueur au trait ne peut pas doubler.
grep -qF '"available": false, "double": false' <<<"$($cli cube "$course" "$(valeur match_id "$($cli matchid 2 1)")")" \
|| echouer "videau chez l'adversaire : double indisponible attendu"
if [ "$echecs" -gt 0 ]; then
echo " $echecs vérification(s) du moteur en échec."
exit 1