From 1cd8eb5f42ee601e62dccbcaaa6dfdce1758c018 Mon Sep 17 00:00:00 2001 From: nicoboy Date: Mon, 23 Feb 2026 13:24:24 +0000 Subject: [PATCH] =?UTF-8?q?T=C3=A9l=C3=A9verser=20les=20fichiers=20vers=20?= =?UTF-8?q?"/"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fichier recap et configuration d'images docker, de template --- 202602-feuilleroute.txt | 44 +++ ARCHITECTURE-STUDIO-AI.md | 210 ++++++++++++++ creation-image-docker-sur-VM.md | 53 ++++ dockerfile.txt | 60 ++++ game.js | 480 ++++++++++++++++++++++++++++++++ 5 files changed, 847 insertions(+) create mode 100644 202602-feuilleroute.txt create mode 100644 ARCHITECTURE-STUDIO-AI.md create mode 100644 creation-image-docker-sur-VM.md create mode 100644 dockerfile.txt create mode 100644 game.js diff --git a/202602-feuilleroute.txt b/202602-feuilleroute.txt new file mode 100644 index 0000000..fa2b136 --- /dev/null +++ b/202602-feuilleroute.txt @@ -0,0 +1,44 @@ +faire un abonnement vast.ai +installer un serveur ollama +faire tourner plusieirs modeles successivement. +faire tourner plusieurs modeles en meme temps. + + +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDw6NFpHs1rqxwoi4Dmy9eQX4wJdQ1EOprD49qO8ltql nicolas b@DESKTOP-KPPH6RI + +ssh -p 13777 root@ssh2.vast.ai -L 8080:localhost:8080 + + +ssh -i "C:\Users\Nicolas B\.ssh\id_vast_demo" -p 17725 root@ssh2.vast.ai -L 8080:localhost:8080 + +ssh -i "C:\Users\Nicolas B\.ssh\id_vast_demo" -p 18271 root@142.170.89.112 -L 8080:localhost:8080 + +jsson QWEN: +{ + "models": [ + { + "title": "Qwen 3 480B (Vast.ai)", + "provider": "openai", + "model": "qwen3-480b", + "apiKey": "EMPTY", + "baseUrl": "http://localhost:8000/v1", + "contextLength": 32768, + "completionOptions": { + "temperature": 0.2, + "maxTokens": 4096 + } + } + ], + "tabAutocompleteModel": { + "title": "Qwen 3 Autocomplete", + "provider": "openai", + "model": "qwen3-480b", + "apiKey": "EMPTY", + "baseUrl": "http://localhost:8000/v1" + }, + "allowAnonymousTelemetry": false +} +a lancer dans l'ide +ssh -L 8000:localhost:8000 root@[IP_DE_L_INSTANCE] -p [PORT_SSH_VAST] + + diff --git a/ARCHITECTURE-STUDIO-AI.md b/ARCHITECTURE-STUDIO-AI.md new file mode 100644 index 0000000..0bcb5e0 --- /dev/null +++ b/ARCHITECTURE-STUDIO-AI.md @@ -0,0 +1,210 @@ +# đŸŽ™ïž Studio IA Multimodal — Architecture & SpĂ©cifications + +## Contexte du projet + +Studio IA accessible Ă  des utilisateurs novices via navigateur web. +Interface unifiĂ©e pour la gĂ©nĂ©ration audio, image, et code, pilotĂ©e par un chatbot Mistral. + +--- + +## 1. Architecture gĂ©nĂ©rale + +``` +[Utilisateur navigateur] + ↓ +[Open WebUI — port 3000] + ↓ +[Ollama — Mistral Medium] + ↓ (Tools/Functions) + ┌────┮────┬──────────┐ +[AudioCraft] [ComfyUI] [Code] + port 7860 port 8188 +``` + +--- + +## 2. Pile logicielle + +| Couche | Technologie | RĂŽle | Port | +|---|---|---|---| +| Interface | Open WebUI | Chat unifiĂ©, gestion users | 3000 | +| LLM | Ollama + Mistral | Chat + orchestration outils | 11434 | +| Audio | AudioCraft (Meta) | MusicGen + AudioGen | 7860 | +| Image | ComfyUI | Stable Diffusion / Flux.1 | 8188 | +| Code | Ollama + CodeLlama | GĂ©nĂ©ration de code | 11434 | +| Reverse proxy | Caddy | Routage + HTTPS | 80/443 | + +--- + +## 3. Image Docker de base + +**Nom cible :** `nicoboy/studio-ai:latest` + +### Contenu de l'image (sans les modĂšles) + +```dockerfile +FROM nvidia/cuda:12.1.1-cudnn8-devel-ubuntu22.04 + +# Python 3.11 +RUN add-apt-repository ppa:deadsnakes/ppa && \ + apt-get install -y python3.11 python3.11-venv python3.11-distutils + +# DĂ©pendances systĂšme +RUN apt-get install -y \ + ffmpeg libsndfile1 sox \ + git wget curl nano screen \ + nodejs npm + +# pip pour Python 3.11 +RUN curl -sS https://bootstrap.pypa.io/get-pip.py | python3.11 + +# Frameworks IA +RUN python3.11 -m pip install \ + torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121 + +# AudioCraft +RUN python3.11 -m pip install \ + git+https://github.com/facebookresearch/audiocraft.git \ + gradio scipy soundfile + +# Open WebUI +RUN python3.11 -m pip install open-webui + +# Ollama +RUN curl -fsSL https://ollama.ai/install.sh | sh +``` + +### Ce qui N'est PAS dans l'image (tĂ©lĂ©chargĂ© au 1er lancement) +- ModĂšles Ollama (Mistral, CodeLlama) → ~8 Go +- ModĂšles AudioCraft (MusicGen, AudioGen) → ~4 Go +- ModĂšles ComfyUI (SDXL, Flux.1) → ~7 Go +- **Total modĂšles : ~20 Go** + +--- + +## 4. Taille estimĂ©e + +| ÉlĂ©ment | Taille | +|---|---| +| Image Docker (frameworks seuls) | ~15-20 Go | +| ModĂšles (tĂ©lĂ©chargĂ©s au lancement) | ~20 Go | +| DonnĂ©es utilisateurs | ~5 Go | +| **Total disque recommandĂ©** | **50-60 Go minimum** | + +> ⚠ 130 Go de disque = confortable pour tout faire tourner. + +--- + +## 5. Template Vast.ai cible + +| ParamĂštre | Valeur | +|---|---| +| Image | `nicoboy/studio-ai:latest` | +| GPU | RTX A4000 / RTX 4060 Ti (16 Go VRAM) | +| Disk | 130 Go | +| Ports | 3000, 7860, 8188, 11434 | +| Launch mode | `Jupyter + SSH` | +| PROVISIONING_SCRIPT | URL Gitea → tĂ©lĂ©charge les modĂšles | +| PORTAL_CONFIG | OpenWebUI:3000, AudioCraft:7860, ComfyUI:8188 | + +--- + +## 6. Provisioning Script (logique au 1er lancement) + +```bash +# 1. TĂ©lĂ©charger les modĂšles Ollama +ollama pull mistral +ollama pull codellama + +# 2. TĂ©lĂ©charger les modĂšles AudioCraft +# (fait automatiquement au 1er appel Python) + +# 3. Installer ComfyUI + modĂšles SD/Flux +git clone https://github.com/comfyanonymous/ComfyUI /root/ComfyUI +cd /root/ComfyUI && pip install -r requirements.txt +wget -P /root/ComfyUI/models/checkpoints/ [URL_MODELE_SDXL] + +# 4. Lancer les services +screen -dmS ollama ollama serve +screen -dmS openwebui open-webui serve --port 3000 +screen -dmS audiocraft python /root/audiocraft-api/server.py +screen -dmS comfyui python /root/ComfyUI/main.py --listen 0.0.0.0 --port 8188 +``` + +--- + +## 7. Configuration Open WebUI (partie dĂ©licate — Ă  faire ensemble) + +> ⚠ Cette section sera configurĂ©e manuellement aprĂšs dĂ©ploiement. + +### 7.1 Connexion Ollama +- URL : `http://localhost:11434` +- ModĂšles : Mistral (chat), CodeLlama (code) + +### 7.2 Tools Ă  crĂ©er (Functions Open WebUI) +- `generate_audio(prompt, duration, type)` → appelle AudioCraft port 7860 +- `generate_image(prompt, steps, width, height)` → appelle ComfyUI port 8188 +- `generate_code(prompt, language)` → appelle CodeLlama via Ollama + +### 7.3 System Prompt de l'assistant +``` +Tu es un assistant crĂ©atif expert. +Tu as accĂšs aux outils suivants : +- generate_audio : pour crĂ©er musique et bruitages +- generate_image : pour crĂ©er des images +- generate_code : pour Ă©crire du code + +Quand un utilisateur fait une demande crĂ©ative, +tu l'aides Ă  prĂ©ciser sa demande puis tu utilises +l'outil appropriĂ©. +``` + +### 7.4 ModĂšle JSON Open WebUI (Ă  importer) +> À gĂ©nĂ©rer une fois l'instance stable. + +--- + +## 8. Étapes de construction (ordre) + +- [x] DĂ©finir l'architecture +- [ ] **Construire l'image Docker** ← Gemini +- [ ] Pousser sur DockerHub (`nicoboy/studio-ai`) +- [ ] Mettre Ă  jour le template Vast.ai avec la nouvelle image +- [ ] Écrire le provisioning script final +- [ ] **Configurer Open WebUI** ← Claude +- [ ] **CrĂ©er les Tools AudioCraft + ComfyUI** ← Claude +- [ ] **RĂ©diger les System Prompts** ← Claude +- [ ] Tests utilisateur + +--- + +## 9. Commandes utiles + +```bash +# VĂ©rifier les services qui tournent +screen -ls + +# Voir les logs d'un service +screen -r openwebui +screen -r audiocraft +screen -r comfyui + +# VĂ©rifier GPU +nvidia-smi + +# VĂ©rifier les modĂšles Ollama +ollama list + +# Tester AudioCraft API +curl http://localhost:7860 +``` + +--- + +## 10. Points d'attention pour Gemini + +1. **Python 3.11 obligatoire** — Open WebUI refuse Python 3.10 +2. **Ne pas inclure les modĂšles dans l'image** — trop lourds, dans le provisioning script +3. **L'image doit ĂȘtre sur DockerHub public** pour que Vast.ai puisse la tĂ©lĂ©charger +4. **CUDA 12.1** — compatible A4000 et RTX 4060 Ti +5. **Tester le build** avec `docker build -t nicoboy/studio-ai .` avant de pusher diff --git a/creation-image-docker-sur-VM.md b/creation-image-docker-sur-VM.md new file mode 100644 index 0000000..75b20a7 --- /dev/null +++ b/creation-image-docker-sur-VM.md @@ -0,0 +1,53 @@ +đŸŽ™ïž Studio-AI : RĂ©capitulatif de Configuration +Version : 1.0 (2026-02-23) +Image Docker : nicoboy/studio-ai:latest +Base : NVIDIA PyTorch NGC (Ubuntu 22.04 + CUDA 13) + +🛠 1. Contenu du Dockerfile +Voici le code final qui a permis de construire l'image avec succĂšs, en Ă©vitant les conflits de dĂ©pendances audio et les erreurs de dĂ©compression. + +Dockerfile +FROM nvcr.io/nvidia/pytorch:26.01-py3 +ENV DEBIAN_FRONTEND=noninteractive + +# 1. Installation des outils systĂšme et librairies de dĂ©veloppement audio +RUN apt-get update && apt-get install -y \ + ffmpeg \ + pkg-config \ + libavcodec-dev \ + libavformat-dev \ + libavutil-dev \ + libswscale-dev \ + libavdevice-dev \ + libavfilter-dev \ + libsndfile1 \ + sox \ + zstd \ + screen \ + nano \ + git \ + wget \ + curl \ + && apt-get clean && rm -rf /var/lib/apt/lists/* + +# 2. Installation de Ollama (Moteur LLM) +RUN curl -fsSL https://ollama.ai/install.sh | sh + +# 3. Installation de Open WebUI (Interface Chat) +RUN pip install --no-cache-dir open-webui + +# 4. Installation de l'Audio (Audiocraft) +# Note : --no-deps est utilisĂ© pour Ă©viter que Audiocraft n'Ă©crase Torch 2.6 +RUN pip install --no-cache-dir gradio scipy soundfile +RUN pip install --no-cache-dir --no-deps git+https://github.com/facebookresearch/audiocraft.git + +# 5. Installation de ComfyUI (GĂ©nĂ©ration d'images) +WORKDIR /root +RUN git clone https://github.com/comfyanonymous/ComfyUI.git && \ + cd ComfyUI && \ + pip install --no-cache-dir -r requirements.txt + +# 6. Configuration rĂ©seau et environnement +EXPOSE 3000 7860 8188 11434 +WORKDIR /root +CMD ["/bin/bash"] \ No newline at end of file diff --git a/dockerfile.txt b/dockerfile.txt new file mode 100644 index 0000000..4ef6120 --- /dev/null +++ b/dockerfile.txt @@ -0,0 +1,60 @@ +FROM nvcr.io/nvidia/pytorch:26.01-py3 +ENV DEBIAN_FRONTEND=noninteractive + +# 1. DĂ©pendances systĂšme et audio +RUN apt-get update && apt-get install -y \ + ffmpeg pkg-config libavcodec-dev libavformat-dev \ + libavutil-dev libswscale-dev libavdevice-dev libavfilter-dev \ + libsndfile1 sox zstd screen nano git wget curl \ + && apt-get clean && rm -rf /var/lib/apt/lists/* + +# 2. Installation d'Ollama +RUN curl -fsSL https://ollama.ai/install.sh | sh + +# 3. Installation des dĂ©pendances Python (ValidĂ©es par nos tests manuels) +# On installe les briques manquantes AVANT Audiocraft +RUN pip install --no-cache-dir \ + open-webui \ + gradio \ + scipy \ + soundfile \ + julius \ + omegaconf \ + hydra-core \ + hydra-colorlog \ + flashy \ + num2words \ + torchmetrics \ + encodec \ + xformers \ + einops \ + spacy==3.7.6 \ + numpy==1.26.4 + +# 4. Installation d'Audiocraft (IsolĂ©e pour ne pas casser PyTorch) +RUN pip install --no-cache-dir --no-deps git+https://github.com/facebookresearch/audiocraft.git + +# 5. Installation ComfyUI +WORKDIR /root +RUN git clone https://github.com/comfyanonymous/ComfyUI.git && \ + cd ComfyUI && \ + pip install --no-cache-dir -r requirements.txt + +# 6. IntĂ©gration de ton script de serveur Audio +RUN mkdir -p /root/audiocraft-api +COPY server.py /root/audiocraft-api/server.py + +# 7. Script d'auto-lancement (Auto-Boot) ultra-robuste +RUN echo '#!/bin/bash\n\ +ollama serve > /root/ollama.log 2>&1 & \n\ +open-webui serve --port 3000 > /root/webui.log 2>&1 & \n\ +python3 /root/ComfyUI/main.py --listen 0.0.0.0 --port 8188 > /root/comfy.log 2>&1 & \n\ +python3 /root/audiocraft-api/server.py > /root/audio.log 2>&1 & \n\ +echo "--- TOUS LES SERVICES SONT EN COURS DE LANCEMENT ---"\n\ +wait' > /start.sh && chmod +x /start.sh + +# 8. Configuration finale +EXPOSE 3000 7860 8188 11434 +WORKDIR /root +# On force l'utilisation de bash pour Ă©viter les problĂšmes de droits +ENTRYPOINT ["/bin/bash", "/start.sh"] \ No newline at end of file diff --git a/game.js b/game.js new file mode 100644 index 0000000..1ddbfc1 --- /dev/null +++ b/game.js @@ -0,0 +1,480 @@ +// Variables globales +let game; +let canvas; +let ctx; + +class Game { + constructor() { + this.canvas = document.getElementById('gameCanvas'); + this.ctx = this.canvas.getContext('2d'); + this.width = this.canvas.width; + this.height = this.canvas.height; + + // État du jeu + this.score = 0; + this.lives = 3; + this.level = 1; + this.gameRunning = false; + this.paused = false; + + // EntitĂ©s du jeu + this.player = null; + this.enemies = []; + this.bubbles = []; + this.platforms = []; + + // ContrĂŽles + this.keys = {}; + } + + init() { + // CrĂ©er le joueur + this.player = new Player(100, this.height - 100, this); + + // CrĂ©er quelques plateformes + this.createPlatforms(); + + // CrĂ©er quelques ennemis + this.createEnemies(); + + // Écouter les Ă©vĂ©nements clavier + this.setupControls(); + + // DĂ©marrer la boucle du jeu + this.gameRunning = true; + this.gameLoop(); + } + + createPlatforms() { + this.platforms = []; + + // Plateforme du sol + this.platforms.push({ + x: 0, + y: this.height - 40, + width: this.width, + height: 40 + }); + + // Plateformes flottantes + this.platforms.push({ + x: 100, + y: this.height - 150, + width: 200, + height: 20 + }); + + this.platforms.push({ + x: 400, + y: this.height - 250, + width: 200, + height: 20 + }); + + this.platforms.push({ + x: 200, + y: this.height - 350, + width: 200, + height: 20 + }); + } + + createEnemies() { + this.enemies = []; + // CrĂ©er quelques ennemis + this.enemies.push(new Enemy(300, this.height - 100, this)); + this.enemies.push(new Enemy(500, this.height - 200, this)); + } + + setupControls() { + document.addEventListener('keydown', (e) => { + this.keys[e.key] = true; + + // Sauter avec espace + if (e.key === ' ' && this.gameRunning && !this.paused) { + if (this.player) { + this.player.jump(); + } + } + + // Tirer une bulle avec Ctrl + if ((e.key === 'Control' || e.key === 'ControlLeft') && this.gameRunning && !this.paused) { + if (this.player) { + this.player.shootBubble(); + } + } + }); + + document.addEventListener('keyup', (e) => { + this.keys[e.key] = false; + }); + } + + update() { + if (!this.gameRunning || this.paused) return; + + // Mettre Ă  jour le joueur + if (this.player) { + this.player.update(); + } + + // Mettre Ă  jour les ennemis + for (let i = 0; i < this.enemies.length; i++) { + this.enemies[i].update(); + + // VĂ©rifier si l'ennemi est hors jeu + if (this.enemies[i].y > this.height) { + this.enemies.splice(i, 1); + i--; + } + } + + // Mettre Ă  jour les bulles + for (let i = 0; i < this.bubbles.length; i++) { + this.bubbles[i].update(); + + // VĂ©rifier si la bulle est hors jeu + if (this.bubbles[i].y < -50 || this.bubbles[i].x > this.width + 50 || this.bubbles[i].x < -50) { + this.bubbles.splice(i, 1); + i--; + continue; + } + + // VĂ©rifier les collisions entre bulles et ennemis + for (let j = 0; j < this.enemies.length; j++) { + if (j < this.enemies.length && this.checkCollision(this.bubbles[i], this.enemies[j])) { + // Ennemi capturĂ© dans une bulle + this.score += 100; + this.updateUI(); + this.bubbles.splice(i, 1); + this.enemies.splice(j, 1); + i--; + break; + } + } + } + + // VĂ©rifier les collisions entre joueur et ennemis + if (this.player) { + for (let i = 0; i < this.enemies.length; i++) { + if (this.checkCollision(this.player, this.enemies[i])) { + this.playerHit(); + break; + } + } + } + + // VĂ©rifier si tous les ennemis sont Ă©liminĂ©s + if (this.enemies.length === 0) { + this.nextLevel(); + } + } + + draw() { + // Effacer le canvas + this.ctx.clearRect(0, 0, this.width, this.height); + + // Dessiner le fond + this.ctx.fillStyle = '#00008B'; + this.ctx.fillRect(0, 0, this.width, this.height); + + // Dessiner les plateformes + this.ctx.fillStyle = '#8B4513'; + for (let platform of this.platforms) { + this.ctx.fillRect(platform.x, platform.y, platform.width, platform.height); + } + + // Dessiner le joueur + if (this.player) { + this.player.draw(); + } + + // Dessiner les ennemis + for (let enemy of this.enemies) { + enemy.draw(); + } + + // Dessiner les bulles + for (let bubble of this.bubbles) { + bubble.draw(); + } + } + + checkCollision(obj1, obj2) { + return obj1.x < obj2.x + obj2.width && + obj1.x + obj1.width > obj2.x && + obj1.y < obj2.y + obj2.height && + obj1.y + obj1.height > obj2.y; + } + + playerHit() { + this.lives--; + this.updateUI(); + + if (this.lives <= 0) { + this.gameOver(); + } else { + // RĂ©initialiser la position du joueur + if (this.player) { + this.player.x = 100; + this.player.y = this.height - 100; + this.player.velocityY = 0; + this.player.velocityX = 0; + } + } + } + + nextLevel() { + this.level++; + this.updateUI(); + + // RĂ©initialiser les ennemis + this.createEnemies(); + + // RĂ©initialiser le joueur + if (this.player) { + this.player.x = 100; + this.player.y = this.height - 100; + this.player.velocityY = 0; + this.player.velocityX = 0; + } + } + + gameOver() { + this.gameRunning = false; + setTimeout(() => { + alert(`Game Over! Score final: ${this.score}`); + }, 100); + } + + updateUI() { + document.getElementById('score').textContent = this.score; + document.getElementById('lives').textContent = this.lives; + document.getElementById('level').textContent = this.level; + } + + gameLoop() { + this.update(); + this.draw(); + if (this.gameRunning) { + requestAnimationFrame(() => this.gameLoop()); + } + } +} + +class Player { + constructor(x, y, game) { + this.x = x; + this.y = y; + this.width = 30; + this.height = 30; + this.velocityX = 0; + this.velocityY = 0; + this.speed = 5; + this.jumpPower = 12; + this.gravity = 0.5; + this.onGround = false; + this.game = game; + } + + update() { + // Gestion des contrĂŽles + this.velocityX = 0; + if (this.game.keys['ArrowLeft']) { + this.velocityX = -this.speed; + } + if (this.game.keys['ArrowRight']) { + this.velocityX = this.speed; + } + + // Appliquer la gravitĂ© + this.velocityY += this.gravity; + + // Mettre Ă  jour la position + this.x += this.velocityX; + this.y += this.velocityY; + + // EmpĂȘcher le joueur de sortir de l'Ă©cran + if (this.x < 0) this.x = 0; + if (this.x + this.width > this.game.width) this.x = this.game.width - this.width; + + // VĂ©rifier les collisions avec les plateformes + this.onGround = false; + for (let platform of this.game.platforms) { + if (this.x < platform.x + platform.width && + this.x + this.width > platform.x && + this.y + this.height <= platform.y + 10 && + this.y + this.height + this.velocityY >= platform.y) { + this.y = platform.y - this.height; + this.velocityY = 0; + this.onGround = true; + } + } + + // EmpĂȘcher le joueur de tomber sous l'Ă©cran + if (this.y > this.game.height) { + this.game.playerHit(); + } + } + + jump() { + if (this.onGround) { + this.velocityY = -this.jumpPower; + this.onGround = false; + } + } + + shootBubble() { + // CrĂ©er une nouvelle bulle + let direction = 1; + if (this.game.keys['ArrowLeft']) direction = -1; + this.game.bubbles.push(new Bubble(this.x + this.width/2, this.y + this.height/2, direction, this.game)); + } + + draw() { + // Dessiner le joueur (cercle vert) + this.game.ctx.fillStyle = '#00FF00'; + this.game.ctx.beginPath(); + this.game.ctx.arc(this.x + this.width/2, this.y + this.height/2, this.width/2, 0, Math.PI * 2); + this.game.ctx.fill(); + + // Dessiner les yeux + this.game.ctx.fillStyle = '#000000'; + let eyeOffset = this.game.keys['ArrowLeft'] ? -5 : (this.game.keys['ArrowRight'] ? 5 : 0); + this.game.ctx.beginPath(); + this.game.ctx.arc(this.x + this.width/2 - 5 + eyeOffset, this.y + this.height/2 - 5, 3, 0, Math.PI * 2); + this.game.ctx.arc(this.x + this.width/2 + 5 + eyeOffset, this.y + this.height/2 - 5, 3, 0, Math.PI * 2); + this.game.ctx.fill(); + } +} + +class Enemy { + constructor(x, y, game) { + this.x = x; + this.y = y; + this.width = 25; + this.height = 25; + this.velocityX = 2; + this.velocityY = 0; + this.gravity = 0.5; + this.game = game; + } + + update() { + // DĂ©placement horizontal + this.x += this.velocityX; + + // Appliquer la gravitĂ© + this.velocityY += this.gravity; + this.y += this.velocityY; + + // VĂ©rifier les collisions avec les plateformes + let onPlatform = false; + for (let platform of this.game.platforms) { + if (this.x < platform.x + platform.width && + this.x + this.width > platform.x && + this.y + this.height <= platform.y + 10 && + this.y + this.height + this.velocityY >= platform.y) { + this.y = platform.y - this.height; + this.velocityY = 0; + onPlatform = true; + + // Changer de direction si on arrive au bord de la plateforme + if (this.x <= platform.x || this.x + this.width >= platform.x + platform.width) { + this.velocityX *= -1; + } + } + } + + // Rebondir sur les murs + if (this.x <= 0 || this.x + this.width >= this.game.width) { + this.velocityX *= -1; + } + } + + draw() { + // Dessiner l'ennemi (cercle rouge) + this.game.ctx.fillStyle = '#FF0000'; + this.game.ctx.beginPath(); + this.game.ctx.arc(this.x + this.width/2, this.y + this.height/2, this.width/2, 0, Math.PI * 2); + this.game.ctx.fill(); + + // Dessiner les yeux + this.game.ctx.fillStyle = '#000000'; + let eyeOffset = this.velocityX < 0 ? -5 : 5; + this.game.ctx.beginPath(); + this.game.ctx.arc(this.x + this.width/2 - 5 + eyeOffset, this.y + this.height/2 - 5, 3, 0, Math.PI * 2); + this.game.ctx.arc(this.x + this.width/2 + 5 + eyeOffset, this.y + this.height/2 - 5, 3, 0, Math.PI * 2); + this.game.ctx.fill(); + } +} + +class Bubble { + constructor(x, y, direction, game) { + this.x = x; + this.y = y; + this.radius = 10; + this.velocityX = direction * 4; + this.velocityY = -3; + this.game = game; + } + + update() { + this.x += this.velocityX; + this.y += this.velocityY; + + // La bulle monte lĂ©gĂšrement + this.velocityY -= 0.1; + + // Limiter la vitesse verticale + if (this.velocityY < -5) this.velocityY = -5; + } + + draw() { + // Dessiner la bulle (cercle transparent avec bordure) + this.game.ctx.strokeStyle = '#FFFFFF'; + this.game.ctx.lineWidth = 2; + this.game.ctx.beginPath(); + this.game.ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2); + this.game.ctx.stroke(); + + // Ajouter un reflet + this.game.ctx.fillStyle = 'rgba(255, 255, 255, 0.3)'; + this.game.ctx.beginPath(); + this.game.ctx.arc(this.x - this.radius/3, this.y - this.radius/3, this.radius/4, 0, Math.PI * 2); + this.game.ctx.fill(); + } +} + +// Fonctions globales +function startGame() { + if (game) { + // RĂ©initialiser le jeu + game.score = 0; + game.lives = 3; + game.level = 1; + game.updateUI(); + game.createPlatforms(); + game.createEnemies(); + if (game.player) { + game.player.x = 100; + game.player.y = game.height - 100; + game.player.velocityY = 0; + game.player.velocityX = 0; + } + game.bubbles = []; + game.gameRunning = true; + game.gameLoop(); + } else { + // CrĂ©er un nouveau jeu + game = new Game(); + game.init(); + } + game.gameRunning = true; + game.paused = false; +} + +function pauseGame() { + if (game && game.gameRunning) { + game.paused = !game.paused; + } \ No newline at end of file