Analyse de la génération de code par IA

Retour à l'accueil

Contexte de l'expérimentation : Chaque modèle d'intelligence artificielle a reçu exactement le même prompt, sans aucune modification.

Le but est d'évaluer leur capacité à interpréter une demande fonctionnelle et à la traduire en un code complet et directement utilisable.

Prompt initial : "Tu es développeur web. Développe moi une application en HTML pour une balle rebondissante en 2D pour laquelle on peut régler la taille, la couleur, la vitesse, le poids et la résistance de l’air. Interface claire et intuitive. Donne le résultat en seul bloc de code."

Analyse comparative des résultats

Cette page compare les résultats de plusieurs modèles de langage pour une tâche de programmation web. L'objectif est de voir comment chaque IA interprète le concept d' "interface claire et intuitive" et comment elle structure son code.

  • Le peloton de tête (Gemini 2.5 Pro, Claude 4, DeepSeek R1) : Ces modèles se distinguent par une excellente interprétation de la demande. Ils ne se contentent pas de fournir les fonctionnalités, mais soignent l'expérience utilisateur avec des interfaces très travaillées, des animations fluides et des ajouts pertinents (boutons reset, pause, statistiques en temps réel). Leur code est moderne, bien commenté et robuste.
  • Les solides challengers (ChatGPT-o3, Qwen 3) : Ces modèles fournissent un code très fonctionnel et propre. Ils respectent toutes les contraintes de la demande avec une approche technique solide (utilisation du canvas, gestion correcte de la boucle d'animation). L'interface est plus simple mais reste efficace et intuitive.
  • Les approches fonctionnelles (Llama 4 Maverick, Grok 3) : Ces modèles se concentrent sur la fonctionnalité brute. Le code fonctionne et les paramètres sont présents, mais l'interface est minimale et la physique parfois simplifiée. Ils répondent à la demande, mais sans l'effort supplémentaire sur l'ergonomie et le design.
  • L'approche alternative (Magistral) : Ce modèle a fait un choix technique différent en utilisant des éléments DOM (div) et du CSS pour l'animation, plutôt que l'élément Canvas. C'est une approche moins performante et moins standard pour ce type d'application, ce qui en fait un cas d'étude intéressant sur les différentes manières de résoudre un problème.

Pour un enseignant, cette comparaison est riche d'enseignements. Elle montre qu'il n'y a pas une seule "bonne" réponse en programmation et que les IA, comme les humains, ont des "styles" et des "priorités" différents. C'est un excellent support pour discuter de la qualité du code, de l'expérience utilisateur et des choix technologiques.

Gemini 2.5 Pro

Ce modèle a fourni un code de très haute qualité, qui démontre une excellente compréhension de la demande initiale.

  • Interface : Très claire, bien organisée avec Flexbox. Les contrôles sont regroupés dans un panneau distinct, ce qui est une excellente pratique.
  • Code : Extrêmement bien structuré et commenté. L'utilisation de fonctions distinctes (updateSettings, resetAndLaunch, draw, updatePosition) rend le code très lisible et facile à maintenir. C'est un exemple pédagogique parfait.
  • Fonctionnalités : Toutes les fonctionnalités demandées sont présentes et fonctionnelles. Le bouton 'Lancer / Réinitialiser' est une interprétation intelligente de la demande.
<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Balle Rebondissante 2D</title>

    <style>
        /* Importation d'une police pour un look plus moderne */
        @import url('https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;700&display=swap');

        body {
            font-family: 'Roboto', sans-serif;
            background-color: #f0f2f5;
            color: #333;
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            min-height: 100vh;
            margin: 0;
            padding: 20px;
            box-sizing: border-box;
        }

        h1 {
            color: #1a237e;
            text-align: center;
        }

        .container {
            display: flex;
            flex-wrap: wrap;
            gap: 30px;
            justify-content: center;
            align-items: flex-start;
            width: 100%;
            max-width: 1200px;
        }

        canvas {
            border: 2px solid #333;
            background-color: #ffffff;
            box-shadow: 0 4px 8px rgba(0,0,0,0.1);
            border-radius: 8px;
        }

        #controls {
            background-color: #ffffff;
            padding: 25px;
            border-radius: 8px;
            box-shadow: 0 4px 8px rgba(0,0,0,0.1);
            display: flex;
            flex-direction: column;
            gap: 20px;
            min-width: 300px;
        }

        #controls h2 {
            margin-top: 0;
            text-align: center;
            color: #3949ab;
            border-bottom: 2px solid #e0e0e0;
            padding-bottom: 10px;
        }

        .control-item {
            display: flex;
            flex-direction: column;
            gap: 5px;
        }

        .control-item label {
            font-weight: bold;
            font-size: 0.9em;
        }

        .control-item span {
            font-size: 0.8em;
            color: #555;
            text-align: center;
            background-color: #f1f1f1;
            padding: 2px 0;
            border-radius: 4px;
        }

        input[type="range"] {
            width: 100%;
            cursor: pointer;
        }

        input[type="color"] {
            width: 100%;
            height: 40px;
            border: 1px solid #ccc;
            padding: 0;
            cursor: pointer;
        }

        button {
            background-color: #3949ab;
            color: white;
            border: none;
            padding: 12px 15px;
            border-radius: 5px;
            cursor: pointer;
            font-size: 1em;
            font-weight: bold;
            transition: background-color 0.3s ease;
            margin-top: 10px;
        }

        button:hover {
            background-color: #1a237e;
        }
    </style>
