Moteur : videau de l'ordinateur comme ComputerTurn, match après une partie, Match ID d'un match
Fenêtre de marché et double facultatif à 0 pli (play.c), réponse au double, règle de Crawford automatique ; commandes double, take et aftergame d'engine-cli, matchid étendu aux matchs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
71bff4def1
commit
c0f827c5ba
+116
-18
@@ -19,9 +19,19 @@
|
||||
* 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 ;
|
||||
* matchid VIDEAU PROPRIÉTAIRE [LONGUEUR SCORE0 SCORE1 CRAWFORD]
|
||||
* Match ID joueur 0 au trait : partie
|
||||
* libre avec la règle Jacoby, ou match
|
||||
* en LONGUEUR points (CRAWFORD : 1
|
||||
* pour la partie Crawford, sinon 0) ;
|
||||
* PROPRIÉTAIRE : -1 (centre), 0 ou 1
|
||||
* double POSITION_ID MATCH_ID (avec --level) l'ordinateur au trait
|
||||
* double-t-il ? (ComputerTurn de gnubg)
|
||||
* take POSITION_ID MATCH_ID (avec --level) l'ordinateur prend-il
|
||||
* le double du joueur au trait ?
|
||||
* aftergame MATCH_ID GAGNANT POINTS le match après une partie : score,
|
||||
* fin du match, partie Crawford
|
||||
* suivante
|
||||
* reference POSITION_ID MATCH_ID [DÉS]
|
||||
* un niveau au format des positions de
|
||||
* référence (tests/reference/README.md) :
|
||||
@@ -42,8 +52,9 @@
|
||||
*
|
||||
* --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.
|
||||
* --level : niveau de jeu de l'ordinateur (voir levels), pour bestmove, moves,
|
||||
* cube, double et take : coups classés et videau 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
|
||||
@@ -71,13 +82,13 @@
|
||||
|
||||
/* Liste des commandes, pour les messages d'erreur. */
|
||||
#define COMMANDS "bestmove, moves, legal, cube, eval, position, halfmoves, matchid, reference, " \
|
||||
"levels, analysemove, analysecube ou luck"
|
||||
"levels, analysemove, analysecube, luck, double, take ou aftergame"
|
||||
|
||||
/* 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
|
||||
#define MAX_ARGS 6
|
||||
|
||||
typedef struct {
|
||||
int ply;
|
||||
@@ -125,6 +136,8 @@ status_code(engine_status status)
|
||||
return "invalid_move";
|
||||
case ENGINE_ERROR_CUBE_ACTION:
|
||||
return "invalid_cube_action";
|
||||
case ENGINE_ERROR_RESULT:
|
||||
return "invalid_result";
|
||||
}
|
||||
return "unknown_error";
|
||||
}
|
||||
@@ -475,24 +488,45 @@ run_half_moves_command(const options *opt)
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
/* matchid : Match ID d'une partie libre avec la règle Jacoby, joueur 0 au
|
||||
* trait. */
|
||||
/* Lit un entier ; renvoie 0 si le texte n'en est pas un. */
|
||||
static int
|
||||
parse_int(const char *text, int *value)
|
||||
{
|
||||
char *end;
|
||||
|
||||
*value = (int) strtol(text, &end, 10);
|
||||
return !*end && end != text;
|
||||
}
|
||||
|
||||
/* matchid : Match ID joueur 0 au trait, sans dés. Partie libre avec la
|
||||
* règle Jacoby, ou match (LONGUEUR SCORE0 SCORE1 CRAWFORD, ce dernier à 0
|
||||
* ou 1), sans Jacoby. */
|
||||
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])
|
||||
if (opt->arg_count != 2 && opt->arg_count != 6)
|
||||
return usage_error("arguments attendus : VIDEAU PROPRIÉTAIRE "
|
||||
"[LONGUEUR SCORE0 SCORE1 CRAWFORD]");
|
||||
if (!parse_int(opt->args[0], &match.cube_value))
|
||||
return usage_error("VIDEAU attend un nombre");
|
||||
match.cube_owner = (int) strtol(opt->args[1], &end, 10);
|
||||
if (*end || end == opt->args[1])
|
||||
if (!parse_int(opt->args[1], &match.cube_owner))
|
||||
return usage_error("PROPRIÉTAIRE attend -1, 0 ou 1");
|
||||
if (opt->arg_count == 6) {
|
||||
if (!parse_int(opt->args[2], &match.match_length) || match.match_length < 1
|
||||
|| !parse_int(opt->args[3], &match.score[0]) || match.score[0] < 0
|
||||
|| !parse_int(opt->args[4], &match.score[1]) || match.score[1] < 0
|
||||
|| match.score[0] >= match.match_length || match.score[1] >= match.match_length)
|
||||
return usage_error("LONGUEUR attend un nombre positif, SCORE0 et SCORE1 des "
|
||||
"scores inférieurs à la longueur");
|
||||
if (!parse_int(opt->args[5], &match.crawford) || match.crawford < 0
|
||||
|| match.crawford > 1)
|
||||
return usage_error("CRAWFORD attend 0 ou 1");
|
||||
match.jacoby = 0;
|
||||
}
|
||||
|
||||
status = engine_encode_match(&match, match_id);
|
||||
if (status != ENGINE_OK)
|
||||
@@ -503,8 +537,67 @@ run_match_id_command(const options *opt)
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
/* double et take (avec --level) : le videau de l'ordinateur, comme
|
||||
* ComputerTurn de gnubg (voir engine.h). */
|
||||
static int
|
||||
run_level_cube_command(const options *opt)
|
||||
{
|
||||
const char *position_id = opt->args[0], *match_id = opt->args[1];
|
||||
engine_status status;
|
||||
|
||||
if (opt->arg_count != 2)
|
||||
return usage_error("arguments attendus : POSITION_ID MATCH_ID");
|
||||
if (opt->level < 0)
|
||||
return usage_error("double et take demandent --level");
|
||||
if (strcmp(opt->command, "double") == 0) {
|
||||
engine_level_double d;
|
||||
status = engine_level_double_decision(position_id, match_id, opt->level, &d);
|
||||
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(", \"available\": %s, \"market_window\": %s, \"double\": %s, \"decision\": ",
|
||||
d.available ? "true" : "false", d.market_window ? "true" : "false",
|
||||
d.doubles ? "true" : "false");
|
||||
if (d.decision)
|
||||
printf("\"%s\"}\n", d.decision);
|
||||
else
|
||||
printf("null}\n");
|
||||
} else {
|
||||
int takes;
|
||||
const char *decision;
|
||||
status = engine_level_take_decision(position_id, match_id, opt->level, &takes, &decision);
|
||||
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(", \"take\": %s, \"decision\": \"%s\"}\n", takes ? "true" : "false", decision);
|
||||
}
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
/* aftergame : le match après une partie (voir engine_match_after_game). */
|
||||
static int
|
||||
run_after_game_command(const options *opt)
|
||||
{
|
||||
engine_match_after after;
|
||||
engine_status status;
|
||||
int winner, points;
|
||||
|
||||
if (opt->arg_count != 3)
|
||||
return usage_error("arguments attendus : MATCH_ID GAGNANT POINTS");
|
||||
if (!parse_int(opt->args[1], &winner) || !parse_int(opt->args[2], &points))
|
||||
return usage_error("GAGNANT et POINTS attendent des nombres");
|
||||
status = engine_match_after_game(opt->args[0], winner, points, &after);
|
||||
if (status != ENGINE_OK)
|
||||
return print_engine_error(status);
|
||||
printf("{\"score\": [%d, %d], \"match_over\": %s, \"crawford\": %s}\n", after.score[0],
|
||||
after.score[1], after.match_over ? "true" : "false", after.crawford ? "true" : "false");
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
/*
|
||||
* reference : un niveau d'un fichier de résultat de tests/reference/, de
|
||||
* reference :un niveau d'un fichier de résultat de tests/reference/, de
|
||||
* même structure que plies["<n>"] (voir tests/reference/README.md).
|
||||
*/
|
||||
static int
|
||||
@@ -731,8 +824,9 @@ parse_options(int argc, char **argv, options *opt)
|
||||
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";
|
||||
&& strcmp(opt->command, "moves") != 0 && strcmp(opt->command, "cube") != 0
|
||||
&& strcmp(opt->command, "double") != 0 && strcmp(opt->command, "take") != 0)
|
||||
return "--level ne s'applique qu'à bestmove, moves, cube, double et take";
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -772,6 +866,10 @@ main(int argc, char **argv)
|
||||
result = run_half_moves_command(&opt);
|
||||
else if (strcmp(opt.command, "matchid") == 0)
|
||||
result = run_match_id_command(&opt);
|
||||
else if (strcmp(opt.command, "double") == 0 || strcmp(opt.command, "take") == 0)
|
||||
result = run_level_cube_command(&opt);
|
||||
else if (strcmp(opt.command, "aftergame") == 0)
|
||||
result = run_after_game_command(&opt);
|
||||
else if (strcmp(opt.command, "levels") == 0)
|
||||
result = run_levels_command(&opt);
|
||||
else if (strcmp(opt.command, "analysemove") == 0)
|
||||
|
||||
+154
@@ -156,6 +156,8 @@ engine_status_message(engine_status status)
|
||||
return "coup joué absent des coups légaux";
|
||||
case ENGINE_ERROR_CUBE_ACTION:
|
||||
return "décision de videau inconnue";
|
||||
case ENGINE_ERROR_RESULT:
|
||||
return "résultat de partie invalide";
|
||||
}
|
||||
return "erreur inconnue";
|
||||
}
|
||||
@@ -667,6 +669,158 @@ engine_level_cube_decision(const char *position_id, const char *match_id, int le
|
||||
return cube_decision(&pos, &context, cube);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- Videau de l'ordinateur */
|
||||
|
||||
/* Valeur maximale du videau (MAX_CUBE dans backgammon.h de gnubg, non
|
||||
* repris ici). */
|
||||
#define MAX_CUBE_VALUE (1 << 12)
|
||||
|
||||
/* La décision de videau de gnubg pour une position, avec ces réglages. */
|
||||
static engine_status
|
||||
find_cube_decision(position *pos, const evalcontext *context, cubedecision *decision)
|
||||
{
|
||||
float output[2][NUM_ROLLOUT_OUTPUTS];
|
||||
float equities[NUM_CUBEFUL_OUTPUTS];
|
||||
|
||||
if (GeneralCubeDecisionE(output, (ConstTanBoard) pos->board, &pos->cube, context, NULL) < 0)
|
||||
return ENGINE_ERROR_INTERNAL;
|
||||
*decision = FindCubeDecision(equities, output, &pos->cube);
|
||||
if ((size_t) *decision >= sizeof cube_decision_names / sizeof cube_decision_names[0])
|
||||
return ENGINE_ERROR_INTERNAL;
|
||||
return ENGINE_OK;
|
||||
}
|
||||
|
||||
/* Voir engine.h : ComputerTurn, play.c l. 1312-1398. */
|
||||
engine_status
|
||||
engine_level_double_decision(const char *position_id, const char *match_id, int level,
|
||||
engine_level_double *result)
|
||||
{
|
||||
position pos;
|
||||
evalcontext context, market_context;
|
||||
float output[NUM_ROLLOUT_OUTPUTS];
|
||||
cubedecision decision;
|
||||
engine_status status;
|
||||
int available = 0;
|
||||
|
||||
memset(result, 0, sizeof *result);
|
||||
status = decode_position(position_id, match_id, &pos);
|
||||
if (status == ENGINE_OK)
|
||||
status = eval_context_for_level(level, &context);
|
||||
if (status != ENGINE_OK)
|
||||
return status;
|
||||
|
||||
/* « if (ms.fCubeUse && !ms.anDice[0] && ms.nCube < MAX_CUBE && GetDPEq(...)) » */
|
||||
GetDPEq(&available, NULL, &pos.cube);
|
||||
if (!available || pos.cube.nCube >= MAX_CUBE_VALUE)
|
||||
return ENGINE_OK;
|
||||
result->available = 1;
|
||||
|
||||
/* Fenêtre de marché : évaluation sans videau, un pli de moins. */
|
||||
market_context = context;
|
||||
market_context.fCubeful = FALSE;
|
||||
if (market_context.nPlies)
|
||||
market_context.nPlies--;
|
||||
if (EvaluatePosition(NULL, (ConstTanBoard) pos.board, output, &pos.cube, &market_context))
|
||||
return ENGINE_ERROR_INTERNAL;
|
||||
if (output[0] < GetDoublePointDeadCube(output, &pos.cube))
|
||||
return ENGINE_OK;
|
||||
result->market_window = 1;
|
||||
|
||||
status = find_cube_decision(&pos, &context, &decision);
|
||||
if (status != ENGINE_OK)
|
||||
return status;
|
||||
result->decision = cube_decision_names[decision];
|
||||
switch (decision) {
|
||||
case DOUBLE_TAKE:
|
||||
case REDOUBLE_TAKE:
|
||||
case DOUBLE_PASS:
|
||||
case REDOUBLE_PASS:
|
||||
case DOUBLE_BEAVER:
|
||||
result->doubles = 1;
|
||||
break;
|
||||
case OPTIONAL_DOUBLE_BEAVER:
|
||||
case OPTIONAL_DOUBLE_TAKE:
|
||||
case OPTIONAL_REDOUBLE_TAKE:
|
||||
case OPTIONAL_DOUBLE_PASS:
|
||||
case OPTIONAL_REDOUBLE_PASS:
|
||||
/* « double if 0-ply except when about to lose game » */
|
||||
result->doubles = context.nPlies == 0 && output[0] > 0.001f;
|
||||
break;
|
||||
default:
|
||||
/* « better leave cube where it is » */
|
||||
break;
|
||||
}
|
||||
return ENGINE_OK;
|
||||
}
|
||||
|
||||
/* Voir engine.h : ComputerTurn, play.c l. 1139-1235. */
|
||||
engine_status
|
||||
engine_level_take_decision(const char *position_id, const char *match_id, int level,
|
||||
int *takes, const char **decision_name)
|
||||
{
|
||||
position pos;
|
||||
evalcontext context;
|
||||
cubedecision decision;
|
||||
engine_status status;
|
||||
|
||||
status = decode_position(position_id, match_id, &pos);
|
||||
if (status == ENGINE_OK)
|
||||
status = eval_context_for_level(level, &context);
|
||||
if (status == ENGINE_OK)
|
||||
status = find_cube_decision(&pos, &context, &decision);
|
||||
if (status != ENGINE_OK)
|
||||
return status;
|
||||
switch (decision) {
|
||||
case DOUBLE_PASS:
|
||||
case TOOGOOD_PASS:
|
||||
case REDOUBLE_PASS:
|
||||
case TOOGOODRE_PASS:
|
||||
case OPTIONAL_DOUBLE_PASS:
|
||||
case OPTIONAL_REDOUBLE_PASS:
|
||||
*takes = 0;
|
||||
break;
|
||||
default:
|
||||
*takes = 1;
|
||||
break;
|
||||
}
|
||||
*decision_name = cube_decision_names[decision];
|
||||
return ENGINE_OK;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ Match */
|
||||
|
||||
/* Voir engine.h : play.c l. 303, 1817-1820 et 4277. */
|
||||
engine_status
|
||||
engine_match_after_game(const char *match_id, int winner, int points,
|
||||
engine_match_after *after)
|
||||
{
|
||||
engine_match m;
|
||||
engine_status status = engine_decode_match(match_id, &m);
|
||||
int length, post_crawford;
|
||||
|
||||
if (status != ENGINE_OK)
|
||||
return status;
|
||||
if (winner < 0 || winner > 1 || points < 1)
|
||||
return ENGINE_ERROR_RESULT;
|
||||
memset(after, 0, sizeof *after);
|
||||
length = m.match_length;
|
||||
after->score[0] = m.score[0];
|
||||
after->score[1] = m.score[1];
|
||||
after->score[winner] += points;
|
||||
if (length == 0)
|
||||
return ENGINE_OK;
|
||||
after->match_over = after->score[winner] >= length;
|
||||
if (after->match_over)
|
||||
return ENGINE_OK;
|
||||
/* Partie Crawford déjà jouée, lue comme gnubg (play.c l. 303). */
|
||||
post_crawford = !m.crawford && (m.score[0] == length - 1 || m.score[1] == length - 1);
|
||||
/* play.c l. 1817-1820. */
|
||||
post_crawford |= m.crawford && after->score[winner] < length;
|
||||
after->crawford = !post_crawford && !m.crawford && after->score[winner] == length - 1
|
||||
&& after->score[!winner] != length - 1;
|
||||
return ENGINE_OK;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- Analyse d'une décision de videau */
|
||||
|
||||
/* Le plus petit de deux nombres. */
|
||||
|
||||
+67
-1
@@ -51,7 +51,8 @@ typedef enum {
|
||||
ENGINE_ERROR_INTERNAL, /* échec signalé par gnubg */
|
||||
ENGINE_ERROR_LEVEL, /* niveau de jeu inconnu */
|
||||
ENGINE_ERROR_MOVE, /* coup joué absent des coups légaux */
|
||||
ENGINE_ERROR_CUBE_ACTION /* décision de videau inconnue */
|
||||
ENGINE_ERROR_CUBE_ACTION, /* décision de videau inconnue */
|
||||
ENGINE_ERROR_RESULT /* résultat de partie invalide */
|
||||
} engine_status;
|
||||
|
||||
#define ENGINE_MAX_PLY 4
|
||||
@@ -374,6 +375,71 @@ engine_status engine_level_best_moves(const char *position_id, const char *match
|
||||
engine_status engine_level_cube_decision(const char *position_id, const char *match_id,
|
||||
int level, engine_cube *cube);
|
||||
|
||||
/*
|
||||
* Le videau de l'ordinateur à ce niveau, comme le joue gnubg de bureau
|
||||
* (ComputerTurn dans play.c, l. 1312-1398, « Consider doubling ») :
|
||||
* - il ne pense à doubler que s'il a accès au videau (GetDPEq : pas en
|
||||
* partie Crawford, pas avec un videau mort…) et que le videau vaut moins
|
||||
* de 4096 (MAX_CUBE) ;
|
||||
* - pré-filtre « fenêtre de marché » : ses chances de gain, évaluées sans
|
||||
* videau à un pli de moins que le niveau (bruit compris), doivent atteindre
|
||||
* le point de double à videau mort (GetDoublePointDeadCube) ; sinon il ne
|
||||
* double pas, sans évaluer la décision complète ;
|
||||
* - dans la fenêtre, il double sur DOUBLE_TAKE, REDOUBLE_TAKE, DOUBLE_PASS,
|
||||
* REDOUBLE_PASS et DOUBLE_BEAVER ; sur un double « facultatif »
|
||||
* (OPTIONAL_*), seulement s'il évalue à 0 pli et que ses chances de gain
|
||||
* dépassent 0,001 (« double if 0-ply except when about to lose game ») ;
|
||||
* sinon il garde le videau.
|
||||
*/
|
||||
typedef struct {
|
||||
int available; /* le joueur au trait a accès au videau */
|
||||
int market_window; /* le pré-filtre est passé : décision évaluée */
|
||||
int doubles; /* l'ordinateur double */
|
||||
const char *decision; /* décision complète (noms d'engine_cube.decision)
|
||||
* si elle a été évaluée, sinon NULL */
|
||||
} engine_level_double;
|
||||
|
||||
engine_status engine_level_double_decision(const char *position_id, const char *match_id,
|
||||
int level, engine_level_double *result);
|
||||
|
||||
/*
|
||||
* L'ordinateur, adversaire du joueur au trait qui vient de doubler, prend-il
|
||||
* ? Comme ComputerTurn (play.c, l. 1139-1235, « normal double by
|
||||
* opponent ») : la décision de videau est évaluée du point de vue de celui
|
||||
* qui double, avec les réglages du niveau de l'ordinateur ; il passe sur
|
||||
* DOUBLE_PASS, TOOGOOD_PASS, REDOUBLE_PASS, TOOGOODRE_PASS et les
|
||||
* OPTIONAL_*_PASS, et prend sinon (Ustagammon ne joue pas les castors :
|
||||
* gnubg prend aussi quand il ne peut pas en faire). position_id et match_id
|
||||
* sont ceux du joueur qui double ; *decision reçoit le nom de la décision.
|
||||
*/
|
||||
engine_status engine_level_take_decision(const char *position_id, const char *match_id,
|
||||
int level, int *takes, const char **decision);
|
||||
|
||||
/* ------------------------------------------------------------------ Match */
|
||||
|
||||
/* Le match après une partie. */
|
||||
typedef struct {
|
||||
int score[2]; /* score après la partie */
|
||||
int match_over; /* le gagnant a atteint la longueur du match */
|
||||
int crawford; /* la partie suivante est la partie Crawford */
|
||||
} engine_match_after;
|
||||
|
||||
/*
|
||||
* Le match après une partie gagnée par winner (0 ou 1, numérotation du
|
||||
* Match ID) pour points points. match_id est celui de la partie jouée : sa
|
||||
* longueur, son score avant la partie et son drapeau Crawford comptent, le
|
||||
* reste est ignoré. Comme gnubg de bureau à la fin d'une partie (play.c,
|
||||
* l. 1817-1820, règle de Crawford automatique) : la partie suivante est la
|
||||
* partie Crawford si le gagnant arrive à un point du but, que son adversaire
|
||||
* n'y est pas et qu'il n'y a pas encore eu de partie Crawford. Qu'une partie
|
||||
* Crawford ait déjà eu lieu se lit dans le Match ID comme gnubg le lit
|
||||
* (play.c, l. 303 et l. 4277) : pas de drapeau Crawford alors qu'un joueur
|
||||
* est à un point du but. En partie libre (longueur 0), le score s'ajoute
|
||||
* seulement. ENGINE_ERROR_RESULT si winner ou points sont invalides.
|
||||
*/
|
||||
engine_status engine_match_after_game(const char *match_id, int winner, int points,
|
||||
engine_match_after *after);
|
||||
|
||||
/* ------------------------------------------------------------- Analyse */
|
||||
|
||||
/* Appréciation de gnubg pour une perte d'équité (0 ou négative). */
|
||||
|
||||
@@ -259,6 +259,62 @@ grep -qF '"analysed": false, "cube": null, "equity_loss": 0, "skill": "none"' \
|
||||
<<<"$($cli analysecube "$course" "$(valeur match_id "$($cli matchid 2 1)")" no_double)" \
|
||||
|| echouer "analysecube, videau chez l'adversaire : pas d'analyse attendue"
|
||||
|
||||
# 7. Match et videau de l'ordinateur (étape 1c).
|
||||
|
||||
# Match ID d'un match : longueur, score et Crawford relus à l'identique.
|
||||
sortie="$($cli matchid 1 -1 5 4 2 1)"
|
||||
grep -qF '"match_length": 5, "score": [4, 2], "cube_value": 1, "cube_owner": -1, "player_on_roll": 0, "crawford": true, "jacoby": false' \
|
||||
<<<"$sortie" || echouer "matchid d'un match mal codé : $sortie"
|
||||
[ "$(valeur match_id "$sortie")" = "$(valeur match_id "$($cli matchid 1 -1 5 4 2 1)")" ] \
|
||||
|| echouer "matchid d'un match : pas reproductible"
|
||||
attendre_erreur usage matchid 1 -1 3 3 0 0
|
||||
attendre_erreur usage matchid 1 -1 3 0 0 2
|
||||
|
||||
# Le match après une partie (règle de Crawford de gnubg, play.c).
|
||||
apres() {
|
||||
local id
|
||||
id="$(valeur match_id "$($cli matchid 1 -1 "$1" "$2" "$3" "$4")")"
|
||||
$cli aftergame "$id" "$5" "$6"
|
||||
}
|
||||
while read -r longueur s0 s1 drapeau gagnant points attendu; do
|
||||
sortie="$(apres "$longueur" "$s0" "$s1" "$drapeau" "$gagnant" "$points")"
|
||||
grep -qF "$attendu" <<<"$sortie" \
|
||||
|| echouer "aftergame $longueur $s0-$s1 crawford $drapeau, $gagnant gagne $points : $sortie"
|
||||
done <<-'EOF'
|
||||
3 0 0 0 0 2 {"score": [2, 0], "match_over": false, "crawford": true}
|
||||
3 0 0 0 1 1 {"score": [0, 1], "match_over": false, "crawford": false}
|
||||
3 1 1 0 0 1 {"score": [2, 1], "match_over": false, "crawford": true}
|
||||
3 2 0 1 1 1 {"score": [2, 1], "match_over": false, "crawford": false}
|
||||
3 2 0 1 0 1 {"score": [3, 0], "match_over": true, "crawford": false}
|
||||
3 2 1 0 1 1 {"score": [2, 2], "match_over": false, "crawford": false}
|
||||
3 2 1 0 1 4 {"score": [2, 5], "match_over": true, "crawford": false}
|
||||
5 3 3 0 1 1 {"score": [3, 4], "match_over": false, "crawford": true}
|
||||
5 4 3 0 1 1 {"score": [4, 4], "match_over": false, "crawford": false}
|
||||
EOF
|
||||
grep -qF '{"score": [2, 0], "match_over": false, "crawford": false}' \
|
||||
<<<"$($cli aftergame "$libre_j0" 0 2)" || echouer "aftergame en partie libre"
|
||||
attendre_erreur invalid_result aftergame "$libre_j0" 2 1
|
||||
attendre_erreur invalid_result aftergame "$libre_j0" 0 0
|
||||
|
||||
# L'ordinateur double (ComputerTurn de gnubg) : course gagnée, double et
|
||||
# passe ; partie Crawford, videau inaccessible ; position perdue, hors de la
|
||||
# fenêtre de marché.
|
||||
grep -qF '"available": true, "market_window": true, "double": true, "decision": "DOUBLE_PASS"' \
|
||||
<<<"$($cli --level 2ply double "$course" "$libre_j0")" || echouer "double en course gagnée"
|
||||
grep -qF '"available": false, "market_window": false, "double": false, "decision": null' \
|
||||
<<<"$($cli --level 2ply double "$depart" "$crawford")" || echouer "double en partie Crawford"
|
||||
perdue="$(valeur swapped_position_id "$($cli position "$course" "$libre_j0")")"
|
||||
grep -qF '"available": true, "market_window": false, "double": false, "decision": null' \
|
||||
<<<"$($cli --level beginner double "$perdue" "$libre_j0")" || echouer "double hors fenêtre de marché"
|
||||
grep -qF '"double": false' <<<"$($cli --level 2ply double "$depart" "$libre_j0")" \
|
||||
|| echouer "double à la position de départ"
|
||||
# L'ordinateur répond au double : passe en course perdue, prend au départ.
|
||||
grep -qF '"take": false, "decision": "DOUBLE_PASS"' \
|
||||
<<<"$($cli --level 2ply take "$course" "$libre_j0")" || echouer "take en course perdue"
|
||||
grep -qF '"take": true' <<<"$($cli --level beginner take "$depart" "$libre_j0")" \
|
||||
|| echouer "take au départ"
|
||||
attendre_erreur usage double "$depart" "$libre_j0"
|
||||
|
||||
# Chance : même jet, même chance ; ordre des dés indifférent.
|
||||
[ "$($cli luck "$milieu" "$milieu_mid" 64 | sed 's/"dice": \[[0-9], [0-9]\]//')" \
|
||||
= "$($cli luck "$milieu" "$milieu_mid" 46 | sed 's/"dice": \[[0-9], [0-9]\]//')" ] \
|
||||
|
||||
Reference in New Issue
Block a user