# Fichier: python_cheats/cheatsheets/théorie_des_jeux.txt
# Cheatsheet Théorie des Jeux - Guide Ultra-Détaillé pour Grands Débutants en Python


[OK] CONCEPTS FONDAMENTAUX (EXPLICATIONS TRÈS DÉTAILLÉES)

# === QU'EST-CE QUE LA THÉORIE DES JEUX ? ===

# Imagine que tu es dans un restaurant avec un ami
# Vous devez décider où manger ce soir
# Toi tu préfères les pizzas, lui préfère les sushis
# Comment prendre une décision qui satisfait tout le monde?

# LA THÉORIE DES JEUX = Science mathématique qui étudie:
# - Les situations où plusieurs personnes/entités prennent des décisions
# - Comment ces décisions influencent les résultats de chacun
# - Comment trouver la meilleure stratégie possible

# Exemples concrets dans la vie:
# 1. Deux entreprises qui fixent leurs prix (Coca vs Pepsi)
# 2. Pays qui négocient un traité commercial
# 3. Joueurs de poker qui décident de miser ou se coucher
# 4. Enchères sur eBay
# 5. Négociation de salaire avec ton employeur

# POURQUOI C'EST IMPORTANT?
# - Aide à prendre de meilleures décisions stratégiques
# - Permet de prévoir le comportement des autres
# - Utilisé en économie, politique, biologie, informatique
# - Applications en IA (AlphaGo, ChatGPT, etc.)


# === VOCABULAIRE DE BASE (TRÈS IMPORTANT!) ===

# JOUEUR (Player)
# = Une personne/entité qui prend des décisions
# = Peut être: humain, entreprise, pays, programme IA, animal
# Exemple: Dans un match de foot, les 2 joueurs = les 2 équipes
# En Python: On les représente souvent comme "Player 1", "Player 2"

# STRATÉGIE (Strategy)
# = Un plan d'action complet pour un joueur
# = Toutes les décisions possibles qu'il peut prendre
# Exemple: Au pierre-papier-ciseaux:
#   - Stratégies possibles: Pierre, Papier, Ciseaux
#   - Chaque joueur choisit UNE stratégie

# GAIN/PAIEMENT (Payoff)
# = Le résultat qu'un joueur obtient après que tous ont joué
# = Peut être: argent, points, satisfaction, survie
# Exemple: Au poker:
#   - Si tu gagnes: +100€ (ton gain)
#   - Si tu perds: -50€ (ton gain négatif)

# JEU (Game)
# = L'ensemble complet des règles:
#   - Qui sont les joueurs?
#   - Quelles stratégies peuvent-ils choisir?
#   - Quels gains obtiennent-ils selon les choix?
# Exemple: Le Monopoly est un jeu avec des règles précises

# MATRICE DE GAINS (Payoff Matrix)
# = Un tableau qui montre tous les gains possibles
# = Chaque case = combinaison de stratégies + gains associés
# Exemple simple (2 joueurs, 2 stratégies chacun):
#
#                 Joueur 2
#              Stratégie A | Stratégie B
# Joueur 1  A   (3, 3)    |  (0, 5)
#           B   (5, 0)    |  (1, 1)
#
# (3, 3) signifie: Joueur 1 gagne 3, Joueur 2 gagne 3

# ÉQUILIBRE DE NASH (Nash Equilibrium)
# = Une situation où aucun joueur ne peut améliorer son gain
#   en changeant SEUL de stratégie
# = Concept CENTRAL de la théorie des jeux!
# Analogie: C'est comme un point stable
#   - Si tu bouges seul, tu perds
#   - Donc personne ne bouge
# Exemple: Deux voitures arrivent à un carrefour
#   - Si tous deux freinent: équilibre (personne n'a intérêt à accélérer)
#   - Si tous deux accélèrent: accident! (pas un équilibre)

# STRATÉGIE DOMINANTE (Dominant Strategy)
# = Une stratégie qui est TOUJOURS meilleure, peu importe ce que font les autres
# = Si tu as une stratégie dominante: JOUE-LA!
# Exemple: Au dilemme du prisonnier (voir plus bas)
#   - Trahir est toujours mieux que coopérer
#   - Peu importe ce que l'autre fait

# STRATÉGIE DOMINÉE (Dominated Strategy)
# = Une stratégie qui est TOUJOURS pire qu'une autre
# = NE JAMAIS jouer une stratégie dominée!
# Exemple:
#   - Si Pierre bat toujours Ciseaux
#   - Et que Papier bat Pierre
#   - Alors Ciseaux est dominé par Papier


# === TYPES DE JEUX ===

# 1. JEUX COOPÉRATIFS vs NON-COOPÉRATIFS
#
# Coopératif:
# - Les joueurs peuvent former des alliances
# - Ils peuvent faire des accords contraignants
# - Exemple: Négociation syndicale
#
# Non-coopératif:
# - Chacun pour soi
# - Pas d'accords possibles
# - Exemple: Enchères aux enchères

# 2. JEUX À SOMME NULLE vs NON-NULLE
#
# Somme nulle:
# - Ce qu'un joueur gagne = ce que l'autre perd
# - Total des gains = 0
# - Exemple: Poker (l'argent change de mains)
#
# Somme non-nulle:
# - Les deux peuvent gagner ou perdre ensemble
# - Exemple: Commerce international (win-win possible)

# 3. JEUX SIMULTANÉS vs SÉQUENTIELS
#
# Simultané:
# - Tous les joueurs choisissent en même temps
# - Personne ne voit le choix des autres avant
# - Exemple: Pierre-papier-ciseaux
#
# Séquentiel:
# - Les joueurs jouent tour à tour
# - Chacun voit ce que les autres ont fait
# - Exemple: Échecs

# 4. JEUX À INFORMATION PARFAITE vs IMPARFAITE
#
# Information parfaite:
# - Tous les joueurs connaissent tout
# - Pas de secrets ni de hasard
# - Exemple: Échecs, Go
#
# Information imparfaite:
# - Certaines informations sont cachées
# - Exemple: Poker (cartes cachées)


[OK] LE DILEMME DU PRISONNIER (EXEMPLE CLASSIQUE)

# === CONTEXTE ===

# L'exemple LE PLUS CÉLÈBRE de la théorie des jeux!

# Histoire:
# Deux criminels sont arrêtés
# La police les interroge séparément (ils ne peuvent pas communiquer)
# Chacun a 2 choix:
#   - COOPÉRER (avec l'autre criminel) = rester silencieux
#   - TRAHIR (l'autre criminel) = dénoncer l'autre

# Résultats possibles:
# 1. Si TOUS DEUX coopèrent (silence):
#    -> Chacun: 1 an de prison (peu de preuves)
#
# 2. Si l'UN trahit, l'AUTRE coopère:
#    -> Traître: 0 an (liberté, récompense)
#    -> Coopérateur: 3 ans (puni sévèrement)
#
# 3. Si TOUS DEUX trahissent:
#    -> Chacun: 2 ans de prison

# === MATRICE DE GAINS ===

#                    Prisonnier 2
#                 Coopère  |  Trahit
# Prisonnier 1
#    Coopère     (-1, -1)  |  (-3, 0)
#    Trahit      (0, -3)   |  (-2, -2)
#
# Note: Gains négatifs = années de prison (plus c'est négatif, plus c'est mauvais)

# === ANALYSE ===

# Point de vue du Prisonnier 1:
# - Si Prisonnier 2 coopère:
#   -> Je coopère: -1 an
#   -> Je trahis: 0 an
#   -> MIEUX: Trahir!
#
# - Si Prisonnier 2 trahit:
#   -> Je coopère: -3 ans
#   -> Je trahis: -2 ans
#   -> MIEUX: Trahir!
#
# CONCLUSION: Trahir est une STRATÉGIE DOMINANTE
# = Peu importe ce que l'autre fait, trahir est toujours mieux!

# === ÉQUILIBRE DE NASH ===

# Les deux trahissent: (-2, -2)
# POURQUOI?
# - Si Prisonnier 1 change seul de stratégie (coopère): il passe à -3 (pire!)
# - Si Prisonnier 2 change seul de stratégie (coopère): il passe à -3 (pire!)
# - Donc AUCUN n'a intérêt à changer seul
# = C'est un équilibre de Nash!

