From bcbd34db3bf3eda753b0546d2bf758b1b030c6c4 Mon Sep 17 00:00:00 2001
From: Flavio <flavio.morrone@hes-so.ch>
Date: Mon, 24 Mar 2025 13:21:26 +0100
Subject: [PATCH] fin projet barbapap

---
 index.html   |  76 +++++++++
 package.json |  24 ++-
 server.js    |  82 +++++++++
 src/index.js | 456 +++++++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 637 insertions(+), 1 deletion(-)
 create mode 100644 index.html
 create mode 100644 server.js
 create mode 100644 src/index.js

diff --git a/index.html b/index.html
new file mode 100644
index 0000000..1a1b90b
--- /dev/null
+++ b/index.html
@@ -0,0 +1,76 @@
+<!DOCTYPE html>
+<html lang="fr">
+  <head>
+    <meta charset="UTF-8" />
+    <title>Barbapapa Battle Royale - Multijoueur</title>
+    <style>
+      body {
+        margin: 0;
+        overflow: hidden;
+        font-family: sans-serif;
+      }
+      /* Menu de démarrage centré */
+      #startMenu {
+        position: absolute;
+        top: 50%;
+        left: 50%;
+        transform: translate(-50%, -50%);
+        background: rgba(0, 0, 0, 0.8);
+        color: white;
+        padding: 20px;
+        border-radius: 10px;
+        text-align: center;
+        z-index: 10;
+      }
+      #startMenu h1 {
+        margin-top: 0;
+      }
+      #startMenu p {
+        margin: 10px 0;
+      }
+      #startMenu input, #startMenu button {
+        margin: 5px;
+        padding: 8px;
+        font-size: 16px;
+      }
+      /* Timer en haut à droite */
+      #timer {
+        position: absolute;
+        top: 10px;
+        right: 10px;
+        font-size: 24px;
+        color: white;
+        z-index: 10;
+        display: none;
+      }
+    </style>
+  </head>
+  <body>
+    <!-- Menu de démarrage -->
+    <div id="startMenu">
+      <h1>Barbapapa Battle Royale</h1>
+      <div id="gameOptions">
+        <button id="createGameButton">Créer une partie</button>
+        <br />
+        <input type="text" id="roomIdInput" placeholder="ID de la partie" />
+        <button id="joinGameButton">Rejoindre une partie</button>
+      </div>
+      <div id="playerSettings">
+        <p>Utilisez le clavier (W, A, S, D) pour vous déplacer.</p>
+        <p>Utilisez la souris pour viser et cliquez pour tirer.</p>
+      </div>
+      <div id="shareLink" style="display: none;">
+        <p>Partagez ce lien pour inviter d'autres joueurs :</p>
+        <input type="text" id="shareLinkInput" readonly />
+      </div>
+    </div>
+
+    <!-- Timer de la partie -->
+    <div id="timer">Temps: 00:00</div>
+    <!-- Barre de vie -->
+    <div id="healthDisplay" style="position:absolute; top:10px; left:10px; font-size:24px; color:red; z-index:10;"></div>
+
+    <!-- Le script principal (par exemple, bundlé avec Vite ou Webpack) -->
+    <script type="module" src="/src/index.js"></script>
+  </body>
+</html>
diff --git a/package.json b/package.json
index 79a2f4c..d3b34a7 100644
--- a/package.json
+++ b/package.json
@@ -1,5 +1,27 @@
 {
   "dependencies": {
+    "express": "^4.21.2",
+    "socket.io": "^4.8.1",
+    "socket.io-client": "^4.8.1",
+    "three": "^0.174.0",
     "three.js": "^0.77.1"
-  }
+  },
+  "name": "barbapapa-br",
+  "version": "1.0.0",
+  "main": "index.js",
+  "devDependencies": {
+    "vite": "^6.2.1"
+  },
+  "scripts": {
+    "test": "echo \"Error: no test specified\" && exit 1",
+    "dev": "vite"
+  },
+  "repository": {
+    "type": "git",
+    "url": "ssh://git@ssh.hesge.ch:10572/isc3/pratique_metier/barbapapa-br.git"
+  },
+  "keywords": [],
+  "author": "",
+  "license": "ISC",
+  "description": ""
 }
