Étape 0d.1 : domaine « partie » en TypeScript, scénario exécuté ; ADR 0009 accepté #11
+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();
|
||||
|
||||
+277
-4
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 ;
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user