Téléverser les fichiers vers "/"
fichier recap et configuration d'images docker, de template
This commit is contained in:
44
202602-feuilleroute.txt
Normal file
44
202602-feuilleroute.txt
Normal file
@ -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]
|
||||||
|
|
||||||
|
|
||||||
210
ARCHITECTURE-STUDIO-AI.md
Normal file
210
ARCHITECTURE-STUDIO-AI.md
Normal file
@ -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
|
||||||
53
creation-image-docker-sur-VM.md
Normal file
53
creation-image-docker-sur-VM.md
Normal file
@ -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"]
|
||||||
60
dockerfile.txt
Normal file
60
dockerfile.txt
Normal file
@ -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"]
|
||||||
480
game.js
Normal file
480
game.js
Normal file
@ -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;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user