PAUGAM Guillaume LOHIER Théophile Création d’un moteur d’échecs Conception et création d’un moteur d’échecs Sujet : On cherche à développer un moteur d’échecs en C++, c’est-à-dire une intelligence artificielle capable d’évaluer une position, et de jouer une partie entière. L’interface graphique utilisée est Xboard / Winboard, et la communication avec celle-ci est assurée par le protocole CECP (Chess Engine Communication Protocol). Les algorithmes possibles à utiliser sont le minimax, l’élagage alpha-bêta, le negascout ou MTD-(f), et on utilise une fonction d’évaluation. Les structures de données sont : Des Bitboards (tableaux de bits) pour les positions des pièces sur l’échiquier. Un arbre de coups possibles. Algorithmes : L’idée générale est de considérer la position initiale comme un nœud, et l’ensemble des coups possibles (et donc les positions résultantes) comme ses nœuds fils. Pour les coups suivants, il est possible d’analyser tout l’arbre, cependant, le temps d’analyse croît exponentiellement avec la profondeur. C’est pour cela qu’on choisit de ne pas explorer les branches qui ne semblent pas prometteuses (on « élague » l’arbre). Elagage alpha-bêta : Cet algorithme est relativement facile à implémenter par rapport aux autres, puisqu’il n’utilise pas de table de transposition. Il recalcule la valeur d’un nœud même si la position exacte a déjà été rencontrée, car il ne les garde pas en mémoire. Par conséquent, il est plus lourd à l’exécution, mais est moins encombrant en termes de stockage. PAUGAM Guillaume LOHIER Théophile Création d’un moteur d’échecs Pseudo-code : fonction alphabeta( nœud, profondeur, α, β) (* β représente le meilleur coup du joueur précédent *) si profondeur = 0 ou nœud terminal retourner eval(nœud) pour chaque fils du noeud α := max(α, -alphabeta(fils, profondeur -1, -β, -α)) si β≤α break (* coupure bêta*) return α (* appel initial *) alphabeta(origine, profondeur, -infini, +infini) On se base sur la symétrie max(a,b)= - min(-a,-b). Algorithme Negascout : Pour être efficace, cet algorithme a besoin de trier les fils du nœud. Cet ordre est déterminé par une recherche peu profonde. Il suppose donc que le premier fils est la variation principale. Sans tri, cet algorithme est inefficace car il réévalue des positions déjà rencontrées. On s’attend en général à un gain de performance d’environ 10% par rapport à un élagage alpha-bêta simple, tout en conservant les mêmes résultats. Il reste l’algorithme le plus utilisé pour les moteurs d’échecs. Pseudo-code : fonction negascout(noeud, profondeur, α, β) si noeud terminal ou profondeur = 0 return eval(noeud) b := β (* intervalle initial (-β, -α) *) pour chaque fils a := -negascout (fils, profondeur - 1, -b, -α) si α < a < β et fils non premier fils (* vérifier intervalle non nul *) a := -negascout(fils, profondeur - 1, -β, -α) (* recherche complète *) α := max(α, a) si α ≥ β retourner α (* coupure bêta *) b := α + 1 (* nouvel intervalle nul *) retourner α PAUGAM Guillaume LOHIER Théophile Création d’un moteur d’échecs MTD-(f) : L’algorithme MTD-(f) (Memory-enhanced Test Driver) est l’algorithme le plus efficace car il gère les tables de transposition et évalue par conséquent moins de nœuds. Il garde en mémoire toutes les positions déjà rencontrées. Il est donc plus lourd et dépend fortement des tables de transpositions. Pseudo-code : fonction MTDF(racine, f, d) g := f b := +∞ a := -∞ tant que a < b si g = a alors β := g+1 sinon β := g g := AlphaBetaAvecMemoire(racine, β-1, β, d) si g < β alors b := g sinon a := g retourner g Fonction d’évaluation : La fonction d’évaluation est de la forme : Eval(nœud) = c1 * matériel + c2 * mobilité des pièces + c3 * control central + c4 * sécurité du roi + … La priorité est de rechercher le gain de matériel (dont le roi) et de le nuancer par les autres paramètres. Heuristiques additionnelles possibles : Heuristique à mouvement nul : L’élagage alpha-bêta élimine normalement les branches où l’on trouve des « coupures », c’est-à-dire des coups où la position résultante est tellement bonne pour le joueur ayant l’initiative que cela implique que l’autre joueur n’a pas joué le meilleur coup. Le but de l’heuristique est de faire passer le tour au joueur qui a normalement le trait (c’est-à-dire le joueur qui doit jouer).Toutefois, s’il garde une position assez favorable pour générer des coupures, alors la position courante génèrerait aussi probablement une coupure. PAUGAM Guillaume LOHIER Théophile Création d’un moteur d’échecs Au final, on fait passer le tour au joueur ayant trait (normalement interdit), et on effectue un élagage alpha-bêta, mais de façon moins profonde. Cela permet d’élaguer une partie de l’arbre en trouvant des coupures plus facilement, et donc d’accélérer l’exécution du logiciel. Cependant, cette technique reste à utiliser dans des cas particuliers et avec modération, car il existe des situations où l’heuristique pourrait faire faire au logiciel des erreurs critiques. Par exemple, la situation du zugzwang, c’est-à-dire quand le joueur ne peut jouer aucun bon coup et préfèrerait passer son tour, est un cas qui est mal géré par l’heuristique. Ainsi, il existe d’autres types de positions où cette heuristique est très inefficace. Heuristique à mémoire : L’idée ici est de garder en mémoire des coups qui dans des positions précédentes étaient forts et de les tester en premier (si légaux), car ils peuvent être bons. S’ils génèrent des coupures, on peut même ne pas considérer les autres coups. Cette méthode est non optimale mais peut générer d’assez bons résultats et est très légère à implémenter.