</head>
<body>

    <h1>Simulateur de Balle Rebondissante 2D</h1>

    <div class="container">
        <canvas id="bouncingBallCanvas"></canvas>

        <div id="controls">
            <h2>Réglages</h2>
            
            <div class="control-item">
                <label for="size">Taille de la balle</label>
                <input type="range" id="size" min="5" max="100" value="20">
                <span id="sizeValue">20px</span>
            </div>

            <div class="control-item">
                <label for="color">Couleur</label>
                <input type="color" id="color" value="#ff6347">
            </div>
            
            <div class="control-item">
                <label for="weight">Poids (Gravité)</label>
                <input type="range" id="weight" min="0.1" max="2" value="0.5" step="0.05">
                <span id="weightValue">0.5</span>
            </div>
            
            <div class="control-item">
                <label for="airResistance">Résistance de l'air</label>
                <input type="range" id="airResistance" min="0" max="0.1" value="0.01" step="0.001">
                <span id="airResistanceValue">0.01</span>
            </div>
            
            <div class="control-item">
                <label for="speed">Vitesse initiale (au lancer)</label>
                <input type="range" id="speed" min="1" max="20" value="10" step="1">
                <span id="speedValue">10</span>
            </div>

            <button id="launchButton">Lancer / Réinitialiser</button>
        </div>
    </div>

    <script>
        document.addEventListener('DOMContentLoaded', () => {

            // --- 1. Initialisation ---
            const canvas = document.getElementById('bouncingBallCanvas');
            const ctx = canvas.getContext('2d');

            // Références aux éléments de contrôle
            const sizeInput = document.getElementById('size');
            const colorInput = document.getElementById('color');
            const weightInput = document.getElementById('weight');
            const airResistanceInput = document.getElementById('airResistance');
            const speedInput = document.getElementById('speed');
            const launchButton = document.getElementById('launchButton');

            // Références aux affichages des valeurs
            const sizeValue = document.getElementById('sizeValue');
            const weightValue = document.getElementById('weightValue');
            const airResistanceValue = document.getElementById('airResistanceValue');
            const speedValue = document.getElementById('speedValue');

            // Définir la taille du canvas
            canvas.width = 600;
            canvas.height = 400;

            // --- 2. Variables de simulation ---
            let ball = {}; // Objet qui contiendra les propriétés de la balle
            let gravity;
            let airResistance;
            let initialSpeed;
            
            // Coefficient de restitution (l'énergie conservée lors d'un rebond)
            const bounciness = 0.8; 

            // --- 3. Fonctions ---

            /**
             * Lit les valeurs des contrôles et met à jour les variables.
             */
            function updateSettings() {
                // Met à jour les propriétés de la balle
                ball.radius = parseInt(sizeInput.value);
                ball.color = colorInput.value;
                
                // Met à jour les variables de physique
                gravity = parseFloat(weightInput.value);
                airResistance = parseFloat(airResistanceInput.value);
                initialSpeed = parseInt(speedInput.value);

                // Met à jour le texte affiché sous les curseurs
                sizeValue.textContent = `${ball.radius}px`;
                weightValue.textContent = gravity.toFixed(2);
                airResistanceValue.textContent = airResistance.toFixed(3);
                speedValue.textContent = initialSpeed;
            }

            /**
             * Réinitialise la position et la vitesse de la balle (utilisé au lancement).
             */
            function resetAndLaunch() {
                updateSettings(); // S'assure que les derniers réglages sont appliqués

                ball.x = ball.radius + 20; // Position de départ X
                ball.y = canvas.height - ball.radius - 100; // Position de départ Y
                
                // Vitesse initiale basée sur le réglage
                ball.vx = initialSpeed; // Vitesse horizontale
                ball.vy = -initialSpeed / 2; // Vitesse verticale (légèrement vers le haut)
            }

            /**
             * Dessine la balle sur le canvas.
             */
            function draw() {
                // Efface le canvas précédent avant de dessiner la nouvelle frame
                ctx.clearRect(0, 0, canvas.width, canvas.height);

                ctx.beginPath();
                // Dessine un cercle : arc(x, y, rayon, angleDébut, angleFin)
                ctx.arc(ball.x, ball.y, ball.radius, 0, Math.PI * 2);
                ctx.fillStyle = ball.color;
                ctx.fill();
                ctx.closePath();
            }

            /**
             * Met à jour la position de la balle en appliquant la physique.
             */
            function updatePosition() {
                // Appliquer la gravité à la vitesse verticale
                ball.vy += gravity;

                // Appliquer la résistance de l'air
                ball.vx *= (1 - airResistance);
                ball.vy *= (1 - airResistance);

                // Mettre à jour la position en fonction de la vitesse
                ball.x += ball.vx;
                ball.y += ball.vy;

                // Détection de collision avec les murs
                // Mur du bas
                if (ball.y + ball.radius > canvas.height) {
                    ball.y = canvas.height - ball.radius; // Corrige la position pour éviter de "couler"
                    ball.vy *= -bounciness; // Inverse la vitesse verticale et applique le coefficient de restitution
                }
                // Mur de droite
                if (ball.x + ball.radius > canvas.width) {
                    ball.x = canvas.width - ball.radius;
                    ball.vx *= -bounciness;
                }
                // Mur de gauche
                if (ball.x - ball.radius < 0) {
                    ball.x = ball.radius;
                    ball.vx *= -bounciness;
                }
                // Mur du haut
                if (ball.y - ball.radius < 0) {
                    ball.y = ball.radius;
                    ball.vy *= -bounciness;
                }
            }

            /**
             * La boucle d'animation principale.
             */
            function animate() {
                updatePosition();
                draw();
                
                // Demande au navigateur d'exécuter cette fonction à la prochaine frame disponible
                requestAnimationFrame(animate);
            }


            // --- 4. Écouteurs d'événements ---

            // Met à jour la couleur et la taille en temps réel sans réinitialiser l'animation
            colorInput.addEventListener('input', () => { ball.color = colorInput.value; });
            sizeInput.addEventListener('input', () => { 
                ball.radius = parseInt(sizeInput.value); 
                sizeValue.textContent = `${ball.radius}px`;
            });

            // Pour les autres propriétés physiques, on met à jour les variables
            weightInput.addEventListener('input', updateSettings);
            airResistanceInput.addEventListener('input', updateSettings);
            speedInput.addEventListener('input', updateSettings);
            
            // Lance/Réinitialise la balle quand on clique sur le bouton
            launchButton.addEventListener('click', resetAndLaunch);

            // --- 5. Démarrage ---
            resetAndLaunch(); // Configure et lance la balle une première fois
            animate(); // Démarre la boucle d'animation
        });
    </script>

</body>
</html>

Claude 4

Claude 4 a interprété 'interface claire et intuitive' avec une forte emphase sur le design et l'expérience utilisateur.

  • Interface : La plus soignée de toutes. Utilisation de dégradés, d'ombres, d'icônes et d'une mise en page très professionnelle. C'est un véritable produit fini.
  • Code : Bien structuré, il intègre des fonctionnalités non demandées mais très pertinentes : bouton pause/reprendre, affichage des statistiques en temps réel, possibilité de repositionner la balle au clic.
  • Fonctionnalités : Va au-delà de la demande en ajoutant un paramètre pour le 'rebond' (élasticité), montrant une compréhension approfondie de la physique d'une balle rebondissante.
