--- title: "IA : Pour les jeux" date: 2023-10-10 tags: ["IA", "jeux"] categories: ["Intelligence artificielle", "Cours"] mathjax: true --- Presque tous les humains jouent, que se soit des jeux de cartes, de sociétés ou vidéo. L'IA pour les jeux est donc facile à vulgariser. Prenons le cas des échecs, dans le cadre de l'IA on s'affranchit du monde physique : elle ne s'occupe que de la **logique**, ici pas de robot qui bouge les pièces. Les **jeux-vidéo** utilisent massivement l'IA mais les contraintes sont fortes (temps-réel) et elle est biaisée sans quoi l'ordinateur gagnera à chaque fois. ## Article de Claude Shannon C'est en 1950 que [Claude Shannon][l_cshannon] écrit un article fondateur sur l'intelligence artificielle : un programme pour les jeux d'échec. En découlera le *nombre de Shannon* qui estime la complexité d'un jeu d'echec. [l_cshannon]: https://fr.wikipedia.org/wiki/Claude_Shannon ## Un jeu simple Prenons une grille de 3x3, chaque joueur pose un jeton de 2 cases, l'un verticalement et l'autre horizontalement. ![Jeux de grille](./images/grille_jeux.svg) Afin de déterminer les situations favorables pour un joueur donné, il est possible de construire un arbre. Celui-ci commence par la position initiale et explore toutes les possibilités. L'exemple ci-dessous en affiche juste une partie : ![Organisation des possibilités en arbres](./images/arbre_jeux.svg) Nous pouvons introduire alors la notion de **facteur de branchement** qui représente le nombre d'enfants pour un nœud donné. Bien entendu ce *facteur de branchement* peut différer en fonction du nœud sur lequel on se situe. Dans notre exemple ci-dessus, il est de 4 pour le premier tout en haut (joueur1) puis de 2 pour celui du joueur 2 tout à gauche. Nous pourrons alors calculer le **facteur de branchement moyen**. Nous pouvons ainsi explorer toutes les possibilités, on parle alors de **devellopement total**. Mais l'arbre peut devenir très vite complexe, dans le cadre de notre grille en 3x3, il contient 75 nœuds, une grille de 4x4 en compterai 65 081, et en 5x5 **plus de 2 milliards** avec un temps de traitement de plus de **700 secondes**! On se retrouve face à une **explosion combinatoire**. ### Simplifier notre arbre Nous pourrions d'abord reconnaitre les états déjà atteints par d'autres chemins et ne pas les explorer de nouveau. Nous pouvons aussi considérer les états symétriques de notre plateau de jeu. Voici un exemple de portion d'arbre symétiques : ![Exemple de portion d'arbre symétrique](./images/arbre_symetrique.svg) nous pourrions "*elaguer*" notre arbre pour nous concentrer sur les branches qui mèment à une stratégie gagnante. Si une branche amie mène à la victoire, ou si une branche ennemie mène à la défaite, alors **nous ne devellopons pas les branches voisines**. Pour ces deux idées, nous sommes obligés de conserver un état de notre arbre en memoire. De plus nous sommes toujours obligés d'aller jusqu'au feuilles. Aucun jeu ne permet de mettre en place ces techniques efficacement **sauf à la toutes fin de la partie**. ## Introduction d'heuristique D'après [Wikipedia][heuristique_wikipedia] : > En algorithmique, une heuristique est une méthode de calcul qui fournit > rapidement une solution réalisable, pas nécessairement optimale ou exacte, > pour un problème d'optimisation difficile. [heuristique_wikipedia]:https://fr.wikipedia.org/wiki/Heuristique_(math%C3%A9matiques) Le principe ici est de dévelloper l'arbre que jusqu'à une certaine profondeur *p*. Les feuilles de l'arbre ne sont plus forcément les position finales du jeu. Donc nous ne pouvons plus utiliser les information *gagné* ou *perdu*. Il faut alors **évaluer les positions**. ### Définition intuitive Ici, nous associons un réel au plateau de jeu (avec adversaire) dans une *fonction statique*. Plus cet heuristique est élevé et positif, plus la victoire est proche, et plus on s'en éloigne lorsqu'elle est petite et négative. ### Mode opératoire 1. On dévellope l'abre jusqu'à une certaine profondeur définie au préalable. Cette profondeur prend en compte le temps de jeu et le facteur de branchement estimé; 2. On évalue la position aux feuilles, cette position dépends du plateau et du joueur; 3. On fait remonter l'évaluation pour **trouver le meilleur coup**. Le but ici est de **maximiser la valeur heuristique** obtenue sur le plateau après les *p* prochains coups et cherchant la branche adaptée. Bien entendu nous supposons alors que les deux joueurs jouent au mieux. C'est même un prérequis pour optimiser les calculs. Le tout est maintenant de savoir comment? ## Recherche avec horizon: MiniMax Cet algorithme se compose de deux fonctions : * `MaxMin` pour remonter le meilleur coup à jouer lorsque le joueur ami joue; ``` Fonction MaxMin(etat) etat : Plateau de jeu courant Si EstFeuille(etat) Alors Retourner evalue(AMI, etat) Fin Si Meilleur ← −∞ Pour Tout successeur s de etat Faire Meilleur ← max(Meilleur,MinMax(s)) Fin Pour Retourner Meilleur Fin Fonction ``` * `MinMax` pour remonyer le meilleur coup à jouer pour l'ennemi. ``` Fonction MinMax(etat) etat : Plateau de jeu courant Si EstFeuille(etat) Alors Retourner evalue(ENNEMI, etat) Fin Si Pire ← +∞ Pour Tout successeur s de etat Faire Pire ← min(Pire,MaxMin(s)) Fin Pour Retourner Pire Fin Fonction ``` Mais cet algorithme a des limitation : * Que se passe-t-il si un joueur joue mal, ou ne suit pas les recommandations de l'heuristique? * Le dilème des prisonniers voir [Wikipedia][prisonnier_wikipedia]; * L'amplification des valeurs heuristiques : une valeur faible mais isolée dans une zone dangereuse sera préférée à une valeur *presque* identiqque dans une zone amie; L'espace des états atteignable dépends du facteur de branchement et du nombre de coups. Il peut vite devenir **gigantesque** : de l'ordre de 35 puissance 30 pour les échecs (35 en facteur debranchement et 30 coups environ). ## recherche avec horizon: AlphaBeta C'est une technique permettant de réduire le nombre de nœuds évalués par l'algorithme *MinMax*. En effet ce dernier effectue un parcours complet de l'arbre jusqu'au niveau donné. *AlphaBeta* permet d'optimiser grandement *MinMax* en réalisant un élagage. en effet il n'est pas nécessaire de parcourir les sous-arbres qui ne changerons pas la configuration du nœud courant, sans conséquence sur l'évaluation. Nous avons deux type des nœuds : * α : le meilleur choix à un moment donné pour *Max* sur le chemin dévellopé. Sa valeur est croissante; * β : le meilleur choix à un moment donné pour *Min* sur le chemin dévellopé. Sa valeur est décroissante; Voici l'algorithme des deux fontions, comme pour *MaxMin*, cependant α sera initialisé avec -∞ et β avec +∞ : ``` Fonction MaxValue(etat, α, β)) # Évaluation niveau AMI etat : Plateau de jeu courant α : Meilleur évaluation courante pour AMI β : Meilleur évaluation courante pour ENNEMI Si EstFeuille(etat) Alors Retourner evalue(etat) # Évaluation heuristique Fin Si Pour Tout successeur s de etat Faire α ← max(α,MinValue(s, α, β)) Si α ≥ β Alors # Coupe β Retourner β Fin Si Fin Pour Retourner α Fin Fonction Fonction MinValue(etat, α, β)) # Évaluation niveau ENNEMI etat : Plateau de jeu courant α : Meilleur évaluation courante pour AMI β : Meilleur évaluation courante pour ENNEMI Si EstFeuille(etat) Alors Retourner evalue(etat) # Évaluation heuristique Fin Si Pour Tout successeur s de etat Faire β ← min(β,MaxValue(s, α, β)) Si α ≥ β Alors # Coupe α Retourner α Fin Si Fin Pour Retourner β Fin Fonction ``` Comme pour *MinMax*, il est possible de combiner les deux fonctions en une seul, il suffit alors de permuter α et β à chaque itération. Les coupes seront faites dès que \\(\alpha \geqslant \beta\\) ### Catégories de nœuds et nœuds traités Nous pouvons classer les nœuds en 4 catégories: * *Type 1* : -∞ +∞ * *Type 2* : -∞ β * *Type 3* : α +∞ * *Type 4* : α β soit un arbre \\(A\\) de profondeur \\(p\\) et un facteur de branchement \\(b\\) : * \\(U_p\\) représente le nombre de feuilles de type 1; * \\(V_p\\) de feuilles de type 2; * \\(W_p\\) de feuilles de type 3; Nous avons donc : * \\(U_p = 1\\) * \\(V_p = (b-1) + bW_{b-1}\\) * \\(W_p = V_{p-1}\\) Ce qui permet de dire : * \\(V_p = (b-1) + b(V_{p-2})\\) * donc \\(V_{p+1} = b + bV_{p-2}\\) * donc \\(V_{p+1} = b(V{p-2} + 1)\\) * après simplification nous obtenons le nombre de nœuds traités : \\(b^{p/2}\\) au lieu de \\(b^p\\) ### Problématique du temps réel Les questions sur le choix des profondeurs doivent être fixées avant de commencer la recherche. On veut aller le plus profond possible dans l'arbre. [prisonnier_wikipedia]:https://fr.wikipedia.org/wiki/Dilemme_du_prisonnier