# === LE PARADOXE ===

# Si les deux coopéraient: (-1, -1) = mieux pour tous!
# Mais ils trahissent tous les deux: (-2, -2) = pire pour tous!
#
# POURQUOI?
# - Chacun agit de manière rationnelle et égoïste
# - Résultat collectif: sous-optimal
#
# C'EST LE CŒUR DE LA THÉORIE DES JEUX!
# = Les intérêts individuels ne mènent pas toujours au meilleur résultat collectif


[OK] IMPLÉMENTATION EN PYTHON (TRÈS DÉTAILLÉE)

# === ÉTAPE 1: REPRÉSENTER UN JEU SIMPLE ===

# On va créer le Dilemme du Prisonnier en Python

# === Version 1: Matrice simple (débutant) ===

# Définir les stratégies
strategies = ["Coopère", "Trahit"]

# Définir la matrice de gains
# payoff_matrix[i][j] = (gain_joueur1, gain_joueur2)
# i = stratégie joueur 1 (0=Coopère, 1=Trahit)
# j = stratégie joueur 2 (0=Coopère, 1=Trahit)

payoff_matrix = [
    [(-1, -1), (-3, 0)],   # Joueur 1 coopère
    [(0, -3), (-2, -2)]    # Joueur 1 trahit
]

# Exemples d'accès:
# payoff_matrix[0][0] = (-1, -1) -> Les deux coopèrent
# payoff_matrix[1][0] = (0, -3)  -> J1 trahit, J2 coopère
# payoff_matrix[0][1] = (-3, 0)  -> J1 coopère, J2 trahit
# payoff_matrix[1][1] = (-2, -2) -> Les deux trahissent

def afficher_matrice():
    """Affiche la matrice de gains de manière lisible"""
    print("=" * 60)
    print("DILEMME DU PRISONNIER - MATRICE DE GAINS")
    print("=" * 60)
    print(f"\n{'':15} | Joueur 2: {strategies[0]:10} | Joueur 2: {strategies[1]:10}")
    print("-" * 60)
    
    for i, strat1 in enumerate(strategies):
        print(f"Joueur 1: {strat1:6} | {str(payoff_matrix[i][0]):20} | {str(payoff_matrix[i][1]):20}")
    print("=" * 60)

# Tester:
afficher_matrice()

# Affiche:
# ============================================================
# DILEMME DU PRISONNIER - MATRICE DE GAINS
# ============================================================
# 
#                 | Joueur 2: Coopère     | Joueur 2: Trahit     
# ------------------------------------------------------------
# Joueur 1: Coopère | (-1, -1)             | (-3, 0)             
# Joueur 1: Trahit  | (0, -3)              | (-2, -2)            
# ============================================================


# === ÉTAPE 2: SIMULER UNE PARTIE ===

def jouer_partie(choix_j1, choix_j2):
    """
    Simule une partie du dilemme du prisonnier
    
    Args:
        choix_j1 (int): 0=Coopère, 1=Trahit
        choix_j2 (int): 0=Coopère, 1=Trahit
    
    Returns:
        tuple: (gain_j1, gain_j2)
    """
    # Récupérer les gains depuis la matrice
    gain_j1, gain_j2 = payoff_matrix[choix_j1][choix_j2]
    
    # Afficher les choix et résultats
    print(f"\nJoueur 1 choisit: {strategies[choix_j1]}")
    print(f"Joueur 2 choisit: {strategies[choix_j2]}")
    print(f"Résultat:")
    print(f"  - Joueur 1: {gain_j1} ans de prison")
    print(f"  - Joueur 2: {gain_j2} ans de prison")
    
    return gain_j1, gain_j2

# Exemples:
print("\n### Scénario 1: Les deux coopèrent ###")
jouer_partie(0, 0)  # 0 = Coopère

print("\n### Scénario 2: Joueur 1 trahit, Joueur 2 coopère ###")
jouer_partie(1, 0)  # 1 = Trahit, 0 = Coopère

print("\n### Scénario 3: Les deux trahissent ###")
jouer_partie(1, 1)


# === ÉTAPE 3: TROUVER LES STRATÉGIES DOMINANTES ===

def trouver_strategie_dominante(joueur):
    """
    Trouve la stratégie dominante d'un joueur (si elle existe)
    
    Args:
        joueur (int): 1 ou 2
    
    Returns:
        int or None: indice de la stratégie dominante, ou None
    """
    print(f"\n### Analyse pour Joueur {joueur} ###")
    
    if joueur == 1:
        # Pour chaque stratégie de J1, comparer les gains
        for strat in range(len(strategies)):
            est_dominante = True
            
            # Comparer avec les autres stratégies
            for autre_strat in range(len(strategies)):
                if strat == autre_strat:
                    continue
                
                # Vérifier si 'strat' est meilleure que 'autre_strat'
                # Pour TOUTES les stratégies de J2
                for choix_j2 in range(len(strategies)):
                    gain_strat = payoff_matrix[strat][choix_j2][0]
                    gain_autre = payoff_matrix[autre_strat][choix_j2][0]
                    
                    if gain_strat <= gain_autre:
                        est_dominante = False
                        break
                
                if not est_dominante:
                    break
            
            if est_dominante:
                print(f"Stratégie dominante: {strategies[strat]}")
                return strat
        
        print("Pas de stratégie dominante")
        return None
    
    else:  # joueur == 2
        # Même logique pour J2
        for strat in range(len(strategies)):
            est_dominante = True
            
            for autre_strat in range(len(strategies)):
                if strat == autre_strat:
                    continue
                
                for choix_j1 in range(len(strategies)):
                    gain_strat = payoff_matrix[choix_j1][strat][1]
                    gain_autre = payoff_matrix[choix_j1][autre_strat][1]
                    
                    if gain_strat <= gain_autre:
                        est_dominante = False
                        break
                
                if not est_dominante:
                    break
            
            if est_dominante:
                print(f"Stratégie dominante: {strategies[strat]}")
                return strat
        
        print("Pas de stratégie dominante")
        return None

# Tester:
trouver_strategie_dominante(1)
trouver_strategie_dominante(2)

# Affiche:
# ### Analyse pour Joueur 1 ###
# Stratégie dominante: Trahit
# 
# ### Analyse pour Joueur 2 ###
# Stratégie dominante: Trahit


# === ÉTAPE 4: TROUVER L'ÉQUILIBRE DE NASH ===

def trouver_equilibres_nash():
    """
    Trouve tous les équilibres de Nash du jeu
    
    Returns:
        list: liste des équilibres (tuples de stratégies)
    """
    equilibres = []
    
    print("\n### Recherche des équilibres de Nash ###")
    
    # Pour chaque combinaison de stratégies
    for i in range(len(strategies)):
        for j in range(len(strategies)):
            gain_j1, gain_j2 = payoff_matrix[i][j]
            
            est_equilibre = True
            
            # Vérifier si J1 peut améliorer son gain en changeant seul
            for autre_i in range(len(strategies)):
                if autre_i != i:
                    autre_gain_j1 = payoff_matrix[autre_i][j][0]
                    if autre_gain_j1 > gain_j1:
                        est_equilibre = False
                        break
            
            # Vérifier si J2 peut améliorer son gain en changeant seul
            if est_equilibre:
                for autre_j in range(len(strategies)):
                    if autre_j != j:
                        autre_gain_j2 = payoff_matrix[i][autre_j][1]
                        if autre_gain_j2 > gain_j2:
                            est_equilibre = False
                            break
            
            # Si aucun ne peut améliorer seul: c'est un équilibre!
            if est_equilibre:
                equilibres.append((i, j))
                print(f"Équilibre trouvé: ({strategies[i]}, {strategies[j]}) -> Gains: {payoff_matrix[i][j]}")
    
    if not equilibres:
        print("Aucun équilibre de Nash trouvé")
    
    return equilibres

# Tester:
equilibres = trouver_equilibres_nash()

# Affiche:
# ### Recherche des équilibres de Nash ###
# Équilibre trouvé: (Trahit, Trahit) -> Gains: (-2, -2)


