En tant que développeur de jeux de rôle depuis huit ans, j'ai toujours cherché des moyens d'automatiser les tests d'équilibrage pour mes systèmes de combat. Quand j'ai découvert qu'une API d'IA pouvait simuler des milliers de tours de combat en quelques secondes, ma productivité a littéralement décuplé. Aujourd'hui, je vais vous montrer comment utiliser HolySheep API pour créer un simulateur de combat D&D qui révolutionnera votre processus d'équilibrage.

为什么游戏开发者需要AI战斗模拟?

La création d'un système de combat équilibré dans un jeu de rôle sur table (ou vidéo) représente l'un des défis les plus complexes du game design. Combien de dégâts doit infliger un sort de niveau 3 ? Un guerrier contre un mage est-il juste à haut niveau ? Ces questions nécessitent des centaines, voire des milliers de simulations pour trouver des réponses fiables.

Traditional manual testing consumes enormous developer resources. An AI-powered simulation approach lets you execute these test scenarios programmatically, achieving results that would take a traditional team weeks to complete in mere minutes. The HolySheep API service offers particularly compelling advantages for this use case, with response times under 50ms and costs reaching 85% lower than comparable alternatives.

先决条件与环境设置

Pour suivre ce tutoriel, vous n'avez besoin d'aucune expérience préalable avec les API. Je vais tout vous expliquer depuis le début.

工具准备

安装必要的库

Ouvrez votre terminal et exécutez la commande suivante :

pip install requests python-dotenv pandas matplotlib numpy

第一步:API连接配置

La configuration initiale représente l'étape la plus importante. Croyez-moi, j'ai perdu trois heures à cause d'une simple erreur de formatage lors de ma première tentative !

import requests
import json
import os
from dotenv import load_dotenv

Charger la clé API depuis le fichier .env

load_dotenv()

============================================

CONFIGURATION HOLYSHEEP API - OBLIGATOIRE

============================================

HOLYSHEEP_API_KEY = os.getenv("HOLYSHEEP_API_KEY") BASE_URL = "https://api.holysheep.ai/v1" # URL officielle HolySheep

IMPORTANT : Ne JAMAIS utiliser ces URLs (invalides pour HolySheep)

- https://api.openai.com/v1

- https://api.anthropic.com/v1

def get_holysheep_response(prompt: str, model: str = "deepseek-v3") -> str: """ Envoie une requête à l'API HolySheep pour générer une réponse IA. Args: prompt: La question ou instruction pour l'IA model: Le modèle à utiliser (deepseek-v3, gpt-4.1, claude-sonnet-4.5, etc.) Returns: La réponse générée par l'IA """ headers = { "Authorization": f"Bearer {HOLYSHEEP_API_KEY}", "Content-Type": "application/json" } payload = { "model": model, "messages": [ {"role": "user", "content": prompt} ], "temperature": 0.7, "max_tokens": 2000 } response = requests.post( f"{BASE_URL}/chat/completions", headers=headers, json=payload, timeout=30 ) if response.status_code == 200: return response.json()["choices"][0]["message"]["content"] else: raise Exception(f"Erreur API {response.status_code}: {response.text}")

Test de connexion

print("=== Test de connexion HolySheep API ===") try: test_response = get_holysheep_response("Répondez uniquement par 'OK' si vous comprenez.") print(f"✓ Connexion réussie ! Réponse: {test_response}") except Exception as e: print(f"✗ Erreur de connexion: {e}") print("Vérifiez votre clé API dans le fichier .env")