<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Balle Rebondissante 2D</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            min-height: 100vh;
            display: flex;
            flex-direction: column;
            color: white;
        }

        .header {
            text-align: center;
            padding: 20px;
            background: rgba(0, 0, 0, 0.2);
            backdrop-filter: blur(10px);
        }

        .header h1 {
            font-size: 2.5rem;
            margin-bottom: 10px;
            text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
        }

        .container {
            display: flex;
            flex: 1;
            gap: 20px;
            padding: 20px;
            max-width: 1400px;
            margin: 0 auto;
            width: 100%;
        }

        .canvas-container {
            flex: 1;
            background: rgba(255, 255, 255, 0.95);
            border-radius: 15px;
            padding: 20px;
            box-shadow: 0 20px 40px rgba(0, 0, 0, 0.2);
            display: flex;
            flex-direction: column;
            align-items: center;
        }

        canvas {
            border: 3px solid #333;
            border-radius: 10px;
            background: linear-gradient(45deg, #f0f0f0 25%, transparent 25%), 
                        linear-gradient(-45deg, #f0f0f0 25%, transparent 25%), 
                        linear-gradient(45deg, transparent 75%, #f0f0f0 75%), 
                        linear-gradient(-45deg, transparent 75%, #f0f0f0 75%);
            background-size: 20px 20px;
            background-position: 0 0, 0 10px, 10px -10px, -10px 0px;
        }

        .controls {
            width: 320px;
            background: rgba(255, 255, 255, 0.1);
            backdrop-filter: blur(15px);
            border-radius: 15px;
            padding: 25px;
            box-shadow: 0 20px 40px rgba(0, 0, 0, 0.2);
            border: 1px solid rgba(255, 255, 255, 0.2);
        }

        .control-group {
            margin-bottom: 25px;
        }

        .control-group label {
            display: block;
            margin-bottom: 8px;
            font-weight: 600;
            font-size: 0.95rem;
            text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.3);
        }

        .control-row {
            display: flex;
            align-items: center;
            gap: 12px;
        }

        input[type="range"] {
            flex: 1;
            height: 8px;
            border-radius: 5px;
            background: rgba(255, 255, 255, 0.3);
            outline: none;
            -webkit-appearance: none;
        }

        input[type="range"]::-webkit-slider-thumb {
            -webkit-appearance: none;
            appearance: none;
            width: 20px;
            height: 20px;
            border-radius: 50%;
            background: #fff;
            cursor: pointer;
            box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
            border: 2px solid #667eea;
        }

        input[type="range"]::-moz-range-thumb {
            width: 20px;
            height: 20px;
            border-radius: 50%;
            background: #fff;
            cursor: pointer;
            box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
            border: 2px solid #667eea;
        }

        input[type="color"] {
            width: 50px;
            height: 50px;
            border: none;
            border-radius: 50%;
            cursor: pointer;
            box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
        }

        .value-display {
            background: rgba(255, 255, 255, 0.2);
            padding: 4px 12px;
            border-radius: 15px;
            font-weight: bold;
            min-width: 60px;
            text-align: center;
            font-size: 0.9rem;
        }

        .button-group {
            display: flex;
            gap: 10px;
            margin-top: 20px;
        }

        button {
            flex: 1;
            padding: 12px;
            border: none;
            border-radius: 8px;
            font-weight: bold;
            cursor: pointer;
            transition: all 0.3s ease;
            font-size: 0.9rem;
        }

        .btn-primary {
            background: linear-gradient(135deg, #667eea, #764ba2);
            color: white;
        }

        .btn-secondary {
            background: rgba(255, 255, 255, 0.2);
            color: white;
            border: 1px solid rgba(255, 255, 255, 0.3);
        }

        button:hover {
            transform: translateY(-2px);
            box-shadow: 0 6px 12px rgba(0, 0, 0, 0.3);
        }

        .physics-info {
            background: rgba(0, 0, 0, 0.1);
            padding: 15px;
            border-radius: 10px;
            margin-top: 20px;
            font-size: 0.85rem;
            line-height: 1.4;
        }

        .stats {
            background: rgba(0, 0, 0, 0.1);
            padding: 15px;
            border-radius: 10px;
            margin-top: 15px;
            font-size: 0.85rem;
        }

        @media (max-width: 768px) {
            .container {
                flex-direction: column;
            }
            
            .controls {
                width: 100%;
            }
            
            canvas {
                width: 100%;
                max-width: 500px;
                height: 300px;
            }
        }
    </style>
</head>
<body>
    <div class="header">
        <h1>🏀 Balle Rebondissante 2D</h1>
        <p>Explorez la physique interactive avec des contrôles en temps réel</p>
    </div>

    <div class="container">
        <div class="canvas-container">
            <canvas id="canvas" width="600" height="400"></canvas>
            <div class="button-group">
                <button class="btn-primary" onclick="resetBall()">🔄 Réinitialiser</button>
                <button class="btn-secondary" onclick="togglePause()" id="pauseBtn">⏸️ Pause</button>
            </div>
        </div>

        <div class="controls">
            <div class="control-group">
                <label>🎨 Couleur de la balle</label>
                <div class="control-row">
                    <input type="color" id="colorPicker" value="#ff6b6b">
                    <span class="value-display" id="colorValue">#ff6b6b</span>
                </div>
            </div>

            <div class="control-group">
                <label>📏 Taille (rayon)</label>
                <div class="control-row">
                    <input type="range" id="sizeSlider" min="5" max="50" value="20">
                    <span class="value-display" id="sizeValue">20px</span>
                </div>
            </div>

            <div class="control-group">
                <label>⚡ Vitesse initiale</label>
                <div class="control-row">
                    <input type="range" id="speedSlider" min="1" max="15" value="8">
                    <span class="value-display" id="speedValue">8</span>
                </div>
            </div>

            <div class="control-group">
                <label>⚖️ Poids (gravité)</label>
                <div class="control-row">
                    <input type="range" id="weightSlider" min="0" max="2" step="0.1" value="0.5">
                    <span class="value-display" id="weightValue">0.5</span>
                </div>
            </div>

            <div class="control-group">
                <label>🌪️ Résistance de l'air</label>
                <div class="control-row">
                    <input type="range" id="airResistanceSlider" min="0" max="0.02" step="0.001" value="0.005">
                    <span class="value-display" id="airResistanceValue">0.005</span>
                </div>
            </div>

            <div class="control-group">
                <label>🏀 Rebond (élasticité)</label>
                <div class="control-row">
                    <input type="range" id="bounceSlider" min="0.1" max="1" step="0.1" value="0.8">
                    <span class="value-display" id="bounceValue">0.8</span>
                </div>
            </div>

            <div class="physics-info">
                <strong>💡 Conseils physiques :</strong><br>
                • Poids → Force gravitationnelle<br>
                • Résistance → Friction avec l'air<br>
                • Rebond → Conservation d'énergie<br>
                • Cliquez pour repositionner la balle
            </div>

            <div class="stats">
                <strong>📊 Statistiques :</strong><br>
                <span id="statsDisplay">Vitesse: 0 | Position: (0,0)</span>
            </div>
        </div>
    </div>

    <script>
        const canvas = document.getElementById('canvas');
        const ctx = canvas.getContext('2d');
        
        // État de la balle
        let ball = {
            x: canvas.width / 2,
            y: canvas.height / 2,
            vx: 0,
            vy: 0,
            radius: 20,
            color: '#ff6b6b',
            gravity: 0.5,
            airResistance: 0.005,
            bounce: 0.8,
            initialSpeed: 8
        };

        let isRunning = true;
        let animationId;

        // Éléments de contrôle
        const colorPicker = document.getElementById('colorPicker');
        const sizeSlider = document.getElementById('sizeSlider');
        const speedSlider = document.getElementById('speedSlider');
        const weightSlider = document.getElementById('weightSlider');
        const airResistanceSlider = document.getElementById('airResistanceSlider');
        const bounceSlider = document.getElementById('bounceSlider');

        // Éléments d'affichage des valeurs
        const colorValue = document.getElementById('colorValue');
        const sizeValue = document.getElementById('sizeValue');
        const speedValue = document.getElementById('speedValue');
        const weightValue = document.getElementById('weightValue');
        const airResistanceValue = document.getElementById('airResistanceValue');
        const bounceValue = document.getElementById('bounceValue');
        const statsDisplay = document.getElementById('statsDisplay');

        // Gestionnaires d'événements pour les contrôles
        colorPicker.addEventListener('input', (e) => {
            ball.color = e.target.value;
            colorValue.textContent = e.target.value;
        });

        sizeSlider.addEventListener('input', (e) => {
            ball.radius = parseInt(e.target.value);
            sizeValue.textContent = e.target.value + 'px';
        });

        speedSlider.addEventListener('input', (e) => {
            ball.initialSpeed = parseInt(e.target.value);
            speedValue.textContent = e.target.value;
        });

        weightSlider.addEventListener('input', (e) => {
            ball.gravity = parseFloat(e.target.value);
            weightValue.textContent = e.target.value;
        });

        airResistanceSlider.addEventListener('input', (e) => {
            ball.airResistance = parseFloat(e.target.value);
            airResistanceValue.textContent = e.target.value;
        });

        bounceSlider.addEventListener('input', (e) => {
            ball.bounce = parseFloat(e.target.value);
            bounceValue.textContent = e.target.value;
        });

        // Clic sur le canvas pour repositionner la balle
        canvas.addEventListener('click', (e) => {
            const rect = canvas.getBoundingClientRect();
            const scaleX = canvas.width / rect.width;
            const scaleY = canvas.height / rect.height;
            
            ball.x = (e.clientX - rect.left) * scaleX;
            ball.y = (e.clientY - rect.top) * scaleY;
            
            // Donner une vitesse aléatoire
            const angle = Math.random() * Math.PI * 2;
            ball.vx = Math.cos(angle) * ball.initialSpeed;
            ball.vy = Math.sin(angle) * ball.initialSpeed;
        });

        function resetBall() {
            ball.x = canvas.width / 2;
            ball.y = canvas.height / 2;
            const angle = Math.random() * Math.PI * 2;
            ball.vx = Math.cos(angle) * ball.initialSpeed;
            ball.vy = Math.sin(angle) * ball.initialSpeed;
        }

        function togglePause() {
            isRunning = !isRunning;
            const pauseBtn = document.getElementById('pauseBtn');
            if (isRunning) {
                pauseBtn.textContent = '⏸️ Pause';
                animate();
            } else {
                pauseBtn.textContent = '▶️ Reprendre';
                cancelAnimationFrame(animationId);
            }
        }

        function updatePhysics() {
            // Appliquer la gravité
            ball.vy += ball.gravity;
            
            // Appliquer la résistance de l'air
            ball.vx *= (1 - ball.airResistance);
            ball.vy *= (1 - ball.airResistance);
            
            // Mettre à jour la position
            ball.x += ball.vx;
            ball.y += ball.vy;
            
            // Collision avec les bords
            if (ball.x + ball.radius > canvas.width) {
                ball.x = canvas.width - ball.radius;
                ball.vx *= -ball.bounce;
            }
            if (ball.x - ball.radius < 0) {
                ball.x = ball.radius;
                ball.vx *= -ball.bounce;
            }
            if (ball.y + ball.radius > canvas.height) {
                ball.y = canvas.height - ball.radius;
                ball.vy *= -ball.bounce;
            }
            if (ball.y - ball.radius < 0) {
                ball.y = ball.radius;
                ball.vy *= -ball.bounce;
            }
        }

        function drawBall() {
            // Effet d'ombre
            ctx.shadowColor = 'rgba(0, 0, 0, 0.3)';
            ctx.shadowBlur = 10;
            ctx.shadowOffsetX = 3;
            ctx.shadowOffsetY = 3;
            
            // Dessiner la balle avec un dégradé
            const gradient = ctx.createRadialGradient(
                ball.x - ball.radius/3, ball.y - ball.radius/3, 0,
                ball.x, ball.y, ball.radius
            );
            gradient.addColorStop(0, lightenColor(ball.color, 40));
            gradient.addColorStop(1, ball.color);
            
            ctx.beginPath();
            ctx.arc(ball.x, ball.y, ball.radius, 0, Math.PI * 2);
            ctx.fillStyle = gradient;
            ctx.fill();
            
            // Réinitialiser l'ombre
            ctx.shadowColor = 'transparent';
        }

        function lightenColor(color, percent) {
            const num = parseInt(color.replace("#", ""), 16);
            const amt = Math.round(2.55 * percent);
            const R = (num >> 16) + amt;
            const G = (num >> 8 & 0x00FF) + amt;
            const B = (num & 0x0000FF) + amt;
            return "#" + (0x1000000 + (R < 255 ? R < 1 ? 0 : R : 255) * 0x10000 +
                (G < 255 ? G < 1 ? 0 : G : 255) * 0x100 +
                (B < 255 ? B < 1 ? 0 : B : 255)).toString(16).slice(1);
        }

        function updateStats() {
            const speed = Math.sqrt(ball.vx * ball.vx + ball.vy * ball.vy).toFixed(1);
            const posX = Math.round(ball.x);
            const posY = Math.round(ball.y);
            statsDisplay.textContent = `Vitesse: ${speed} | Position: (${posX}, ${posY})`;
        }

        function animate() {
            if (!isRunning) return;
            
            // Effacer le canvas
            ctx.clearRect(0, 0, canvas.width, canvas.height);
            
            // Mettre à jour la physique
            updatePhysics();
            
            // Dessiner la balle
            drawBall();
            
            // Mettre à jour les statistiques
            updateStats();
            
            animationId = requestAnimationFrame(animate);
        }

        // Initialiser la balle avec une vitesse aléatoire
        resetBall();
        
        // Démarrer l'animation
        animate();
    </script>
</body>
</html>

DeepSeek R1

Ce modèle a également mis l'accent sur l'esthétique, avec un style très moderne et des effets visuels.

  • Interface : Très stylisée avec un fond en dégradé et des panneaux de contrôle 'verre dépoli' (backdrop-filter). C'est visuellement très attractif.
  • Code : Le code est propre et ajoute des détails graphiques comme un effet de lumière sur la balle pour simuler un reflet, ce qui n'était pas demandé mais améliore le rendu.
  • Fonctionnalités : Toutes les fonctionnalités sont là. Le bouton de réinitialisation lance la balle avec une direction légèrement aléatoire, ce qui ajoute un peu de dynamisme.
<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Balle Rebondissante</title>
    <style>
        * {
            box-sizing: border-box;
            margin: 0;
            padding: 0;
            font-family: 'Segoe UI', sans-serif;
        }
        body {
            display: flex;
            flex-direction: column;
            align-items: center;
            min-height: 100vh;
            background: linear-gradient(135deg, #1a2a6c, #2c3e50);
            padding: 20px;
            color: white;
        }
        header {
            text-align: center;
            margin-bottom: 25px;
        }
        h1 {
            font-size: 2.5rem;
            margin-bottom: 10px;
            text-shadow: 0 2px 4px rgba(0,0,0,0.3);
        }
        .container {
            display: flex;
            gap: 30px;
            max-width: 1200px;
            width: 100%;
        }
        .controls {
            background: rgba(255, 255, 255, 0.1);
            backdrop-filter: blur(10px);
            border-radius: 15px;
            padding: 25px;
            width: 350px;
            box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
        }
        .control-group {
            margin-bottom: 20px;
        }
        label {
            display: block;
            margin-bottom: 8px;
            font-weight: 500;
        }
        input[type="range"], input[type="color"] {
            width: 100%;
        }
        input[type="range"] {
            height: 8px;
            border-radius: 4px;
            background: #4a5568;
            outline: none;
        }
        .value-display {
            display: flex;
            justify-content: space-between;
            margin-top: 5px;
            font-size: 0.9rem;
            color: #cbd5e0;
        }
        canvas {
            background: rgba(0, 0, 0, 0.2);
            border-radius: 15px;
            box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
        }
        button {
            background: linear-gradient(45deg, #3498db, #8e44ad);
            color: white;
            border: none;
            padding: 12px 25px;
            border-radius: 50px;
            font-size: 1rem;
            cursor: pointer;
            transition: all 0.3s ease;
            font-weight: 600;
            margin-top: 10px;
            width: 100%;
            box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
        }
        button:hover {
            transform: translateY(-3px);
            box-shadow: 0 6px 20px rgba(0, 0, 0, 0.3);
        }
        .simulation {
            flex: 1;
        }
        .info {
            font-style: italic;
            color: #a0aec0;
            font-size: 0.9rem;
            margin-top: 15px;
            text-align: center;
        }
        @media (max-width: 900px) {
            .container {
                flex-direction: column;
                align-items: center;
            }
            .controls {
                width: 100%;
                max-width: 500px;
            }
        }
    </style>
</head>
<body>
    <header>
        <h1>Balle Rebondissante 2D</h1>
        <p>Simulation physique interactive avec paramètres ajustables</p>
    </header>

    <div class="container">
        <div class="controls">
            <div class="control-group">
                <label for="size">Taille de la balle</label>
                <input type="range" id="size" min="5" max="60" value="30">
                <div class="value-display">
                    <span>Petite</span>
                    <span id="size-value">30 px</span>
                    <span>Grande</span>
                </div>
            </div>

            <div class="control-group">
                <label for="color">Couleur de la balle</label>
                <input type="color" id="color" value="#FF6B6B">
            </div>

            <div class="control-group">
                <label for="speed">Vitesse initiale</label>
                <input type="range" id="speed" min="1" max="20" value="8" step="0.5">
                <div class="value-display">
                    <span>Lente</span>
                    <span id="speed-value">8</span>
                    <span>Rapide</span>
                </div>
            </div>

            <div class="control-group">
                <label for="gravity">Poids (Gravité)</label>
                <input type="range" id="gravity" min="0.1" max="2" value="0.5" step="0.1">
                <div class="value-display">
                    <span>Léger</span>
                    <span id="gravity-value">0.5</span>
                    <span>Lourd</span>
                </div>
            </div>

            <div class="control-group">
                <label for="resistance">Résistance de l'air</label>
                <input type="range" id="resistance" min="0.95" max="1" value="0.99" step="0.001">
                <div class="value-display">
                    <span>Forte</span>
                    <span id="resistance-value">0.99</span>
                    <span>Faible</span>
                </div>
            </div>

            <button id="reset">Réinitialiser la simulation</button>
            <p class="info">Les paramètres modifient la physique en temps réel</p>
        </div>

        <div class="simulation">
            <canvas id="ballCanvas" width="700" height="500"></canvas>
        </div>
    </div>

    <script>
        // Initialisation du canvas
        const canvas = document.getElementById('ballCanvas');
        const ctx = canvas.getContext('2d');

        // Configuration initiale
        const config = {
            x: canvas.width / 2,
            y: canvas.height / 4,
            radius: 30,
            color: '#FF6B6B',
            vx: 0,
            vy: 0,
            speed: 8,
            gravity: 0.5,
            damping: 0.99,
            airResistance: 0.99
        };

        // Référence aux éléments de contrôle
        const sizeSlider = document.getElementById('size');
        const colorPicker = document.getElementById('color');
        const speedSlider = document.getElementById('speed');
        const gravitySlider = document.getElementById('gravity');
        const resistanceSlider = document.getElementById('resistance');
        const resetButton = document.getElementById('reset');

        // Affichage des valeurs
        const sizeValue = document.getElementById('size-value');
        const speedValue = document.getElementById('speed-value');
        const gravityValue = document.getElementById('gravity-value');
        const resistanceValue = document.getElementById('resistance-value');

        // Initialiser les valeurs
        sizeValue.textContent = sizeSlider.value + ' px';
        speedValue.textContent = speedSlider.value;
        gravityValue.textContent = gravitySlider.value;
        resistanceValue.textContent = resistanceSlider.value;

        // Gestionnaires d'événements
        sizeSlider.addEventListener('input', () => {
            config.radius = parseInt(sizeSlider.value);
            sizeValue.textContent = sizeSlider.value + ' px';
        });

        colorPicker.addEventListener('input', () => {
            config.color = colorPicker.value;
        });

        speedSlider.addEventListener('input', () => {
            config.speed = parseFloat(speedSlider.value);
            speedValue.textContent = speedSlider.value;
        });

        gravitySlider.addEventListener('input', () => {
            config.gravity = parseFloat(gravitySlider.value);
            gravityValue.textContent = gravitySlider.value;
        });

        resistanceSlider.addEventListener('input', () => {
            config.airResistance = parseFloat(resistanceSlider.value);
            resistanceValue.textContent = resistanceSlider.value;
        });

        resetButton.addEventListener('click', () => {
            config.x = canvas.width / 2;
            config.y = canvas.height / 4;
            config.vx = (Math.random() - 0.5) * config.speed;
            config.vy = Math.random() * config.speed;
        });

        // Fonction de dessin
        function drawBall() {
            ctx.clearRect(0, 0, canvas.width, canvas.height);

            // Dessiner la balle
            ctx.beginPath();
            ctx.arc(config.x, config.y, config.radius, 0, Math.PI * 2);
            ctx.fillStyle = config.color;
            ctx.fill();
            ctx.closePath();

            // Ajouter un effet de lumière
            ctx.beginPath();
            ctx.arc(
                config.x - config.radius * 0.3,
                config.y - config.radius * 0.3,
                config.radius * 0.4,
                0,
                Math.PI * 2
            );
            ctx.fillStyle = 'rgba(255, 255, 255, 0.4)';
            ctx.fill();
        }

        // Fonction de mise à jour de la physique
        function updatePhysics() {
            // Appliquer la gravité
            config.vy += config.gravity;

            // Appliquer la résistance de l'air
            config.vx *= config.airResistance;
            config.vy *= config.airResistance;

            // Mettre à jour la position
            config.x += config.vx;
            config.y += config.vy;

            // Collisions avec les bords
            if (config.x - config.radius < 0) {
                config.x = config.radius;
                config.vx = -config.vx * config.damping;
            } else if (config.x + config.radius > canvas.width) {
                config.x = canvas.width - config.radius;
                config.vx = -config.vx * config.damping;
            }

            if (config.y - config.radius < 0) {
                config.y = config.radius;
                config.vy = -config.vy * config.damping;
            } else if (config.y + config.radius > canvas.height) {
                config.y = canvas.height - config.radius;
                config.vy = -config.vy * config.damping;

                // Frottement au sol
                config.vx *= 0.9;
            }
        }

        // Boucle d'animation
        function animate() {
            updatePhysics();
            drawBall();
            requestAnimationFrame(animate);
        }

        // Démarrer la simulation
        resetButton.click();
        animate();
    </script>
</body>
</html>

ChatGPT-o3

ChatGPT a produit un code techniquement très solide et bien pensé.

  • Interface : Simple, fonctionnelle et responsive. L'interface s'adapte bien à différentes tailles d'écran.
  • Code : Très propre et bien organisé. Il prend en compte le `devicePixelRatio` (DPR) pour un affichage net sur les écrans à haute résolution (Retina), ce qui est un détail technique avancé et appréciable. La physique est également bien gérée.
  • Fonctionnalités : Toutes les demandes sont respectées à la lettre. Le code est efficace et va droit au but.
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Balle rebondissante 2D</title>
<style>
    body{
        margin:0;
        font-family:Arial,Helvetica,sans-serif;
        background:#f4f4f4;
        display:flex;
        flex-direction:column;
        align-items:center;
    }
    #controls{
        width:100%;
        display:flex;
        flex-wrap:wrap;
        gap:1rem;
        justify-content:center;
        background:#fff;
        box-shadow:0 2px 6px rgba(0,0,0,.1);
        padding:1rem;
    }
    .ctrl{
        display:flex;
        flex-direction:column;
        font-size:.9rem;
    }
    .ctrl span{font-weight:bold}
    #canvasWrapper{
        width:80vw;
        max-width:800px;
        aspect-ratio:4/3;
        background:#fff;
        border:1px solid #ccc;
        margin:1.2rem 0;
        position:relative;
    }
    canvas{
        width:100%;
        height:100%;
        display:block;
    }
    button{
        cursor:pointer;
        padding:.5rem 1.2rem;
        font-size:1rem;
    }
</style>
</head>
<body>

<!-- Panneau de contrôles -->
<div id="controls">
    <div class="ctrl">
        <label>Taille (px): <span id="sizeVal">40</span></label>
        <input id="size" type="range" min="10" max="100" value="40">
    </div>
    <div class="ctrl">
        <label>Couleur:</label>
        <input id="color" type="color" value="#ff5722">
    </div>
    <div class="ctrl">
        <label>Vitesse initiale (px/s): <span id="speedVal">200</span></label>
        <input id="speed" type="range" min="0" max="500" value="200">
    </div>
    <div class="ctrl">
        <label>Poids (kg): <span id="massVal">1</span></label>
        <input id="mass" type="range" min="0.2" max="5" step="0.1" value="1">
    </div>
    <div class="ctrl">
        <label>Résistance de l’air (0-0,1): <span id="dragVal">0.01</span></label>
        <input id="drag" type="range" min="0" max="0.1" step="0.005" value="0.01">
    </div>
    <button id="reset">Reset</button>
</div>

<!-- Zone de dessin -->
<div id="canvasWrapper">
    <canvas id="canvas"></canvas>
</div>

<script>
/* ---------- Mise en place du canvas ---------- */
const canvas   = document.getElementById('canvas');
const ctx      = canvas.getContext('2d');
const DPR      = window.devicePixelRatio || 1;

function resizeCanvas(){
    const rect = canvas.parentElement.getBoundingClientRect();
    canvas.width  = rect.width  * DPR;
    canvas.height = rect.height * DPR;
}
resizeCanvas();
window.addEventListener('resize',resizeCanvas);

/* ---------- Contrôles UI ---------- */
const ui    = {
    size : document.getElementById('size'),
    color: document.getElementById('color'),
    speed: document.getElementById('speed'),
    mass : document.getElementById('mass'),
    drag : document.getElementById('drag')
};
const label = {
    sizeVal : document.getElementById('sizeVal'),
    speedVal: document.getElementById('speedVal'),
    massVal : document.getElementById('massVal'),
    dragVal : document.getElementById('dragVal')
};
Object.keys(ui).forEach(k=>{
    ui[k].addEventListener('input',()=>updateParam(k));
});

/* ---------- Balle & Physique ---------- */
let ball = {};
const g  = 980;          // gravité (px/s²)
const ELASTICITY = 0.75; // coefficient de restitution

function resetBall(){
    // angle 45° vers le haut-droite
    const speed   = +ui.speed.value;
    const angle   = -Math.PI/4;
    ball = {
        r    : +ui.size.value,
        color: ui.color.value,
        x    : canvas.width/2,
        y    : canvas.height/2,
        vx   : speed*Math.cos(angle),
        vy   : speed*Math.sin(angle),
        mass : +ui.mass.value,
        drag : +ui.drag.value
    };
    reflectUI();
}
function reflectUI(){
    label.sizeVal .textContent = ui.size.value;
    label.speedVal.textContent = ui.speed.value;
    label.massVal .textContent = ui.mass.value;
    label.dragVal .textContent = ui.drag.value;
}
function updateParam(key){
    reflectUI();
    if(key==='color'){ ball.color = ui.color.value; return; }
    const val = +ui[key].value;
    switch(key){
        case 'size': ball.r    = val; break;
        case 'mass': ball.mass = val; break;
        case 'drag': ball.drag = val; break;
        case 'speed': {        // on garde la direction actuelle
            const ang = Math.atan2(ball.vy,ball.vx);
            ball.vx   = val*Math.cos(ang);
            ball.vy   = val*Math.sin(ang);
        } break;
    }
}
document.getElementById('reset').addEventListener('click',resetBall);

/* ---------- Boucle d’animation ---------- */
let last = 0;
function loop(ts){
    const dt = Math.min(0.033,(ts-last)/1000); // sécurité dt ≤ 33 ms
    last = ts;
    update(dt);
    render();
    requestAnimationFrame(loop);
}
function update(dt){
    // Gravité
    ball.vy += g*dt;
    
    // Trainée (drag quadratique simplifiée)
    const v  = Math.hypot(ball.vx,ball.vy);
    if(v>0){
        const Fd = ball.drag * v*v;      // force de trainée
        const ax = -Fd * (ball.vx/v) / ball.mass;
        const ay = -Fd * (ball.vy/v) / ball.mass;
        ball.vx += ax*dt;
        ball.vy += ay*dt;
    }
    // Mouvement
    ball.x += ball.vx*dt;
    ball.y += ball.vy*dt;
    
    // Collisions parois
    if(ball.x-ball.r < 0){
        ball.x  = ball.r;
        ball.vx = Math.abs(ball.vx)*ELASTICITY;
    }
    if(ball.x+ball.r > canvas.width){
        ball.x  = canvas.width-ball.r;
        ball.vx = -Math.abs(ball.vx)*ELASTICITY;
    }
    if(ball.y-ball.r < 0){
        ball.y  = ball.r;
        ball.vy = Math.abs(ball.vy)*ELASTICITY;
    }
    if(ball.y+ball.r > canvas.height){
        ball.y  = canvas.height-ball.r;
        ball.vy = -Math.abs(ball.vy)*ELASTICITY;
    }
}
function render(){
    ctx.clearRect(0,0,canvas.width,canvas.height);
    ctx.fillStyle = ball.color;
    ctx.beginPath();
    ctx.arc(ball.x,ball.y,ball.r,0,Math.PI*2);
    ctx.fill();
}

/* ---------- Démarrage ---------- */
resetBall();
requestAnimationFrame(loop);
</script>
</body>
</html>

Qwen 3

Qwen a fourni une solution directe et fonctionnelle, en utilisant des techniques modernes.

  • Interface : Utilise `grid` pour la mise en page des contrôles, ce qui est une approche moderne en CSS. L'interface est claire et sans fioritures.
  • Code : Le code est concis et facile à comprendre. La logique de mise à jour des propriétés de la balle est bien centralisée.
  • Fonctionnalités : L'essentiel est là et fonctionne correctement. C'est une réponse solide qui remplit le cahier des charges.
<!DOCTYPE html>
<html>
<head>
    <title>Balle Rebondissante 2D</title>
    <style>
        body { font-family: Arial, sans-serif; display: flex; flex-direction: column; align-items: center; margin: 0; padding: 20px; background: #f0f0f0; }
        canvas { border: 1px solid #ccc; margin: 20px 0; }
        .controls { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px; margin-bottom: 20px; background: white; padding: 15px; border-radius: 8px; box-shadow: 0 2px 5px rgba(0,0,0,0.1); }
        label { display: flex; flex-direction: column; font-size: 14px; }
        input[type="range"], input[type="color"] { margin-top: 5px; }
    </style>
</head>
<body>
    <h1>Balle Rebondissante 2D</h1>
    <div class="controls">
        <label>Taille : <span id="sizeVal">20</span>px
            <input type="range" id="size" min="5" max="100" value="20">
        </label>
        <label>Couleur :
            <input type="color" id="color" value="#ff0000">
        </label>
        <label>Vitesse : <span id="speedVal">5</span>px/s
            <input type="range" id="speed" min="1" max="20" value="5">
        </label>
        <label>Poids : <span id="weightVal">0.5</span>kg
            <input type="range" id="weight" min="0.1" max="2" step="0.1" value="0.5">
        </label>
        <label>Résistance : <span id="airResVal">0.99</span>
            <input type="range" id="airResistance" min="0.9" max="1" step="0.01" value="0.99">
        </label>
    </div>
    <canvas id="canvas" width="600" height="400"></canvas>

    <script>
        const canvas = document.getElementById('canvas');
        const ctx = canvas.getContext('2d');
        let ball = {
            x: 100,
            y: 100,
            radius: 20,
            color: '#ff0000',
            vx: 5,
            vy: 1,
            weight: 0.5,
            airResistance: 0.99
        };

        const inputs = {
            size: document.getElementById('size'),
            color: document.getElementById('color'),
            speed: document.getElementById('speed'),
            weight: document.getElementById('weight'),
            airResistance: document.getElementById('airResistance')
        };

        const displayValues = {
            sizeVal: document.getElementById('sizeVal'),
            speedVal: document.getElementById('speedVal'),
            weightVal: document.getElementById('weightVal'),
            airResVal: document.getElementById('airResVal')
        };

        function updateBallProperties() {
            ball.radius = parseInt(inputs.size.value);
            ball.color = inputs.color.value;
            ball.vx = parseFloat(inputs.speed.value);
            ball.weight = parseFloat(inputs.weight.value);
            ball.airResistance = parseFloat(inputs.airResistance.value);
            Object.entries(displayValues).forEach(([key, el]) => el.textContent = ball[key.replace('Val', '')]);
        }

        Object.values(inputs).forEach(input => input.addEventListener('input', updateBallProperties));
        updateBallProperties();

        function animate() {
            ctx.clearRect(0, 0, canvas.width, canvas.height);
            
            // Appliquer la physique
            ball.vy += 0.2 * ball.weight; // Gravité
            ball.vx *= ball.airResistance;
            ball.vy *= ball.airResistance;
            
            ball.x += ball.vx;
            ball.y += ball.vy;

            // Gestion des collisions
            if (ball.x + ball.radius > canvas.width || ball.x - ball.radius < 0) {
                ball.vx *= -1;
                ball.x = Math.max(ball.radius, Math.min(canvas.width - ball.radius, ball.x));
            }
            if (ball.y + ball.radius > canvas.height || ball.y - ball.radius < 0) {
                ball.vy *= -0.8; // Amortissement au rebond
                ball.y = Math.max(ball.radius, Math.min(canvas.height - ball.radius, ball.y));
            }

            // Dessiner la balle
            ctx.beginPath();
            ctx.arc(ball.x, ball.y, ball.radius, 0, Math.PI*2);
            ctx.fillStyle = ball.color;
            ctx.fill();
            ctx.closePath();

            requestAnimationFrame(animate);
        }

        animate();
    </script>
</body>
</html>

Llama 4 Maverick

Ce modèle a opté pour une approche très simple et directe.

  • Interface : Très basique, sans effort de style particulier. Les éléments sont simplement placés les uns sous les autres.
  • Code : Le code est fonctionnel mais moins structuré que d'autres. La gestion du temps (`dt`) pour la boucle d'animation est une bonne pratique, mais la physique reste simple.
  • Fonctionnalités : Tous les paramètres sont contrôlables, mais leur impact n'est pas toujours aussi visible que dans d'autres versions.
<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Balle rebondissante 2D</title>
    <style>
        body {
            font-family: Arial, sans-serif;
        }
        #canvas {
            border: 1px solid black;
        }
        .controls {
            margin-top: 10px;
        }
        label {
            display: block;
            margin-bottom: 5px;
        }
    </style>
</head>
<body>
    <canvas id="canvas" width="600" height="400"></canvas>
    <div class="controls">
        <label for="taille">Taille (px)</label>
        <input type="range" id="taille" min="10" max="100" value="30">
        <span id="tailleValue">30</span>
        <br>
        <label for="couleur">Couleur</label>
        <input type="color" id="couleur" value="#FF0000">
        <br>
        <label for="vitesse">Vitesse (px/s)</label>
        <input type="range" id="vitesse" min="100" max="500" value="200">
        <span id="vitesseValue">200</span>
        <br>
        <label for="poids">Poids (kg)</label>
        <input type="range" id="poids" min="0.1" max="10" step="0.1" value="1">
        <span id="poidsValue">1</span>
        <br>
        <label for="resistance">Résistance de l'air</label>
        <input type="range" id="resistance" min="0" max="0.1" step="0.001" value="0.01">
        <span id="resistanceValue">0.01</span>
    </div>

    <script>
        const canvas = document.getElementById('canvas');
        const ctx = canvas.getContext('2d');
        let balle = {
            x: canvas.width / 2,
            y: canvas.height / 2,
            vx: 200,
            vy: -200,
            taille: 30,
            couleur: '#FF0000',
            poids: 1,
            resistance: 0.01
        };

        function dessinerBalle() {
            ctx.clearRect(0, 0, canvas.width, canvas.height);
            ctx.fillStyle = balle.couleur;
            ctx.beginPath();
            ctx.arc(balle.x, balle.y, balle.taille, 0, 2 * Math.PI);
            ctx.fill();
        }

        function mettreAJourBalle(dt) {
            balle.x += balle.vx * dt;
            balle.y += balle.vy * dt;

            if (balle.x - balle.taille < 0 || balle.x + balle.taille > canvas.width) {
                balle.vx = -balle.vx;
            }
            if (balle.y - balle.taille < 0 || balle.y + balle.taille > canvas.height) {
                balle.vy = -balle.vy * (1 - balle.resistance);
            }

            balle.vy += 9.81 * dt; // gravité
            balle.vx *= (1 - balle.resistance); // résistance de l'air
            balle.vy *= (1 - balle.resistance); // résistance de l'air
        }

        let lastTime = 0;
        function boucle(t) {
            let dt = (t - lastTime) / 1000;
            lastTime = t;

            mettreAJourBalle(dt);
            dessinerBalle();

            requestAnimationFrame(boucle);
        }

        document.getElementById('taille').addEventListener('input', (e) => {
            balle.taille = parseInt(e.target.value);
            document.getElementById('tailleValue').innerText = balle.taille;
        });

        document.getElementById('couleur').addEventListener('input', (e) => {
            balle.couleur = e.target.value;
        });

        document.getElementById('vitesse').addEventListener('input', (e) => {
            let vitesse = parseInt(e.target.value);
            balle.vx = vitesse * (balle.vx > 0 ? 1 : -1);
            balle.vy = vitesse * (balle.vy > 0 ? 1 : -1);
            document.getElementById('vitesseValue').innerText = vitesse;
        });

        document.getElementById('poids').addEventListener('input', (e) => {
            balle.poids = parseFloat(e.target.value);
            document.getElementById('poidsValue').innerText = balle.poids;
        });

        document.getElementById('resistance').addEventListener('input', (e) => {
            balle.resistance = parseFloat(e.target.value);
            document.getElementById('resistanceValue').innerText = balle.resistance;
        });

        requestAnimationFrame(boucle);
    </script>
</body>
</html>

Grok 3

Grok a fourni une solution qui fonctionne, mais avec une structure de code plus basique.

  • Interface : L'interface est fonctionnelle, avec une mise en page en grille simple.
  • Code : La logique est un peu répétitive (plusieurs `addEventListener` qui appellent la même fonction). Un bouton 'Démarrer/Arrêter' a été ajouté, ce qui est une bonne initiative.
  • Fonctionnalités : Les fonctionnalités demandées sont présentes. La physique est basique mais suffisante pour l'effet visuel.
<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Balle Rebondissante 2D</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            margin: 0;
            padding: 20px;
            background-color: #f0f0f0;
            display: flex;
            flex-direction: column;
            align-items: center;
        }
        #container {
            display: flex;
            flex-direction: column;
            align-items: center;
            background-color: white;
            padding: 20px;
            border-radius: 10px;
            box-shadow: 0 0 10px rgba(0,0,0,0.1);
            width: 80%;
            max-width: 800px;
        }
        #canvas {
            border: 2px solid #333;
            background-color: #e0e0e0;
            margin-bottom: 20px;
        }
        #controls {
            display: grid;
            grid-template-columns: repeat(2, 1fr);
            gap: 15px;
            width: 100%;
        }
        .control-group {
            display: flex;
            flex-direction: column;
            margin-bottom: 10px;
        }
        label {
            font-weight: bold;
            margin-bottom: 5px;
        }
        input[type="range"], input[type="color"] {
            width: 100%;
        }
        span {
            margin-top: 5px;
            font-size: 14px;
            color: #555;
        }
        h1 {
            margin-top: 0;
            color: #333;
        }
        #startStop {
            margin-top: 20px;
            padding: 10px 20px;
            font-size: 16px;
            cursor: pointer;
            background-color: #4CAF50;
            color: white;
            border: none;
            border-radius: 5px;
        }
        #startStop:hover {
            background-color: #45a049;
        }
    </style>