[OK] CLASSE POUR REPRÉSENTER UN JEU (INTERMÉDIAIRE)

# === VERSION ORIENTÉE OBJET ===

class Jeu:
    """
    Classe pour représenter un jeu sous forme normale (matrice)
    """
    
    def __init__(self, strategies_j1, strategies_j2, matrice_gains):
        """
        Initialise un jeu
        
        Args:
            strategies_j1 (list): Liste des stratégies du joueur 1
            strategies_j2 (list): Liste des stratégies du joueur 2
            matrice_gains (list): Matrice des gains [i][j] = (gain_j1, gain_j2)
        """
        self.strategies_j1 = strategies_j1
        self.strategies_j2 = strategies_j2
        self.matrice_gains = matrice_gains
        
        # Vérifier la cohérence
        if len(matrice_gains) != len(strategies_j1):
            raise ValueError("La matrice doit avoir autant de lignes que J1 a de stratégies")
        
        for ligne in matrice_gains:
            if len(ligne) != len(strategies_j2):
                raise ValueError("Chaque ligne doit avoir autant de colonnes que J2 a de stratégies")
    
    def afficher(self):
        """Affiche la matrice de gains"""
        print("\n" + "=" * 80)
        print("MATRICE DE GAINS")
        print("=" * 80)
        
        # En-tête
        header = f"{'':20} |"
        for strat in self.strategies_j2:
            header += f" J2: {strat:15} |"
        print(header)
        print("-" * 80)
        
        # Lignes
        for i, strat1 in enumerate(self.strategies_j1):
            ligne = f"J1: {strat1:15} |"
            for j in range(len(self.strategies_j2)):
                gains = self.matrice_gains[i][j]
                ligne += f" {str(gains):17} |"
            print(ligne)
        
        print("=" * 80)
    
    def jouer(self, choix_j1, choix_j2):
        """
        Simule une partie
        
        Args:
            choix_j1 (int): Indice de la stratégie de J1
            choix_j2 (int): Indice de la stratégie de J2
        
        Returns:
            tuple: (gain_j1, gain_j2)
        """
        gain_j1, gain_j2 = self.matrice_gains[choix_j1][choix_j2]
        
        print(f"\nRésultats:")
        print(f"  J1 joue: {self.strategies_j1[choix_j1]:15} -> Gain: {gain_j1}")
        print(f"  J2 joue: {self.strategies_j2[choix_j2]:15} -> Gain: {gain_j2}")
        
        return gain_j1, gain_j2
    
    def strategie_dominante(self, joueur):
        """
        Trouve la stratégie dominante d'un joueur
        
        Args:
            joueur (int): 1 ou 2
        
        Returns:
            int or None: Indice de la stratégie dominante
        """
        if joueur == 1:
            strategies = self.strategies_j1
            n_strategies = len(strategies)
            
            for strat in range(n_strategies):
                est_dominante = True
                
                for autre_strat in range(n_strategies):
                    if strat == autre_strat:
                        continue
                    
                    for choix_j2 in range(len(self.strategies_j2)):
                        gain_strat = self.matrice_gains[strat][choix_j2][0]
                        gain_autre = self.matrice_gains[autre_strat][choix_j2][0]
                        
                        if gain_strat <= gain_autre:
                            est_dominante = False
                            break
                    
                    if not est_dominante:
                        break
                
                if est_dominante:
                    return strat
            
            return None
        
        else:  # joueur == 2
            strategies = self.strategies_j2
            n_strategies = len(strategies)
            
            for strat in range(n_strategies):
                est_dominante = True
                
                for autre_strat in range(n_strategies):
                    if strat == autre_strat:
                        continue
                    
                    for choix_j1 in range(len(self.strategies_j1)):
                        gain_strat = self.matrice_gains[choix_j1][strat][1]
                        gain_autre = self.matrice_gains[choix_j1][autre_strat][1]
                        
                        if gain_strat <= gain_autre:
                            est_dominante = False
                            break
                    
                    if not est_dominante:
                        break
                
                if est_dominante:
                    return strat
            
            return None
    
    def equilibres_nash(self):
        """
        Trouve tous les équilibres de Nash
        
        Returns:
            list: Liste de tuples (i, j) des équilibres
        """
        equilibres = []
        
        for i in range(len(self.strategies_j1)):
            for j in range(len(self.strategies_j2)):
                gain_j1, gain_j2 = self.matrice_gains[i][j]
                
                est_equilibre = True
                
                # J1 ne peut pas améliorer seul
                for autre_i in range(len(self.strategies_j1)):
                    if autre_i != i:
                        autre_gain_j1 = self.matrice_gains[autre_i][j][0]
                        if autre_gain_j1 > gain_j1:
                            est_equilibre = False
                            break
                
                # J2 ne peut pas améliorer seul
                if est_equilibre:
                    for autre_j in range(len(self.strategies_j2)):
                        if autre_j != j:
                            autre_gain_j2 = self.matrice_gains[i][autre_j][1]
                            if autre_gain_j2 > gain_j2:
                                est_equilibre = False
                                break
                
                if est_equilibre:
                    equilibres.append((i, j))
        
        return equilibres


# === EXEMPLE D'UTILISATION ===

# Créer le Dilemme du Prisonnier
prisonnier = Jeu(
    strategies_j1=["Coopère", "Trahit"],
    strategies_j2=["Coopère", "Trahit"],
    matrice_gains=[
        [(-1, -1), (-3, 0)],
        [(0, -3), (-2, -2)]
    ]
)

# Afficher
prisonnier.afficher()

# Jouer une partie
prisonnier.jouer(0, 0)  # Les deux coopèrent
prisonnier.jouer(1, 1)  # Les deux trahissent

# Trouver stratégies dominantes
dom_j1 = prisonnier.strategie_dominante(1)
dom_j2 = prisonnier.strategie_dominante(2)

print(f"\nStratégie dominante J1: {prisonnier.strategies_j1[dom_j1] if dom_j1 is not None else 'Aucune'}")
print(f"Stratégie dominante J2: {prisonnier.strategies_j2[dom_j2] if dom_j2 is not None else 'Aucune'}")

# Trouver équilibres de Nash
equilibres = prisonnier.equilibres_nash()
print(f"\nÉquilibres de Nash:")
for eq in equilibres:
    print(f"  ({prisonnier.strategies_j1[eq[0]]}, {prisonnier.strategies_j2[eq[1]]}) -> {prisonnier.matrice_gains[eq[0]][eq[1]]}")


[OK] AUTRES JEUX CLASSIQUES

# === 1. LA BATAILLE DES SEXES ===

# Contexte:
# Un couple veut sortir ensemble ce soir
# Monsieur préfère le foot, Madame préfère l'opéra
# Mais l'important c'est d'être ensemble!

# Matrice:
#                  Madame
#              Opéra  |  Foot
# Monsieur
#    Opéra    (2, 3)  |  (0, 0)
#    Foot     (0, 0)  |  (3, 2)

bataille_sexes = Jeu(
    strategies_j1=["Opéra", "Foot"],
    strategies_j2=["Opéra", "Foot"],
    matrice_gains=[
        [(2, 3), (0, 0)],
        [(0, 0), (3, 2)]
    ]
)

print("\n### BATAILLE DES SEXES ###")
bataille_sexes.afficher()

equilibres = bataille_sexes.equilibres_nash()
print(f"\nÉquilibres de Nash: {len(equilibres)}")
for eq in equilibres:
    print(f"  ({bataille_sexes.strategies_j1[eq[0]]}, {bataille_sexes.strategies_j2[eq[1]]}) -> {bataille_sexes.matrice_gains[eq[0]][eq[1]]}")

# Résultat:
# 2 équilibres de Nash!
# - (Opéra, Opéra): Madame préfère (3 vs 2)
# - (Foot, Foot): Monsieur préfère (3 vs 2)
#
# PROBLÈME: Comment choisir entre les deux?
# = C'est un problème de coordination!


# === 2. LE JEU DE LA POULE MOUILLÉE (CHICKEN GAME) ===