[Capture d'écran indicative : Résultat du test de connexion dans le terminal — vous devriez voir « ✓ Connexion réussie ! »]

第二步:创建D&D角色与战斗系统

Maintenant que notre connexion fonctionne, créons les classes Python qui représenteront nos personnages D&D et le système de combat. Cette architecture modulaire vous permettra de tester différents scénarios facilement.

import random
from dataclasses import dataclass
from typing import List, Dict, Optional
from enum import Enum

class CharacterClass(Enum):
    """Classes D&D disponibles pour la simulation"""
    GUERRIER = "Guerrier"
    MAGE = "Mage"
    VOLEUR = "Voleur"
    CLERC = "Clerc"
    RANGER = "Ranger"
    BARBARE = "Barbare"

@dataclass
class Character:
    """
    Représentation d'un personnage D&D avec ses statistiques.
    
    Attributs:
        name: Nom du personnage (ex: "Gandalf", "Conan")
        char_class: Classe D&D du personnage
        level: Niveau du personnage (1-20)
        hp: Points de vie actuels
        max_hp: Points de vie maximums
        strength: Score de Force
        dexterity: Score de Dextérité
        intelligence: Score d'Intelligence
        constitution: Score de Constitution
        armor_class: Classe d'armure (CA)
    """
    name: str
    char_class: CharacterClass
    level: int
    hp: int
    max_hp: int
    strength: int
    dexterity: int
    intelligence: int
    constitution: int
    armor_class: int
    
    def is_alive(self) -> bool:
        """Vérifie si le personnage est encore en vie"""
        return self.hp > 0
    
    def take_damage(self, damage: int) -> int:
        """
        Applique des dégâts au personnage.
        
        Returns:
            Les dégâts réels subis (peuvent être réduits)
        """
        actual_damage = max(0, damage)
        self.hp = max(0, self.hp - actual_damage)
        return actual_damage
    
    def heal(self, amount: int) -> int:
        """
        Soigne le personnage.
        
        Returns:
            Le nombre de points de vie réellement restaurés
        """
        previous_hp = self.hp
        self.hp = min(self.max_hp, self.hp + amount)
        return self.hp - previous_hp
    
    def get_modifier(self, attribute: int) -> int:
        """Calcule le modificateur d'attribut (D&D 5e rules)"""
        return (attribute - 10) // 2

class CombatSystem:
    """
    Système de combat D&D simplifié pour la simulation IA.
    
    Règles implémentées:
    - Attaque de base avec jet d'attaque
    - Jets de sauvegarde
    - Sorts de mages avec消耗 de emplacements
    - Critique sur 20 naturel
    """
    
    CRIT_RANGE = 20  # Seuil de critique
    CRIT_MULTIPLIER = 2  # Multiplicateur de dégâts critiques
    
    def __init__(self, verbose: bool = False):
        self.verbose = verbose
        self.combat_log: List[str] = []
    
    def roll_d20(self, modifier: int = 0, advantage: bool = False) -> Dict:
        """
        Simule un jet de d20 avec modificateur.
        
        Args:
            modifier: Modificateur à ajouter au jet
            advantage: Si True, garde le meilleur de 2 jets
        
        Returns:
            Dict avec 'roll', 'modifier', 'total', 'is_crit', 'is_nat_1'
        """
        if advantage:
            rolls = [random.randint(1, 20), random.randint(1, 20)]
            roll = max(rolls)
        else:
            roll = random.randint(1, 20)
        
        natural = roll
        total = roll + modifier
        
        return {
            "roll": roll,
            "modifier": modifier,
            "total": total,
            "is_crit": natural == self.CRIT_RANGE,
            "is_nat_1": natural == 1
        }
    
    def calculate_attack_damage(self, attacker: Character, defender: Character, 
                                  attack_bonus: int, damage_dice: str,
                                  damage_modifier_stat: str = "strength") -> Dict:
        """
        Calcule les dégâts d'une attaque.
        
        Args:
            attacker: Personnage qui attaque
            defender: Personnage qui reçoit l'attaque
            attack_bonus: Bonus d'attaque de l'arme/sort
            damage_dice: Dés de dégâts (ex: "2d6+3")
            damage_modifier_stat: Attribut utilisé pour les dégâts
        
        Returns:
            Dict avec détails du calcul d'attaque
        """
        stat_modifier = getattr(attacker, f"get_{damage_modifier_stat}_modifier")(0)
        if damage_modifier_stat == "strength":
            stat_modifier = attacker.get_modifier(attacker.strength)
        elif damage_modifier_stat == "intelligence":
            stat_modifier = attacker.get_modifier(attacker.intelligence)
        
        # Jet d'attaque
        attack_roll = self.roll_d20(stat_modifier)
        
        # Vérifier si l'attaque touche
        attack_total = attack_roll["total"] + attack_bonus
        hits = (attack_total >= defender.armor_class) and not attack_roll["is_nat_1"]
        
        # Calcul des dégâts
        damage_result = {"hits": hits, "damage": 0, "critical": False, "details": []}
        
        if hits:
            # Parser les dés de dégâts (format: "XdY+Z")
            import re
            match = re.match(r"(\d+)d(\d+)([+-]\d+)?", damage_dice)
            if match:
                num_dice = int(match.group(1))
                dice_size = int(match.group(2))
                bonus = int(match.group(3)) if match.group(3) else 0
                
                rolls = [random.randint(1, dice_size) for _ in range(num_dice)]
                base_damage = sum(rolls) + bonus
                
                # Critique ?
                if attack_roll["is_crit"]:
                    crit_rolls = [random.randint(1, dice_size) for _ in range(num_dice)]
                    base_damage += sum(crit_rolls)
                    damage_result["critical"] = True
                
                damage_result["damage"] = base_damage
                damage_result["details"] = rolls
        
        if self.verbose:
            crit_text = " CRITIQUE!" if damage_result["critical"] else ""
            self.combat_log.append(
                f"{attacker.name} attaque {defender.name} avec un {attack_roll['roll']}+"
                f"{attack_bonus} = {attack_total} vs CA {defender.armor_class} → "
                f"{'Touche' if hits else 'Rate'}{crit_text}"
            )
        
        return damage_result

print("✓ Classes de combat D&D créées avec succès !")
print("\nExemple de création de personnage:")

Démonstration

guerrier = Character( name="Conan le Barbare", char_class=CharacterClass.BARBARE, level=5, hp=52, max_hp=52, strength=18, dexterity=13, intelligence=10, constitution=16, armor_class=16 ) mage = Character( name="Gandalf le Gris", char_class=CharacterClass.MAGE, level=5, hp=27, max_hp=27, strength=10, dexterity=14, intelligence=18, constitution=12, armor_class=13 ) print(f"\n👤 {guerrier.name} ({guerrier.char_class.value} Nv{guerrier.level})") print(f" HP: {guerrier.hp}/{guerrier.max_hp} | FOR: {guerrier.strength} | DEX: {guerrier.dexterity}") print(f" CA: {guerrier.armor_class}") print(f"\n🧙 {mage.name} ({mage.char_class.value} Nv{mage.level})") print(f" HP: {mage.hp}/{mage.max_hp} | INT: {mage.intelligence} | CON: {mage.constitution}") print(f" CA: {mage.armor_class}")

第三步:集成HolySheep AI进行智能战斗决策

Voici la partie la plus excitante : utiliser l'IA pour prendre des décisions tactiques intelligentes ! Au lieu de coder chaque règle manuellement, nous allons demander à HolySheep de générer des décisions de combat réalistes basées sur le contexte.

import json
from typing import Tuple, List

class AICombatAdvisor:
    """
    Utilise HolySheep API pour générer des décisions tactiques de combat.
    
    Cette classe permet de:
    - Demander à l'IA de suggérer la meilleure action
    - Simuler des stratégies de combat complètes
    - Générer des rapports d'analyse de balance
    """
    
    def __init__(self, api_key: str):
        self.api_key = api_key
        self.base_url = "https://api.holysheep.ai/v1"
    
    def _call_holysheep(self, prompt: str, model: str = "deepseek-v3") -> str:
        """Appel interne à l'API HolySheep"""
        import requests
        
        headers = {
            "Authorization": f"Bearer {self.api_key}",
            "Content-Type": "application/json"
        }
        
        payload = {
            "model": model,
            "messages": [{"role": "user", "content": prompt}],
            "temperature": 0.7,
            "max_tokens": 1500
        }
        
        response = requests.post(
            f"{self.base_url}/chat/completions",
            headers=headers,
            json=payload
        )
        
        if response.status_code == 200:
            return response.json()["choices"][0]["message"]["content"]
        else:
            raise Exception(f"Erreur API: {response.status_code}")
    
    def suggest_action(self, character: Character, enemies: List[Character],
                       allies: List[Character]) -> Dict:
        """
        Demande à l'IA de suggérer la meilleure action pour un personnage.
        
        Returns:
            Dict avec 'action', 'target', 'reasoning', 'expected_outcome'
        """
        enemies_info = ", ".join([
            f"{e.name} ({e.hp}/{e.max_hp} HP, CA {e.armor_class})" 
            for e in enemies if e.is_alive()
        ])
        
        allies_info = ", ".join([
            f"{a.name} ({a.hp}/{a.max_hp} HP)" 
            for a in allies if a.is_alive()
        ])
        
        prompt = f"""
Tu es un stratège de combat D&D 5e expert. Analyse la situation suivante et suggère LA MEILLEURE action tactique.

Personnage actuel: {character.name}
Classe: {character.char_class.value} Niveau {character.level}
HP: {character.hp}/{character.max_hp}
Stats: FOR {character.strength}, DEX {character.dexterity}, INT {character.intelligence}, CON {character.constitution}
Classe d'armure: {character.armor_class}

Ennemis en vue: {enemies_info}
Alliés présents: {allies_info}

Réponds UNIQUEMENT au format JSON suivant (sans markdown):
{{
    "action": "Attaque/Sort/Défense/Soin/Évitement",
    "nom_action": "Nom spécifique de l'action ou du sort",
    "cible": "Nom de la cible ou 'soi-même' ou 'aucune'",
    "raisonnement": "Explication courte de 2-3 phrases",
    "esperance_gagne": "0-100%"
}}
"""
        
        try:
            response = self._call_holysheep(prompt)
            # Parser la réponse JSON
            return json.loads(response)
        except:
            return {
                "action": "Attaque",
                "nom_action": "Attaque de base",
                "cible": enemies[0].name if enemies else "aucune",
                "raisonnement": "Action par défaut (erreur de parsing IA)",
                "esperance_gagne": "50%"
            }
    
    def analyze_balance(self, scenario: str, iterations: int = 100) -> Dict:
        """
        Analyse l'équilibre d'un scénario de combat via IA.
        
        Args:
            scenario: Description du scénario à tester
            iterations: Nombre de simulations à analyser
        
        Returns:
            Rapport d'analyse de balance
        """
        prompt = f"""
En tant qu'expert en game design D&D 5e, analyse l'équilibre du scénario suivant:

SCÉNARIO: {scenario}

Considérant {iterations} simulations potentielles:
- Les dégâts sont-ils appropriés au niveau?
- Y a-t-il des combos trop puissants?
- Les classes sont-elles équilibrées entre elles?
- Quels ajustements recommanderais-tu?

Réponds au format JSON:
{{
    "equilibre_global": "Équilibré/Tourdisé/Imbéquilibré",
    "score_equilibre": 1-10,
    "problemes_identifies": ["problème 1", "problème 2"],
    "recommandations": ["ajustement 1", "ajustement 2"],
    "classes_problematiques": ["liste des classes"],
    "suggestion_de_modification": "Description détaillée"
}}
"""
        
        response = self._call_holysheep(prompt)
        return json.loads(response)

============================================

EXEMPLE D'UTILISATION

============================================

Initialisation de l'IA de combat

combat_ai = AICombatAdvisor(api_key="YOUR_HOLYSHEEP_API_KEY")

Scénario de test : Guerrier vs Mage

print("=== Analyse de Balance: Guerrier Nv5 vs Mage Nv5 ===\n") balance_report = combat_ai.analyze_balance( scenario="Un Guerrier (FOR 18, CA 18, 52 HP) affronte un Mage " "(INT 18, CA 13, 27 HP) en combat 1v1. Le mage a accès aux " "sorts de niveau 1-3 incluant Boule de feu (8d6).", iterations=100 ) print(f"📊 Équilibre Global: {balance_report['equilibre_global']}") print(f" Score: {balance_report['score_equilibre']}/10\n") print("⚠️ Problèmes identifiés:") for pb in balance_report['problemes_identifies']: print(f" - {pb}") print("\n💡 Recommandations:") for rec in balance_report['recommandations']: print(f" → {rec}") print(f"\n🔧 Suggestion: {balance_report['suggestion_de_modification']}")

第四步:大规模战斗模拟与统计分析

Pour tester efficacement la balance, nous devons exécuter des centaines de simulations et analyser les résultats statistiquement. Voici le système de simulation massife.

import numpy as np
import pandas as pd
from collections import defaultdict
import time
from concurrent.futures import ThreadPoolExecutor
import matplotlib.pyplot as plt

class MassCombatSimulator:
    """
    Simulateur de combat haute performance utilisant HolySheep API.
    
    Fonctionnalités:
    - Simulation parallèle de nombreux combats
    - Collecte de métriques détaillées
    - Génération de rapports statistiques
    - Visualisation des résultats
    """
    
    def __init__(self, api_key: str):
        self.api_key = api_key
        self.results = []
        self.combat_system = CombatSystem(verbose=False)
        self.ai_advisor = AICombatAdvisor(api_key)
    
    def simulate_single_combat(self, attacker: Character, defender: Character,
                                 use_ai_tactics: bool = False) -> Dict:
        """
        Simule un combat complet 1v1 jusqu'à la mort.
        
        Returns:
            Dict avec tous les détails du combat
        """
        combat_log = []
        turn = 0
        max_turns = 50  # Sécurité contre boucle infinie
        
        # Sauvegarde des HP originaux
        attacker_start_hp = attacker.hp
        defender_start_hp = defender.hp
        
        # Réinitialisation
        attacker.hp = attacker.max_hp
        defender.hp = defender.max_hp
        
        while attacker.is_alive() and defender.is_alive() and turn < max_turns:
            turn += 1
            
            # Tour de l'attaquant
            if use_ai_tactics:
                decision = self.ai_advisor.suggest_action(
                    attacker, [defender], []
                )
                # Exécuter l'action suggérée (simplifié)
                if "Boule de feu" in decision.get("nom_action", ""):
                    dmg = self._cast_fireball(attacker)
                    actual_dmg = defender.take_damage(dmg)
                else:
                    dmg_result = self.combat_system.calculate_attack_damage(
                        attacker, defender, 
                        attack_bonus=7,  # Bonus d'attaque au niveau 5
                        damage_dice="2d6+4" if attacker.strength > 14 else "1d8+3",
                        damage_modifier_stat="strength"
                    )
                    actual_dmg = dmg_result["damage"] if dmg_result["hits"] else 0
            else:
                # Combat standard (règles codées)
                dmg_result = self.combat_system.calculate_attack_damage(
                    attacker, defender,
                    attack_bonus=7,
                    damage_dice="2d6+4",
                    damage_modifier_stat="strength"
                )
                actual_dmg = dmg_result["damage"] if dmg_result["hits"] else 0
            
            if defender.is_alive() and actual_dmg > 0:
                combat_log.append(f"Tour {turn}: {attacker.name} inflige {actual_dmg} dmg")
            
            # Tour du défenseur (s'il est vivant)
            if defender.is_alive():
                if defender.char_class == CharacterClass.MAGE:
                    # Mage lance un sort
                    dmg = self._cast_fireball(defender) if turn % 3 == 0 else self._magic_missile(defender)
                else:
                    dmg_result = self.combat_system.calculate_attack_damage(
                        defender, attacker,
                        attack_bonus=5,
                        damage_dice="1d8+3",
                        damage_modifier_stat="strength"
                    )
                    dmg = dmg_result["damage"] if dmg_result["hits"] else 0
                
                if attacker.is_alive() and dmg > 0:
                    attacker.take_damage(dmg)
                    combat_log.append(f"Tour {turn}: {defender.name} inflige {dmg} dmg")
        
        winner = attacker.name if attacker.is_alive() else defender.name
        
        return {
            "winner": winner,
            "turns": turn,
            "attacker_remaining_hp": attacker.hp,
            "defender_remaining_hp": defender.hp,
            "attacker_dealt": attacker_start_hp - defender.hp if winner == attacker.name else defender_start_hp,
            "defender_dealt": defender_start_hp - attacker.hp if winner == defender.name else attacker_start_hp,
            "combat_log": combat_log
        }
    
    def _cast_fireball(self, caster: Character) -> int:
        """Simule un sort de Boule de feu (8d6 dégâts)"""
        int_mod = caster.get_modifier(caster.intelligence)
        rolls = [random.randint(1, 6) for _ in range(8)]
        return sum(rolls) + int_mod
    
    def _magic_missile(self, caster: Character) -> int:
        """Simule un sort de Missile Magique (3d4+3 dégâts)"""
        rolls = [random.randint(1, 4) for _ in range(3)]
        return sum(rolls) + 3
    
    def run_mass_simulation(self, scenario: str, iterations: int = 100,
                            use_ai: bool = False) -> pd.DataFrame:
        """
        Exécute plusieurs simulations de combat en parallèle.
        
        Args:
            scenario: Description du scénario
            iterations: Nombre de combats à simuler
            use_ai: Si True, utilise les suggestions tactiques de l'IA
        
        Returns:
            DataFrame pandas avec tous les résultats
        """
        print(f"🚀 Lancement de {iterations} simulations...")
        start_time = time.time()
        
        results_list = []
        
        for i in range(iterations):
            if i % 50 == 0:
                print(f"   Progression: {i}/{iterations} ({100*i/iterations:.0f}%)")
            
            # Création des personnages pour ce combat
            if "Guerrier" in scenario:
                fighter = Character("Fighter", CharacterClass.GUERRIER, 5, 52, 52, 18, 13, 10, 16, 18)
                wizard = Character("Wizard", CharacterClass.MAGE, 5, 27, 27, 10, 14, 18, 12, 13)
            else:
                fighter = Character("Fighter", CharacterClass.GUERRIER, 5, 52, 52, 18, 13, 10, 16, 18)
                rogue = Character("Rogue", CharacterClass.VOLEUR, 5, 38, 38, 12, 18, 14, 10, 15)
            
            result = self.simulate_single_combat(fighter, wizard, use_ai)
            result["iteration"] = i
            results_list.append(result)
        
        elapsed = time.time() - start_time
        print(f"✅ Simulations terminées en {elapsed:.2f} secondes")
        print(f"   Vitesse moyenne: {iterations/elapsed:.1f} combats/seconde")
        
        self.results = results_list
        return pd.DataFrame(results_list)
    
    def generate_report(self, df: pd.DataFrame) -> Dict:
        """
        Génère un rapport statistique complet.
        """
        wins = df["winner"].value_counts()
        
        report = {
            "total_combats": len(df),
            "victoires_par_classe": wins.to_dict(),
            "durée_moyenne": df["turns"].mean(),
            "durée_mediane": df["turns"].median(),
            "durée_std": df["turns"].std(),
            "hp_restant_moyen_vainqueur": df["winner_remaining_hp"].mean() if "winner_remaining_hp" in df.columns else 0,
            "taux_equilibre": abs(wins.iloc[0] - wins.iloc[1]) / len(df) * 100 if len(wins) == 2 else 0
        }
        
        return report

============================================

EXÉCUTION DES SIMULATIONS

============================================

print("=" * 60) print(" SIMULATION DE COMBAT D&D - HOLYSHEEP AI") print("=" * 60) simulator = MassCombatSimulator(api_key="YOUR_HOLYSHEEP_API_KEY")

Lancer 200 simulations Guerrier vs Mage

results_df = simulator.run_mass_simulation( scenario="Guerrier Nv5 vs Mage Nv5", iterations=200, use_ai=False )

Générer le rapport

report = simulator.generate_report(results_df) print("\n" + "=" * 60) print(" 📊 RAPPORT D'ÉQUILIBRE - GUERRIER VS MAGE") print("=" * 60) print(f"\nTotal des combats simulés: {report['total_combats']}") print(f"\nVictoires:") for classe, victories in report['victoires_par_classe'].items(): pct = victories / report['total_combats'] * 100 print(f" {classe}: {victories} ({pct:.1f}%)") print(f"\nDurée moyenne des combats: {report['durée_moyenne']:.1f} tours") print(f"Écart-type: {report['durée_std']:.1f} tours") print(f"\nScore d'équilibre: {100 - report['taux_equilibre']:.1f}/100")

Statistiques finales

print("\n" + "=" * 60) print(" 🏆 STATISTIQUES GLOBALES") print("=" * 60) print(f"\nMeilleure performance: Classe dominante identifiée") print(f"Analyse AI recommandée: Voir section HolySheep API")

结果可视化与平衡性报告

Les données sont excellentes, mais une visualisation claire est essentielle pour présenter vos résultats à une équipe de développement. Créons des graphiques professionnels.

import matplotlib.pyplot as plt
import numpy as np

def generate_visualizations(simulator: MassCombatSimulator, df: pd.DataFrame):
    """
    Génère des visualisations professionnelles des résultats de simulation.
    """
    
    fig, axes = plt.subplots(2, 2, figsize=(14, 10))
    fig.suptitle('Analyse d\'Équilibre D&D - HolySheep AI Simulation', 
                 fontsize=16, fontweight='bold')
    
    # 1. Graphique en camembert des victoires
    ax1 = axes[0, 0]
    wins = df['winner'].value_counts()
    colors = ['#FF6B6B', '#4ECDC4']
    labels = list(wins.index)
    sizes = list(wins.values)
    
    wedges, texts, autotexts = ax1.pie(sizes, labels=labels, autopct='%1.1f%%',
                                        colors=colors, explode=(0.05, 0.05),
                                        shadow=True, startangle=90)
    ax1.set_title('Répartition des Victoires', fontsize=12, fontweight='bold')
    
    # 2. Distribution de la durée des combats
    ax2 = axes[0, 1]
    ax2.hist(df['turns'], bins=20, color='#45B7D1', edgecolor='white', alpha=0.8)
    ax2.axvline(df['turns'].mean(), color='red', linestyle='--', 
                linewidth=2, label=f'Moyenne: {df["turns"].mean():.1f}')
    ax2.axvline(df['turns'].median(), color='orange', linestyle='--', 
                linewidth=2, label=f'Médiane: {df["turns"].median():.1f}')
    ax2.set_xlabel('Nombre de Tours')
    ax2.set_ylabel('Fréquence')
    ax2.set_title('Distribution de la Durée des Combats', fontsize=12, fontweight='bold')
    ax2.legend()
    
    # 3. HP restants par gagnant
    ax3 = axes[1, 0]
    winners_data = df[df['winner'] == df['winner'].mode()[0]]
    losers_data = df[df['winner'] != df['winner'].mode()[0]]
    
    ax3.boxplot([winners_data['winner_remaining_hp'].dropna() if 'winner_remaining_hp' in df.columns else winners_data['attacker_remaining_hp'],
                 losers_data['attacker_remaining_hp']], 
                labels=['Vainqueurs', ' Perdants'])
    ax3.set_ylabel('HP Restants')
    ax3.set_title('HP Restants après Combat', fontsize=12, fontweight='bold')
    
    # 4. Comparaison des modèles HolySheep (coût/efficacité)
    ax4 = axes[1, 1]
    models = ['DeepSeek V3.2', 'Gemini 2.5 Flash', 'GPT-4.1', 'Claude Sonnet 4.5']
    costs = [0.42, 2.50, 8.00, 15.00]
    latencies = [45, 35, 120, 180]  # en ms
    
    x = np.arange(len(models))
    width = 0.35
    
    bars1 = ax4.bar(x - width/2, [c/15*100 for c in costs], width, 
                    label='Coût ($/1M tokens)', color='#FF6B6B', alpha=0.8)
    ax4_twin = ax4.twinx()
    bars2 = ax4_twin.bar(x + width/2, latencies, width, 
                         label='Latence (ms)', color='#4ECDC4', alpha=0.8)
    
    ax4.set_ylabel('Coût relatif (%)', color='#FF6B6B')
    ax4_twin.set_ylabel('Latence (ms)', color='#4ECDC4')
    ax4.set_xticks(x)
    ax4.set_xticklabels(models, rotation=15)
    ax4.set_title('Comparaison des Modèles IA - HolySheep', fontsize=12, fontweight='bold')
    ax4.legend(loc='upper left')
    ax4_twin.legend(loc='upper right')
    
    plt.tight_layout()
    plt.savefig('combat_balance_report.png', dpi=300, bbox_inches='tight')
    print("📊 Graphiques sauvegardés: combat_balance_report.png")
    
    return fig

Génération des visualisations

generate_visualizations(simulator, results_df)

Rapport final

print("\n" + "=" * 60) print(" 📋 RAPPORT FINAL D'ÉQUILIBRE") print("=" * 60) print(""" SCÉN