diff --git a/Makefile b/Makefile index 3883141..758116e 100644 --- a/Makefile +++ b/Makefile @@ -165,8 +165,8 @@ reference-image: ## Construit l'image Docker du gnubg de référence (cache Dock docker build -t $(REFERENCE_IMAGE) docker/gnubg-reference @echo "Image $(REFERENCE_IMAGE) prête." -reference: reference-image ## Régénère tests/reference/results/ avec le gnubg de référence - rm -rf tests/reference/results +reference: reference-image ## Régénère tests/reference/results/ et analysis/ avec le gnubg de référence + rm -rf tests/reference/results tests/reference/analysis docker run --rm --user "$$(id -u):$$(id -g)" -e HOME=/tmp \ -v "$(CURDIR)/tests/reference:/reference" $(REFERENCE_IMAGE) \ gnubg --tty --quiet --no-rc --python=/reference/generate.py diff --git a/tests/reference/analyses.json b/tests/reference/analyses.json new file mode 100644 index 0000000..1eeb7fb --- /dev/null +++ b/tests/reference/analyses.json @@ -0,0 +1,283 @@ +{ + "moves": [ + { + "id": "move-opening-31-best", + "description": "Ouverture 3-1 jouée au mieux (8/5 6/5) ; chance du premier jet.", + "position_id": "4HPwATDgc/ABMA", + "match_id": "cIkFAAAAAAAA", + "dice": [3, 1], + "played": "8/5 6/5" + }, + { + "id": "move-opening-31-error", + "description": "Ouverture 3-1 jouée 24/23 13/10 au lieu de faire le point 5.", + "position_id": "4HPwATDgc/ABMA", + "match_id": "cIkFAAAAAAAA", + "dice": [3, 1], + "played": "24/23 13/10" + }, + { + "id": "move-opening-65-error", + "description": "Ouverture 6-5 jouée 13/8 13/7 au lieu de courir 24/13.", + "position_id": "4HPwATDgc/ABMA", + "match_id": "cAkXAAAAAAAA", + "dice": [6, 5], + "played": "13/8 13/7" + }, + { + "id": "move-middlegame-close", + "description": "Milieu de partie, 6-4 : un coup proche du meilleur.", + "position_id": "mM/gATCw5+ABMA", + "match_id": "cAkTAAAAAAAA", + "dice": [6, 4], + "played": "8/2 6/2" + }, + { + "id": "move-middlegame-blunder", + "description": "Milieu de partie, 6-4 : un des pires coups.", + "position_id": "mM/gATCw5+ABMA", + "match_id": "cAkTAAAAAAAA", + "dice": [6, 4], + "played": "8/4 8/2" + }, + { + "id": "move-match", + "description": "Match en 7 points à 3-3, 5-3 : frappe au lieu du meilleur coup.", + "position_id": "xGfhATDQ2+BBIA", + "match_id": "cImuACAAEAAE", + "dice": [5, 3], + "played": "24/16" + }, + { + "id": "move-race", + "description": "Course, 5-4 : sortie maladroite.", + "position_id": "cLezAQBwN2cGAA", + "match_id": "cIkSAAAAAAAA", + "dice": [5, 4], + "played": "6/1 5/1" + }, + { + "id": "move-blitz-doubles", + "description": "Attaque, double 3 : un coup parmi 110.", + "position_id": "4HPwAVC0XYMHAA", + "match_id": "cIkNAAAAAAAA", + "dice": [3, 3], + "played": "13/10 13/10 5/2 4/1" + }, + { + "id": "move-crawford", + "description": "Match en 5 points, partie Crawford, 4-3.", + "position_id": "4Dn4AGiynYMHAA", + "match_id": "8AmuAEAAEAAE", + "dice": [4, 3], + "played": "5/2 5/1" + }, + { + "id": "move-jacoby", + "description": "Partie libre avec la règle Jacoby, 6-4.", + "position_id": "4HPwAVC0XYMHAA", + "match_id": "cAkTAAAAAAAA", + "dice": [6, 4], + "played": "8/2 5/1" + }, + { + "id": "move-bearoff-doubles", + "description": "Sortie, double 6 : coup unique ; chance d'un double.", + "position_id": "xgAA4MgAAAAAAA", + "match_id": "cAkbAAAAAAAA", + "dice": [6, 6], + "played": "6/off 6/off 4/off 1/off" + }, + { + "id": "move-forced", + "description": "Rentrée forcée depuis la barre, 3-1.", + "position_id": "tm3ABwBw5+ABYA", + "match_id": "cIkFAAAAAAAA", + "dice": [3, 1], + "played": "bar/24" + }, + { + "id": "move-luck-unlucky", + "description": "Milieu de partie, 5-4 : jet malchanceux, joué au mieux.", + "position_id": "xGfhATDQ2+BBIA", + "match_id": "cIkOAAAAAAAA", + "dice": [5, 4], + "played": null + }, + { + "id": "move-luck-unlucky-bearoff", + "description": "Fin de sortie, 2-1 : jet malchanceux, joué au mieux.", + "position_id": "TQAAYAsAAAAAAA", + "match_id": "cAkFAAAAAAAA", + "dice": [2, 1], + "played": null + }, + { + "id": "move-luck-lucky", + "description": "Course, double 4 : jet chanceux, joué au mieux.", + "position_id": "cLezAQBwN2cGAA", + "match_id": "cIkSAAAAAAAA", + "dice": [4, 4], + "played": null + }, + { + "id": "move-opening-double", + "description": "Position de départ et double 6 : pas un premier jet, chance comptée sur les 36 jets.", + "position_id": "4HPwATDgc/ABMA", + "match_id": "cIkFAAAAAAAA", + "dice": [6, 6], + "played": null + } + ], + "cubes": [ + { + "id": "cube-double-take-no-double", + "description": "Double/prend : le joueur au trait ne double pas.", + "position_id": "6O5uAAC4uzsAAA", + "match_id": "cAkAAAAAAAAA", + "action": "no_double" + }, + { + "id": "cube-double-take-double", + "description": "Double/prend : le joueur au trait double.", + "position_id": "6O5uAAC4uzsAAA", + "match_id": "cAkAAAAAAAAA", + "action": "double" + }, + { + "id": "cube-double-take-take", + "description": "Double/prend : l'adversaire prend.", + "position_id": "6O5uAAC4uzsAAA", + "match_id": "cAkAAAAAAAAA", + "action": "take" + }, + { + "id": "cube-double-take-pass", + "description": "Double/prend : l'adversaire passe à tort.", + "position_id": "6O5uAAC4uzsAAA", + "match_id": "cAkAAAAAAAAA", + "action": "pass" + }, + { + "id": "cube-double-pass-take", + "description": "Double/passe : l'adversaire prend à tort.", + "position_id": "cHd3AADvvQMAAA", + "match_id": "cAkAAAAAAAAA", + "action": "take" + }, + { + "id": "cube-double-pass-pass", + "description": "Double/passe : l'adversaire passe.", + "position_id": "cHd3AADvvQMAAA", + "match_id": "cAkAAAAAAAAA", + "action": "pass" + }, + { + "id": "cube-no-double-double", + "description": "Pas de double : le joueur au trait double trop tôt.", + "position_id": "2J0dBgDYnRsGAA", + "match_id": "cAkAAAAAAAAA", + "action": "double" + }, + { + "id": "cube-no-double-no-double", + "description": "Pas de double : le joueur au trait ne double pas.", + "position_id": "2J0dBgDYnRsGAA", + "match_id": "cAkAAAAAAAAA", + "action": "no_double" + }, + { + "id": "cube-redouble-take", + "description": "Redouble : videau à 2 possédé par le joueur au trait, l'adversaire prend.", + "position_id": "6O5uAAC4uzsAAA", + "match_id": "UQkAAAAAAAAA", + "action": "take" + }, + { + "id": "cube-match-double", + "description": "Match en 7 points, 3-5 : le joueur au trait double.", + "position_id": "cHd3AAC4uzsAAA", + "match_id": "cAngAFAAGAAE", + "action": "double" + }, + { + "id": "cube-match-pass", + "description": "Match en 7 points, 3-5 : l'adversaire passe.", + "position_id": "cHd3AAC4uzsAAA", + "match_id": "cAngAFAAGAAE", + "action": "pass" + }, + { + "id": "cube-post-crawford-double", + "description": "Match en 5 points, après Crawford, mené 2-4 : le joueur au trait double.", + "position_id": "2J0dBgDYnRsGAA", + "match_id": "cAmgAEAAEAAE", + "action": "double" + }, + { + "id": "cube-crawford-not-analysed", + "description": "Partie Crawford : pas de double possible, gnubg n'analyse pas le videau.", + "position_id": "cHd3AAC4uzsAAA", + "match_id": "8AmgAEAAEAAE", + "action": "no_double" + }, + { + "id": "cube-opponent-owns-not-analysed", + "description": "Videau à 2 possédé par l'adversaire : pas de double possible, pas d'analyse.", + "position_id": "6O5uAAC4uzsAAA", + "match_id": "EQEAAAAAAAAA", + "action": "no_double" + } + ], + "levels": [ + { + "id": "level-opening-31", + "description": "Ouverture 3-1.", + "position_id": "4HPwATDgc/ABMA", + "match_id": "cIkFAAAAAAAA", + "dice": [3, 1] + }, + { + "id": "level-middlegame-1", + "description": "Milieu de partie, 6-4 : le bruit change le coup choisi.", + "position_id": "mM/gATCw5+ABMA", + "match_id": "cAkTAAAAAAAA", + "dice": [6, 4] + }, + { + "id": "level-middlegame-2", + "description": "Milieu de partie avec contact, 5-3.", + "position_id": "xGfhATDQ2+BBIA", + "match_id": "cIkOAAAAAAAA", + "dice": [5, 3] + }, + { + "id": "level-prime-vs-prime", + "description": "Prime contre prime, 4-2.", + "position_id": "4LZtAAbAbZsBAw", + "match_id": "cAkKAAAAAAAA", + "dice": [4, 2] + }, + { + "id": "level-blitz-doubles", + "description": "Attaque, double 3.", + "position_id": "4HPwAVC0XYMHAA", + "match_id": "cIkNAAAAAAAA", + "dice": [3, 3] + }, + { + "id": "level-cube-double-take", + "description": "Décision de videau double/prend, à chaque niveau.", + "position_id": "6O5uAAC4uzsAAA", + "match_id": "cAkAAAAAAAAA", + "dice": null + }, + { + "id": "level-cube-no-double", + "description": "Décision de videau sans double, à chaque niveau.", + "position_id": "2J0dBgDYnRsGAA", + "match_id": "cAkAAAAAAAAA", + "dice": null + } + ] +} diff --git a/tests/reference/analysis/cube-crawford-not-analysed.json b/tests/reference/analysis/cube-crawford-not-analysed.json new file mode 100644 index 0000000..c355f7a --- /dev/null +++ b/tests/reference/analysis/cube-crawford-not-analysed.json @@ -0,0 +1,24 @@ +{ + "id": "cube-crawford-not-analysed", + "description": "Partie Crawford : pas de double possible, gnubg n'analyse pas le videau.", + "position_id": "cHd3AAC4uzsAAA", + "match_id": "8AmgAEAAEAAE", + "action": "no_double", + "type": "cube", + "gnubg_commit": "14b67984737ded0f6de43d63dcb789f729d9b06b", + "met": "Kazaross-XG2", + "plies": { + "0": { + "analysed": false, + "cube": null, + "equity_loss": -0.0, + "skill": "none" + }, + "2": { + "analysed": false, + "cube": null, + "equity_loss": -0.0, + "skill": "none" + } + } +} diff --git a/tests/reference/analysis/cube-double-pass-pass.json b/tests/reference/analysis/cube-double-pass-pass.json new file mode 100644 index 0000000..b7ee7f0 --- /dev/null +++ b/tests/reference/analysis/cube-double-pass-pass.json @@ -0,0 +1,38 @@ +{ + "id": "cube-double-pass-pass", + "description": "Double/passe : l'adversaire passe.", + "position_id": "cHd3AADvvQMAAA", + "match_id": "cAkAAAAAAAAA", + "action": "pass", + "type": "cube", + "gnubg_commit": "14b67984737ded0f6de43d63dcb789f729d9b06b", + "met": "Kazaross-XG2", + "plies": { + "0": { + "analysed": true, + "cube": { + "no_double": 0.9999689, + "double_take": 2.0565755, + "double_pass": 1.0, + "optimal": 1.0, + "decision": "DOUBLE_PASS", + "decision_text": "Double, pass" + }, + "equity_loss": -0.0, + "skill": "none" + }, + "2": { + "analysed": true, + "cube": { + "no_double": 1.0, + "double_take": 2.0675642, + "double_pass": 1.0, + "optimal": 1.0, + "decision": "DOUBLE_PASS", + "decision_text": "Double, pass" + }, + "equity_loss": -0.0, + "skill": "none" + } + } +} diff --git a/tests/reference/analysis/cube-double-pass-take.json b/tests/reference/analysis/cube-double-pass-take.json new file mode 100644 index 0000000..2991e84 --- /dev/null +++ b/tests/reference/analysis/cube-double-pass-take.json @@ -0,0 +1,38 @@ +{ + "id": "cube-double-pass-take", + "description": "Double/passe : l'adversaire prend à tort.", + "position_id": "cHd3AADvvQMAAA", + "match_id": "cAkAAAAAAAAA", + "action": "take", + "type": "cube", + "gnubg_commit": "14b67984737ded0f6de43d63dcb789f729d9b06b", + "met": "Kazaross-XG2", + "plies": { + "0": { + "analysed": true, + "cube": { + "no_double": 0.9999689, + "double_take": 2.0565755, + "double_pass": 1.0, + "optimal": 1.0, + "decision": "DOUBLE_PASS", + "decision_text": "Double, pass" + }, + "equity_loss": -1.0565755, + "skill": "very_bad" + }, + "2": { + "analysed": true, + "cube": { + "no_double": 1.0, + "double_take": 2.0675642, + "double_pass": 1.0, + "optimal": 1.0, + "decision": "DOUBLE_PASS", + "decision_text": "Double, pass" + }, + "equity_loss": -1.0675642, + "skill": "very_bad" + } + } +} diff --git a/tests/reference/analysis/cube-double-take-double.json b/tests/reference/analysis/cube-double-take-double.json new file mode 100644 index 0000000..755378a --- /dev/null +++ b/tests/reference/analysis/cube-double-take-double.json @@ -0,0 +1,38 @@ +{ + "id": "cube-double-take-double", + "description": "Double/prend : le joueur au trait double.", + "position_id": "6O5uAAC4uzsAAA", + "match_id": "cAkAAAAAAAAA", + "action": "double", + "type": "cube", + "gnubg_commit": "14b67984737ded0f6de43d63dcb789f729d9b06b", + "met": "Kazaross-XG2", + "plies": { + "0": { + "analysed": true, + "cube": { + "no_double": 0.8189599, + "double_take": 0.88185024, + "double_pass": 1.0, + "optimal": 0.88185024, + "decision": "DOUBLE_TAKE", + "decision_text": "Double, take" + }, + "equity_loss": -0.0, + "skill": "none" + }, + "2": { + "analysed": true, + "cube": { + "no_double": 0.83056116, + "double_take": 0.9218164, + "double_pass": 1.0, + "optimal": 0.9218164, + "decision": "DOUBLE_TAKE", + "decision_text": "Double, take" + }, + "equity_loss": -0.0, + "skill": "none" + } + } +} diff --git a/tests/reference/analysis/cube-double-take-no-double.json b/tests/reference/analysis/cube-double-take-no-double.json new file mode 100644 index 0000000..4ec2721 --- /dev/null +++ b/tests/reference/analysis/cube-double-take-no-double.json @@ -0,0 +1,38 @@ +{ + "id": "cube-double-take-no-double", + "description": "Double/prend : le joueur au trait ne double pas.", + "position_id": "6O5uAAC4uzsAAA", + "match_id": "cAkAAAAAAAAA", + "action": "no_double", + "type": "cube", + "gnubg_commit": "14b67984737ded0f6de43d63dcb789f729d9b06b", + "met": "Kazaross-XG2", + "plies": { + "0": { + "analysed": true, + "cube": { + "no_double": 0.8189599, + "double_take": 0.88185024, + "double_pass": 1.0, + "optimal": 0.88185024, + "decision": "DOUBLE_TAKE", + "decision_text": "Double, take" + }, + "equity_loss": -0.06289035, + "skill": "bad" + }, + "2": { + "analysed": true, + "cube": { + "no_double": 0.83056116, + "double_take": 0.9218164, + "double_pass": 1.0, + "optimal": 0.9218164, + "decision": "DOUBLE_TAKE", + "decision_text": "Double, take" + }, + "equity_loss": -0.09125525, + "skill": "bad" + } + } +} diff --git a/tests/reference/analysis/cube-double-take-pass.json b/tests/reference/analysis/cube-double-take-pass.json new file mode 100644 index 0000000..4ab0f07 --- /dev/null +++ b/tests/reference/analysis/cube-double-take-pass.json @@ -0,0 +1,38 @@ +{ + "id": "cube-double-take-pass", + "description": "Double/prend : l'adversaire passe à tort.", + "position_id": "6O5uAAC4uzsAAA", + "match_id": "cAkAAAAAAAAA", + "action": "pass", + "type": "cube", + "gnubg_commit": "14b67984737ded0f6de43d63dcb789f729d9b06b", + "met": "Kazaross-XG2", + "plies": { + "0": { + "analysed": true, + "cube": { + "no_double": 0.8189599, + "double_take": 0.88185024, + "double_pass": 1.0, + "optimal": 0.88185024, + "decision": "DOUBLE_TAKE", + "decision_text": "Double, take" + }, + "equity_loss": -0.11814976, + "skill": "bad" + }, + "2": { + "analysed": true, + "cube": { + "no_double": 0.83056116, + "double_take": 0.9218164, + "double_pass": 1.0, + "optimal": 0.9218164, + "decision": "DOUBLE_TAKE", + "decision_text": "Double, take" + }, + "equity_loss": -0.07818359, + "skill": "bad" + } + } +} diff --git a/tests/reference/analysis/cube-double-take-take.json b/tests/reference/analysis/cube-double-take-take.json new file mode 100644 index 0000000..edb3b36 --- /dev/null +++ b/tests/reference/analysis/cube-double-take-take.json @@ -0,0 +1,38 @@ +{ + "id": "cube-double-take-take", + "description": "Double/prend : l'adversaire prend.", + "position_id": "6O5uAAC4uzsAAA", + "match_id": "cAkAAAAAAAAA", + "action": "take", + "type": "cube", + "gnubg_commit": "14b67984737ded0f6de43d63dcb789f729d9b06b", + "met": "Kazaross-XG2", + "plies": { + "0": { + "analysed": true, + "cube": { + "no_double": 0.8189599, + "double_take": 0.88185024, + "double_pass": 1.0, + "optimal": 0.88185024, + "decision": "DOUBLE_TAKE", + "decision_text": "Double, take" + }, + "equity_loss": -0.0, + "skill": "none" + }, + "2": { + "analysed": true, + "cube": { + "no_double": 0.83056116, + "double_take": 0.9218164, + "double_pass": 1.0, + "optimal": 0.9218164, + "decision": "DOUBLE_TAKE", + "decision_text": "Double, take" + }, + "equity_loss": -0.0, + "skill": "none" + } + } +} diff --git a/tests/reference/analysis/cube-match-double.json b/tests/reference/analysis/cube-match-double.json new file mode 100644 index 0000000..8e89779 --- /dev/null +++ b/tests/reference/analysis/cube-match-double.json @@ -0,0 +1,38 @@ +{ + "id": "cube-match-double", + "description": "Match en 7 points, 3-5 : le joueur au trait double.", + "position_id": "cHd3AAC4uzsAAA", + "match_id": "cAngAFAAGAAE", + "action": "double", + "type": "cube", + "gnubg_commit": "14b67984737ded0f6de43d63dcb789f729d9b06b", + "met": "Kazaross-XG2", + "plies": { + "0": { + "analysed": true, + "cube": { + "no_double": 0.93459773, + "double_take": 1.1651278, + "double_pass": 1.0, + "optimal": 1.0, + "decision": "DOUBLE_PASS", + "decision_text": "Double, pass" + }, + "equity_loss": -0.0, + "skill": "none" + }, + "2": { + "analysed": true, + "cube": { + "no_double": 0.9405141, + "double_take": 1.1657741, + "double_pass": 1.0, + "optimal": 1.0, + "decision": "DOUBLE_PASS", + "decision_text": "Double, pass" + }, + "equity_loss": -0.0, + "skill": "none" + } + } +} diff --git a/tests/reference/analysis/cube-match-pass.json b/tests/reference/analysis/cube-match-pass.json new file mode 100644 index 0000000..2f9519a --- /dev/null +++ b/tests/reference/analysis/cube-match-pass.json @@ -0,0 +1,38 @@ +{ + "id": "cube-match-pass", + "description": "Match en 7 points, 3-5 : l'adversaire passe.", + "position_id": "cHd3AAC4uzsAAA", + "match_id": "cAngAFAAGAAE", + "action": "pass", + "type": "cube", + "gnubg_commit": "14b67984737ded0f6de43d63dcb789f729d9b06b", + "met": "Kazaross-XG2", + "plies": { + "0": { + "analysed": true, + "cube": { + "no_double": 0.93459773, + "double_take": 1.1651278, + "double_pass": 1.0, + "optimal": 1.0, + "decision": "DOUBLE_PASS", + "decision_text": "Double, pass" + }, + "equity_loss": -0.0, + "skill": "none" + }, + "2": { + "analysed": true, + "cube": { + "no_double": 0.9405141, + "double_take": 1.1657741, + "double_pass": 1.0, + "optimal": 1.0, + "decision": "DOUBLE_PASS", + "decision_text": "Double, pass" + }, + "equity_loss": -0.0, + "skill": "none" + } + } +} diff --git a/tests/reference/analysis/cube-no-double-double.json b/tests/reference/analysis/cube-no-double-double.json new file mode 100644 index 0000000..50feee1 --- /dev/null +++ b/tests/reference/analysis/cube-no-double-double.json @@ -0,0 +1,38 @@ +{ + "id": "cube-no-double-double", + "description": "Pas de double : le joueur au trait double trop tôt.", + "position_id": "2J0dBgDYnRsGAA", + "match_id": "cAkAAAAAAAAA", + "action": "double", + "type": "cube", + "gnubg_commit": "14b67984737ded0f6de43d63dcb789f729d9b06b", + "met": "Kazaross-XG2", + "plies": { + "0": { + "analysed": true, + "cube": { + "no_double": 0.35839266, + "double_take": 0.18619786, + "double_pass": 1.0, + "optimal": 0.35839266, + "decision": "NODOUBLE_TAKE", + "decision_text": "No double, take" + }, + "equity_loss": -0.1721948, + "skill": "very_bad" + }, + "2": { + "analysed": true, + "cube": { + "no_double": 0.28532878, + "double_take": 0.11171284, + "double_pass": 1.0, + "optimal": 0.28532878, + "decision": "NODOUBLE_TAKE", + "decision_text": "No double, take" + }, + "equity_loss": -0.17361593, + "skill": "very_bad" + } + } +} diff --git a/tests/reference/analysis/cube-no-double-no-double.json b/tests/reference/analysis/cube-no-double-no-double.json new file mode 100644 index 0000000..6e62667 --- /dev/null +++ b/tests/reference/analysis/cube-no-double-no-double.json @@ -0,0 +1,38 @@ +{ + "id": "cube-no-double-no-double", + "description": "Pas de double : le joueur au trait ne double pas.", + "position_id": "2J0dBgDYnRsGAA", + "match_id": "cAkAAAAAAAAA", + "action": "no_double", + "type": "cube", + "gnubg_commit": "14b67984737ded0f6de43d63dcb789f729d9b06b", + "met": "Kazaross-XG2", + "plies": { + "0": { + "analysed": true, + "cube": { + "no_double": 0.35839266, + "double_take": 0.18619786, + "double_pass": 1.0, + "optimal": 0.35839266, + "decision": "NODOUBLE_TAKE", + "decision_text": "No double, take" + }, + "equity_loss": -0.0, + "skill": "none" + }, + "2": { + "analysed": true, + "cube": { + "no_double": 0.28532878, + "double_take": 0.11171284, + "double_pass": 1.0, + "optimal": 0.28532878, + "decision": "NODOUBLE_TAKE", + "decision_text": "No double, take" + }, + "equity_loss": -0.0, + "skill": "none" + } + } +} diff --git a/tests/reference/analysis/cube-opponent-owns-not-analysed.json b/tests/reference/analysis/cube-opponent-owns-not-analysed.json new file mode 100644 index 0000000..e7d4049 --- /dev/null +++ b/tests/reference/analysis/cube-opponent-owns-not-analysed.json @@ -0,0 +1,24 @@ +{ + "id": "cube-opponent-owns-not-analysed", + "description": "Videau à 2 possédé par l'adversaire : pas de double possible, pas d'analyse.", + "position_id": "6O5uAAC4uzsAAA", + "match_id": "EQEAAAAAAAAA", + "action": "no_double", + "type": "cube", + "gnubg_commit": "14b67984737ded0f6de43d63dcb789f729d9b06b", + "met": "Kazaross-XG2", + "plies": { + "0": { + "analysed": false, + "cube": null, + "equity_loss": -0.0, + "skill": "none" + }, + "2": { + "analysed": false, + "cube": null, + "equity_loss": -0.0, + "skill": "none" + } + } +} diff --git a/tests/reference/analysis/cube-post-crawford-double.json b/tests/reference/analysis/cube-post-crawford-double.json new file mode 100644 index 0000000..4305a6b --- /dev/null +++ b/tests/reference/analysis/cube-post-crawford-double.json @@ -0,0 +1,38 @@ +{ + "id": "cube-post-crawford-double", + "description": "Match en 5 points, après Crawford, mené 2-4 : le joueur au trait double.", + "position_id": "2J0dBgDYnRsGAA", + "match_id": "cAmgAEAAEAAE", + "action": "double", + "type": "cube", + "gnubg_commit": "14b67984737ded0f6de43d63dcb789f729d9b06b", + "met": "Kazaross-XG2", + "plies": { + "0": { + "analysed": true, + "cube": { + "no_double": 0.26475775, + "double_take": 0.2732438, + "double_pass": 1.0, + "optimal": 0.2732438, + "decision": "DOUBLE_TAKE", + "decision_text": "Double, take" + }, + "equity_loss": -0.0, + "skill": "none" + }, + "2": { + "analysed": true, + "cube": { + "no_double": 0.22408937, + "double_take": 0.22408937, + "double_pass": 1.0, + "optimal": 0.22408937, + "decision": "OPTIONAL_DOUBLE_TAKE", + "decision_text": "Optional double, take" + }, + "equity_loss": -0.0, + "skill": "none" + } + } +} diff --git a/tests/reference/analysis/cube-redouble-take.json b/tests/reference/analysis/cube-redouble-take.json new file mode 100644 index 0000000..f02fefc --- /dev/null +++ b/tests/reference/analysis/cube-redouble-take.json @@ -0,0 +1,38 @@ +{ + "id": "cube-redouble-take", + "description": "Redouble : videau à 2 possédé par le joueur au trait, l'adversaire prend.", + "position_id": "6O5uAAC4uzsAAA", + "match_id": "UQkAAAAAAAAA", + "action": "take", + "type": "cube", + "gnubg_commit": "14b67984737ded0f6de43d63dcb789f729d9b06b", + "met": "Kazaross-XG2", + "plies": { + "0": { + "analysed": true, + "cube": { + "no_double": 0.840925, + "double_take": 0.88185024, + "double_pass": 1.0, + "optimal": 0.88185024, + "decision": "REDOUBLE_TAKE", + "decision_text": "Redouble, take" + }, + "equity_loss": -0.0, + "skill": "none" + }, + "2": { + "analysed": true, + "cube": { + "no_double": 0.84627455, + "double_take": 0.9218164, + "double_pass": 1.0, + "optimal": 0.9218164, + "decision": "REDOUBLE_TAKE", + "decision_text": "Redouble, take" + }, + "equity_loss": -0.0, + "skill": "none" + } + } +} diff --git a/tests/reference/analysis/level-blitz-doubles.json b/tests/reference/analysis/level-blitz-doubles.json new file mode 100644 index 0000000..ef18f98 --- /dev/null +++ b/tests/reference/analysis/level-blitz-doubles.json @@ -0,0 +1,50 @@ +{ + "id": "level-blitz-doubles", + "description": "Attaque, double 3.", + "position_id": "4HPwAVC0XYMHAA", + "match_id": "cIkNAAAAAAAA", + "dice": [ + 3, + 3 + ], + "type": "level", + "gnubg_commit": "14b67984737ded0f6de43d63dcb789f729d9b06b", + "met": "Kazaross-XG2", + "levels": { + "beginner": { + "plies": 0, + "noise": 0.06, + "move": "5/2(2) 4/1*(2)" + }, + "casual": { + "plies": 0, + "noise": 0.05, + "move": "5/2(2) 4/1*(2)" + }, + "intermediate": { + "plies": 0, + "noise": 0.04, + "move": "5/2(2) 4/1*(2)" + }, + "advanced": { + "plies": 0, + "noise": 0.015, + "move": "13/10 6/3 4/1*(2)" + }, + "0ply": { + "plies": 0, + "noise": 0.0, + "move": "13/10 6/3 4/1*(2)" + }, + "1ply": { + "plies": 1, + "noise": 0.0, + "move": "13/10(2) 8/5 6/3" + }, + "2ply": { + "plies": 2, + "noise": 0.0, + "move": "13/7 13/10 6/3" + } + } +} diff --git a/tests/reference/analysis/level-cube-double-take.json b/tests/reference/analysis/level-cube-double-take.json new file mode 100644 index 0000000..6ab6a8b --- /dev/null +++ b/tests/reference/analysis/level-cube-double-take.json @@ -0,0 +1,82 @@ +{ + "id": "level-cube-double-take", + "description": "Décision de videau double/prend, à chaque niveau.", + "position_id": "6O5uAAC4uzsAAA", + "match_id": "cAkAAAAAAAAA", + "dice": null, + "type": "level", + "gnubg_commit": "14b67984737ded0f6de43d63dcb789f729d9b06b", + "met": "Kazaross-XG2", + "levels": { + "beginner": { + "plies": 0, + "noise": 0.06, + "no_double": 0.42431837, + "double_take": 0.26415053, + "double_pass": 1.0, + "optimal": 0.42431837, + "decision": "NODOUBLE_TAKE", + "decision_text": "No double, take" + }, + "casual": { + "plies": 0, + "noise": 0.05, + "no_double": 0.49009216, + "double_take": 0.36710066, + "double_pass": 1.0, + "optimal": 0.49009216, + "decision": "NODOUBLE_TAKE", + "decision_text": "No double, take" + }, + "intermediate": { + "plies": 0, + "noise": 0.04, + "no_double": 0.55586565, + "double_take": 0.47005057, + "double_pass": 1.0, + "optimal": 0.55586565, + "decision": "NODOUBLE_TAKE", + "decision_text": "No double, take" + }, + "advanced": { + "plies": 0, + "noise": 0.015, + "no_double": 0.7202995, + "double_take": 0.7274252, + "double_pass": 1.0, + "optimal": 0.7274252, + "decision": "DOUBLE_TAKE", + "decision_text": "Double, take" + }, + "0ply": { + "plies": 0, + "noise": 0.0, + "no_double": 0.8189599, + "double_take": 0.88185024, + "double_pass": 1.0, + "optimal": 0.88185024, + "decision": "DOUBLE_TAKE", + "decision_text": "Double, take" + }, + "1ply": { + "plies": 1, + "noise": 0.0, + "no_double": 0.79611415, + "double_take": 0.8986737, + "double_pass": 1.0, + "optimal": 0.8986737, + "decision": "DOUBLE_TAKE", + "decision_text": "Double, take" + }, + "2ply": { + "plies": 2, + "noise": 0.0, + "no_double": 0.83056116, + "double_take": 0.9218164, + "double_pass": 1.0, + "optimal": 0.9218164, + "decision": "DOUBLE_TAKE", + "decision_text": "Double, take" + } + } +} diff --git a/tests/reference/analysis/level-cube-no-double.json b/tests/reference/analysis/level-cube-no-double.json new file mode 100644 index 0000000..403257e --- /dev/null +++ b/tests/reference/analysis/level-cube-no-double.json @@ -0,0 +1,82 @@ +{ + "id": "level-cube-no-double", + "description": "Décision de videau sans double, à chaque niveau.", + "position_id": "2J0dBgDYnRsGAA", + "match_id": "cAkAAAAAAAAA", + "dice": null, + "type": "level", + "gnubg_commit": "14b67984737ded0f6de43d63dcb789f729d9b06b", + "met": "Kazaross-XG2", + "levels": { + "beginner": { + "plies": 0, + "noise": 0.06, + "no_double": -0.16227412, + "double_take": -0.6363357, + "double_pass": 1.0, + "optimal": -0.16227412, + "decision": "NODOUBLE_BEAVER", + "decision_text": "No double, beaver" + }, + "casual": { + "plies": 0, + "noise": 0.05, + "no_double": -0.075523466, + "double_take": -0.4993003, + "double_pass": 1.0, + "optimal": -0.075523466, + "decision": "NODOUBLE_BEAVER", + "decision_text": "No double, beaver" + }, + "intermediate": { + "plies": 0, + "noise": 0.04, + "no_double": 0.011226978, + "double_take": -0.36226535, + "double_pass": 1.0, + "optimal": 0.011226978, + "decision": "NODOUBLE_BEAVER", + "decision_text": "No double, beaver" + }, + "advanced": { + "plies": 0, + "noise": 0.015, + "no_double": 0.22810358, + "double_take": -0.019677103, + "double_pass": 1.0, + "optimal": 0.22810358, + "decision": "NODOUBLE_BEAVER", + "decision_text": "No double, beaver" + }, + "0ply": { + "plies": 0, + "noise": 0.0, + "no_double": 0.35839266, + "double_take": 0.18619786, + "double_pass": 1.0, + "optimal": 0.35839266, + "decision": "NODOUBLE_TAKE", + "decision_text": "No double, take" + }, + "1ply": { + "plies": 1, + "noise": 0.0, + "no_double": 0.33552775, + "double_take": 0.15480106, + "double_pass": 1.0, + "optimal": 0.33552775, + "decision": "NODOUBLE_TAKE", + "decision_text": "No double, take" + }, + "2ply": { + "plies": 2, + "noise": 0.0, + "no_double": 0.28532878, + "double_take": 0.11171284, + "double_pass": 1.0, + "optimal": 0.28532878, + "decision": "NODOUBLE_TAKE", + "decision_text": "No double, take" + } + } +} diff --git a/tests/reference/analysis/level-middlegame-1.json b/tests/reference/analysis/level-middlegame-1.json new file mode 100644 index 0000000..7e8490f --- /dev/null +++ b/tests/reference/analysis/level-middlegame-1.json @@ -0,0 +1,50 @@ +{ + "id": "level-middlegame-1", + "description": "Milieu de partie, 6-4 : le bruit change le coup choisi.", + "position_id": "mM/gATCw5+ABMA", + "match_id": "cAkTAAAAAAAA", + "dice": [ + 6, + 4 + ], + "type": "level", + "gnubg_commit": "14b67984737ded0f6de43d63dcb789f729d9b06b", + "met": "Kazaross-XG2", + "levels": { + "beginner": { + "plies": 0, + "noise": 0.06, + "move": "24/20 24/18" + }, + "casual": { + "plies": 0, + "noise": 0.05, + "move": "24/20 24/18" + }, + "intermediate": { + "plies": 0, + "noise": 0.04, + "move": "24/20 24/18" + }, + "advanced": { + "plies": 0, + "noise": 0.015, + "move": "24/14" + }, + "0ply": { + "plies": 0, + "noise": 0.0, + "move": "24/14" + }, + "1ply": { + "plies": 1, + "noise": 0.0, + "move": "24/14" + }, + "2ply": { + "plies": 2, + "noise": 0.0, + "move": "24/14" + } + } +} diff --git a/tests/reference/analysis/level-middlegame-2.json b/tests/reference/analysis/level-middlegame-2.json new file mode 100644 index 0000000..982ba64 --- /dev/null +++ b/tests/reference/analysis/level-middlegame-2.json @@ -0,0 +1,50 @@ +{ + "id": "level-middlegame-2", + "description": "Milieu de partie avec contact, 5-3.", + "position_id": "xGfhATDQ2+BBIA", + "match_id": "cIkOAAAAAAAA", + "dice": [ + 5, + 3 + ], + "type": "level", + "gnubg_commit": "14b67984737ded0f6de43d63dcb789f729d9b06b", + "met": "Kazaross-XG2", + "levels": { + "beginner": { + "plies": 0, + "noise": 0.06, + "move": "13/5" + }, + "casual": { + "plies": 0, + "noise": 0.05, + "move": "13/5" + }, + "intermediate": { + "plies": 0, + "noise": 0.04, + "move": "13/5" + }, + "advanced": { + "plies": 0, + "noise": 0.015, + "move": "13/5" + }, + "0ply": { + "plies": 0, + "noise": 0.0, + "move": "13/5" + }, + "1ply": { + "plies": 1, + "noise": 0.0, + "move": "13/5" + }, + "2ply": { + "plies": 2, + "noise": 0.0, + "move": "13/5" + } + } +} diff --git a/tests/reference/analysis/level-opening-31.json b/tests/reference/analysis/level-opening-31.json new file mode 100644 index 0000000..e31d6e4 --- /dev/null +++ b/tests/reference/analysis/level-opening-31.json @@ -0,0 +1,50 @@ +{ + "id": "level-opening-31", + "description": "Ouverture 3-1.", + "position_id": "4HPwATDgc/ABMA", + "match_id": "cIkFAAAAAAAA", + "dice": [ + 3, + 1 + ], + "type": "level", + "gnubg_commit": "14b67984737ded0f6de43d63dcb789f729d9b06b", + "met": "Kazaross-XG2", + "levels": { + "beginner": { + "plies": 0, + "noise": 0.06, + "move": "8/5 6/5" + }, + "casual": { + "plies": 0, + "noise": 0.05, + "move": "8/5 6/5" + }, + "intermediate": { + "plies": 0, + "noise": 0.04, + "move": "8/5 6/5" + }, + "advanced": { + "plies": 0, + "noise": 0.015, + "move": "8/5 6/5" + }, + "0ply": { + "plies": 0, + "noise": 0.0, + "move": "8/5 6/5" + }, + "1ply": { + "plies": 1, + "noise": 0.0, + "move": "8/5 6/5" + }, + "2ply": { + "plies": 2, + "noise": 0.0, + "move": "8/5 6/5" + } + } +} diff --git a/tests/reference/analysis/level-prime-vs-prime.json b/tests/reference/analysis/level-prime-vs-prime.json new file mode 100644 index 0000000..a561b19 --- /dev/null +++ b/tests/reference/analysis/level-prime-vs-prime.json @@ -0,0 +1,50 @@ +{ + "id": "level-prime-vs-prime", + "description": "Prime contre prime, 4-2.", + "position_id": "4LZtAAbAbZsBAw", + "match_id": "cAkKAAAAAAAA", + "dice": [ + 4, + 2 + ], + "type": "level", + "gnubg_commit": "14b67984737ded0f6de43d63dcb789f729d9b06b", + "met": "Kazaross-XG2", + "levels": { + "beginner": { + "plies": 0, + "noise": 0.06, + "move": "13/11 7/3" + }, + "casual": { + "plies": 0, + "noise": 0.05, + "move": "13/11 7/3" + }, + "intermediate": { + "plies": 0, + "noise": 0.04, + "move": "13/11 7/3" + }, + "advanced": { + "plies": 0, + "noise": 0.015, + "move": "13/11 7/3" + }, + "0ply": { + "plies": 0, + "noise": 0.0, + "move": "13/9 7/5" + }, + "1ply": { + "plies": 1, + "noise": 0.0, + "move": "13/9 7/5" + }, + "2ply": { + "plies": 2, + "noise": 0.0, + "move": "13/11 13/9" + } + } +} diff --git a/tests/reference/analysis/move-bearoff-doubles.json b/tests/reference/analysis/move-bearoff-doubles.json new file mode 100644 index 0000000..cb72f8f --- /dev/null +++ b/tests/reference/analysis/move-bearoff-doubles.json @@ -0,0 +1,54 @@ +{ + "id": "move-bearoff-doubles", + "description": "Sortie, double 6 : coup unique ; chance d'un double.", + "position_id": "xgAA4MgAAAAAAA", + "match_id": "cAkbAAAAAAAA", + "dice": [ + 6, + 6 + ], + "played": "6/off 6/off 4/off 1/off", + "type": "move", + "gnubg_commit": "14b67984737ded0f6de43d63dcb789f729d9b06b", + "met": "Kazaross-XG2", + "plies": { + "0": { + "played_move": "6/off(2) 4/off 1/off", + "played_position_id": "xgAAYAAAAAAAAA", + "move_count": 1, + "rank": 1, + "played_equity": 0.8888838, + "best_move": "6/off(2) 4/off 1/off", + "best_equity": 0.8888838, + "equity_loss": -0.0, + "skill": "none", + "ranking": [ + { + "move": "6/off(2) 4/off 1/off", + "equity": 0.8888838 + } + ] + }, + "2": { + "played_move": "6/off(2) 4/off 1/off", + "played_position_id": "xgAAYAAAAAAAAA", + "move_count": 1, + "rank": 1, + "played_equity": 0.8888838, + "best_move": "6/off(2) 4/off 1/off", + "best_equity": 0.8888838, + "equity_loss": -0.0, + "skill": "none", + "ranking": [ + { + "move": "6/off(2) 4/off 1/off", + "equity": 0.8888838 + } + ] + } + }, + "luck": { + "value": 1.6398247, + "rating": "very_lucky" + } +} diff --git a/tests/reference/analysis/move-blitz-doubles.json b/tests/reference/analysis/move-blitz-doubles.json new file mode 100644 index 0000000..3360416 --- /dev/null +++ b/tests/reference/analysis/move-blitz-doubles.json @@ -0,0 +1,522 @@ +{ + "id": "move-blitz-doubles", + "description": "Attaque, double 3 : un coup parmi 110.", + "position_id": "4HPwAVC0XYMHAA", + "match_id": "cIkNAAAAAAAA", + "dice": [ + 3, + 3 + ], + "played": "13/10 13/10 5/2 4/1", + "type": "move", + "gnubg_commit": "14b67984737ded0f6de43d63dcb789f729d9b06b", + "met": "Kazaross-XG2", + "plies": { + "0": { + "played_move": "13/10(2) 5/2 4/1*", + "played_position_id": "4HPwAWBVXTMGAA", + "move_count": 110, + "rank": 109, + "played_equity": 0.084023334, + "best_move": "13/10 6/3 4/1*(2)", + "best_equity": 0.9465638, + "equity_loss": -0.8625404, + "skill": "very_bad", + "ranking": [ + { + "move": "13/10 6/3 4/1*(2)", + "equity": 0.9465638 + }, + { + "move": "8/5 6/3 4/1*(2)", + "equity": 0.9422797 + }, + { + "move": "13/7 13/10 6/3", + "equity": 0.94128984 + }, + { + "move": "13/7 8/5 6/3", + "equity": 0.9370698 + }, + { + "move": "13/10(2) 7/4 6/3", + "equity": 0.93615985 + }, + { + "move": "13/10(2) 8/5 6/3", + "equity": 0.9330082 + }, + { + "move": "13/10 8/5 7/4 6/3", + "equity": 0.932737 + }, + { + "move": "13/10 7/1* 6/3", + "equity": 0.9313148 + }, + { + "move": "8/5 7/1* 6/3", + "equity": 0.93129885 + }, + { + "move": "13/4 6/3", + "equity": 0.92980635 + }, + { + "move": "13/10 8/5(2) 6/3", + "equity": 0.9264537 + }, + { + "move": "13/10(3) 6/3", + "equity": 0.9262361 + }, + { + "move": "13/10(2) 4/1*(2)", + "equity": 0.92529553 + }, + { + "move": "8/5(2) 7/4 6/3", + "equity": 0.9252107 + }, + { + "move": "7/1* 6/3 4/1", + "equity": 0.92395884 + }, + { + "move": "13/10 6/3 5/2(2)", + "equity": 0.9204093 + }, + { + "move": "13/7 4/1*(2)", + "equity": 0.9190106 + }, + { + "move": "13/7(2)", + "equity": 0.9184398 + }, + { + "move": "13/10 8/5 4/1*(2)", + "equity": 0.9182539 + }, + { + "move": "7/4 6/3 5/2(2)", + "equity": 0.91752934 + }, + { + "move": "13/7 13/10(2)", + "equity": 0.91483337 + }, + { + "move": "13/10 8/2 6/3", + "equity": 0.912639 + }, + { + "move": "13/7 13/10 8/5", + "equity": 0.91152656 + }, + { + "move": "13/10(2) 7/1*", + "equity": 0.9115156 + }, + { + "move": "13/1*", + "equity": 0.9111869 + }, + { + "move": "13/10(2) 6/3 4/1*", + "equity": 0.91055506 + }, + { + "move": "8/2 8/5 6/3", + "equity": 0.9091699 + }, + { + "move": "8/2 7/4 6/3", + "equity": 0.90897554 + }, + { + "move": "13/7 8/5(2)", + "equity": 0.90817225 + }, + { + "move": "13/7 6/3 4/1*", + "equity": 0.90714073 + }, + { + "move": "13/10(2) 8/5 7/4", + "equity": 0.9070756 + }, + { + "move": "8/2(2)", + "equity": 0.9065211 + }, + { + "move": "13/10 8/5 7/1*", + "equity": 0.90571 + }, + { + "move": "13/10(3) 7/4", + "equity": 0.9040942 + }, + { + "move": "13/10 8/5 6/3 4/1*", + "equity": 0.90343916 + }, + { + "move": "8/5(2) 4/1*(2)", + "equity": 0.90325713 + }, + { + "move": "5/2(2) 4/1*(2)", + "equity": 0.90183336 + }, + { + "move": "13/10 7/1* 4/1", + "equity": 0.9013542 + }, + { + "move": "13/4 7/4", + "equity": 0.9010268 + }, + { + "move": "13/7 5/2(2)", + "equity": 0.90037674 + }, + { + "move": "8/5(2) 7/1*", + "equity": 0.89956194 + }, + { + "move": "13/10 8/5(2) 7/4", + "equity": 0.8988826 + }, + { + "move": "8/2 6/3 5/2", + "equity": 0.8984738 + }, + { + "move": "13/4 13/10", + "equity": 0.89749956 + }, + { + "move": "6/3(2) 4/1*(2)", + "equity": 0.8951012 + }, + { + "move": "13/10(2) 8/5(2)", + "equity": 0.89493525 + }, + { + "move": "13/7 8/2", + "equity": 0.8946096 + }, + { + "move": "13/7 6/3(2)", + "equity": 0.89403474 + }, + { + "move": "7/1* 5/2(2)", + "equity": 0.8924208 + }, + { + "move": "8/5 7/1* 4/1", + "equity": 0.891961 + }, + { + "move": "13/10(2) 6/3(2)", + "equity": 0.89077055 + }, + { + "move": "13/4 8/5", + "equity": 0.8907301 + }, + { + "move": "8/5(2) 6/3 4/1*", + "equity": 0.8900244 + }, + { + "move": "13/10(2) 5/2(2)", + "equity": 0.8890666 + }, + { + "move": "7/4 6/3(3)", + "equity": 0.8890004 + }, + { + "move": "13/10 7/4 5/2(2)", + "equity": 0.8888954 + }, + { + "move": "8/2 8/5 7/4", + "equity": 0.8876569 + }, + { + "move": "13/10(4)", + "equity": 0.88716996 + }, + { + "move": "7/1* 6/3(2)", + "equity": 0.8856077 + }, + { + "move": "8/2 4/1*(2)", + "equity": 0.8853629 + }, + { + "move": "13/10 6/3(3)", + "equity": 0.8853096 + }, + { + "move": "13/7 6/3 5/2", + "equity": 0.8831558 + }, + { + "move": "13/10 8/2 7/4", + "equity": 0.8819176 + }, + { + "move": "7/1* 4/1(2)", + "equity": 0.88113916 + }, + { + "move": "13/10 7/4 6/3(2)", + "equity": 0.88064635 + }, + { + "move": "13/10(3) 8/5", + "equity": 0.88049895 + }, + { + "move": "6/3 5/2(2) 4/1*", + "equity": 0.8790827 + }, + { + "move": "13/10(2) 8/2", + "equity": 0.8782023 + }, + { + "move": "6/3 5/2 4/1*(2)", + "equity": 0.8774586 + }, + { + "move": "8/5 6/3(3)", + "equity": 0.8774268 + }, + { + "move": "8/2 7/1*", + "equity": 0.8772669 + }, + { + "move": "13/7 13/10 4/1*", + "equity": 0.87700987 + }, + { + "move": "13/10 8/5 6/3(2)", + "equity": 0.8757389 + }, + { + "move": "13/10 8/2 8/5", + "equity": 0.87530077 + }, + { + "move": "13/10(2) 6/3 5/2", + "equity": 0.87215745 + }, + { + "move": "13/7 8/5 4/1*", + "equity": 0.87202764 + }, + { + "move": "8/5 7/4 6/3(2)", + "equity": 0.86896765 + }, + { + "move": "7/1* 6/3 5/2", + "equity": 0.8687051 + }, + { + "move": "13/10 7/4 6/3 5/2", + "equity": 0.8679464 + }, + { + "move": "8/2 6/3 4/1*", + "equity": 0.86750233 + }, + { + "move": "13/10(2) 8/5 4/1*", + "equity": 0.86701244 + }, + { + "move": "8/5(2) 6/3(2)", + "equity": 0.86167765 + }, + { + "move": "13/10(3) 4/1*", + "equity": 0.8568015 + }, + { + "move": "6/3(2) 5/2(2)", + "equity": 0.85501754 + }, + { + "move": "8/2 7/4 5/2", + "equity": 0.8537997 + }, + { + "move": "8/2 5/2(2)", + "equity": 0.8524791 + }, + { + "move": "13/10 8/5(2) 4/1*", + "equity": 0.84969926 + }, + { + "move": "13/10 8/2 5/2", + "equity": 0.8466199 + }, + { + "move": "8/2 6/3(2)", + "equity": 0.80814815 + }, + { + "move": "13/10 5/2(2) 4/1*", + "equity": 0.8037353 + }, + { + "move": "13/10 5/2 4/1*(2)", + "equity": 0.77350885 + }, + { + "move": "13/10 6/3(2) 4/1*", + "equity": 0.74067366 + }, + { + "move": "13/7 13/10 5/2", + "equity": 0.72703636 + }, + { + "move": "13/10(2) 7/4 5/2", + "equity": 0.7053956 + }, + { + "move": "6/3(3) 4/1*", + "equity": 0.66575944 + }, + { + "move": "13/10 7/1* 5/2", + "equity": 0.6462204 + }, + { + "move": "8/5 6/3(2) 4/1*", + "equity": 0.6374039 + }, + { + "move": "8/2 8/5 4/1*", + "equity": 0.62353325 + }, + { + "move": "13/10 8/2 4/1*", + "equity": 0.61257124 + }, + { + "move": "13/10 6/3 5/2 4/1*", + "equity": 0.5349428 + }, + { + "move": "7/1* 5/2 4/1", + "equity": 0.45721298 + }, + { + "move": "8/2 5/2 4/1*", + "equity": 0.44362232 + }, + { + "move": "13/4 5/2", + "equity": 0.43828273 + }, + { + "move": "13/10(3) 5/2", + "equity": 0.2798175 + }, + { + "move": "13/10 6/3(2) 5/2", + "equity": 0.24207813 + }, + { + "move": "6/3(3) 5/2", + "equity": 0.23385207 + }, + { + "move": "7/4 6/3(2) 5/2", + "equity": 0.22096781 + }, + { + "move": "13/7 5/2 4/1*", + "equity": 0.18849733 + }, + { + "move": "13/10(2) 5/2 4/1*", + "equity": 0.084023334 + } + ] + }, + "2": { + "played_move": "13/10(2) 5/2 4/1*", + "played_position_id": "4HPwAWBVXTMGAA", + "move_count": 110, + "rank": 7, + "played_equity": -0.022027964, + "best_move": "13/10 7/1* 6/3", + "best_equity": 1.0, + "equity_loss": -1.022028, + "skill": "very_bad", + "ranking": [ + { + "move": "13/10 7/1* 6/3", + "equity": 1.0 + }, + { + "move": "13/7 13/10 6/3", + "equity": 1.0 + }, + { + "move": "13/10(2) 8/5 6/3", + "equity": 1.0 + }, + { + "move": "13/7 8/5 6/3", + "equity": 1.0 + }, + { + "move": "13/10(2) 7/4 6/3", + "equity": 1.0 + }, + { + "move": "13/10 8/5 7/4 6/3", + "equity": 1.0 + }, + { + "move": "13/10(2) 5/2 4/1*", + "equity": -0.022027964 + }, + { + "move": "13/10 8/5(2) 6/3", + "equity": 1.0 + }, + { + "move": "13/4 6/3", + "equity": 1.0 + }, + { + "move": "13/10 6/3 4/1*(2)", + "equity": 0.99692726 + } + ] + } + }, + "luck": { + "value": 0.011263669, + "rating": "none" + } +} diff --git a/tests/reference/analysis/move-crawford.json b/tests/reference/analysis/move-crawford.json new file mode 100644 index 0000000..90985fc --- /dev/null +++ b/tests/reference/analysis/move-crawford.json @@ -0,0 +1,126 @@ +{ + "id": "move-crawford", + "description": "Match en 5 points, partie Crawford, 4-3.", + "position_id": "4Dn4AGiynYMHAA", + "match_id": "8AmuAEAAEAAE", + "dice": [ + 4, + 3 + ], + "played": "5/2 5/1", + "type": "move", + "gnubg_commit": "14b67984737ded0f6de43d63dcb789f729d9b06b", + "met": "Kazaross-XG2", + "plies": { + "0": { + "played_move": "5/2 5/1*", + "played_position_id": "4Dn4AHDNnIMHAA", + "move_count": 20, + "rank": 2, + "played_equity": 0.7229391, + "best_move": "8/5 6/2", + "best_equity": 0.7345603, + "equity_loss": -0.011621237, + "skill": "none", + "ranking": [ + { + "move": "8/5 6/2", + "equity": 0.7345603 + }, + { + "move": "5/2 5/1*", + "equity": 0.7229391 + }, + { + "move": "8/1*", + "equity": 0.71705425 + }, + { + "move": "13/10 6/2", + "equity": 0.7142835 + }, + { + "move": "13/9 8/5", + "equity": 0.6963019 + }, + { + "move": "8/5 8/4", + "equity": 0.68610644 + }, + { + "move": "13/10 8/4", + "equity": 0.6855368 + }, + { + "move": "13/10 13/9", + "equity": 0.6818689 + }, + { + "move": "13/6", + "equity": 0.6633271 + }, + { + "move": "13/9 5/2", + "equity": 0.6538005 + } + ] + }, + "2": { + "played_move": "5/2 5/1*", + "played_position_id": "4Dn4AHDNnIMHAA", + "move_count": 20, + "rank": 4, + "played_equity": 0.6911188, + "best_move": "8/5 6/2", + "best_equity": 0.7697691, + "equity_loss": -0.078650296, + "skill": "bad", + "ranking": [ + { + "move": "8/5 6/2", + "equity": 0.7697691 + }, + { + "move": "13/10 6/2", + "equity": 0.7615671 + }, + { + "move": "13/9 8/5", + "equity": 0.69608325 + }, + { + "move": "5/2 5/1*", + "equity": 0.6911188 + }, + { + "move": "8/1*", + "equity": 0.6836242 + }, + { + "move": "13/10 13/9", + "equity": 0.6807338 + }, + { + "move": "13/10 8/4", + "equity": 0.6733323 + }, + { + "move": "13/6", + "equity": 0.66055346 + }, + { + "move": "8/5 8/4", + "equity": 0.6556259 + }, + { + "move": "13/9 5/2", + "equity": 0.645951 + } + ] + } + }, + "luck": { + "value": -0.050364614, + "rating": "none" + } +} diff --git a/tests/reference/analysis/move-forced.json b/tests/reference/analysis/move-forced.json new file mode 100644 index 0000000..755a2e4 --- /dev/null +++ b/tests/reference/analysis/move-forced.json @@ -0,0 +1,54 @@ +{ + "id": "move-forced", + "description": "Rentrée forcée depuis la barre, 3-1.", + "position_id": "tm3ABwBw5+ABYA", + "match_id": "cIkFAAAAAAAA", + "dice": [ + 3, + 1 + ], + "played": "bar/24", + "type": "move", + "gnubg_commit": "14b67984737ded0f6de43d63dcb789f729d9b06b", + "met": "Kazaross-XG2", + "plies": { + "0": { + "played_move": "bar/24", + "played_position_id": "tm3ABwBw5+ABUA", + "move_count": 1, + "rank": 1, + "played_equity": -1.0, + "best_move": "bar/24", + "best_equity": -1.0, + "equity_loss": -0.0, + "skill": "none", + "ranking": [ + { + "move": "bar/24", + "equity": -1.0 + } + ] + }, + "2": { + "played_move": "bar/24", + "played_position_id": "tm3ABwBw5+ABUA", + "move_count": 1, + "rank": 1, + "played_equity": -1.0, + "best_move": "bar/24", + "best_equity": -1.0, + "equity_loss": -0.0, + "skill": "none", + "ranking": [ + { + "move": "bar/24", + "equity": -1.0 + } + ] + } + }, + "luck": { + "value": 0.0, + "rating": "none" + } +} diff --git a/tests/reference/analysis/move-jacoby.json b/tests/reference/analysis/move-jacoby.json new file mode 100644 index 0000000..1deab70 --- /dev/null +++ b/tests/reference/analysis/move-jacoby.json @@ -0,0 +1,142 @@ +{ + "id": "move-jacoby", + "description": "Partie libre avec la règle Jacoby, 6-4.", + "position_id": "4HPwAVC0XYMHAA", + "match_id": "cAkTAAAAAAAA", + "dice": [ + 6, + 4 + ], + "played": "8/2 5/1", + "type": "move", + "gnubg_commit": "14b67984737ded0f6de43d63dcb789f729d9b06b", + "met": "Kazaross-XG2", + "plies": { + "0": { + "played_move": "8/2 5/1*", + "played_position_id": "4HPwAWDVuoIHAA", + "move_count": 14, + "rank": 14, + "played_equity": 0.50889754, + "best_move": "13/3", + "best_equity": 0.9328213, + "equity_loss": -0.42392373, + "skill": "very_bad", + "ranking": [ + { + "move": "13/3", + "equity": 0.9328213 + }, + { + "move": "13/9 13/7", + "equity": 0.9175232 + }, + { + "move": "8/2 7/3", + "equity": 0.91263336 + }, + { + "move": "13/7 8/4", + "equity": 0.90736973 + }, + { + "move": "13/9 7/1*", + "equity": 0.9053596 + }, + { + "move": "8/2 6/2", + "equity": 0.9017469 + }, + { + "move": "8/4 7/1*", + "equity": 0.89993495 + }, + { + "move": "13/7 6/2", + "equity": 0.8962312 + }, + { + "move": "7/1* 5/1", + "equity": 0.8907822 + }, + { + "move": "7/1* 6/2", + "equity": 0.87481093 + }, + { + "move": "13/9 8/2", + "equity": 0.87251335 + }, + { + "move": "8/4 8/2", + "equity": 0.8716274 + }, + { + "move": "13/7 5/1*", + "equity": 0.87005275 + }, + { + "move": "8/2 5/1*", + "equity": 0.50889754 + } + ] + }, + "2": { + "played_move": "8/2 5/1*", + "played_position_id": "4HPwAWDVuoIHAA", + "move_count": 14, + "rank": 7, + "played_equity": 0.3304975, + "best_move": "13/3", + "best_equity": 1.0, + "equity_loss": -0.6695025, + "skill": "very_bad", + "ranking": [ + { + "move": "13/3", + "equity": 1.0 + }, + { + "move": "13/9 13/7", + "equity": 0.99894416 + }, + { + "move": "8/2 7/3", + "equity": 0.99641544 + }, + { + "move": "13/7 8/4", + "equity": 0.98718643 + }, + { + "move": "13/7 6/2", + "equity": 0.9808178 + }, + { + "move": "13/9 7/1*", + "equity": 0.9452111 + }, + { + "move": "8/2 5/1*", + "equity": 0.3304975 + }, + { + "move": "8/4 7/1*", + "equity": 0.93114305 + }, + { + "move": "7/1* 5/1", + "equity": 0.91711015 + }, + { + "move": "8/2 6/2", + "equity": 0.9160911 + } + ] + } + }, + "luck": { + "value": -0.002478838, + "rating": "none" + } +} diff --git a/tests/reference/analysis/move-luck-lucky.json b/tests/reference/analysis/move-luck-lucky.json new file mode 100644 index 0000000..f98e789 --- /dev/null +++ b/tests/reference/analysis/move-luck-lucky.json @@ -0,0 +1,126 @@ +{ + "id": "move-luck-lucky", + "description": "Course, double 4 : jet chanceux, joué au mieux.", + "position_id": "cLezAQBwN2cGAA", + "match_id": "cIkSAAAAAAAA", + "dice": [ + 4, + 4 + ], + "played": null, + "type": "move", + "gnubg_commit": "14b67984737ded0f6de43d63dcb789f729d9b06b", + "met": "Kazaross-XG2", + "plies": { + "0": { + "played_move": "13/5 11/3", + "played_position_id": "cLezAQDk3ZwEAA", + "move_count": 106, + "rank": 1, + "played_equity": 0.16933289, + "best_move": "13/5 11/3", + "best_equity": 0.16933289, + "equity_loss": -0.0, + "skill": "none", + "ranking": [ + { + "move": "13/5 11/3", + "equity": 0.16933289 + }, + { + "move": "13/9(2) 11/3", + "equity": 0.16701066 + }, + { + "move": "13/5 9/5 7/3", + "equity": 0.16552198 + }, + { + "move": "13/5(2)", + "equity": 0.16473158 + }, + { + "move": "13/5 13/9 11/7", + "equity": 0.16365689 + }, + { + "move": "13/5 13/9 7/3", + "equity": 0.16245355 + }, + { + "move": "13/5 11/7 9/5", + "equity": 0.16167049 + }, + { + "move": "11/3 9/5(2)", + "equity": 0.16043647 + }, + { + "move": "13/9 11/3 11/7", + "equity": 0.1603657 + }, + { + "move": "13/5 11/7(2)", + "equity": 0.1602792 + } + ] + }, + "2": { + "played_move": "13/5 11/3", + "played_position_id": "cLezAQDk3ZwEAA", + "move_count": 106, + "rank": 1, + "played_equity": 0.15129912, + "best_move": "13/5 11/3", + "best_equity": 0.15129912, + "equity_loss": -0.0, + "skill": "none", + "ranking": [ + { + "move": "13/5 11/3", + "equity": 0.15129912 + }, + { + "move": "13/5 13/9 11/7", + "equity": 0.15059008 + }, + { + "move": "13/9(2) 11/3", + "equity": 0.14950007 + }, + { + "move": "13/5 11/7 9/5", + "equity": 0.14858595 + }, + { + "move": "11/3 11/7 9/5", + "equity": 0.14829463 + }, + { + "move": "13/9 11/3 11/7", + "equity": 0.1463402 + }, + { + "move": "13/5 9/5 7/3", + "equity": 0.13938732 + }, + { + "move": "13/5 11/7(2)", + "equity": 0.13921705 + }, + { + "move": "13/9(2) 11/7(2)", + "equity": 0.13873637 + }, + { + "move": "13/5 13/9 6/2", + "equity": 0.13852078 + } + ] + } + }, + "luck": { + "value": 0.43559298, + "rating": "lucky" + } +} diff --git a/tests/reference/analysis/move-luck-unlucky-bearoff.json b/tests/reference/analysis/move-luck-unlucky-bearoff.json new file mode 100644 index 0000000..a96a1ff --- /dev/null +++ b/tests/reference/analysis/move-luck-unlucky-bearoff.json @@ -0,0 +1,78 @@ +{ + "id": "move-luck-unlucky-bearoff", + "description": "Fin de sortie, 2-1 : jet malchanceux, joué au mieux.", + "position_id": "TQAAYAsAAAAAAA", + "match_id": "cAkFAAAAAAAA", + "dice": [ + 2, + 1 + ], + "played": null, + "type": "move", + "gnubg_commit": "14b67984737ded0f6de43d63dcb789f729d9b06b", + "met": "Kazaross-XG2", + "plies": { + "0": { + "played_move": "2/off 1/off", + "played_position_id": "TQAAoAIAAAAAAA", + "move_count": 4, + "rank": 2, + "played_equity": -0.8607614, + "best_move": "3/off", + "best_equity": -0.8607614, + "equity_loss": -0.0, + "skill": "none", + "ranking": [ + { + "move": "3/off", + "equity": -0.8607614 + }, + { + "move": "2/off 1/off", + "equity": -0.8607614 + }, + { + "move": "2/1 2/off", + "equity": -0.95561147 + }, + { + "move": "3/1 2/1", + "equity": -1.0 + } + ] + }, + "2": { + "played_move": "2/off 1/off", + "played_position_id": "TQAAoAIAAAAAAA", + "move_count": 4, + "rank": 2, + "played_equity": -0.8607614, + "best_move": "3/off", + "best_equity": -0.8607614, + "equity_loss": -0.0, + "skill": "none", + "ranking": [ + { + "move": "3/off", + "equity": -0.8607614 + }, + { + "move": "2/off 1/off", + "equity": -0.8607614 + }, + { + "move": "2/1 2/off", + "equity": -0.95561147 + }, + { + "move": "3/1 2/1", + "equity": -1.0 + } + ] + } + }, + "luck": { + "value": -0.32329792, + "rating": "unlucky" + } +} diff --git a/tests/reference/analysis/move-luck-unlucky.json b/tests/reference/analysis/move-luck-unlucky.json new file mode 100644 index 0000000..16cb6c9 --- /dev/null +++ b/tests/reference/analysis/move-luck-unlucky.json @@ -0,0 +1,126 @@ +{ + "id": "move-luck-unlucky", + "description": "Milieu de partie, 5-4 : jet malchanceux, joué au mieux.", + "position_id": "xGfhATDQ2+BBIA", + "match_id": "cIkOAAAAAAAA", + "dice": [ + 5, + 4 + ], + "played": null, + "type": "move", + "gnubg_commit": "14b67984737ded0f6de43d63dcb789f729d9b06b", + "met": "Kazaross-XG2", + "plies": { + "0": { + "played_move": "18/9", + "played_position_id": "xGfhATDQ28IDIA", + "move_count": 24, + "rank": 1, + "played_equity": 0.2544027, + "best_move": "18/9", + "best_equity": 0.2544027, + "equity_loss": -0.0, + "skill": "none", + "ranking": [ + { + "move": "18/9", + "equity": 0.2544027 + }, + { + "move": "18/14 13/8", + "equity": 0.22496489 + }, + { + "move": "24/20 18/13", + "equity": 0.20747662 + }, + { + "move": "24/20 13/8", + "equity": 0.1158272 + }, + { + "move": "13/9 13/8", + "equity": 0.09201278 + }, + { + "move": "24/15", + "equity": 0.061017293 + }, + { + "move": "18/13 6/2", + "equity": 0.03581623 + }, + { + "move": "13/4", + "equity": -0.028887823 + }, + { + "move": "13/8 6/2", + "equity": -0.033572298 + }, + { + "move": "18/13 8/4", + "equity": -0.09662818 + } + ] + }, + "2": { + "played_move": "18/9", + "played_position_id": "xGfhATDQ28IDIA", + "move_count": 24, + "rank": 1, + "played_equity": 0.27687487, + "best_move": "18/9", + "best_equity": 0.27687487, + "equity_loss": -0.0, + "skill": "none", + "ranking": [ + { + "move": "18/9", + "equity": 0.27687487 + }, + { + "move": "18/14 13/8", + "equity": 0.21801135 + }, + { + "move": "24/20 18/13", + "equity": 0.21000355 + }, + { + "move": "24/20 13/8", + "equity": 0.1144446 + }, + { + "move": "13/9 13/8", + "equity": 0.09412725 + }, + { + "move": "24/15", + "equity": 0.07892294 + }, + { + "move": "18/13 6/2", + "equity": -0.020656409 + }, + { + "move": "13/4", + "equity": -0.028887823 + }, + { + "move": "13/8 6/2", + "equity": -0.033572298 + }, + { + "move": "18/13 8/4", + "equity": -0.09662818 + } + ] + } + }, + "luck": { + "value": -0.3615805, + "rating": "unlucky" + } +} diff --git a/tests/reference/analysis/move-match.json b/tests/reference/analysis/move-match.json new file mode 100644 index 0000000..6eb7a82 --- /dev/null +++ b/tests/reference/analysis/move-match.json @@ -0,0 +1,126 @@ +{ + "id": "move-match", + "description": "Match en 7 points à 3-3, 5-3 : frappe au lieu du meilleur coup.", + "position_id": "xGfhATDQ2+BBIA", + "match_id": "cImuACAAEAAE", + "dice": [ + 5, + 3 + ], + "played": "24/16", + "type": "move", + "gnubg_commit": "14b67984737ded0f6de43d63dcb789f729d9b06b", + "met": "Kazaross-XG2", + "plies": { + "0": { + "played_move": "24/16*", + "played_position_id": "xGfwAFjQ2+CRAA", + "move_count": 28, + "rank": 3, + "played_equity": 0.4581785, + "best_move": "13/5", + "best_equity": 0.7553502, + "equity_loss": -0.29717168, + "skill": "very_bad", + "ranking": [ + { + "move": "13/5", + "equity": 0.7553502 + }, + { + "move": "18/13 8/5", + "equity": 0.5814649 + }, + { + "move": "24/16*", + "equity": 0.4581785 + }, + { + "move": "18/10", + "equity": 0.31719947 + }, + { + "move": "8/5 8/3", + "equity": 0.31371918 + }, + { + "move": "18/15 13/8", + "equity": 0.26037523 + }, + { + "move": "24/21 18/13", + "equity": 0.23547156 + }, + { + "move": "8/3 6/3", + "equity": 0.15432969 + }, + { + "move": "13/10 13/8", + "equity": 0.13837479 + }, + { + "move": "18/13 5/2", + "equity": 0.12815686 + } + ] + }, + "2": { + "played_move": "24/16*", + "played_position_id": "xGfwAFjQ2+CRAA", + "move_count": 28, + "rank": 2, + "played_equity": 0.46805125, + "best_move": "13/5", + "best_equity": 0.85497326, + "equity_loss": -0.386922, + "skill": "very_bad", + "ranking": [ + { + "move": "13/5", + "equity": 0.85497326 + }, + { + "move": "24/16*", + "equity": 0.46805125 + }, + { + "move": "18/13 8/5", + "equity": 0.6511238 + }, + { + "move": "18/10", + "equity": 0.31719947 + }, + { + "move": "8/5 8/3", + "equity": 0.31371918 + }, + { + "move": "18/15 13/8", + "equity": 0.26037523 + }, + { + "move": "24/21 18/13", + "equity": 0.23547156 + }, + { + "move": "8/3 6/3", + "equity": 0.15432969 + }, + { + "move": "13/10 13/8", + "equity": 0.13837479 + }, + { + "move": "18/13 5/2", + "equity": 0.12815686 + } + ] + } + }, + "luck": { + "value": 0.026781201, + "rating": "none" + } +} diff --git a/tests/reference/analysis/move-middlegame-blunder.json b/tests/reference/analysis/move-middlegame-blunder.json new file mode 100644 index 0000000..e581af8 --- /dev/null +++ b/tests/reference/analysis/move-middlegame-blunder.json @@ -0,0 +1,142 @@ +{ + "id": "move-middlegame-blunder", + "description": "Milieu de partie, 6-4 : un des pires coups.", + "position_id": "mM/gATCw5+ABMA", + "match_id": "cAkTAAAAAAAA", + "dice": [ + 6, + 4 + ], + "played": "8/4 8/2", + "type": "move", + "gnubg_commit": "14b67984737ded0f6de43d63dcb789f729d9b06b", + "met": "Kazaross-XG2", + "plies": { + "0": { + "played_move": "8/4 8/2", + "played_position_id": "mM/gATDSnuABMA", + "move_count": 14, + "rank": 14, + "played_equity": -0.35321844, + "best_move": "24/14", + "best_equity": 0.15439394, + "equity_loss": -0.50761235, + "skill": "very_bad", + "ranking": [ + { + "move": "24/14", + "equity": 0.15439394 + }, + { + "move": "8/2 6/2", + "equity": 0.10821653 + }, + { + "move": "24/18 13/9", + "equity": 0.10337136 + }, + { + "move": "24/20 24/18", + "equity": 0.072762795 + }, + { + "move": "13/9 13/7", + "equity": 0.0029048775 + }, + { + "move": "24/20 13/7", + "equity": 9.1438415e-05 + }, + { + "move": "13/3", + "equity": -0.012697897 + }, + { + "move": "24/18 6/2", + "equity": -0.075399 + }, + { + "move": "24/18 8/4", + "equity": -0.088734664 + }, + { + "move": "24/20 8/2", + "equity": -0.095189385 + }, + { + "move": "13/9 8/2", + "equity": -0.09528468 + }, + { + "move": "13/7 8/4", + "equity": -0.12819159 + }, + { + "move": "13/7 6/2", + "equity": -0.14467186 + }, + { + "move": "8/4 8/2", + "equity": -0.35321844 + } + ] + }, + "2": { + "played_move": "8/4 8/2", + "played_position_id": "mM/gATDSnuABMA", + "move_count": 14, + "rank": 6, + "played_equity": -0.36824828, + "best_move": "24/14", + "best_equity": 0.13111727, + "equity_loss": -0.49936557, + "skill": "very_bad", + "ranking": [ + { + "move": "24/14", + "equity": 0.13111727 + }, + { + "move": "8/2 6/2", + "equity": 0.11303549 + }, + { + "move": "24/18 13/9", + "equity": 0.105096616 + }, + { + "move": "24/20 24/18", + "equity": 0.049068402 + }, + { + "move": "13/3", + "equity": -0.0040049786 + }, + { + "move": "8/4 8/2", + "equity": -0.36824828 + }, + { + "move": "13/9 13/7", + "equity": -0.014309813 + }, + { + "move": "24/20 13/7", + "equity": -0.029367238 + }, + { + "move": "24/18 8/4", + "equity": -0.081171095 + }, + { + "move": "24/18 6/2", + "equity": -0.09313507 + } + ] + } + }, + "luck": { + "value": -0.07414439, + "rating": "none" + } +} diff --git a/tests/reference/analysis/move-middlegame-close.json b/tests/reference/analysis/move-middlegame-close.json new file mode 100644 index 0000000..59487b5 --- /dev/null +++ b/tests/reference/analysis/move-middlegame-close.json @@ -0,0 +1,126 @@ +{ + "id": "move-middlegame-close", + "description": "Milieu de partie, 6-4 : un coup proche du meilleur.", + "position_id": "mM/gATCw5+ABMA", + "match_id": "cAkTAAAAAAAA", + "dice": [ + 6, + 4 + ], + "played": "8/2 6/2", + "type": "move", + "gnubg_commit": "14b67984737ded0f6de43d63dcb789f729d9b06b", + "met": "Kazaross-XG2", + "plies": { + "0": { + "played_move": "8/2 6/2", + "played_position_id": "mM/gATDGzuABMA", + "move_count": 14, + "rank": 2, + "played_equity": 0.10821653, + "best_move": "24/14", + "best_equity": 0.15439394, + "equity_loss": -0.04617741, + "skill": "doubtful", + "ranking": [ + { + "move": "24/14", + "equity": 0.15439394 + }, + { + "move": "8/2 6/2", + "equity": 0.10821653 + }, + { + "move": "24/18 13/9", + "equity": 0.10337136 + }, + { + "move": "24/20 24/18", + "equity": 0.072762795 + }, + { + "move": "13/9 13/7", + "equity": 0.0029048775 + }, + { + "move": "24/20 13/7", + "equity": 9.1438415e-05 + }, + { + "move": "13/3", + "equity": -0.012697897 + }, + { + "move": "24/18 6/2", + "equity": -0.075399 + }, + { + "move": "24/18 8/4", + "equity": -0.088734664 + }, + { + "move": "24/20 8/2", + "equity": -0.095189385 + } + ] + }, + "2": { + "played_move": "8/2 6/2", + "played_position_id": "mM/gATDGzuABMA", + "move_count": 14, + "rank": 2, + "played_equity": 0.11303549, + "best_move": "24/14", + "best_equity": 0.13111727, + "equity_loss": -0.018081777, + "skill": "none", + "ranking": [ + { + "move": "24/14", + "equity": 0.13111727 + }, + { + "move": "8/2 6/2", + "equity": 0.11303549 + }, + { + "move": "24/18 13/9", + "equity": 0.105096616 + }, + { + "move": "24/20 24/18", + "equity": 0.049068402 + }, + { + "move": "13/3", + "equity": -0.0040049786 + }, + { + "move": "13/9 13/7", + "equity": -0.014309813 + }, + { + "move": "24/20 13/7", + "equity": -0.029367238 + }, + { + "move": "24/18 8/4", + "equity": -0.081171095 + }, + { + "move": "24/18 6/2", + "equity": -0.09313507 + }, + { + "move": "24/20 8/2", + "equity": -0.095189385 + } + ] + } + }, + "luck": { + "value": -0.07414439, + "rating": "none" + } +} diff --git a/tests/reference/analysis/move-opening-31-best.json b/tests/reference/analysis/move-opening-31-best.json new file mode 100644 index 0000000..50dbcd5 --- /dev/null +++ b/tests/reference/analysis/move-opening-31-best.json @@ -0,0 +1,126 @@ +{ + "id": "move-opening-31-best", + "description": "Ouverture 3-1 jouée au mieux (8/5 6/5) ; chance du premier jet.", + "position_id": "4HPwATDgc/ABMA", + "match_id": "cIkFAAAAAAAA", + "dice": [ + 3, + 1 + ], + "played": "8/5 6/5", + "type": "move", + "gnubg_commit": "14b67984737ded0f6de43d63dcb789f729d9b06b", + "met": "Kazaross-XG2", + "plies": { + "0": { + "played_move": "8/5 6/5", + "played_position_id": "4HPwATCwZ/ABMA", + "move_count": 16, + "rank": 1, + "played_equity": 0.21258597, + "best_move": "8/5 6/5", + "best_equity": 0.21258597, + "equity_loss": -0.0, + "skill": "none", + "ranking": [ + { + "move": "8/5 6/5", + "equity": 0.21258597 + }, + { + "move": "24/23 13/10", + "equity": -0.010789523 + }, + { + "move": "24/20", + "equity": -0.017392516 + }, + { + "move": "24/23 24/21", + "equity": -0.023609947 + }, + { + "move": "13/9", + "equity": -0.027675275 + }, + { + "move": "24/21 6/5", + "equity": -0.04369401 + }, + { + "move": "13/10 6/5", + "equity": -0.04984435 + }, + { + "move": "24/23 8/5", + "equity": -0.104100645 + }, + { + "move": "24/23 6/3", + "equity": -0.10542354 + }, + { + "move": "24/21 8/7", + "equity": -0.107585564 + } + ] + }, + "2": { + "played_move": "8/5 6/5", + "played_position_id": "4HPwATCwZ/ABMA", + "move_count": 16, + "rank": 1, + "played_equity": 0.20373425, + "best_move": "8/5 6/5", + "best_equity": 0.20373425, + "equity_loss": -0.0, + "skill": "none", + "ranking": [ + { + "move": "8/5 6/5", + "equity": 0.20373425 + }, + { + "move": "24/23 13/10", + "equity": -0.022293653 + }, + { + "move": "24/20", + "equity": -0.031027086 + }, + { + "move": "24/23 24/21", + "equity": -0.03213631 + }, + { + "move": "13/9", + "equity": -0.040205758 + }, + { + "move": "24/21 6/5", + "equity": -0.04369401 + }, + { + "move": "13/10 6/5", + "equity": -0.04984435 + }, + { + "move": "24/23 8/5", + "equity": -0.104100645 + }, + { + "move": "24/23 6/3", + "equity": -0.10542354 + }, + { + "move": "24/21 8/7", + "equity": -0.107585564 + } + ] + } + }, + "luck": { + "value": 0.21258597, + "rating": "none" + } +} diff --git a/tests/reference/analysis/move-opening-31-error.json b/tests/reference/analysis/move-opening-31-error.json new file mode 100644 index 0000000..0ac69a9 --- /dev/null +++ b/tests/reference/analysis/move-opening-31-error.json @@ -0,0 +1,126 @@ +{ + "id": "move-opening-31-error", + "description": "Ouverture 3-1 jouée 24/23 13/10 au lieu de faire le point 5.", + "position_id": "4HPwATDgc/ABMA", + "match_id": "cIkFAAAAAAAA", + "dice": [ + 3, + 1 + ], + "played": "24/23 13/10", + "type": "move", + "gnubg_commit": "14b67984737ded0f6de43d63dcb789f729d9b06b", + "met": "Kazaross-XG2", + "plies": { + "0": { + "played_move": "24/23 13/10", + "played_position_id": "4HPwATDgc+IBKA", + "move_count": 16, + "rank": 2, + "played_equity": -0.010789523, + "best_move": "8/5 6/5", + "best_equity": 0.21258597, + "equity_loss": -0.2233755, + "skill": "very_bad", + "ranking": [ + { + "move": "8/5 6/5", + "equity": 0.21258597 + }, + { + "move": "24/23 13/10", + "equity": -0.010789523 + }, + { + "move": "24/20", + "equity": -0.017392516 + }, + { + "move": "24/23 24/21", + "equity": -0.023609947 + }, + { + "move": "13/9", + "equity": -0.027675275 + }, + { + "move": "24/21 6/5", + "equity": -0.04369401 + }, + { + "move": "13/10 6/5", + "equity": -0.04984435 + }, + { + "move": "24/23 8/5", + "equity": -0.104100645 + }, + { + "move": "24/23 6/3", + "equity": -0.10542354 + }, + { + "move": "24/21 8/7", + "equity": -0.107585564 + } + ] + }, + "2": { + "played_move": "24/23 13/10", + "played_position_id": "4HPwATDgc+IBKA", + "move_count": 16, + "rank": 2, + "played_equity": -0.0118339015, + "best_move": "8/5 6/5", + "best_equity": 0.1998143, + "equity_loss": -0.21164821, + "skill": "very_bad", + "ranking": [ + { + "move": "8/5 6/5", + "equity": 0.1998143 + }, + { + "move": "24/23 13/10", + "equity": -0.0118339015 + }, + { + "move": "24/20", + "equity": -0.031027086 + }, + { + "move": "24/23 24/21", + "equity": -0.03213631 + }, + { + "move": "13/9", + "equity": -0.040205758 + }, + { + "move": "24/21 6/5", + "equity": -0.04369401 + }, + { + "move": "13/10 6/5", + "equity": -0.04984435 + }, + { + "move": "24/23 8/5", + "equity": -0.104100645 + }, + { + "move": "24/23 6/3", + "equity": -0.10542354 + }, + { + "move": "24/21 8/7", + "equity": -0.107585564 + } + ] + } + }, + "luck": { + "value": 0.21258597, + "rating": "none" + } +} diff --git a/tests/reference/analysis/move-opening-65-error.json b/tests/reference/analysis/move-opening-65-error.json new file mode 100644 index 0000000..0fbce8c --- /dev/null +++ b/tests/reference/analysis/move-opening-65-error.json @@ -0,0 +1,102 @@ +{ + "id": "move-opening-65-error", + "description": "Ouverture 6-5 jouée 13/8 13/7 au lieu de courir 24/13.", + "position_id": "4HPwATDgc/ABMA", + "match_id": "cAkXAAAAAAAA", + "dice": [ + 6, + 5 + ], + "played": "13/8 13/7", + "type": "move", + "gnubg_commit": "14b67984737ded0f6de43d63dcb789f729d9b06b", + "met": "Kazaross-XG2", + "plies": { + "0": { + "played_move": "13/8 13/7", + "played_position_id": "4HPwATDg68EBMA", + "move_count": 7, + "rank": 3, + "played_equity": -0.05142035, + "best_move": "24/13", + "best_equity": 0.082985915, + "equity_loss": -0.13440627, + "skill": "very_bad", + "ranking": [ + { + "move": "24/13", + "equity": 0.082985915 + }, + { + "move": "24/18 13/8", + "equity": 0.03631018 + }, + { + "move": "13/8 13/7", + "equity": -0.05142035 + }, + { + "move": "13/2", + "equity": -0.13522762 + }, + { + "move": "24/18 8/3", + "equity": -0.14635906 + }, + { + "move": "13/7 8/3", + "equity": -0.17384717 + }, + { + "move": "8/3 8/2", + "equity": -0.37779096 + } + ] + }, + "2": { + "played_move": "13/8 13/7", + "played_position_id": "4HPwATDg68EBMA", + "move_count": 7, + "rank": 3, + "played_equity": -0.061850794, + "best_move": "24/13", + "best_equity": 0.07996891, + "equity_loss": -0.1418197, + "skill": "very_bad", + "ranking": [ + { + "move": "24/13", + "equity": 0.07996891 + }, + { + "move": "24/18 13/8", + "equity": 0.03882884 + }, + { + "move": "13/8 13/7", + "equity": -0.061850794 + }, + { + "move": "13/2", + "equity": -0.12083713 + }, + { + "move": "24/18 8/3", + "equity": -0.13999933 + }, + { + "move": "13/7 8/3", + "equity": -0.17384717 + }, + { + "move": "8/3 8/2", + "equity": -0.37779096 + } + ] + } + }, + "luck": { + "value": 0.082985915, + "rating": "none" + } +} diff --git a/tests/reference/analysis/move-opening-double.json b/tests/reference/analysis/move-opening-double.json new file mode 100644 index 0000000..5ab51f1 --- /dev/null +++ b/tests/reference/analysis/move-opening-double.json @@ -0,0 +1,126 @@ +{ + "id": "move-opening-double", + "description": "Position de départ et double 6 : pas un premier jet, chance comptée sur les 36 jets.", + "position_id": "4HPwATDgc/ABMA", + "match_id": "cIkFAAAAAAAA", + "dice": [ + 6, + 6 + ], + "played": null, + "type": "move", + "gnubg_commit": "14b67984737ded0f6de43d63dcb789f729d9b06b", + "met": "Kazaross-XG2", + "plies": { + "0": { + "played_move": "24/18(2) 13/7(2)", + "played_position_id": "4HPwATDg28HBAA", + "move_count": 11, + "rank": 1, + "played_equity": 0.539388, + "best_move": "24/18(2) 13/7(2)", + "best_equity": 0.539388, + "equity_loss": -0.0, + "skill": "none", + "ranking": [ + { + "move": "24/18(2) 13/7(2)", + "equity": 0.539388 + }, + { + "move": "24/18 13/7(3)", + "equity": 0.33099693 + }, + { + "move": "24/18(2) 8/2(2)", + "equity": 0.27119082 + }, + { + "move": "13/7(2) 8/2(2)", + "equity": 0.1563462 + }, + { + "move": "24/18 13/7(2) 8/2", + "equity": 0.14750561 + }, + { + "move": "13/7(4)", + "equity": 0.14310652 + }, + { + "move": "24/18(2) 13/7 8/2", + "equity": 0.12353878 + }, + { + "move": "13/7(3) 8/2", + "equity": 0.101389736 + }, + { + "move": "24/18 13/7 8/2(2)", + "equity": -0.05435923 + }, + { + "move": "24/18 8/2(3)", + "equity": -0.12482335 + } + ] + }, + "2": { + "played_move": "24/18(2) 13/7(2)", + "played_position_id": "4HPwATDg28HBAA", + "move_count": 11, + "rank": 1, + "played_equity": 0.5886327, + "best_move": "24/18(2) 13/7(2)", + "best_equity": 0.5886327, + "equity_loss": -0.0, + "skill": "none", + "ranking": [ + { + "move": "24/18(2) 13/7(2)", + "equity": 0.5886327 + }, + { + "move": "24/18 13/7(3)", + "equity": 0.33312234 + }, + { + "move": "24/18(2) 8/2(2)", + "equity": 0.27119082 + }, + { + "move": "13/7(2) 8/2(2)", + "equity": 0.1563462 + }, + { + "move": "24/18 13/7(2) 8/2", + "equity": 0.14750561 + }, + { + "move": "13/7(4)", + "equity": 0.14310652 + }, + { + "move": "24/18(2) 13/7 8/2", + "equity": 0.12353878 + }, + { + "move": "13/7(3) 8/2", + "equity": 0.101389736 + }, + { + "move": "24/18 13/7 8/2(2)", + "equity": -0.05435923 + }, + { + "move": "24/18 8/2(3)", + "equity": -0.12482335 + } + ] + } + }, + "luck": { + "value": 0.43498537, + "rating": "lucky" + } +} diff --git a/tests/reference/analysis/move-race.json b/tests/reference/analysis/move-race.json new file mode 100644 index 0000000..09dea1e --- /dev/null +++ b/tests/reference/analysis/move-race.json @@ -0,0 +1,202 @@ +{ + "id": "move-race", + "description": "Course, 5-4 : sortie maladroite.", + "position_id": "cLezAQBwN2cGAA", + "match_id": "cIkSAAAAAAAA", + "dice": [ + 5, + 4 + ], + "played": "6/1 5/1", + "type": "move", + "gnubg_commit": "14b67984737ded0f6de43d63dcb789f729d9b06b", + "met": "Kazaross-XG2", + "plies": { + "0": { + "played_move": "6/1 5/1", + "played_position_id": "cLezAQDDNmcGAA", + "move_count": 29, + "rank": 29, + "played_equity": -0.31466243, + "best_move": "13/9 11/6", + "best_equity": -0.22230339, + "equity_loss": -0.092359036, + "skill": "bad", + "ranking": [ + { + "move": "13/9 11/6", + "equity": -0.22230339 + }, + { + "move": "11/6 9/5", + "equity": -0.22993731 + }, + { + "move": "13/8 9/5", + "equity": -0.23005769 + }, + { + "move": "13/9 13/8", + "equity": -0.23073325 + }, + { + "move": "13/8 11/7", + "equity": -0.23153114 + }, + { + "move": "11/7 11/6", + "equity": -0.2335976 + }, + { + "move": "13/8 7/3", + "equity": -0.23374501 + }, + { + "move": "11/6 7/3", + "equity": -0.23394687 + }, + { + "move": "13/4", + "equity": -0.23507257 + }, + { + "move": "13/9 7/2", + "equity": -0.23529215 + }, + { + "move": "7/3 7/2", + "equity": -0.23544219 + }, + { + "move": "11/2", + "equity": -0.235758 + }, + { + "move": "11/7 9/4", + "equity": -0.23589191 + }, + { + "move": "13/8 6/2", + "equity": -0.24026787 + }, + { + "move": "9/5 7/2", + "equity": -0.24087214 + }, + { + "move": "13/8 5/1", + "equity": -0.24252185 + }, + { + "move": "9/5 9/4", + "equity": -0.2440444 + }, + { + "move": "9/4 6/2", + "equity": -0.24487257 + }, + { + "move": "9/4 7/3", + "equity": -0.24512678 + }, + { + "move": "11/6 5/1", + "equity": -0.25016445 + }, + { + "move": "9/4 5/1", + "equity": -0.25572774 + }, + { + "move": "7/2 5/1", + "equity": -0.258138 + }, + { + "move": "7/3 6/1", + "equity": -0.25878713 + }, + { + "move": "7/2 6/2", + "equity": -0.26088732 + }, + { + "move": "13/9 6/1", + "equity": -0.26104733 + }, + { + "move": "9/5 6/1", + "equity": -0.26680842 + }, + { + "move": "11/7 6/1", + "equity": -0.2670043 + }, + { + "move": "6/2 6/1", + "equity": -0.27360544 + }, + { + "move": "6/1 5/1", + "equity": -0.31466243 + } + ] + }, + "2": { + "played_move": "6/1 5/1", + "played_position_id": "cLezAQDDNmcGAA", + "move_count": 29, + "rank": 7, + "played_equity": -0.29347342, + "best_move": "13/9 11/6", + "best_equity": -0.21922025, + "equity_loss": -0.07425317, + "skill": "bad", + "ranking": [ + { + "move": "13/9 11/6", + "equity": -0.21922025 + }, + { + "move": "13/9 13/8", + "equity": -0.2202243 + }, + { + "move": "11/6 9/5", + "equity": -0.22022447 + }, + { + "move": "13/8 9/5", + "equity": -0.22088149 + }, + { + "move": "11/7 11/6", + "equity": -0.22277355 + }, + { + "move": "13/4", + "equity": -0.2230124 + }, + { + "move": "6/1 5/1", + "equity": -0.29347342 + }, + { + "move": "11/7 9/4", + "equity": -0.22985312 + }, + { + "move": "13/8 11/7", + "equity": -0.23169218 + }, + { + "move": "13/9 7/2", + "equity": -0.23561361 + } + ] + } + }, + "luck": { + "value": 0.043956697, + "rating": "none" + } +} diff --git a/tests/reference/compare.py b/tests/reference/compare.py index a9e4ec2..677da81 100644 --- a/tests/reference/compare.py +++ b/tests/reference/compare.py @@ -138,6 +138,123 @@ def compare_position(report, cli, path): return len(position["plies"]) +# -------------------------------------------------------------------------- +# Analyses (analysis/.json, voir README.md, section « Analyses »). + +# Seuils de gnubg (arSkillLevel et arLuckLevel) : une appréciation ou une +# chance n'est comparée strictement que loin de ces seuils. +SKILL_THRESHOLDS = (0.03, 0.06, 0.12) +LUCK_THRESHOLDS = (0.3, 0.6) + + +def run_cli(cli, args): + command = shlex.split(cli) + args + output = subprocess.run(command, capture_output=True, text=True) + if output.returncode != 0: + raise SystemExit("%s a échoué : %s" % (" ".join(command), output.stdout + output.stderr)) + return json.loads(output.stdout) + + +def near_threshold(value, thresholds, tolerance): + return any(abs(abs(value) - t) <= tolerance for t in thresholds) + + +def same_label(report, where, field, expected, actual, value, thresholds): + """Même appréciation ou chance, sauf si la valeur de gnubg est à moins de + la tolérance d'un seuil : remarque sans échec.""" + if expected == actual: + return + if near_threshold(value, thresholds, report.tolerance): + report.notes.append("%s : %s %s pour gnubg, %s pour le moteur ; valeur %.4f proche " + "d'un seuil" % (where, field, expected, actual, value)) + else: + report.same(where, field, expected, actual) + + +def compare_move_analysis(report, cli, case): + dice = "%d%d" % tuple(case["dice"]) + args = [case["position_id"], case["match_id"], dice] + luck = run_cli(cli, ["luck"] + args) + where = "%s chance" % case["id"] + report.number(where, "luck", case["luck"]["value"], luck["luck"]) + same_label(report, where, "rating", case["luck"]["rating"], luck["rating"], + case["luck"]["value"], LUCK_THRESHOLDS) + for ply, expected in sorted(case["plies"].items()): + actual = run_cli(cli, ["--ply", ply, "analysemove"] + args + + [expected["played_position_id"]]) + where = "%s à %s pli(s)" % (case["id"], ply) + report.same(where, "move_count", expected["move_count"], actual["move_count"]) + report.same(where, "coup joué", expected["played_move"], actual["played"]["move"]) + # Rang : le même, sauf si les coups entre les deux rangs ont, pour + # gnubg, la même équité que le coup joué à la tolérance près. + if expected["rank"] != actual["rank"]: + low, high = sorted((expected["rank"], actual["rank"])) + between = expected["ranking"][low - 1:high] + tied = len(between) == high - low + 1 and all( + abs(m["equity"] - expected["played_equity"]) <= report.tolerance for m in between) + if tied: + report.notes.append("%s : rang %d pour gnubg, %d pour le moteur ; égalité" + % (where, expected["rank"], actual["rank"])) + else: + report.same(where, "rank", expected["rank"], actual["rank"]) + if expected["best_move"] != actual["best"]["move"]: + ranking = expected["ranking"] + tied = len(ranking) > 1 and ranking[0]["equity"] - ranking[1]["equity"] <= report.tolerance + if tied: + report.notes.append("%s : meilleur coup %s pour gnubg, %s pour le moteur ; égalité" + % (where, expected["best_move"], actual["best"]["move"])) + else: + report.same(where, "meilleur coup", expected["best_move"], actual["best"]["move"]) + report.number(where, "played_equity", expected["played_equity"], actual["played"]["equity"]) + report.number(where, "best_equity", expected["best_equity"], actual["best"]["equity"]) + report.number(where, "equity_loss", expected["equity_loss"], actual["equity_loss"]) + same_label(report, where, "skill", expected["skill"], actual["skill"], + expected["equity_loss"], SKILL_THRESHOLDS) + + +def compare_cube_analysis(report, cli, case): + for ply, expected in sorted(case["plies"].items()): + actual = run_cli(cli, ["--ply", ply, "analysecube", case["position_id"], + case["match_id"], case["action"]]) + where = "%s à %s pli(s)" % (case["id"], ply) + report.same(where, "analysed", expected["analysed"], actual["analysed"]) + if expected["analysed"] and actual["analysed"]: + compare_cube(report, where, expected["cube"], actual["cube"]) + report.number(where, "equity_loss", expected["equity_loss"], actual["equity_loss"]) + same_label(report, where, "skill", expected["skill"], actual["skill"], + expected["equity_loss"], SKILL_THRESHOLDS) + + +def compare_levels(report, cli, case, settings): + for name, expected in sorted(case["levels"].items()): + where = "%s niveau %s" % (case["id"], name) + report.same(where, "réglages", (expected["plies"], expected["noise"]), + (settings[name]["plies"], round(settings[name]["noise"], 6))) + if case["dice"]: + actual = run_cli(cli, ["--level", name, "bestmove", case["position_id"], + case["match_id"], "%d%d" % tuple(case["dice"])]) + report.same(where, "coup choisi", expected["move"], actual["best_move"]["move"]) + else: + actual = run_cli(cli, ["--level", name, "cube", case["position_id"], case["match_id"]]) + compare_cube(report, where, expected, actual) + + +def compare_analyses(report, cli, folder): + """Compare toutes les analyses ; renvoie leur nombre.""" + settings = {level["name"]: level for level in run_cli(cli, ["levels"])["levels"]} + paths = sorted(glob.glob(os.path.join(folder, "*.json"))) + for path in paths: + with open(path, encoding="utf-8") as f: + case = json.load(f) + if case["type"] == "move": + compare_move_analysis(report, cli, case) + elif case["type"] == "cube": + compare_cube_analysis(report, cli, case) + else: + compare_levels(report, cli, case, settings) + return len(paths) + + def print_table(lines): header = ("position et niveau", "champ", "attendu", "obtenu", "écart") rows = [header] + lines @@ -152,6 +269,7 @@ def main(): parser.add_argument("--cli", default="build/engine-cli", help="programme interrogé, éventuellement avec ses arguments") parser.add_argument("--results", default=os.path.join(here, "results")) + parser.add_argument("--analysis", default=os.path.join(here, "analysis")) parser.add_argument("--tolerance", type=float, default=TOLERANCE) parser.add_argument("--details", action="store_true", help="affiche tous les nombres comparés") @@ -160,14 +278,15 @@ def main(): report = Report(args.tolerance) paths = sorted(glob.glob(os.path.join(args.results, "*.json"))) levels = sum(compare_position(report, args.cli, path) for path in paths) + analyses = compare_analyses(report, args.cli, args.analysis) if args.details: print_table(report.lines) for note in report.notes: print(" Remarque : " + note) - print(" %d positions, %d niveaux, %d nombres comparés ; écart maximal %.2e (%s), " - "tolérance %.0e." % (len(paths), levels, len(report.lines), report.max_gap, - report.max_gap_where, args.tolerance)) + print(" %d positions, %d niveaux, %d analyses, %d nombres comparés ; écart maximal %.2e " + "(%s), tolérance %.0e." % (len(paths), levels, analyses, len(report.lines), + report.max_gap, report.max_gap_where, args.tolerance)) if report.failures: print(" %d écart(s) hors critères :" % len(report.failures)) print_table(report.failures) diff --git a/tests/reference/generate.py b/tests/reference/generate.py index 9330197..858abaf 100644 --- a/tests/reference/generate.py +++ b/tests/reference/generate.py @@ -161,6 +161,259 @@ def check_settings(): raise SystemExit("table d'équité de match inattendue") +# -------------------------------------------------------------------------- +# Analyses (analyses.json -> analysis/.json) : coup joué, décision de +# videau, chance d'un jet et niveaux de jeu. Les valeurs viennent de l'analyse +# de partie de gnubg (« analyse game ») sur une courte partie rejouée ici : +# la position, puis le coup ou la décision. Voir README.md. + +# Appréciations et chances : noms de gnubg (skillString et luckString de +# gnubgmodule.c ; absents du dictionnaire quand rien n'est à signaler) vers +# ceux d'engine-cli. +SKILLS = {None: "none", "doubtful": "doubtful", "bad": "bad", "very bad": "very_bad"} +LUCKS = {None: "none", "verybad": "very_unlucky", "bad": "unlucky", "good": "lucky", + "verygood": "very_lucky"} + +# Niveaux de jeu : réglages prédéfinis de gnubg (aecSettings, eval.c +# l. 318-328), en (plis, bruit) ; l'élagage est actif à partir d'1 pli, le +# bruit est déterministe. Ceux qui sont vérifiés : 3 et 4 plis sont trop +# lents pour la suite de tests. +LEVEL_SETTINGS = { + "beginner": (0, 0.06), "casual": (0, 0.05), "intermediate": (0, 0.04), + "advanced": (0, 0.015), "0ply": (0, 0.0), "1ply": (1, 0.0), "2ply": (2, 0.0), + "3ply": (3, 0.0), "4ply": (4, 0.0), +} +CHECKED_LEVELS = ("beginner", "casual", "intermediate", "advanced", "0ply", "1ply", "2ply") +RANKING_KEPT = 10 # coups gardés dans le classement d'une analyse (plus le coup joué) + + +def level_context(name): + plies, noise = LEVEL_SETTINGS[name] + return {"cubeful": 1, "plies": plies, "deterministic": 1, + "prune": 1 if plies > 0 else 0, "noise": noise} + + +def start_game(case): + """Nouvelle partie entre deux joueurs humains (gnubg ne joue rien de + lui-même), placée sur la position du cas, cache vidé.""" + gnubg.command("set player 0 human") + gnubg.command("set player 1 human") + gnubg.command("new session") + gnubg.command("clear cache") + gnubg.command("set gnubgid %s:%s" % (case["position_id"], case["match_id"])) + read_back = (gnubg.positionid(), gnubg.matchid()) + if read_back != (case["position_id"], case["match_id"]): + raise SystemExit("%s : gnubg relit %s" % (case["id"], read_back)) + + +def set_analysis(ply): + """Réglages de l'analyse de partie : niveau ply sans bruit, comme le + réglage prédéfini de gnubg ; filtre de coups et analyse de la chance par + défaut (« Normal », ecLuck).""" + for kind in ("chequerplay", "cubedecision"): + prefix = "set analysis %s evaluation " % kind + gnubg.command("set analysis %s type evaluation" % kind) + gnubg.command(prefix + "plies %d" % ply) + gnubg.command(prefix + "cubeful on") + gnubg.command(prefix + "prune " + ("on" if ply > 0 else "off")) + gnubg.command(prefix + "deterministic on") + gnubg.command(prefix + "noise 0") + + +def analyse_game(): + """Analyse la partie en cours ; renvoie ses enregistrements et ses + statistiques par joueur (« X », « O »). Le cache est vidé avant : les + évaluations à 2 plis faites pour choisir un coup y laissent des + résultats à 0 pli des réseaux d'élagage, que l'analyse à 0 pli + reprendrait (voir analyse_move).""" + gnubg.command("clear cache") + gnubg.command("analyse game") + game = gnubg.match(analysis=1, verbose=1, statistics=1, boards=1)["games"][-1] + return game["game"], game["stats"] + + +def last_record(records, action): + found = [r for r in records if r.get("action") == action] + if not found: + raise SystemExit("aucun enregistrement « %s » dans la partie analysée" % action) + return found[-1] + + +def swapped(board): + """La même position vue de l'autre joueur (gnubg.board() donne le joueur + au trait en second).""" + return (board[1], board[0]) + + +def set_dice(case): + """Les dés du cas, qui priment sur ceux du Match ID.""" + gnubg.command("set dice %d %d" % tuple(case["dice"])) + + +def best_move(): + """Le meilleur coup selon gnubg (findbestmove, réglages d'évaluation + courants), en notation de gnubg. Pas « hint » : gnubg garderait son + analyse dans l'enregistrement du coup, et « analyse game » la + reprendrait au lieu de calculer au niveau demandé.""" + board = gnubg.board() + return gnubg.movetupletostring(gnubg.findbestmove(), board) + + +def play_move(case): + """Joue le coup du cas, ou le meilleur selon gnubg s'il n'est pas donné + (cas qui ne servent qu'à la chance du jet).""" + gnubg.command("move " + (case.get("played") or best_move())) + + +def analyse_move(case, ply): + start_game(case) + set_dice(case) + before = gnubg.board() + play_move(case) + # Sans analyse du videau : gnubg la ferait juste avant la chance du jet, + # et à 2 plis elle laisse dans son cache d'évaluation des résultats à + # 0 pli calculés avec les réseaux d'élagage, que l'analyse de la chance + # (0 pli, sans élagage) reprend ensuite : la chance dépendrait alors de ce + # qui a été calculé avant (écart observé : 5e-3 sur move-middlegame-close). + gnubg.command("set analysis cube off") + # Après le coup, l'adversaire est au trait : la position vue de celui qui + # vient de jouer est la position retournée. + played_id = gnubg.positionid(swapped(gnubg.board())) + set_analysis(ply) + records, stats = analyse_game() + record = last_record(records, "move") + analysis = record["analysis"] + moves, index = analysis["moves"], analysis["imove"] + for m in moves: + if m["type"] != "eval": + raise SystemExit("%s : coup évalué autrement que prévu : %r" % (case["id"], m)) + + def notation(move): + return gnubg.movetupletostring(move, before) + + kept = moves[:max(RANKING_KEPT, index + 1)] + level = { + "played_move": notation(record["move"]), + "played_position_id": played_id, + "move_count": len(moves), + "rank": index + 1, + "played_equity": f32(moves[index]["score"]), + "best_move": notation(moves[0]["move"]), + "best_equity": f32(moves[0]["score"]), + # Perte calculée par gnubg pour ses statistiques (rChequerSkill), + # comptée en positif. + "equity_loss": f32(-stats[record["player"]]["moves"]["error-skill"]), + "skill": SKILLS[analysis.get("skill")], + "ranking": [{"move": notation(m["move"]), "equity": f32(m["score"])} for m in kept], + } + luck = {"value": f32(analysis["luck-value"]), "rating": LUCKS[analysis.get("luck")]} + return level, luck + + +def analyse_cube(case, ply): + start_game(case) + gnubg.command("set analysis cube on") + set_analysis(ply) + # Décision de videau de gnubg au même niveau, avant toute action. + decision = cube_decision(ply) + action = case["action"] + if action == "no_double": + # Ne pas doubler, c'est jeter les dés : n'importe quel jet et le + # meilleur coup, l'analyse du videau portant sur la position avant. + gnubg.command("set dice 2 1") + gnubg.command("move " + best_move()) + records, stats = analyse_game() + record = last_record(records, "move") + analysed = "nd-cubeful-eq" in record["analysis"] + skill = record["analysis"].get("cube-skill") + else: + # gnubg n'analyse un double qu'une fois la réponse donnée : après + # « double », l'adversaire prend, ce qui ne change rien à l'analyse + # du double. + gnubg.command("double") + gnubg.command("drop" if action == "pass" else "take") + records, stats = analyse_game() + analysed = "nd-cubeful-eq" in last_record(records, "double").get("analysis", {}) + record = last_record(records, {"double": "double", "take": "take", "pass": "drop"}[action]) + skill = record.get("analysis", {}).get("skill") + return { + "analysed": analysed, + "cube": decision if analysed else None, + "equity_loss": f32(-stats[record["player"]]["cube"]["error-skill"]), + "skill": SKILLS[skill], + } + + +def level_move(case, name): + """Le coup que joue l'ordinateur de gnubg réglé à ce niveau.""" + start_game(case) + set_dice(case) + before = gnubg.board() + turn = gnubg.posinfo()["turn"] + plies, noise = LEVEL_SETTINGS[name] + prefix = "set player %d chequerplay evaluation " % turn + gnubg.command("set player %d gnubg" % turn) + gnubg.command(prefix + "plies %d" % plies) + gnubg.command(prefix + "noise %g" % noise) + gnubg.command(prefix + "deterministic on") + gnubg.command(prefix + "cubeful on") + gnubg.command(prefix + "prune " + ("on" if plies > 0 else "off")) + gnubg.command("play") + record = last_record(gnubg.match(analysis=0, boards=1)["games"][-1]["game"], "move") + gnubg.command("set player %d human" % turn) + return {"move": gnubg.movetupletostring(record["move"], before)} + + +def level_cube(case, name): + """Décision de videau de gnubg avec les réglages de ce niveau.""" + start_game(case) + r = gnubg.cfevaluate(gnubg.board(), cube_info(), level_context(name)) + return { + "no_double": f32(r[1]), + "double_take": f32(r[2]), + "double_pass": f32(r[3]), + "optimal": f32(r[0]), + "decision": CUBE_DECISIONS[r[4]], + "decision_text": r[5], + } + + +def write_analysis(out_dir, result): + with open(os.path.join(out_dir, result["id"] + ".json"), "w", encoding="utf-8") as f: + json.dump(result, f, ensure_ascii=False, indent=2) + f.write("\n") + print("analyse écrite :", result["id"]) + + +def generate_analyses(commit): + with open(os.path.join(FOLDER, "analyses.json"), encoding="utf-8") as f: + cases = json.load(f) + out_dir = os.path.join(FOLDER, "analysis") + os.makedirs(out_dir, exist_ok=True) + # Pas d'affichage du plateau ; pas de nouvelle partie ni de jet de dés + # automatiques, qui ajouteraient des enregistrements après le dernier. + gnubg.command("set display off") + gnubg.command("set automatic game off") + gnubg.command("set automatic roll off") + for kind in ("moves", "cubes", "levels"): + for case in cases[kind]: + result = dict(case, type=kind[:-1], gnubg_commit=commit, met=MET) + if kind == "moves": + result["plies"] = {} + for ply in PLIES: + result["plies"][str(ply)], result["luck"] = analyse_move(case, ply) + elif kind == "cubes": + result["plies"] = {str(ply): analyse_cube(case, ply) for ply in PLIES} + else: + result["levels"] = {} + for name in CHECKED_LEVELS: + plies, noise = LEVEL_SETTINGS[name] + level = {"plies": plies, "noise": noise} + level.update(level_move(case, name) if case["dice"] else level_cube(case, name)) + result["levels"][name] = level + write_analysis(out_dir, result) + + def main(): with open(os.path.join(FOLDER, "positions.json"), encoding="utf-8") as f: positions = json.load(f)["positions"] @@ -195,6 +448,7 @@ def main(): json.dump(result, f, ensure_ascii=False, indent=2) f.write("\n") print("référence écrite :", pos["id"]) + generate_analyses(commit) main()