# Contexte:
# Deux voitures foncent l'une vers l'autre
# Chacun peut:
#   - FREINER (être une "poule mouillée")
#   - FONCER (être courageux)
#
# Résultats:
# - Si un seul freine: il perd la face, l'autre gagne
# - Si les deux freinent: match nul
# - Si aucun ne freine: CRASH! (pire résultat)

# Matrice:
#                 Joueur 2
#              Freiner | Foncer
# Joueur 1
#    Freiner   (0, 0)  | (-1, 1)
#    Foncer    (1, -1) | (-10, -10)

chicken = Jeu(
    strategies_j1=["Freiner", "Foncer"],
    strategies_j2=["Freiner", "Foncer"],
    matrice_gains=[
        [(0, 0), (-1, 1)],
        [(1, -1), (-10, -10)]
    ]
)

print("\n### JEU DE LA POULE MOUILLÉE ###")
chicken.afficher()

equilibres = chicken.equilibres_nash()
print(f"\nÉquilibres de Nash:")
for eq in equilibres:
    print(f"  ({chicken.strategies_j1[eq[0]]}, {chicken.strategies_j2[eq[1]]}) -> {chicken.matrice_gains[eq[0]][eq[1]]}")

# Résultat:
# 2 équilibres:
# - (Freiner, Foncer): J2 gagne
# - (Foncer, Freiner): J1 gagne
#
# Pas d'équilibre à (Foncer, Foncer) car les deux ont intérêt à changer!
# = Jeu de "brinkmanship" (guerre des nerfs)


# === 3. PIERRE-PAPIER-CISEAUX ===

# Le jeu le plus connu!
# - Pierre bat Ciseaux
# - Ciseaux bat Papier
# - Papier bat Pierre

pierre_papier_ciseaux = Jeu(
    strategies_j1=["Pierre", "Papier", "Ciseaux"],
    strategies_j2=["Pierre", "Papier", "Ciseaux"],
    matrice_gains=[
        [(0, 0), (-1, 1), (1, -1)],   # Pierre
        [(1, -1), (0, 0), (-1, 1)],   # Papier
        [(-1, 1), (1, -1), (0, 0)]    # Ciseaux
    ]
)

print("\n### PIERRE-PAPIER-CISEAUX ###")
pierre_papier_ciseaux.afficher()

equilibres = pierre_papier_ciseaux.equilibres_nash()
print(f"\nÉquilibres de Nash (purs): {len(equilibres)}")

# Résultat:
# AUCUN équilibre de Nash en stratégies pures!
#
# POURQUOI?
# - Si J1 joue Pierre, J2 préfère Papier
# - Si J2 joue Papier, J1 préfère Ciseaux
# - Si J1 joue Ciseaux, J2 préfère Pierre
# - Etc. -> Cycle infini!
#
# SOLUTION: Équilibre en stratégies MIXTES
# = Jouer chaque stratégie avec probabilité 1/3
# (voir section avancée plus bas)


[OK] STRATÉGIES MIXTES (AVANCÉ)

# === QU'EST-CE QU'UNE STRATÉGIE MIXTE? ===

# Jusqu'ici, on a vu des stratégies PURES:
# - Toujours jouer la même chose
# - Exemple: "Je joue toujours Pierre"

# STRATÉGIE MIXTE:
# - Jouer différentes stratégies avec certaines probabilités
# - Exemple: "Je joue Pierre 40%, Papier 30%, Ciseaux 30%"

# POURQUOI?
# - Dans certains jeux (comme Pierre-Papier-Ciseaux), il n'y a pas d'équilibre en stratégies pures
# - La solution: randomiser pour être imprévisible!

# === REPRÉSENTATION EN PYTHON ===

import random

class StrategieMixte:
    """
    Représente une stratégie mixte (probabilités sur stratégies pures)
    """
    
    def __init__(self, strategies, probabilites):
        """
        Args:
            strategies (list): Liste des stratégies pures
            probabilites (list): Probabilités associées (doivent sommer à 1)
        """
        if len(strategies) != len(probabilites):
            raise ValueError("Nombre de stratégies != nombre de probabilités")
        
        if abs(sum(probabilites) - 1.0) > 1e-6:
            raise ValueError(f"Les probabilités doivent sommer à 1 (somme actuelle: {sum(probabilites)})")
        
        self.strategies = strategies
        self.probabilites = probabilites
    
    def choisir(self):
        """
        Choisit une stratégie pure selon les probabilités
        
        Returns:
            int: Indice de la stratégie choisie
        """
        return random.choices(
            range(len(self.strategies)),
            weights=self.probabilites
        )[0]
    
    def __repr__(self):
        parts = [f"{strat}: {prob:.2%}" for strat, prob in zip(self.strategies, self.probabilites)]
        return "StrategieMixte(" + ", ".join(parts) + ")"


# === EXEMPLE: Pierre-Papier-Ciseaux avec stratégie mixte ===

# Équilibre de Nash en stratégies mixtes:
# Chaque joueur joue chaque stratégie avec probabilité 1/3

strategie_equilibre = StrategieMixte(
    strategies=["Pierre", "Papier", "Ciseaux"],
    probabilites=[1/3, 1/3, 1/3]
)

print(f"\n### Stratégie d'équilibre: {strategie_equilibre} ###")

# Simuler 1000 parties
resultats = {"J1 gagne": 0, "J2 gagne": 0, "Égalité": 0}

for _ in range(1000):
    choix_j1 = strategie_equilibre.choisir()
    choix_j2 = strategie_equilibre.choisir()
    
    gain_j1, gain_j2 = pierre_papier_ciseaux.matrice_gains[choix_j1][choix_j2]
    
    if gain_j1 > gain_j2:
        resultats["J1 gagne"] += 1
    elif gain_j2 > gain_j1:
        resultats["J2 gagne"] += 1
    else:
        resultats["Égalité"] += 1

print("\nRésultats sur 1000 parties:")
for resultat, count in resultats.items():
    print(f"  {resultat}: {count} ({count/10:.1f}%)")

# Résultat attendu:
# Environ 33% pour chaque issue (J1 gagne, J2 gagne, Égalité)
# = Stratégie mixte rend le jeu équitable!


# === CALCULER L'ESPÉRANCE DE GAIN ===

def esperance_gain(jeu, strategie_j1, strategie_j2, joueur):
    """
    Calcule l'espérance de gain d'un joueur avec des stratégies mixtes
    
    Args:
        jeu (Jeu): Le jeu considéré
        strategie_j1 (StrategieMixte): Stratégie mixte de J1
        strategie_j2 (StrategieMixte): Stratégie mixte de J2
        joueur (int): 1 ou 2
    
    Returns:
        float: Espérance de gain
    """
    esperance = 0.0
    
    for i, prob_i in enumerate(strategie_j1.probabilites):
        for j, prob_j in enumerate(strategie_j2.probabilites):
            gain = jeu.matrice_gains[i][j][joueur - 1]  # joueur - 1 car indices 0/1
            esperance += prob_i * prob_j * gain
    
    return esperance

# Tester avec Pierre-Papier-Ciseaux
esperance_j1 = esperance_gain(pierre_papier_ciseaux, strategie_equilibre, strategie_equilibre, 1)
esperance_j2 = esperance_gain(pierre_papier_ciseaux, strategie_equilibre, strategie_equilibre, 2)

print(f"\nEspérance de gain J1: {esperance_j1:.4f}")
print(f"Espérance de gain J2: {esperance_j2:.4f}")

# Résultat: ~0 pour les deux
# = À l'équilibre, aucun joueur n'a d'avantage!


[OK] JEUX RÉPÉTÉS (TRÈS IMPORTANT!)

# === QU'EST-CE QU'UN JEU RÉPÉTÉ? ===

# Jusqu'ici: jeu joué UNE SEULE FOIS
# = "One-shot game"

# JEU RÉPÉTÉ:
# - Le même jeu est joué PLUSIEURS FOIS
# - Les joueurs peuvent apprendre et s'adapter
# - L'histoire des coups précédents influence les décisions futures

# POURQUOI C'EST DIFFÉRENT?
# Exemple: Dilemme du Prisonnier
# - Une seule fois: trahir est optimal
# - Répété: coopérer peut devenir optimal!
#   -> Si tu trahis, l'autre te punira la prochaine fois

