#!/usr/bin/env bash # Tests du moteur embarqué, via build/engine-cli (étape 0a.3) : # meilleurs coups d'ouverture connus, nombre de coups légaux, bornes et # symétrie des probabilités, décision de videau évidente, identifiants # invalides. La comparaison systématique au gnubg de bureau est faite par # tests/test_engine_reference.sh. # # Lance `make engine` (sans effet si tout est à jour). set -euo pipefail cd "$(dirname "$0")/.." # Lancé depuis `make test` : repartir d'un make indépendant du make parent. unset MAKEFLAGS MFLAGS MAKELEVEL make engine >/dev/null cli=build/engine-cli depart=4HPwATDgc/ABMA # Match ID de partie libre, videau au centre, joueur 0 au trait. libre_j0=MAEAAAAAAAAA # Le même, joueur 1 au trait. libre_j1=cAkAAAAAAAAA # Match en 5 points, 4-2, partie Crawford, joueur 0 au trait. crawford=sAGgAEAAEAAA # Course : le joueur au trait n'a plus que deux dames, sur ses points 6 et 5 ; # l'adversaire a ses 15 dames sur son point 1. course=/38AAABQAAAAAA echecs=0 echouer() { echo " $1" echecs=$((echecs + 1)) } # Valeur d'une clé JSON numérique ou textuelle (première occurrence). valeur() { grep -oE "\"$1\": (\"[^\"]*\"|[-0-9.eE+]+)" <<<"$2" | head -n1 | sed -E 's/^"[^"]*": "?//; s/"$//' } # Évalue une condition numérique avec awk ; ex. : vrai "0 <= 0.5 && 0.5 <= 1" vrai() { awk "BEGIN { exit !($1) }" } # 1. Meilleurs coups d'ouverture, sans ambiguïté, à 0 et 2 plis. for ply in 0 2; do while read -r des attendu; do sortie="$($cli --ply "$ply" bestmove "$depart" "$libre_j0" "$des")" coup="$(valeur move "$sortie")" [ "$coup" = "$attendu" ] \ || echouer "ouverture $des à $ply pli(s) : « $coup » au lieu de « $attendu »" done <<-EOF 31 8/5 6/5 42 8/4 6/4 53 8/3 6/3 61 13/7 8/7 EOF done # Le Match ID du critère de réussite du plan (joueur 1 au trait) donne le même coup. coup="$(valeur move "$($cli bestmove "$depart" "$libre_j1" 31)")" [ "$coup" = "8/5 6/5" ] || echouer "ouverture 31 avec $libre_j1 : « $coup »" # 2. Nombre de coups légaux. Course, jet 21 : 6/3, 5/2 ou 6/4 5/4. n="$(valeur count "$($cli legal "$course" "$libre_j0" 21)")" [ "$n" = 3 ] || echouer "course, jet 21 : $n coups légaux au lieu de 3" # 3. Probabilités : bornes, cohérence et symétrie. verifier_probabilites() { local nom="$1" json="$2" g gg gbg pg pbg g="$(valeur win "$json")" gg="$(valeur win_gammon "$json")" gbg="$(valeur win_backgammon "$json")" pg="$(valeur lose_gammon "$json")" pbg="$(valeur lose_backgammon "$json")" vrai "0 <= $g && $g <= 1" || echouer "$nom : gain $g hors de [0, 1]" vrai "0 <= $gbg && $gbg <= $gg && $gg <= $g" \ || echouer "$nom : gain $g, gammon $gg, backgammon $gbg incohérents" vrai "0 <= $pbg && $pbg <= $pg && $pg <= 1 - $g" \ || echouer "$nom : gain $g, perte gammon $pg, perte backgammon $pbg incohérents" } for ply in 0 2; do j0="$($cli --ply "$ply" eval "$depart" "$libre_j0")" j1="$($cli --ply "$ply" eval "$depart" "$libre_j1")" verifier_probabilites "départ à $ply pli(s)" "$j0" # Le départ est symétrique : peu importe quel joueur est au trait. [ "${j0#*\"probabilities\"}" = "${j1#*\"probabilities\"}" ] \ || echouer "départ à $ply pli(s) : évaluations différentes selon le joueur au trait" # Avantage au joueur qui lance les dés en premier, mais faible. e="$(valeur cubeless_equity "$j0")" vrai "0 < $e && $e < 0.2" || echouer "départ à $ply pli(s) : équité $e hors de ]0, 0.2[" done course_eval="$($cli eval "$course" "$libre_j0")" verifier_probabilites "course" "$course_eval" g="$(valeur win "$course_eval")" vrai "$g > 0.999" || echouer "course : gain $g, attendu quasi certain" # Les coups classés ont aussi des probabilités cohérentes. verifier_probabilites "coup 64 à 2 plis" "$($cli --ply 2 bestmove "$depart" "$libre_j0" 64)" # 4. Videau : au départ, pas de double, en partie libre comme en match. for match_id in "$libre_j0" "$crawford"; do decision="$(valeur decision "$($cli cube "$depart" "$match_id")")" case "$decision" in NODOUBLE_* | NO_REDOUBLE_* | NOT_AVAILABLE) ;; *) echouer "videau au départ ($match_id) : « $decision » au lieu de pas de double" ;; esac done # Partie Crawford : le videau n'est pas disponible. decision="$(valeur decision "$($cli cube "$depart" "$crawford")")" [ "$decision" = "NODOUBLE_DEADCUBE" ] || [ "$decision" = "NOT_AVAILABLE" ] \ || echouer "videau en partie Crawford : « $decision »" # À l'inverse, la course gagnée d'avance est un double/passe (règle Jacoby : # jouer pour le gammon ne rapporte rien, videau au centre). decision="$(valeur decision "$($cli cube "$course" "$libre_j0")")" [ "$decision" = "DOUBLE_PASS" ] || echouer "videau en course gagnée : « $decision »" # 5. Erreurs : JSON, code d'erreur précis et code de sortie non nul. attendre_erreur() { local code_attendu="$1" shift local sortie statut=0 sortie="$($cli "$@")" || statut=$? [ "$statut" -ne 0 ] || echouer "« $* » aurait dû échouer" grep -qF "\"code\": \"$code_attendu\"" <<<"$sortie" \ || echouer "« $* » : code d'erreur attendu $code_attendu, reçu : $sortie" } attendre_erreur invalid_position_id bestmove 'ceci-nest-pas-un-id' "$libre_j0" 31 attendre_erreur invalid_position_id bestmove '//////////////' "$libre_j0" 31 attendre_erreur invalid_match_id bestmove "$depart" 'XYZ' 31 attendre_erreur invalid_dice bestmove "$depart" "$libre_j0" 71 attendre_erreur invalid_ply --ply 9 bestmove "$depart" "$libre_j0" 31 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 if [ "$echecs" -gt 0 ]; then echo " $echecs vérification(s) du moteur en échec." exit 1 fi