← Retour au menu des cours

Cours 7 : Théorie des jeux

Summary

Ce cours a duré deux heures car on a commencé par faire la dernière question du précédent exercice (voir cours 6), puis on a appliqué l'algorithme de Dijkstra sur quelques exemples (voir cours 4).

Ensuite, on a parlé de Théorie des jeux.

Jeux à somme nulle

On s'intéresse uniquement aux jeux à deux joueurs, et tour à tour. On les appelle Joueur 1 et Joueur 2.

De plus, on impose que le jeu est à somme nulle. Cela signifie qu'à la fin du jeu, les joueurs ont un score opposé. Soit uu le score final de Joueur 1. Le Joueur 2 a donc pour score u-u.

À retenir : Le plus souvent uu ne prend que trois valeurs, 1-1, 00, et 11. Cependant, cette généralisation pour uRu \in \mathbb{R} va nous être utile lorsqu'on va parler d'heuristique

Exemples

Le Morpion

Le jeu des bâtonnets de Ford Boyard

Représentation d'un tel jeu

Formellement, un jeu est défini par :

C'est éxactement ce qu'on appelle un graphe orienté biparti G=(S1S2,A)G = (S_1 \cup S_2, A) avec un état initial s0Ss_0 \in S.

Enfin, les états finaux (c'est-à-dire ceux qui n'ont pas de transitions), sont annotés par le score uu obtenu par le Joueur 1.

Si le score est positif, cela signifie que Joueur 1 gagne, s'il est négatif Joueur 1 perd et sinon il y a match nul.

Voici par exemple le graphe du jeu des bâtonnets de Ford Boyard

graphe

Stratégie

Une stratégie pour Joueur 1 est une fonction de S1S_1 dans S2S_2 avec que des coups valides.

Une stratégie pour Joueur 1 est dite gagnante si, quelque soient les actions jouées par Joueur 2, Joueur 1 gagne.

Une stratégie pour Joueur 1 assure le nul si, quelque soient les actions jouées par Joueur 2, Joueur 1 ne perd pas.

Quelques propriétés (à utiliser sans modération)

Propriété 1 : Si il n'y a pas d'état final nul, alors :

Propriété 2 : Dans tous les cas :

Attracteur

Du point de vue du Joueur 1, on note:

On appelle cet ensemble A1\mathcal A_1 (attracteur pour le Joueur 1).

On peut construire similairement A2\mathcal A_2 et on a alors

A1NA2\mathcal A_1 \sqcup \mathcal N \sqcup \mathcal A_2

Les états de A1\mathcal A_1 possèdent une stratégie gagnante pour le Joueur 1. Les états de A2\mathcal A_2 possèdent une stratégie gagnante pour le Joueur 2. Les états de N\mathcal N possèdent une stratégie qui assure le nul à la fois pour le Joueur 1 et pour le Joueur 2.

Algorithme Minimax

On s'interesse ici aux jeux à somme nulle :

On peut représenter n'importe que jeu fini à deux joueurs tour par tour pas un arbre orienté :


L’algorithme Minimax permet de déterminer la stratégie optimale dans un jeu à somme nulle, en supposant que l’adversaire joue également de manière optimale.

On distingue deux types de nœuds :

Les feuilles ont une valeur :

On remonte l’arbre depuis les feuilles :

valeur(n)=max(valeurs des enfants) \text{valeur}(n) = \max(\text{valeurs des enfants})
valeur(n)=min(valeurs des enfants) \text{valeur}(n) = \min(\text{valeurs des enfants})

Mais comment on fait si on ne connaît pas le score d'un des enfants ? Et bien on fait un appel récursif :

Minimax(n)={valeur(n)si n est une feuillemaxcenfants(n)Minimax(c)si n est MAXmincenfants(n)Minimax(c)si n est MIN \text{Minimax}(n) = \begin{cases} \text{valeur}(n) & \text{si } n \text{ est une feuille} \\ \max\limits_{c \in enfants(n)} \text{Minimax}(c) & \text{si } n \text{ est MAX} \\ \min\limits_{c \in enfants(n)} \text{Minimax}(c) & \text{si } n \text{ est MIN} \end{cases}

En python, l'algorithme est tout simple (soit sûre de bien le comprendre) :

def minimax(node, c_est_moi_qui_joue):
    if node.est_terminal():
        return node.valeur

    if c_est_moi_qui_joue:
        best_value = -infinity
        for child in node.enfants:
            value = minimax(child, False) # Appel récursif
            best_value = max(best_value, value)
        return best_value
    else:
        best_value = +infinity
        for child in node.enfants:
            value = minimax(child, True) # Appel récursif
            best_value = min(best_value, value)
        return best_value
Complexiteˊ temporelle=O(bd) \text{Complexité temporelle} = O(b^d)

ou dd est la profondeur de l'arbre, et b est le nombre d'actions possibles à chaque étape.

⚠️ Cela devient rapidement très coûteux (ex : échecs).

Pour les échecs, on utilise une heuristique, qui correspond à un nombre entre 1-1 et 11 pour chaque nœud :

Au lieu de faire des appels récursifs pour toute la profondeur de l'arbre, on s'arrête avant, grâce à l'heuristique.

Attention : c'est algorithme heuristique n'est plus optimal, c'est juste un joueur pas trop mauvais.