# === TYPES DE JEUX RÉPÉTÉS ===

# 1. Répétition FINIE (nombre fixe de tours)
#    - Exemple: 10 tours
#    - PROBLÈME: Au dernier tour, c'est comme un one-shot
#    - Par récurrence: trahir reste optimal!

# 2. Répétition INFINIE (ou incertaine)
#    - Les joueurs ne savent pas quand ça s'arrête
#    - Ou: il y a toujours une probabilité de rejouer
#    - SOLUTION: coopération possible!

# === STRATÉGIES CÉLÈBRES ===

# 1. TIT-FOR-TAT (Donnant-Donnant)
#    - Tour 1: Coopère
#    - Tours suivants: Fais ce que l'autre a fait au tour précédent
#    - = Si l'autre coopère, tu coopères
#    - = Si l'autre trahit, tu trahis

# 2. TOUJOURS TRAHIR (Always Defect)
#    - Tous les tours: Trahis

# 3. TOUJOURS COOPÉRER (Always Cooperate)
#    - Tous les tours: Coopère

# 4. GRIM TRIGGER (Déclencheur)
#    - Coopère jusqu'à ce que l'autre trahisse
#    - Dès qu'il trahit: trahis pour toujours

# 5. PAVLOV (Win-Stay, Lose-Shift)
#    - Si le résultat était bon: répète
#    - Si le résultat était mauvais: change


# === IMPLÉMENTATION EN PYTHON ===

class JeuRepete:
    """
    Simule un jeu répété (dilemme du prisonnier)
    """
    
    def __init__(self, jeu, n_tours):
        """
        Args:
            jeu (Jeu): Le jeu de base (doit être 2x2)
            n_tours (int): Nombre de tours
        """
        self.jeu = jeu
        self.n_tours = n_tours
        self.historique = []  # Liste de (choix_j1, choix_j2, gains)
    
    def jouer_tours(self, strategie_j1, strategie_j2):
        """
        Joue plusieurs tours avec des stratégies adaptatives
        
        Args:
            strategie_j1: Fonction (historique) -> choix
            strategie_j2: Fonction (historique) -> choix
        
        Returns:
            tuple: (total_j1, total_j2)
        """
        self.historique = []
        total_j1 = 0
        total_j2 = 0
        
        for tour in range(self.n_tours):
            # Chaque stratégie choisit en fonction de l'historique
            choix_j1 = strategie_j1(self.historique)
            choix_j2 = strategie_j2(self.historique)
            
            # Jouer le tour
            gain_j1, gain_j2 = self.jeu.matrice_gains[choix_j1][choix_j2]
            
            # Enregistrer
            self.historique.append((choix_j1, choix_j2, (gain_j1, gain_j2)))
            total_j1 += gain_j1
            total_j2 += gain_j2
        
        return total_j1, total_j2
    
    def afficher_historique(self):
        """Affiche l'historique des tours"""
        print(f"\n### Historique des {len(self.historique)} tours ###")
        for i, (choix_j1, choix_j2, gains) in enumerate(self.historique, 1):
            print(f"Tour {i}: J1={self.jeu.strategies_j1[choix_j1]:10} | J2={self.jeu.strategies_j2[choix_j2]:10} | Gains={gains}")


# === STRATÉGIES ADAPTATIVES ===

def tit_for_tat(historique):
    """
    Stratégie Tit-for-Tat
    Tour 1: Coopère
    Tours suivants: Répète le dernier coup de l'adversaire
    """
    if not historique:
        return 0  # Coopère au premier tour
    else:
        # Répète le dernier coup de l'adversaire
        _, dernier_coup_adversaire, _ = historique[-1]
        return dernier_coup_adversaire

def toujours_trahir(historique):
    """Stratégie: Toujours trahir"""
    return 1  # Trahit toujours

def toujours_cooperer(historique):
    """Stratégie: Toujours coopérer"""
    return 0  # Coopère toujours

def grim_trigger(historique):
    """
    Stratégie Grim Trigger
    Coopère jusqu'à ce que l'adversaire trahisse
    Puis trahis pour toujours
    """
    if not historique:
        return 0  # Coopère au premier tour
    
    # Vérifie si l'adversaire a déjà trahi
    for _, choix_adversaire, _ in historique:
        if choix_adversaire == 1:  # Trahison
            return 1  # Trahis pour toujours
    
    return 0  # Coopère

def pavlov(historique):
    """
    Stratégie Pavlov (Win-Stay, Lose-Shift)
    Si dernier résultat bon: répète
    Si dernier résultat mauvais: change
    """
    if not historique:
        return 0  # Coopère au premier tour
    
    mon_dernier_coup, _, (mon_gain, _) = historique[-1]
    
    # Bon résultat = gain >= -1
    # (Coopérer ensemble: -1, Trahir seul: 0, Trahir ensemble: -2)
    if mon_gain >= -1:
        return mon_dernier_coup  # Répète
    else:
        return 1 - mon_dernier_coup  # Change


# === TOURNOI DE STRATÉGIES ===

def tournoi(jeu, n_tours, strategies):
    """
    Organise un tournoi entre plusieurs stratégies
    
    Args:
        jeu (Jeu): Le jeu de base
        n_tours (int): Nombre de tours par match
        strategies (dict): {nom: fonction_strategie}
    
    Returns:
        dict: Scores totaux de chaque stratégie
    """
    noms = list(strategies.keys())
    scores = {nom: 0 for nom in noms}
    
    print(f"\n### TOURNOI - {n_tours} tours par match ###")
    
    # Chaque stratégie affronte toutes les autres
    for i, nom1 in enumerate(noms):
        for j, nom2 in enumerate(noms):
            if i >= j:  # Éviter les doublons et matchs contre soi-même
                continue
            
            jeu_repete = JeuRepete(jeu, n_tours)
            
            # J1 utilise strategie i, J2 utilise strategie j
            total_j1, total_j2 = jeu_repete.jouer_tours(strategies[nom1], strategies[nom2])
            
            scores[nom1] += total_j1
            scores[nom2] += total_j2
            
            print(f"{nom1:20} vs {nom2:20} -> {nom1}: {total_j1:6.1f} | {nom2}: {total_j2:6.1f}")
    
    # Afficher les résultats finaux
    print(f"\n### RÉSULTATS FINAUX ###")
    for nom in sorted(scores, key=scores.get, reverse=True):
        print(f"{nom:20} -> Score total: {scores[nom]:8.1f}")
    
    return scores


# === EXEMPLE DE TOURNOI ===

strategies_tournoi = {
    "Tit-for-Tat": tit_for_tat,
    "Toujours Trahir": toujours_trahir,
    "Toujours Coopérer": toujours_cooperer,
    "Grim Trigger": grim_trigger,
    "Pavlov": pavlov
}

# Lancer le tournoi
scores = tournoi(prisonnier, n_tours=100, strategies=strategies_tournoi)

# Résultat typique:
# Tit-for-Tat gagne souvent!
# POURQUOI?
# - Nice: Commence par coopérer
# - Forgiving: Revient à la coopération si l'autre coopère
# - Retaliatory: Punit les trahisons
# - Clear: Stratégie simple et prévisible
#
# = Équilibre entre coopération et punition!


[OK] APPLICATIONS RÉELLES

# === 1. ÉCONOMIE ET MARCHÉS ===

# === Guerre des prix (Duopole) ===

# Deux entreprises vendent le même produit
# Chacune peut:
#   - Prix HAUT: maximise profit mais risque de perdre des clients
#   - Prix BAS: attire clients mais réduit marge

# Matrice typique:
#                 Entreprise 2
#              Prix Haut | Prix Bas
# Entreprise 1
#  Prix Haut   (100, 100) | (20, 120)
#  Prix Bas    (120, 20)  | (50, 50)

guerre_prix = Jeu(
    strategies_j1=["Prix Haut", "Prix Bas"],
    strategies_j2=["Prix Haut", "Prix Bas"],
    matrice_gains=[
        [(100, 100), (20, 120)],
        [(120, 20), (50, 50)]
    ]
)

print("\n### GUERRE DES PRIX ###")
guerre_prix.afficher()