</head>
<body>
    <div id="container">
        <h1>Balle Rebondissante 2D</h1>
        <canvas id="canvas" width="600" height="400"></canvas>
        <div id="controls">
            <div class="control-group">
                <label for="size">Taille (px):</label>
                <input type="range" id="size" min="10" max="100" value="30">
                <span id="sizeValue">30 px</span>
            </div>
            <div class="control-group">
                <label for="color">Couleur:</label>
                <input type="color" id="color" value="#ff0000">
            </div>
            <div class="control-group">
                <label for="speedX">Vitesse Horizontale (px/s):</label>
                <input type="range" id="speedX" min="1" max="10" value="5">
                <span id="speedXValue">5 px/s</span>
            </div>
            <div class="control-group">
                <label for="speedY">Vitesse Verticale (px/s):</label>
                <input type="range" id="speedY" min="1" max="10" value="5">
                <span id="speedYValue">5 px/s</span>
            </div>
            <div class="control-group">
                <label for="weight">Poids (kg):</label>
                <input type="range" id="weight" min="1" max="10" value="1" step="0.1">
                <span id="weightValue">1 kg</span>
            </div>
            <div class="control-group">
                <label for="airResistance">Résistance de l'air (%):</label>
                <input type="range" id="airResistance" min="0" max="1" value="0.1" step="0.01">
                <span id="airResistanceValue">0.1 %</span>
            </div>
        </div>
        <button id="startStop">Démarrer</button>
    </div>

    <script>
        const canvas = document.getElementById('canvas');
        const ctx = canvas.getContext('2d');
        const startStopBtn = document.getElementById('startStop');

        let ball = {
            x: canvas.width / 2,
            y: canvas.height / 2,
            radius: 30,
            color: '#ff0000',
            vx: 5,
            vy: 5,
            weight: 1,
            airResistance: 0.1
        };
        let isRunning = false;
        let animationId;

        function updateBall() {
            ball.radius = parseInt(document.getElementById('size').value);
            ball.color = document.getElementById('color').value;
            ball.vx = parseInt(document.getElementById('speedX').value);
            ball.vy = parseInt(document.getElementById('speedY').value);
            ball.weight = parseFloat(document.getElementById('weight').value);
            ball.airResistance = parseFloat(document.getElementById('airResistance').value);
        }

        function updateDisplayValues() {
            document.getElementById('sizeValue').textContent = ball.radius + ' px';
            document.getElementById('speedXValue').textContent = ball.vx + ' px/s';
            document.getElementById('speedYValue').textContent = ball.vy + ' px/s';
            document.getElementById('weightValue').textContent = ball.weight.toFixed(1) + ' kg';
            document.getElementById('airResistanceValue').textContent = ball.airResistance.toFixed(2) + ' %';
        }

        function drawBall() {
            ctx.clearRect(0, 0, canvas.width, canvas.height);
            ctx.beginPath();
            ctx.arc(ball.x, ball.y, ball.radius, 0, Math.PI * 2);
            ctx.fillStyle = ball.color;
            ctx.fill();
            ctx.closePath();
        }

        function updatePosition() {
            ball.x += ball.vx;
            ball.y += ball.vy;

            // Collision avec les murs
            if (ball.x + ball.radius > canvas.width) {
                ball.x = canvas.width - ball.radius;
                ball.vx = -ball.vx * (1 - ball.airResistance);
            } else if (ball.x - ball.radius < 0) {
                ball.x = ball.radius;
                ball.vx = -ball.vx * (1 - ball.airResistance);
            }

            if (ball.y + ball.radius > canvas.height) {
                ball.y = canvas.height - ball.radius;
                ball.vy = -ball.vy * (1 - ball.airResistance);
            } else if (ball.y - ball.radius < 0) {
                ball.y = ball.radius;
                ball.vy = -ball.vy * (1 - ball.airResistance);
            }

            // Appliquer la gravité en fonction du poids
            ball.vy += 0.5 * ball.weight;

            // Appliquer la résistance de l'air
            ball.vx *= (1 - ball.airResistance);
            ball.vy *= (1 - ball.airResistance);
        }

        function animate() {
            updatePosition();
            drawBall();
            if (isRunning) {
                animationId = requestAnimationFrame(animate);
            }
        }

        startStopBtn.addEventListener('click', () => {
            if (isRunning) {
                isRunning = false;
                startStopBtn.textContent = 'Démarrer';
                cancelAnimationFrame(animationId);
            } else {
                isRunning = true;
                startStopBtn.textContent = 'Arrêter';
                updateBall();
                animate();
            }
        });

        document.getElementById('size').addEventListener('input', updateBall);
        document.getElementById('color').addEventListener('input', updateBall);
        document.getElementById('speedX').addEventListener('input', updateBall);
        document.getElementById('speedY').addEventListener('input', updateBall);
        document.getElementById('weight').addEventListener('input', updateBall);
        document.getElementById('airResistance').addEventListener('input', updateBall);

        document.getElementById('size').addEventListener('input', updateDisplayValues);
        document.getElementById('speedX').addEventListener('input', updateDisplayValues);
        document.getElementById('speedY').addEventListener('input', updateDisplayValues);
        document.getElementById('weight').addEventListener('input', updateDisplayValues);
        document.getElementById('airResistance').addEventListener('input', updateDisplayValues);

        updateDisplayValues();
        drawBall();
    </script>
