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
+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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user