equilibres = guerre_prix.equilibres_nash()
print(f"\nÉquilibre de Nash:")
for eq in equilibres:
    print(f"  ({guerre_prix.strategies_j1[eq[0]]}, {guerre_prix.strategies_j2[eq[1]]}) -> {guerre_prix.matrice_gains[eq[0]][eq[1]]}")

# Résultat:
# Équilibre: (Prix Bas, Prix Bas)
# = Les deux se font la guerre des prix
# = Résultat sous-optimal (50, 50) au lieu de (100, 100)
# = C'est un dilemme du prisonnier!


# === 2. POLITIQUE ET RELATIONS INTERNATIONALES ===

# === Course aux armements ===

# Deux pays peuvent:
#   - S'ARMER: sécurité mais coûteux
#   - DÉSARMER: économies mais risque d'attaque

# Matrice:
#                 Pays 2
#              Désarme | S'arme
# Pays 1
#  Désarme    (3, 3)  | (-1, 4)
#  S'arme     (4, -1) | (0, 0)

course_armements = Jeu(
    strategies_j1=["Désarme", "S'arme"],
    strategies_j2=["Désarme", "S'arme"],
    matrice_gains=[
        [(3, 3), (-1, 4)],
        [(4, -1), (0, 0)]
    ]
)

print("\n### COURSE AUX ARMEMENTS ###")
course_armements.afficher()

# Résultat:
# Équilibre: (S'arme, S'arme)
# = Dilemme du prisonnier!
# = Traités de désarmement nécessaires pour atteindre (Désarme, Désarme)


# === 3. BIOLOGIE ET ÉVOLUTION ===

# === Jeu Faucon-Colombe (Hawk-Dove) ===

# Deux animaux se disputent une ressource (nourriture)
# Chacun peut:
#   - FAUCON: aggressif, se bat
#   - COLOMBE: pacifique, fuit si menacé

# Matrice (valeur ressource = 4, coût combat = 6):
#                  Animal 2
#               Faucon | Colombe
# Animal 1
#  Faucon      (-1, -1) | (4, 0)
#  Colombe     (0, 4)   | (2, 2)

faucon_colombe = Jeu(
    strategies_j1=["Faucon", "Colombe"],
    strategies_j2=["Faucon", "Colombe"],
    matrice_gains=[
        [(-1, -1), (4, 0)],
        [(0, 4), (2, 2)]
    ]
)

print("\n### FAUCON-COLOMBE ###")
faucon_colombe.afficher()

equilibres = faucon_colombe.equilibres_nash()
print(f"\nÉquilibres de Nash:")
for eq in equilibres:
    print(f"  ({faucon_colombe.strategies_j1[eq[0]]}, {faucon_colombe.strategies_j2[eq[1]]}) -> {faucon_colombe.matrice_gains[eq[0]][eq[1]]}")

# Résultat:
# 2 équilibres asymétriques:
# - (Faucon, Colombe)
# - (Colombe, Faucon)
#
# Dans la nature: stratégie mixte
# = Population avec % faucons et % colombes


# === 4. INFORMATIQUE ET IA ===

# === Allocation de ressources (Cloud Computing) ===

# Deux serveurs cloud veulent utiliser une ressource limitée (bande passante)
# Chacun peut:
#   - DEMANDER PEU: garantit d'obtenir, mais sous-utilise
#   - DEMANDER BEAUCOUP: risque de conflit, mais maximise utilisation

class AllocationRessources:
    """Simule l'allocation de ressources entre serveurs"""
    
    def __init__(self, ressource_totale=100):
        self.ressource_totale = ressource_totale
    
    def calculer_gain(self, demande1, demande2):
        """
        Calcule les gains selon les demandes
        
        Si somme des demandes <= ressource totale: chacun obtient sa demande
        Sinon: ressource divisée proportionnellement
        """
        somme = demande1 + demande2
        
        if somme <= self.ressource_totale:
            # Pas de conflit
            gain1 = demande1
            gain2 = demande2
        else:
            # Conflit: allocation proportionnelle
            gain1 = (demande1 / somme) * self.ressource_totale
            gain2 = (demande2 / somme) * self.ressource_totale
        
        return gain1, gain2
    
    def trouver_equilibre_nash(self, demandes_possibles):
        """
        Trouve l'équilibre de Nash pour des demandes discrètes
        
        Args:
            demandes_possibles (list): Liste des demandes possibles
        """
        equilibres = []
        
        for d1 in demandes_possibles:
            for d2 in demandes_possibles:
                gain1, gain2 = self.calculer_gain(d1, d2)
                
                est_equilibre = True
                
                # Vérifier si serveur 1 peut améliorer
                for autre_d1 in demandes_possibles:
                    if autre_d1 != d1:
                        autre_gain1, _ = self.calculer_gain(autre_d1, d2)
                        if autre_gain1 > gain1:
                            est_equilibre = False
                            break
                
                # Vérifier si serveur 2 peut améliorer
                if est_equilibre:
                    for autre_d2 in demandes_possibles:
                        if autre_d2 != d2:
                            _, autre_gain2 = self.calculer_gain(d1, autre_d2)
                            if autre_gain2 > gain2:
                                est_equilibre = False
                                break
                
                if est_equilibre:
                    equilibres.append((d1, d2, gain1, gain2))
        
        return equilibres

# Exemple
allocation = AllocationRessources(ressource_totale=100)
demandes = [20, 40, 60, 80]

print("\n### ALLOCATION DE RESSOURCES CLOUD ###")
equilibres = allocation.trouver_equilibre_nash(demandes)
print(f"\nÉquilibres de Nash:")
for d1, d2, g1, g2 in equilibres:
    print(f"  Demandes: ({d1}, {d2}) -> Gains: ({g1:.2f}, {g2:.2f})")


[OK] BIBLIOTHÈQUES PYTHON POUR LA THÉORIE DES JEUX

# === 1. NASHPY - Calcul d'équilibres de Nash ===

# Installation:
# pip install nashpy

import nashpy as nash
import numpy as np

# Créer un jeu (Dilemme du Prisonnier)
# Matrices de gains séparées pour chaque joueur

A = np.array([
    [-1, -3],  # Gains J1: Coopère vs (Coopère, Trahit)
    [0, -2]    # Gains J1: Trahit vs (Coopère, Trahit)
])

B = np.array([
    [-1, 0],   # Gains J2: Coopère vs (Coopère, Trahit)
    [-3, -2]   # Gains J2: Trahit vs (Coopère, Trahit)
])

# Créer le jeu
jeu_nash = nash.Game(A, B)

print("\n### NASHPY - Dilemme du Prisonnier ###")
print(f"Joueur 1 matrice:\n{jeu_nash.payoff_matrices[0]}")
print(f"\nJoueur 2 matrice:\n{jeu_nash.payoff_matrices[1]}")

# Trouver les équilibres de Nash
equilibres = jeu_nash.support_enumeration()
print(f"\nÉquilibres de Nash:")
for eq in equilibres:
    print(f"  J1: {eq[0]}, J2: {eq[1]}")

# Résultat:
# Équilibre en stratégies pures: ([0, 1], [0, 1])
# = (Trahit, Trahit)


# === Exemple avec stratégies mixtes: Pierre-Papier-Ciseaux ===

A_ppc = np.array([
    [0, -1, 1],   # Pierre vs (Pierre, Papier, Ciseaux)
    [1, 0, -1],   # Papier vs (Pierre, Papier, Ciseaux)
    [-1, 1, 0]    # Ciseaux vs (Pierre, Papier, Ciseaux)
])

B_ppc = -A_ppc  # Jeu à somme nulle: gain J2 = -gain J1

jeu_ppc = nash.Game(A_ppc, B_ppc)

print("\n### NASHPY - Pierre-Papier-Ciseaux ###")
equilibres_ppc = jeu_ppc.support_enumeration()
for eq in equilibres_ppc:
    print(f"  J1: {eq[0]}, J2: {eq[1]}")

# Résultat:
# Équilibre mixte: [1/3, 1/3, 1/3] pour les deux
# = Stratégie uniforme!


# === 2. GAMBIT - Outil complet de théorie des jeux ===

# Installation:
# pip install gambit