</body>
</html>

Magistral

Fait intéressant, Magistral n'a pas utilisé l'élément `<canvas>`, qui est le standard pour ce genre d'animation. Il a préféré animer un élément `<div>` avec du CSS et du JavaScript.

  • Interface : L'interface de contrôle est claire.
  • Code : Le choix de ne pas utiliser Canvas est original. C'est une technique qui fonctionne pour des animations simples, mais qui est beaucoup moins performante et flexible pour des applications plus complexes. C'est un excellent cas d'étude pour comparer différentes approches techniques.
  • Fonctionnalités : Le résultat visuel est atteint, mais la physique est moins réaliste et la performance est inférieure à celle des solutions basées sur Canvas.
<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Balle Rebondissante</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            display: flex;
            flex-direction: column;
            align-items: center;
            gap: 20px;
            padding: 20px;
        }
        h1 {
            text-align: center;
        }
        .container {
            width: 500px;
            height: 500px;
            border: 1px solid black;
            position: relative;
            overflow: hidden;
        }
        .ball {
            position: absolute;
            border-radius: 50%;
        }
        .controls {
            display: flex;
            flex-direction: column;
            gap: 10px;
            width: 500px;
        }
        .control-group {
            display: flex;
            align-items: center;
            gap: 10px;
        }
        .control-group label {
            width: 150px;
        }
        button {
            padding: 8px 16px;
            cursor: pointer;
        }
        .button-group {
            display: flex;
            gap: 10px;
        }
    </style>