diff --git a/server.js b/server.js
new file mode 100644
index 0000000..17dc3df
--- /dev/null
+++ b/server.js
@@ -0,0 +1,82 @@
+// server.js
+const express = require('express');
+const http = require('http');
+const cors = require('cors');
+const { Server } = require('socket.io');
+
+const app = express();
+app.use(cors({ origin: "http://localhost:5173" })); // Autorise le client Vite
+const server = http.createServer(app);
+const io = new Server(server, {
+  cors: {
+    origin: "http://localhost:5173",
+    methods: ["GET", "POST"]
+  }
+});
+
+// Objet pour stocker les joueurs par salle
+const players = {};
+
+// Optionnel : servir des fichiers statiques
+app.use(express.static('public'));
+
+io.on('connection', (socket) => {
+  console.log('Nouvelle connexion : ' + socket.id);
+
+  socket.on('joinGame', (data) => {
+    const { room, playerInfo } = data;
+    socket.join(room);
+
+    // Initialiser la salle si nécessaire
+    if (!players[room]) {
+      players[room] = {};
+    }
+    // Enregistrer le joueur
+    players[room][socket.id] = playerInfo;
+    console.log(`${socket.id} a rejoint la salle ${room}`);
+
+    // Envoyer à la socket nouvellement connectée la liste des joueurs déjà présents
+    const existing = [];
+    for (const id in players[room]) {
+      if (id !== socket.id) {
+        existing.push({ id, playerInfo: players[room][id] });
+      }
+    }
+    socket.emit('existingPlayers', existing);
+
+    // Notifier les autres joueurs de l'arrivée du nouveau joueur
+    socket.to(room).emit('newPlayer', { id: socket.id, playerInfo });
+
+    // Vérifier le nombre de joueurs dans la salle
+    const roomSize = Object.keys(players[room]).length;
+    if (roomSize >= 2) {
+      io.to(room).emit('readyToStart', { room });
+    }
+  });
+
+  socket.on('playerUpdate', (data) => {
+    const { room, playerData } = data;
+    if (players[room]) {
+      players[room][socket.id] = playerData;
+    }
+    socket.to(room).emit('playerUpdate', { id: socket.id, playerData });
+  });
+
+  socket.on('shoot', (data) => {
+    const { room, shootData } = data;
+    socket.to(room).emit('shoot', { id: socket.id, shootData });
+  });
+
+  socket.on('disconnect', () => {
+    console.log('Déconnexion : ' + socket.id);
+    socket.rooms.forEach((room) => {
+      if (room !== socket.id && players[room]) {
+        delete players[room][socket.id];
+        socket.to(room).emit('playerDisconnected', { id: socket.id });
+      }
+    });
+  });
+});
+
+const PORT = process.env.PORT || 3000;
+server.listen(PORT, () => console.log(`Serveur démarré sur le port ${PORT}`));
diff --git a/src/index.js b/src/index.js
new file mode 100644
index 0000000..e9be771
--- /dev/null
+++ b/src/index.js
@@ -0,0 +1,456 @@
+// src/index.js
+
+// Import des modules depuis npm
+import * as THREE from 'three';
+import { io } from 'socket.io-client';
+
+// -------------------------------
+// 1. Configuration de Socket.IO
+// -------------------------------
+const socket = io('http://localhost:3000'); // Se connecter au serveur Socket.IO
+
+let currentRoom = null;   // Code de la salle
+let gameStarted = false;  // Indique que la partie a démarré
+
+// -------------------------------
+// 2. Références à l'interface HTML
+// -------------------------------
+const startMenu = document.getElementById('startMenu');
+const createGameButton = document.getElementById('createGameButton');
+const joinGameButton = document.getElementById('joinGameButton');
+const roomIdInput = document.getElementById('roomIdInput');
+const shareLinkDiv = document.getElementById('shareLink');
+const shareLinkInput = document.getElementById('shareLinkInput');
+const timerDiv = document.getElementById('timer');
+const healthDisplay = document.getElementById('healthDisplay');
+
+// -------------------------------
+// 3. Paramètres du jeu
+// -------------------------------
+const playerSpeed = 10;         // Vitesse de déplacement du joueur
+const zoneShrinkRate = 0.2;     // Taux de rétrécissement de la safe zone (unités/s)
+const matchDuration = 300;      // 5 minutes = 300 s
+let matchTimeLeft = matchDuration;
+let lastTime = performance.now() / 1000;
+
+// Pour suivre les touches (déplacement)
+const keysPressed = {};
+window.addEventListener('keydown', (event) => { keysPressed[event.code] = true; });
+window.addEventListener('keyup', (event) => { keysPressed[event.code] = false; });
+
+// Seuil de collision balle/personnage et dégâts
+const collisionThreshold = 1.5;
+const bulletDamage = 20;
+
+// -------------------------------
+// 4. Variables de jeu et stockage
+// -------------------------------
+let myPlayer = null;            // { mesh, health, speed }
+const otherPlayers = {};        // Mapping socketID => { mesh, health }
+const bullets = [];             // Tableau des balles, chaque balle : { mesh, velocity, lifeTime, ownerId }
+const obstacles = [];           // Tableau des obstacles
+
+// -------------------------------
+// 5. Initialisation de Three.js et de la scène
+// -------------------------------
+const scene = new THREE.Scene();
+scene.background = new THREE.Color(0x87ceeb);
+
+const camera = new THREE.PerspectiveCamera(
+  75,
+  window.innerWidth / window.innerHeight,
+  0.1,
+  1000
+);
+
+// Création du renderer
+const renderer = new THREE.WebGLRenderer({ antialias: true });
+renderer.setSize(window.innerWidth, window.innerHeight);
+document.body.appendChild(renderer.domElement);
+
+// Éclairage pour un bon rendu
+const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
+scene.add(ambientLight);
+const hemiLight = new THREE.HemisphereLight(0xffffff, 0x444444, 1);
+hemiLight.position.set(0, 200, 0);
+scene.add(hemiLight);
+const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
+directionalLight.position.set(50, 50, 50);
+scene.add(directionalLight);
+
+// -------------------------------
+// 6. Configuration de la caméra troisième personne
+// (Rotation horizontale uniquement, angle vertical verrouillé)
+// -------------------------------
+let cameraYaw = 0;            // Rotation horizontale (en radians)
+const cameraDistance = 10;    // Distance de la caméra par rapport au joueur
+const cameraHeight = 5;       // Hauteur fixe de la caméra
+const cameraSensitivity = 0.002;
+
+// Activer le pointer lock sur le canvas
+renderer.domElement.addEventListener('click', () => {
+  renderer.domElement.requestPointerLock();
+});
+
+// Utiliser document pour capturer les mouvements de la souris
+document.addEventListener('mousemove', (event) => {
+  if (document.pointerLockElement === renderer.domElement) {
+    // Ajouter le mouvement horizontal pour augmenter le yaw
+    cameraYaw += event.movementX * cameraSensitivity;
+  }
+});
+
+// Met à jour la position de la caméra derrière le joueur
+function updateCamera() {
+  if (!myPlayer) return;
+  const offset = new THREE.Vector3();
+  offset.x = cameraDistance * Math.sin(cameraYaw);
+  offset.z = cameraDistance * Math.cos(cameraYaw);
+  camera.position.copy(myPlayer.mesh.position)
+    .add(new THREE.Vector3(0, cameraHeight, 0))
+    .sub(offset);
+  camera.lookAt(myPlayer.mesh.position);
+}
+
+// -------------------------------
+// 7. Création du sol et de la safe zone
+// -------------------------------
+// Sol agrandi
+const groundGeometry = new THREE.PlaneGeometry(200, 200, 32, 32);
+const groundMaterial = new THREE.MeshLambertMaterial({ color: 0x90ee90, side: THREE.DoubleSide });
+const ground = new THREE.Mesh(groundGeometry, groundMaterial);
+ground.rotation.x = -Math.PI / 2;
+scene.add(ground);
+
+// Effet organique sur le sol
+const posAttr = groundGeometry.attributes.position;
+for (let i = 0; i < posAttr.count; i++) {
+  const x = posAttr.getX(i);
+  posAttr.setX(i, x + (Math.random() - 0.5) * 2);
+  posAttr.setZ(i, (Math.random() - 0.5) * 2);
+}
+groundGeometry.computeVertexNormals();
+
+// Safe zone agrandie (sphère en wireframe)
+let zoneRadius = 100;
+let safeZoneGeometry = new THREE.SphereGeometry(zoneRadius, 32, 32);
+const safeZoneMaterial = new THREE.MeshBasicMaterial({
+  color: 0xff0000,
+  opacity: 0.3,
+  transparent: true,
+  wireframe: true
+});
+const safeZone = new THREE.Mesh(safeZoneGeometry, safeZoneMaterial);
+safeZone.position.set(0, 0, 0);
+scene.add(safeZone);
+
+// -------------------------------
+// 8. Création des obstacles
+// -------------------------------
+function initObstacles() {
+  // Créer 5 obstacles (murs) sur la map
+  for (let i = 0; i < 5; i++) {
+    const width = 10 + Math.random() * 5;
+    const height = 5 + Math.random() * 5;
+    const depth = 5 + Math.random() * 5;
+    const geometry = new THREE.BoxGeometry(width, height, depth);
+    const material = new THREE.MeshLambertMaterial({ color: 0x888888 });
+    const obstacle = new THREE.Mesh(geometry, material);
+    // Positionner les obstacles aléatoirement sur le sol (dans une zone raisonnable)
+    obstacle.position.set((Math.random() - 0.5) * 150, height / 2, (Math.random() - 0.5) * 150);
+    scene.add(obstacle);
+    obstacles.push(obstacle);
+  }
+}
+
+// -------------------------------
+// 9. Fonctions d'initialisation et de jeu
+// -------------------------------
+// Crée le joueur local dans une zone de spawn à l'intérieur de la safe zone
+function initMyPlayer() {
+  if (myPlayer) return;
+  const playerGeometry = new THREE.SphereGeometry(1.5, 32, 32);
+  const material = new THREE.MeshLambertMaterial({ color: 0x00ff00 });
+  const mesh = new THREE.Mesh(playerGeometry, material);
+  // Spawn aléatoire dans un carré de [-50, 50] pour x et z
+  mesh.position.set((Math.random() - 0.5) * 100, 1.5, (Math.random() - 0.5) * 100);
+  scene.add(mesh);
+  myPlayer = { mesh, health: 100, speed: playerSpeed };
+  updateHealthDisplay();
+}
+
+// Met à jour l'affichage de la santé sous forme de cœurs (chaque cœur = 20 points)
+function updateHealthDisplay() {
+  if (!myPlayer) return;
+  const heartCount = Math.floor(myPlayer.health / 20);
+  healthDisplay.innerText = "♥".repeat(heartCount);
+}
+
+// Vérifie les collisions entre balles et personnages ou obstacles
+function checkBulletCollisions(delta) {
+  for (let i = bullets.length - 1; i >= 0; i--) {
+    const bullet = bullets[i];
+    // Collision avec le joueur local (si ce n'est pas lui qui a tiré)
+    if (bullet.ownerId !== socket.id && myPlayer) {
+      const dist = bullet.mesh.position.distanceTo(myPlayer.mesh.position);
+      if (dist < collisionThreshold) {
+        myPlayer.health -= bulletDamage;
+        if (myPlayer.health < 0) myPlayer.health = 0;
+        updateHealthDisplay();
+        scene.remove(bullet.mesh);
+        bullets.splice(i, 1);
+        if (myPlayer.health <= 0) {
+          alert("Perdu !");
+          scene.remove(myPlayer.mesh);
+          myPlayer = null;
+          socket.disconnect();
+          window.location.reload();
+        }
+        continue;
+      }
+    }
+    // Collision avec les autres joueurs
+    for (const id in otherPlayers) {
+      if (bullet.ownerId === id) continue;
+      const other = otherPlayers[id];
+      const dist = bullet.mesh.position.distanceTo(other.mesh.position);
+      if (dist < collisionThreshold) {
+        other.health -= bulletDamage;
+        if (other.health < 0) other.health = 0;
+        scene.remove(bullet.mesh);
+        bullets.splice(i, 1);
+        if (other.health <= 0) {
+          scene.remove(other.mesh);
+          delete otherPlayers[id];
+        }
+        break;
+      }
+    }
+    // Collision avec les obstacles
+    for (const obstacle of obstacles) {
+      const box = new THREE.Box3().setFromObject(obstacle);
+      if (box.containsPoint(bullet.mesh.position)) {
+        scene.remove(bullet.mesh);
+        bullets.splice(i, 1);
+        break;
+      }
+    }
+  }
+}
+
+// Boucle d'animation principale
+function animate() {
+  if (!gameStarted) return;
+  requestAnimationFrame(animate);
+  
+  const now = performance.now() / 1000;
+  const delta = now - lastTime;
+  lastTime = now;
+  
+  // Mise à jour du timer
+  matchTimeLeft -= delta;
+  if (matchTimeLeft < 0) matchTimeLeft = 0;
+  const minutes = Math.floor(matchTimeLeft / 60);
+  const seconds = Math.floor(matchTimeLeft % 60);
+  timerDiv.innerText = `Temps: ${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;
+  
+  // Rétrécissement progressif de la safe zone
+  if (zoneRadius > 5) {
+    zoneRadius -= delta * zoneShrinkRate;
+    safeZone.geometry.dispose();
+    safeZone.geometry = new THREE.SphereGeometry(zoneRadius, 32, 32);
+  }
+  
+  // Dégâts si le joueur est en dehors de la safe zone
+  if (myPlayer) {
+    const playerDistance = myPlayer.mesh.position.distanceTo(new THREE.Vector3(0, 0, 0));
+    if (playerDistance > zoneRadius) {
+      myPlayer.health -= 5 * delta;
+      if (myPlayer.health < 0) myPlayer.health = 0;
+      updateHealthDisplay();
+      if (myPlayer.health <= 0) {
+        alert("Perdu !");
+        scene.remove(myPlayer.mesh);
+        myPlayer = null;
+        socket.disconnect();
+        window.location.reload();
+      }
+    }
+  }
+  
+  // Déplacement du joueur adapté à l'orientation de la caméra
+  let moveDir = new THREE.Vector3();
+  const forward = new THREE.Vector3(Math.sin(cameraYaw), 0, Math.cos(cameraYaw));
+  // Pour obtenir le vecteur "right" correct, on le définit comme (-cos(cameraYaw), 0, sin(cameraYaw))
+  const right = new THREE.Vector3(Math.cos(cameraYaw), 0, -Math.sin(cameraYaw));
+  if (keysPressed['KeyW']) moveDir.add(forward);
+  if (keysPressed['KeyS']) moveDir.sub(forward);
+  if (keysPressed['KeyA']) moveDir.add(right);
+  if (keysPressed['KeyD']) moveDir.sub(right);
+  if (moveDir.length() > 0) {
+    moveDir.normalize();
+    myPlayer.mesh.position.add(moveDir.multiplyScalar(myPlayer.speed * delta));
+  }
+  
+  // Envoyer la mise à jour de la position au serveur
+  if (myPlayer) {
+    socket.emit('playerUpdate', {
+      room: currentRoom,
+      playerData: { position: myPlayer.mesh.position, health: myPlayer.health }
+    });
+  }
+  
+  // Mise à jour des balles tirées
+  for (let i = bullets.length - 1; i >= 0; i--) {
+    const bullet = bullets[i];
+    bullet.mesh.position.add(bullet.velocity.clone().multiplyScalar(delta));
+    bullet.lifeTime -= delta;
+    if (bullet.lifeTime <= 0) {
+      scene.remove(bullet.mesh);
+      bullets.splice(i, 1);
+    }
+  }
+  
+  // Vérifier les collisions entre balles et joueurs/obstacles
+  checkBulletCollisions(delta);
+  
+  // Mise à jour de la caméra
+  updateCamera();
+  
+  renderer.render(scene, camera);
+}
+
+// Démarre la partie côté client
+function startGame() {
+  startMenu.style.display = 'none';
+  timerDiv.style.display = 'block';
+  healthDisplay.style.display = 'block';
+  gameStarted = true;
+  lastTime = performance.now() / 1000;
+  // Initialiser les obstacles une fois au début de la partie
+  initObstacles();
+  animate();
+}
+
+// -------------------------------
+// 10. Gestion du tir via la souris (tir horizontal)
+// -------------------------------
+window.addEventListener('mousedown', () => {
+  if (!gameStarted || !myPlayer) return;
+  // Obtenir la direction de la caméra et forcer y = 0 pour garantir un tir horizontal
+  const shootDir = new THREE.Vector3();
+  camera.getWorldDirection(shootDir);
+  shootDir.y = 0;
+  shootDir.normalize();
+  
+  // Envoyer l'événement de tir au serveur
+  socket.emit('shoot', { room: currentRoom, shootData: { position: myPlayer.mesh.position.clone(), direction: shootDir.clone() } });
+  
+  // Créer localement la balle avec ownerId = socket.id
+  createBullet({ position: myPlayer.mesh.position.clone(), direction: shootDir, ownerId: socket.id });
+});
+
+// Fonction de création d'une balle
+function createBullet(shootData) {
+  const bulletGeometry = new THREE.SphereGeometry(0.3, 16, 16);
+  const bulletMaterial = new THREE.MeshLambertMaterial({ color: 0xffff00 });
+  const bulletMesh = new THREE.Mesh(bulletGeometry, bulletMaterial);
+  bulletMesh.position.copy(shootData.position);
+  scene.add(bulletMesh);
+  bullets.push({
+    mesh: bulletMesh,
+    velocity: new THREE.Vector3().copy(shootData.direction).multiplyScalar(30),
+    lifeTime: 2, // Durée de vie en secondes
+    ownerId: shootData.ownerId
+  });
+}
+
+// -------------------------------
+// 11. Gestion des événements Socket.IO
+// -------------------------------
+socket.on('connect', () => {
+  console.log("Connecté au serveur avec l'ID :", socket.id);
+});
+
+// Lorsqu'un nouveau joueur rejoint la partie (après nous)
+socket.on('newPlayer', (data) => {
+  if (data.id === socket.id) return;
+  console.log("Nouveau joueur :", data.id);
+  const playerGeometry = new THREE.SphereGeometry(1.5, 32, 32);
+  const material = new THREE.MeshLambertMaterial({ color: 0xff00ff });
+  const mesh = new THREE.Mesh(playerGeometry, material);
+  mesh.position.copy(data.playerInfo.position);
+  scene.add(mesh);
+  otherPlayers[data.id] = { mesh, health: data.playerInfo.health };
+});
+
+// Réception de la liste des joueurs déjà présents dans la salle
+socket.on('existingPlayers', (playersArray) => {
+  playersArray.forEach((data) => {
+    if (data.id === socket.id) return;
+    console.log("Joueur existant :", data.id);
+    const playerGeometry = new THREE.SphereGeometry(1.5, 32, 32);
+    const material = new THREE.MeshLambertMaterial({ color: 0xff00ff });
+    const mesh = new THREE.Mesh(playerGeometry, material);
+    mesh.position.copy(data.playerInfo.position);
+    scene.add(mesh);
+    otherPlayers[data.id] = { mesh, health: data.playerInfo.health };
+  });
+});
+
+// Mise à jour de la position d'un joueur distant
+socket.on('playerUpdate', (data) => {
+  if (data.id === socket.id) return;
+  if (otherPlayers[data.id]) {
+    otherPlayers[data.id].mesh.position.copy(data.playerData.position);
+  }
+});
+
+// Lorsqu'un joueur se déconnecte
+socket.on('playerDisconnected', (data) => {
+  console.log("Joueur déconnecté :", data.id);
+  if (otherPlayers[data.id]) {
+    scene.remove(otherPlayers[data.id].mesh);
+    delete otherPlayers[data.id];
+  }
+});
+
+// Réception d'un tir d'un autre joueur
+socket.on('shoot', (data) => {
+  if (data.id === socket.id) return;
+  createBullet({ position: data.shootData.position, direction: data.shootData.direction, ownerId: data.id });
+});
+
+// L'événement "readyToStart" indique que la salle compte au moins 2 joueurs
+socket.on('readyToStart', (data) => {
+  console.log("Salle", data.room, ": 2 joueurs connectés. Démarrage de la partie !");
+  startGame();
+});
+
+// -------------------------------
+// 12. Gestion du menu de démarrage
+// -------------------------------
+createGameButton.addEventListener('click', () => {
+  if (!myPlayer) initMyPlayer();
+  currentRoom = Math.random().toString(36).substr(2, 6);
+  shareLinkDiv.style.display = 'block';
+  shareLinkInput.value = window.location.origin + '?room=' + currentRoom;
+  socket.emit('joinGame', { room: currentRoom, playerInfo: { position: myPlayer.mesh.position, health: 100 } });
+});
+
+joinGameButton.addEventListener('click', () => {
+  if (!myPlayer) initMyPlayer();
+  const room = roomIdInput.value.trim();
+  if (!room) return;
+  currentRoom = room;
+  socket.emit('joinGame', { room: currentRoom, playerInfo: { position: myPlayer.mesh.position, health: 100 } });
+});
+
+// -------------------------------
+// 13. Gestion du redimensionnement de la fenêtre
+// -------------------------------
+window.addEventListener('resize', () => {
+  camera.aspect = window.innerWidth / window.innerHeight;
+  camera.updateProjectionMatrix();
+  renderer.setSize(window.innerWidth, window.innerHeight);
+});
-- 
GitLab