# GAMBIT offre:
# - Interface graphique pour créer des jeux
# - Calcul d'équilibres pour jeux complexes
# - Support pour jeux séquentiels (arbres de décision)
# - Algorithmes avancés (Lemke-Howson, etc.)

# Note: Gambit est plus complexe, nécessite apprentissage approfondi


# === 3. AXELROD - Tournois de stratégies pour jeux répétés ===

# Installation:
# pip install axelrod

import axelrod as axl

# Créer des joueurs avec différentes stratégies
joueurs = [
    axl.TitForTat(),           # Tit-for-Tat
    axl.Defector(),            # Toujours trahir
    axl.Cooperator(),          # Toujours coopérer
    axl.Grudger(),             # Grim Trigger
    axl.WinStayLoseShift(),    # Pavlov
    axl.Random(),              # Aléatoire
]

# Créer un tournoi
tournoi_axl = axl.Tournament(joueurs, turns=100, repetitions=10)

# Lancer le tournoi
resultats = tournoi_axl.play()

print("\n### AXELROD - Tournoi de stratégies ###")
print(f"Scores moyens:")
for i, joueur in enumerate(joueurs):
    score_moyen = resultats.scores[i].mean()
    print(f"  {joueur.__class__.__name__:20} -> {score_moyen:.2f}")

# Résultat typique:
# TitForTat obtient souvent le meilleur score!


[OK] PROJET COMPLET: SIMULATEUR DE NÉGOCIATION

# === CONTEXTE ===

# Deux entreprises négocient le partage d'un profit de 100€
# Chacune propose un montant qu'elle veut
# - Si somme <= 100: accord (chacun reçoit ce qu'il a demandé)
# - Si somme > 100: désaccord (personne ne reçoit rien)

# C'est un jeu de négociation (bargaining game)

class JeuNegociation:
    """
    Simule un jeu de négociation simple
    """
    
    def __init__(self, montant_total=100):
        self.montant_total = montant_total
    
    def negocier(self, demande_j1, demande_j2):
        """
        Exécute une négociation
        
        Args:
            demande_j1 (float): Montant demandé par J1demande_j2 (float): Montant demandé par J2
        
        Returns:
            tuple: (gain_j1, gain_j2, accord)
        """
        somme = demande_j1 + demande_j2
        
        if somme <= self.montant_total:
            # Accord
            return demande_j1, demande_j2, True
        else:
            # Désaccord
            return 0, 0, False
    
    def simuler_strategies(self, strategies_j1, strategies_j2):
        """
        Simule plusieurs stratégies
        
        Args:
            strategies_j1 (list): Liste de demandes possibles pour J1
            strategies_j2 (list): Liste de demandes possibles pour J2
        """
        print(f"\n### SIMULATION NÉGOCIATION (Total: {self.montant_total}) ###")
        print(f"\n{'J1 demande':>12} | {'J2 demande':>12} | {'J1 reçoit':>12} | {'J2 reçoit':>12} | {'Accord':>10}")
        print("-" * 70)
        
        for d1 in strategies_j1:
            for d2 in strategies_j2:
                g1, g2, accord = self.negocier(d1, d2)
                statut = "OUI" if accord else "NON"
                print(f"{d1:12.2f} | {d2:12.2f} | {g1:12.2f} | {g2:12.2f} | {statut:>10}")
    
    def trouver_equilibre_nash(self, strategies_j1, strategies_j2):
        """
        Trouve les équilibres de Nash
        """
        equilibres = []
        
        for d1 in strategies_j1:
            for d2 in strategies_j2:
                g1, g2, accord = self.negocier(d1, d2)
                
                if not accord:
                    continue  # Pas d'équilibre sur désaccord
                
                est_equilibre = True
                
                # J1 ne peut pas améliorer
                for autre_d1 in strategies_j1:
                    if autre_d1 != d1:
                        autre_g1, _, autre_accord = self.negocier(autre_d1, d2)
                        if autre_accord and autre_g1 > g1:
                            est_equilibre = False
                            break
                
                # J2 ne peut pas améliorer
                if est_equilibre:
                    for autre_d2 in strategies_j2:
                        if autre_d2 != d2:
                            _, autre_g2, autre_accord = self.negocier(d1, autre_d2)
                            if autre_accord and autre_g2 > g2:
                                est_equilibre = False
                                break
                
                if est_equilibre:
                    equilibres.append((d1, d2, g1, g2))
        
        return equilibres

# Exemple d'utilisation
negociation = JeuNegociation(montant_total=100)

# Stratégies discrètes (pour simplifier)
strategies = [20, 30, 40, 50, 60, 70, 80]

# Simuler
negociation.simuler_strategies(strategies, strategies)

# Trouver équilibres
print(f"\n### ÉQUILIBRES DE NASH ###")
equilibres = negociation.trouver_equilibre_nash(strategies, strategies)
for d1, d2, g1, g2 in equilibres:
    print(f"  Demandes: ({d1}, {d2}) -> Gains: ({g1}, {g2})")

# Résultat:
# TOUS les couples où somme = 100 sont des équilibres!
# Exemple: (40, 60), (50, 50), (60, 40), etc.
#
# POURQUOI?
# - Si J1 demande plus: désaccord (0 < gain actuel)
# - Si J1 demande moins: il perd volontairement
# - Donc aucun n'a intérêt à dévier seul
#
# = C'est un jeu de coordination avec PLUSIEURS équilibres


[OK] EXERCICES PRATIQUES

# === EXERCICE 1: Créer un nouveau jeu ===

# Crée un jeu "Inspection" entre un inspecteur et une entreprise
# - Entreprise peut: Tricher ou Respecter les règles
# - Inspecteur peut: Inspecter ou Ne pas inspecter
#
# Gains:
# - (Respecter, Ne pas inspecter): Entreprise=10, Inspecteur=0
# - (Respecter, Inspecter): Entreprise=8 (coût conformité), Inspecteur=-2 (coût inspection)
# - (Tricher, Ne pas inspecter): Entreprise=20, Inspecteur=-10 (problème non détecté)
# - (Tricher, Inspecter): Entreprise=-5 (amende), Inspecteur=5 (récompense)

# SOLUTION:
jeu_inspection = Jeu(
    strategies_j1=["Respecter", "Tricher"],
    strategies_j2=["Ne pas inspecter", "Inspecter"],
    matrice_gains=[
        [(10, 0), (8, -2)],
        [(20, -10), (-5, 5)]
    ]
)

print("\n### EXERCICE 1: Jeu d'Inspection ###")
jeu_inspection.afficher()

# Trouve l'équilibre de Nash
equilibres = jeu_inspection.equilibres_nash()
print(f"\nÉquilibres de Nash:")
for eq in equilibres:
    print(f"  ({jeu_inspection.strategies_j1[eq[0]]}, {jeu_inspection.strategies_j2[eq[1]]}) -> {jeu_inspection.matrice_gains[eq[0]][eq[1]]}")

# Résultat attendu:
# Équilibre en stratégies MIXTES
# - Entreprise triche avec probabilité p
# - Inspecteur inspecte avec probabilité q
# = Rend l'autre indifférent


# === EXERCICE 2: Tournoi de nouvelles stratégies ===

# Crée 2 nouvelles stratégies pour le dilemme du prisonnier répété:

def strategie_aleatoire(historique):
    """Joue aléatoirement (50% coopère, 50% trahit)"""
    return random.choice([0, 1])

def strategie_majoritaire(historique):
    """
    Joue ce que l'adversaire a joué le plus souvent
    Si égalité ou premier tour: coopère
    """
    if not historique:
        return 0
    
    # Compte les coopérations et trahisons de l'adversaire
    cooperations = sum(1 for _, choix_adv, _ in historique if choix_adv == 0)
    trahisons = len(historique) - cooperations
    
    if cooperations >= trahisons:
        return 0  # Coopère
    else:
        return 1  # Trahit

# Ajoute-les au tournoi
strategies_exercice = {
    "Tit-for-Tat": tit_for_tat,
    "Toujours Trahir": toujours_trahir,
    "Toujours Coopérer": toujours_cooperer,
    "Aléatoire": strategie_aleatoire,
    "Majoritaire": strategie_majoritaire
}