</head>
<body>
    <h1>Balle Rebondissante</h1>
    <div class="container" id="container">
        <div class="ball" id="ball"></div>
    </div>
    <div class="controls">
        <div class="control-group">
            <label for="size">Taille:</label>
            <input type="range" id="size" min="10" max="100" value="20">
            <span id="sizeValue">20px</span>
        </div>
        <div class="control-group">
            <label for="color">Couleur:</label>
            <input type="color" id="color" value="#ff0000">
        </div>
        <div class="control-group">
            <label for="speedX">Vitesse X:</label>
            <input type="range" id="speedX" min="-10" max="10" step="0.1" value="2">
            <span id="speedXValue">2.0</span>
        </div>
        <div class="control-group">
            <label for="speedY">Vitesse Y:</label>
            <input type="range" id="speedY" min="-10" max="10" step="0.1" value="2">
            <span id="speedYValue">2.0</span>
        </div>
        <div class="control-group">
            <label for="weight">Poids:</label>
            <input type="range" id="weight" min="0" max="2" step="0.1" value="1">
            <span id="weightValue">1.0</span>
        </div>
        <div class="control-group">
            <label for="airResistance">Résistance de l'air:</label>
            <input type="range" id="airResistance" min="0" max="0.5" step="0.01" value="0.1">
            <span id="airResistanceValue">0.10</span>
        </div>
        <div class="button-group">
            <button id="toggleAnimation">Démarrer</button>
            <button id="resetPosition">Réinitialiser la position</button>
        </div>
    </div>

    <script>
        // Variables pour la balle
        let size = 20;
        let color = '#ff0000';
        let speedX = 2;
        let speedY = 2;
        let weight = 1;
        let airResistance = 0.1;
        let isAnimating = false;
        let position = { x: 50, y: 50 };

        // Références aux éléments DOM
        const container = document.getElementById('container');
        const ball = document.getElementById('ball');
        const sizeInput = document.getElementById('size');
        const colorInput = document.getElementById('color');
        const speedXInput = document.getElementById('speedX');
        const speedYInput = document.getElementById('speedY');
        const weightInput = document.getElementById('weight');
        const airResistanceInput = document.getElementById('airResistance');
        const toggleAnimationButton = document.getElementById('toggleAnimation');
        const resetPositionButton = document.getElementById('resetPosition');
        const sizeValue = document.getElementById('sizeValue');
        const speedXValue = document.getElementById('speedXValue');
        const speedYValue = document.getElementById('speedYValue');
        const weightValue = document.getElementById('weightValue');
        const airResistanceValue = document.getElementById('airResistanceValue');

        // Fonction pour mettre à jour l'affichage de la balle
        function updateBall() {
            ball.style.width = `${size}px`;
            ball.style.height = `${size}px`;
            ball.style.backgroundColor = color;
            ball.style.left = `${position.x}px`;
            ball.style.top = `${position.y}px`;
        }

        // Fonction pour mettre à jour la position de la balle
        function updatePosition() {
            let newX = position.x + speedX;
            let newY = position.y + speedY;

            // Rebond sur les bords
            if (newX <= 0 || newX >= container.clientWidth - size) {
                newX = newX <= 0 ? 0 : container.clientWidth - size;
                speedX = -speedX * 0.9;
            }

            if (newY <= 0 || newY >= container.clientHeight - size) {
                newY = newY <= 0 ? 0 : container.clientHeight - size;
                speedY = -speedY * 0.9;
            }

            // Appliquer la résistance de l'air et le poids
            speedX = speedX * (1 - airResistance);
            speedY = speedY * (1 - airResistance) + weight;

            // Mettre à jour la position
            position = { x: newX, y: newY };
        }

        // Fonction pour démarrer ou arrêter l'animation
        function toggleAnimation() {
            isAnimating = !isAnimating;
            toggleAnimationButton.textContent = isAnimating ? 'Arrêter' : 'Démarrer';
            if (isAnimating) {
                animate();
            }
        }

        // Fonction pour réinitialiser la position de la balle
        function resetPosition() {
            position = { x: 50, y: 50 };
            updateBall();
        }

        // Fonction d'animation
        function animate() {
            if (!isAnimating) return;
            updatePosition();
            updateBall();
            requestAnimationFrame(animate);
        }

        // Écouteurs d'événements pour les contrôles
        sizeInput.addEventListener('input', () => {
            size = parseInt(sizeInput.value);
            sizeValue.textContent = `${size}px`;
            updateBall();
        });

        colorInput.addEventListener('input', () => {
            color = colorInput.value;
            updateBall();
        });

        speedXInput.addEventListener('input', () => {
            speedX = parseFloat(speedXInput.value);
            speedXValue.textContent = speedX.toFixed(1);
        });

        speedYInput.addEventListener('input', () => {
            speedY = parseFloat(speedYInput.value);
            speedYValue.textContent = speedY.toFixed(1);
        });

        weightInput.addEventListener('input', () => {
            weight = parseFloat(weightInput.value);
            weightValue.textContent = weight.toFixed(1);
        });

        airResistanceInput.addEventListener('input', () => {
            airResistance = parseFloat(airResistanceInput.value);
            airResistanceValue.textContent = airResistance.toFixed(2);
        });

        toggleAnimationButton.addEventListener('click', toggleAnimation);
        resetPositionButton.addEventListener('click', resetPosition);

        // Initialisation
        updateBall();
    </script>
</body>
</html>