print("\n### EXERCICE 2: Tournoi avec nouvelles stratégies ###")
scores = tournoi(prisonnier, n_tours=100, strategies=strategies_exercice)


# === EXERCICE 3: Jeu séquentiel (arbre de décision) ===

# Implémente un jeu séquentiel simple:
# - Joueur 1 choisit: Gauche ou Droite
# - Si Gauche: jeu termine avec gains (2, 1)
# - Si Droite: Joueur 2 choisit: Haut ou Bas
#   - Si Haut: gains (1, 3)
#   - Si Bas: gains (0, 0)

class JeuSequentiel:
    """
    Représente un jeu séquentiel (arbre de décision)
    """
    
    def __init__(self):
        # Arbre représenté comme dictionnaire
        # Clé = noeud, Valeur = {choix: (suivant, gains)}
        self.arbre = {
            "racine": {
                "Gauche": (None, (2, 1)),  # None = feuille (fin du jeu)
                "Droite": ("noeud_j2", None)  # Continue vers noeud_j2
            },
            "noeud_j2": {
                "Haut": (None, (1, 3)),
                "Bas": (None, (0, 0))
            }
        }
    
    def afficher(self):
        """Affiche l'arbre"""
        print("\n### ARBRE DE DÉCISION ###")
        print("Racine (J1):")
        print("  Gauche -> Gains: (2, 1)")
        print("  Droite -> J2 choisit:")
        print("    Haut -> Gains: (1, 3)")
        print("    Bas -> Gains: (0, 0)")
    
    def resoudre_backward_induction(self):
        """
        Résout par induction à rebours (backward induction)
        = Méthode pour trouver l'équilibre dans un jeu séquentiel
        """
        print("\n### RÉSOLUTION PAR INDUCTION À REBOURS ###")
        
        # Étape 1: Analyser le choix de J2
        print("\nÉtape 1: J2 au noeud_j2")
        print("  Si Haut: J2 gagne 3")
        print("  Si Bas: J2 gagne 0")
        print("  -> J2 choisit Haut (3 > 0)")
        
        # Étape 2: Analyser le choix de J1 sachant que J2 jouera Haut
        print("\nÉtape 2: J1 à la racine")
        print("  Si Gauche: J1 gagne 2")
        print("  Si Droite: jeu continue, J2 jouera Haut -> J1 gagne 1")
        print("  -> J1 choisit Gauche (2 > 1)")
        
        print("\nÉQUILIBRE PARFAIT EN SOUS-JEUX:")
        print("  J1: Gauche")
        print("  J2: Haut (si le noeud est atteint)")
        print("  Résultat: (2, 1)")

# Tester
jeu_seq = JeuSequentiel()
jeu_seq.afficher()
jeu_seq.resoudre_backward_induction()


[OK] CONSEILS POUR ALLER PLUS LOIN

# === 1. LECTURES RECOMMANDÉES ===

# Livres:
# - "The Art of Strategy" par Avinash Dixit & Barry Nalebuff
#   -> Introduction accessible avec exemples concrets
# - "Game Theory: An Introduction" par Steven Tadelis
#   -> Niveau universitaire mais très complet
# - "Thinking Strategically" par Avinash Dixit
#   -> Applications pratiques

# Cours en ligne:
# - Coursera: "Game Theory" par Stanford & UBC
# - MIT OpenCourseWare: 14.12 Economic Applications of Game Theory
# - Khan Academy: Introduction to Game Theory


# === 2. PROJETS À RÉALISER ===

# Projet 1: Simulateur d'enchères
# - Implémente différents types d'enchères (premier prix, second prix, anglaise)
# - Compare les stratégies optimales
# - Analyse les revenus du vendeur

# Projet 2: IA pour jeux de société
# - Crée une IA pour le Tic-Tac-Toe basée sur la théorie des jeux
# - Utilise minimax avec élagage alpha-beta
# - Étends à des jeux plus complexes (Puissance 4, etc.)

# Projet 3: Modèle de marché
# - Simule un marché avec plusieurs entreprises
# - Chacune choisit prix et quantité
# - Analyse l'équilibre de Cournot ou Bertrand

# Projet 4: Réseau de confiance
# - Modélise un réseau social où les agents décident de coopérer ou trahir
# - Simule l'évolution de la coopération dans le réseau
# - Analyse l'impact de la structure du réseau


# === 3. DOMAINES D'APPLICATION ===

# Machine Learning:
# - Multi-agent reinforcement learning
# - GANs (Generative Adversarial Networks) = jeu entre générateur et discriminateur
# - Nash Q-learning pour agents multiples

# Cryptomonnaies:
# - Analyse des incitations dans Bitcoin (minage, attaques)
# - Mécanismes de consensus (preuve de travail, preuve d'enjeu)
# - Game theory of smart contracts

# Sécurité informatique:
# - Jeux attaquant-défenseur
# - Allocation de ressources de sécurité
# - Analyse de protocoles cryptographiques

# Économie:
# - Conception de mécanismes (mechanism design)
# - Théorie des enchères
# - Marchés financiers


[OK] RÉSUMÉ FINAL

# === CE QU'ON A APPRIS ===

# 1. CONCEPTS DE BASE
#    - Joueurs, stratégies, gains
#    - Matrice de gains
#    - Équilibre de Nash
#    - Stratégies dominantes/dominées

# 2. TYPES DE JEUX
#    - Coopératifs vs non-coopératifs
#    - Somme nulle vs non-nulle
#    - Simultanés vs séquentiels
#    - Information parfaite vs imparfaite

# 3. JEUX CLASSIQUES
#    - Dilemme du prisonnier
#    - Bataille des sexes
#    - Jeu de la poule mouillée
#    - Pierre-papier-ciseaux

# 4. STRATÉGIES AVANCÉES
#    - Stratégies mixtes
#    - Jeux répétés
#    - Tit-for-Tat et autres stratégies adaptatives

# 5. IMPLÉMENTATION PYTHON
#    - Classe Jeu pour représenter matrices
#    - Algorithmes pour trouver équilibres
#    - Simulations et tournois
#    - Bibliothèques: nashpy, axelrod, gambit

# 6. APPLICATIONS
#    - Économie: guerre des prix, négociations
#    - Politique: course aux armements
#    - Biologie: évolution des comportements
#    - Informatique: allocation de ressources, IA


# === POURQUOI? COMMENT? QUAND? ===

# POURQUOI étudier la théorie des jeux?
# - Prendre de meilleures décisions stratégiques
# - Comprendre et prévoir le comportement des autres
# - Modéliser des situations complexes avec plusieurs acteurs
# - Applications en IA, économie, politique, biologie

# COMMENT l'utiliser en Python?
# - Créer des classes pour représenter les jeux
# - Implémenter des algorithmes de recherche d'équilibres
# - Simuler des parties et tournois
# - Utiliser des bibliothèques spécialisées (nashpy, axelrod)

# QUAND l'appliquer?
# - Situations de conflit ou coopération
# - Décisions impliquant plusieurs acteurs
# - Optimisation de stratégies
# - Analyse de systèmes multi-agents
# - Conception de mécanismes et protocoles


# === PROCHAINES ÉTAPES ===

# 1. Pratiquer avec les exercices ci-dessus
# 2. Lire un livre d'introduction (voir recommandations)
# 3. Suivre un cours en ligne (Coursera, MIT OCW)
# 4. Implémenter vos propres jeux
# 5. Participer à des compétitions d'IA (Kaggle, etc.)
# 6. Appliquer à vos problèmes réels


# === RESSOURCES ===

# Documentation:
# - nashpy: https://nashpy.readthedocs.io/
# - axelrod: https://axelrod.readthedocs.io/
# - gambit: http://www.gambit-project.org/

# Communautés:
# - r/gametheory (Reddit)
# - Economics Stack Exchange (Game Theory tag)
# - GitHub: repositories avec "game-theory"

# Tutoriels:
# - Real Python: Game Theory tutorials
# - Towards Data Science: Game Theory articles
# - YouTube: "Game Theory 101" channel


# BONNE CHANCE ET BON CODAGE! [VIDEO_GAME][PYTHON]
```