diff --git a/Transcendence/srcs/backend/routes/game_room.js b/Transcendence/srcs/backend/routes/game_room.js index d3f722b..81886f1 100644 --- a/Transcendence/srcs/backend/routes/game_room.js +++ b/Transcendence/srcs/backend/routes/game_room.js @@ -33,7 +33,6 @@ router.get('/playing', authenticateToken, async(req, res) => } }); - // IMPORTANT: This route must be before /:roomId to avoid "current" being interpreted as a roomId router.get('/current', authenticateToken, async(req, res) => { @@ -150,7 +149,6 @@ router.post('/:roomId/leave', authenticateToken, async(req, res) => } }); - // Join a room as spectator router.post('/:roomId/spectate', authenticateToken, async(req, res) => { @@ -184,5 +182,4 @@ router.post('/:roomId/leave-spectate', authenticateToken, async(req, res) => } }); - -export default router; +export default router; \ No newline at end of file diff --git a/Transcendence/srcs/backend/services/game_room.js b/Transcendence/srcs/backend/services/game_room.js index f59e7e8..e412297 100644 --- a/Transcendence/srcs/backend/services/game_room.js +++ b/Transcendence/srcs/backend/services/game_room.js @@ -108,7 +108,6 @@ async function leaveSpectateRoom(roomId, userId) } } - async function joinRoom(roomId, userId) { const room = await getRoomById(roomId); @@ -181,7 +180,7 @@ async function getCurrentRoom(userId) `SELECT r.* FROM game_rooms r JOIN game_players gp ON r.id = gp.room_id - WHERE gp.user_id = $1 AND r.status = 'waiting' + WHERE gp.user_id = $1 AND r.status IN ('waiting', 'playing') LIMIT 1`, [userId] ); @@ -252,4 +251,4 @@ export default updateRoomStatus, resetRoomScores, cleanupEndedRooms -}; +}; \ No newline at end of file diff --git a/Transcendence/srcs/backend/services/socket.js b/Transcendence/srcs/backend/services/socket.js index aede582..79479ff 100644 --- a/Transcendence/srcs/backend/services/socket.js +++ b/Transcendence/srcs/backend/services/socket.js @@ -600,10 +600,36 @@ function setupSocketIO(io) const gameState = gameRooms.get(roomId); if (gameState) { + const wasDrawer = gameState.drawer === username; + gameState.players = gameState.players.filter(p => p !== username); delete gameState.scores[username]; io.to(roomId).emit('scores-updated', gameState.scores); + + // If the drawer left and there are still enough players, choose a new drawer + if (wasDrawer && gameState.players.length >= 1) + { + // Pick the next player as the new drawer + gameState.currentPlayerIndex = gameState.currentPlayerIndex % gameState.players.length; + const newDrawer = gameState.players[gameState.currentPlayerIndex]; + gameState.drawer = newDrawer; + + // Reset the word state for the new round + gameState.currentWord = ''; + gameState.revealedLetters = []; + gameState.revealedWord = []; + gameState.guessedLetters = []; + gameState.wrongGuesses = 0; + + console.log(`Drawer ${username} left, new drawer is ${newDrawer}`); + + io.to(roomId).emit('game-drawer-changed', { + newDrawer: newDrawer, + reason: 'drawer_left', + message: `${username} (dessinateur) a quitté, ${newDrawer} devient le nouveau dessinateur` + }); + } } await checkAndStopSinglePlayerGame(io, roomId, dbRoomId); @@ -769,7 +795,9 @@ function setupSocketIO(io) username: socket.user.username }); console.log(`Spectator ${socket.user.username} disconnected from ${roomId}`); - } else { + } + else + { // Regular player disconnect socket.to(roomId).emit('game-player-left', { username: socket.user.username, @@ -789,25 +817,9 @@ function setupSocketIO(io) // Check if game should auto-stop due to single player await checkAndStopSinglePlayerGame(io, roomId, dbRoomId); - - - socket.to(roomId).emit('game-player-left', { - username: socket.user.username, - userId: socket.user.userId - }); - - // Get updated player list and broadcast - if (dbRoomId) { - try { - const players = await gameRoomService.getRoomPlayers(dbRoomId); - io.to(roomId).emit('game-players-updated', { players }); - } catch (err) { - console.log('Room may have been deleted on disconnect:', err.message); - } + // Broadcast updated rooms list + broadcastRoomsList(io); } - - // Broadcast updated rooms list - broadcastRoomsList(io); } }); }); @@ -815,7 +827,8 @@ function setupSocketIO(io) // ── Helpers tetris duel ────────────────────────────────────────────────── -function _tetrisLeave(socket) { +function _tetrisLeave(socket) +{ const code = socket.tetrisRoomCode; if (!code) return; const room = tetrisRooms.get(code); @@ -842,4 +855,4 @@ function _tetrisRelayToOpponent(socket, event, data) { } export { broadcastRoomsList }; -export default setupSocketIO; +export default setupSocketIO; \ No newline at end of file diff --git a/Transcendence/srcs/frontend/src/app.js b/Transcendence/srcs/frontend/src/app.js index 30e5a8c..b1d528c 100644 --- a/Transcendence/srcs/frontend/src/app.js +++ b/Transcendence/srcs/frontend/src/app.js @@ -69,7 +69,6 @@ class App { initPage() { const page = document.querySelector('.page'); if (!page) { - console.warn('Page not found'); return; } diff --git a/Transcendence/srcs/frontend/src/avatar.js b/Transcendence/srcs/frontend/src/avatar.js index be7c939..bf164e1 100644 --- a/Transcendence/srcs/frontend/src/avatar.js +++ b/Transcendence/srcs/frontend/src/avatar.js @@ -16,7 +16,9 @@ export class AvatarWindow extends Window { this.buildUI(); this.bindEvents(); - this.loadAvatar(); + if (localStorage.getItem(STORAGE_KEYS.AUTH_TOKEN)) { + this.loadAvatar(); + } // Listen for login events eventBus.on(Events.USER_LOGGED_IN, () => this.loadAvatar()); diff --git a/Transcendence/srcs/frontend/src/config.js b/Transcendence/srcs/frontend/src/config.js index 9fae344..34bab36 100644 --- a/Transcendence/srcs/frontend/src/config.js +++ b/Transcendence/srcs/frontend/src/config.js @@ -23,11 +23,14 @@ export const API = { }, ROOMS: { LIST: '/api/rooms', + PLAYING: '/api/rooms/playing', CREATE: '/api/rooms', GET: (id) => `/api/rooms/${id}`, PLAYERS: (id) => `/api/rooms/${id}/players`, JOIN: (id) => `/api/rooms/${id}/join`, LEAVE: (id) => `/api/rooms/${id}/leave`, + SPECTATE: (id) => `/api/rooms/${id}/spectate`, + LEAVE_SPECTATE: (id) => `/api/rooms/${id}/leave-spectate`, CURRENT: '/api/rooms/current' }, STATS: { diff --git a/Transcendence/srcs/frontend/src/events.js b/Transcendence/srcs/frontend/src/events.js index 632a64c..f323d30 100644 --- a/Transcendence/srcs/frontend/src/events.js +++ b/Transcendence/srcs/frontend/src/events.js @@ -53,11 +53,13 @@ class EventBus { */ emit(event, data) { if (this.listeners.has(event)) { + const listeners = this.listeners.get(event); this.listeners.get(event).forEach(callback => { try { callback(data); - } catch (error) { - console.error(`Error in listener for "${event}":`, error); + } + catch (err) { + // Show that some events are not fully handled, but don't break the app } }); } diff --git a/Transcendence/srcs/frontend/src/game_room.js b/Transcendence/srcs/frontend/src/game_room.js index c19993d..f8e8cb0 100644 --- a/Transcendence/srcs/frontend/src/game_room.js +++ b/Transcendence/srcs/frontend/src/game_room.js @@ -36,9 +36,9 @@ export class GameRoomWindow extends Window { this.updateTabsAccess(); // Verifier si l'utilisateur est deja dans un salon au chargement - if (this.isLoggedIn()) { + const token = localStorage.getItem(STORAGE_KEYS.AUTH_TOKEN); + if (token) this.checkCurrentRoom(); - } } buildUI() { @@ -49,6 +49,11 @@ export class GameRoomWindow extends Window { }); this.browseTab.dataset.tab = 'browse'; + this.spectatorTab = this.createElement('button', CSS.GAMEROOM_TAB, { + text: 'Spectateur' + }); + this.spectatorTab.dataset.tab = 'spectator'; + this.createTab = this.createElement('button', CSS.GAMEROOM_TAB, { text: 'Creer' }); @@ -60,7 +65,7 @@ export class GameRoomWindow extends Window { this.lobbyTab.dataset.tab = 'lobby'; this.lobbyTab.style.display = 'none'; - this.tabs.append(this.browseTab, this.createTab, this.lobbyTab); + this.tabs.append(this.browseTab, this.spectatorTab, this.createTab, this.lobbyTab); this.content = this.createElement('div', CSS.GAMEROOM_CONTENT); @@ -99,9 +104,12 @@ export class GameRoomWindow extends Window { this.list = this.createElement('div', CSS.GAMEROOM_LIST); + this.spectatorList = this.createElement('div', CSS.GAMEROOM_LIST); + this.spectatorList.style.display = 'none'; + this.message = this.createElement('div', CSS.MESSAGE); - this.content.append(this.createContainer, this.lobbyContainer, this.list, this.message); + this.content.append(this.createContainer, this.lobbyContainer, this.list, this.spectatorList, this.message); this.body.append(this.tabs, this.content); } @@ -160,7 +168,7 @@ export class GameRoomWindow extends Window { // Boutons du jeu this.gameButtons = this.createElement('div', 'gameroom__game-buttons'); - this.backToLobbyBtn = this.createElement('button', [CSS.BTN, CSS.BTN_SECONDARY], { text: 'Retour au lobby' }); + this.backToLobbyBtn = this.createElement('button', [CSS.BTN, CSS.BTN_SECONDARY], { text: 'Quitter la partie' }); this.endRoundBtn = this.createElement('button', [CSS.BTN, CSS.BTN_DANGER], { text: 'Terminer le jeu' }); this.gameButtons.append(this.backToLobbyBtn, this.endRoundBtn); @@ -198,7 +206,7 @@ export class GameRoomWindow extends Window { this.lastY = 0; this.canvas.addEventListener('mousedown', (e) => { - if (!this.gameState.isPlaying || !this.isCurrentUserDrawer()) return; + if (!this.gameState.isPlaying || !this.isCurrentUserDrawer() || this.isSpectating) return; this.isDrawing = true; [this.lastX, this.lastY] = [e.offsetX, e.offsetY]; }); @@ -365,7 +373,25 @@ export class GameRoomWindow extends Window { }); this.socket.on('game-player-left', (data) => { - console.log(`${data.username} left the room`); + this.showMessage(`${data.username} a quitté le salon`, 'info'); + + if (this.gameState.isPlaying) + { + if (this.gameState.players) + this.gameState.players = this.gameState.players.filter(p => p !== data.username); + } + + if (this.gameState.scores) + { + delete this.gameState.scores[data.username]; + this.updateScoresDisplay(this.gameState.scores); + } + + // Note: If the drawer left, the server will emit 'game-drawer-changed' + // with the new drawer, so we don't need to handle it here + + if (this.currentRoom && !this.gameState.isPlaying) + this.loadLobby(); }); // Game started @@ -384,6 +410,12 @@ export class GameRoomWindow extends Window { this.setupRound(); }); + // Game start error + this.socket.on('game-start-error', (data) => { + console.error('Game start error:', data.error); + this.showMessage(data.error || 'Impossible de démarrer la partie', 'error'); + }); + // Word was set by drawer this.socket.on('game-word-set', (data) => { console.log(`Word set by ${data.drawer}, length: ${data.wordLength}`); @@ -396,6 +428,13 @@ export class GameRoomWindow extends Window { } this.updateWordDisplay(); + + // Don't change UI for spectators + if (this.isSpectating) { + this.currentDrawerInfo.textContent = '👁️ MODE SPECTATEUR - Vous regardez la partie'; + return; + } + this.currentDrawerInfo.textContent = `${data.drawer} dessine (${data.wordLength} lettres)`; // Enable guess input for non-drawers @@ -451,7 +490,22 @@ export class GameRoomWindow extends Window { // Game ended this.socket.on('game-ended', () => { - this.resetGameUI(); + // If spectating, return to spectator list + if (this.isSpectating) { + this.resetGameUI(); + this.currentRoom = null; + this.isSpectating = false; + this.switchTab('spectator'); + this.showMessage('La partie est terminée', 'info'); + } else { + this.resetGameUI(); + this.loadLobby(); + } + }); + + // Game message from server + this.socket.on('game-message', (data) => { + this.showMessage(data.message, data.type || 'info'); }); // Sync state for late joiners @@ -463,12 +517,25 @@ export class GameRoomWindow extends Window { this.gameState.revealedLetters = data.revealedLetters || []; this.gameState.revealedWord = data.revealedWord || new Array(data.wordLength).fill('_'); this.gameState.players = data.players; + this.gameState.scores = data.scores || {}; this.showGameUI(); this.updateWordDisplay(); + + // Update scores display + if (data.scores) { + this.updateScoresDisplay(data.scores); + } + this.currentDrawerInfo.textContent = `${data.drawer} dessine (${data.wordLength} lettres)`; - if (!this.isCurrentUserDrawer()) { + // Don't enable input for spectators + if (this.isSpectating) { + this.guessContainer.style.display = 'none'; + this.wordInputContainer.style.display = 'none'; + this.drawTools.style.display = 'none'; + this.currentDrawerInfo.textContent = '👁️ MODE SPECTATEUR - Vous regardez la partie'; + } else if (!this.isCurrentUserDrawer()) { this.guessContainer.style.display = 'flex'; if (data.wordLength > 0) { this.letterInput.disabled = false; @@ -482,11 +549,73 @@ export class GameRoomWindow extends Window { } } }); + + // Spectator events + this.socket.on('game-spectate-joined', (data) => { + console.log('Successfully joined as spectator:', data.roomId); + this.isSpectating = true; + + // Prepare UI for spectating + this.spectatorList.style.display = 'none'; + this.list.style.display = 'none'; + this.createContainer.style.display = 'none'; + this.lobbyContainer.style.display = 'flex'; + + // Hide lobby elements, keep game container for when state syncs + this.playerList.style.display = 'none'; + this.lobbyButtons.style.display = 'none'; + this.lobbyTitle.textContent = 'Mode Spectateur'; + + this.showMessage('Vous regardez la partie...', 'success'); + // The game state will be synced via game-state-sync event + }); + + this.socket.on('game-spectate-error', (data) => { + console.error('Spectate error:', data.error); + this.showMessage(data.error || 'Impossible de regarder cette partie', 'error'); + }); + + this.socket.on('game-spectator-joined', (data) => { + console.log(`Spectator ${data.username} joined`); + }); + + this.socket.on('game-spectator-left', (data) => { + console.log(`Spectator ${data.username} left`); + }); + + // Drawer changed (when drawer leaves during game) + this.socket.on('game-drawer-changed', (data) => { + console.log('Drawer changed:', data); + this.showMessage(data.message, 'info'); + + // Update game state with new drawer + this.gameState.drawer = data.newDrawer; + this.gameState.currentPlayerIndex = this.gameState.players.indexOf(data.newDrawer); + + // Reset round state + this.gameState.currentWord = ''; + this.gameState.wordLength = 0; + this.gameState.revealedLetters = []; + this.gameState.revealedWord = []; + this.gameState.guessedLetters = []; + + // Clear canvas and history + this.clearCanvas(); + this.guessHistory.innerHTML = ''; + this.wordDisplay.textContent = ''; + + // Setup UI for new round with new drawer + this.setupRound(); + }); } disconnectGameSocket() { if (this.socket) { - this.socket.emit('game-leave-room'); + if (this.isSpectating) { + this.socket.emit('game-leave-spectate'); + } else { + this.socket.emit('game-leave-room'); + } } } @@ -533,13 +662,14 @@ export class GameRoomWindow extends Window { this.currentTab = tabName; - [this.browseTab, this.createTab, this.lobbyTab].forEach(tab => { + [this.browseTab, this.spectatorTab, this.createTab, this.lobbyTab].forEach(tab => { tab.classList.toggle(CSS.GAMEROOM_TAB_ACTIVE, tab.dataset.tab === tabName); }); this.createContainer.style.display = tabName === 'create' ? 'flex' : 'none'; this.lobbyContainer.style.display = tabName === 'lobby' ? 'flex' : 'none'; this.list.style.display = tabName === 'browse' ? 'flex' : 'none'; + this.spectatorList.style.display = tabName === 'spectator' ? 'flex' : 'none'; this.loadCurrentTab(); } @@ -551,6 +681,10 @@ export class GameRoomWindow extends Window { // Connect to socket to receive real-time room updates this.ensureSocketConnected(); break; + case 'spectator': + this.loadPlayingRooms(); + this.ensureSocketConnected(); + break; case 'create': this.message.textContent = ''; this.ensureSocketConnected(); @@ -776,6 +910,123 @@ export class GameRoomWindow extends Window { } } + createSpectatorRoomItem(room) { + const item = this.createElement('div', CSS.GAMEROOM_ITEM); + + const name = this.createElement('span', CSS.GAMEROOM_NAME, { + text: room.name + }); + + const players = this.createElement('span', CSS.GAMEROOM_PLAYERS, { + text: `${room.player_count || 0}/${room.max_players || 8}` + }); + + const status = this.createElement('span', 'gameroom__status', { + text: '🎮 En cours' + }); + status.style.color = '#4CAF50'; + status.style.fontWeight = 'bold'; + + const actions = this.createElement('div', CSS.GAMEROOM_ACTIONS); + + const spectateBtn = this.createElement('button', [CSS.BTN, CSS.BTN_PRIMARY], { + text: 'Regarder' + }); + spectateBtn.addEventListener('click', () => this.spectateRoom(room.id)); + actions.appendChild(spectateBtn); + + item.append(name, players, status, actions); + return item; + } + + async loadPlayingRooms() { + const token = localStorage.getItem(STORAGE_KEYS.AUTH_TOKEN); + if (!token) { + this.showMessage('Connectez-vous pour voir les parties en cours', 'info'); + return; + } + + try { + const response = await fetch(API.ROOMS.PLAYING, { + headers: this.getHeaders() + }); + const data = await response.json(); + + if (!response.ok) { + this.showMessage(data.error || 'Erreur', 'error'); + return; + } + + this.renderPlayingRoomsList(data || []); + } catch (error) { + console.error('Load playing rooms error:', error); + this.showMessage('Erreur de connexion', 'error'); + } + } + + renderPlayingRoomsList(rooms) { + this.spectatorList.innerHTML = ''; + this.message.textContent = ''; + + if (rooms.length === 0) { + this.showMessage('Aucune partie en cours', 'info'); + return; + } + + rooms.forEach(room => { + const item = this.createSpectatorRoomItem(room); + this.spectatorList.appendChild(item); + }); + } + + async spectateRoom(roomId) { + const token = localStorage.getItem(STORAGE_KEYS.AUTH_TOKEN); + if (!token) { + this.showMessage('Connectez-vous pour regarder', 'info'); + return; + } + + // Check if user is already in a room as a player + if (this.currentRoom && !this.isSpectating) { + this.showMessage('Vous êtes déjà dans un salon. Quittez-le d\'abord.', 'error'); + return; + } + + // Check if already spectating another game + if (this.isSpectating && this.currentRoom && this.currentRoom.id !== roomId) { + this.showMessage('Vous regardez déjà une autre partie', 'error'); + return; + } + + try { + const response = await fetch(API.ROOMS.SPECTATE(roomId), { + method: 'POST', + headers: this.getHeaders() + }); + const data = await response.json(); + + if (!response.ok) { + this.showMessage(data.error || 'Impossible de regarder cette partie', 'error'); + return; + } + + // Store room info and mark as spectating + this.currentRoom = data; + this.isSpectating = true; + + // Join as spectator via socket + await this.ensureSocketConnected(); + if (this.socket?.connected) { + this.socket.emit('game-spectate-room', { roomId: roomId }); + } + + this.showMessage('Connexion à la partie...', 'info'); + } catch (error) { + console.error('Spectate room error:', error); + this.showMessage('Erreur de connexion', 'error'); + } + } + async joinRoom(roomId) { const token = localStorage.getItem(STORAGE_KEYS.AUTH_TOKEN); if (!token) { @@ -844,6 +1095,8 @@ export class GameRoomWindow extends Window { async loadLobby() { if (!this.currentRoom) return; + this.gameState.scores = {}; + try { const response = await fetch(API.ROOMS.PLAYERS(this.currentRoom.id), { headers: this.getHeaders() @@ -870,6 +1123,10 @@ export class GameRoomWindow extends Window { text: 'Aucun joueur' }); this.playerList.appendChild(empty); + // Disable start button if no players + this.startGameBtn.disabled = true; + this.startGameBtn.style.opacity = '0.5'; + this.startGameBtn.title = 'Il faut au moins 2 joueurs'; return; } @@ -899,6 +1156,17 @@ export class GameRoomWindow extends Window { item.append(avatar, name, statsContainer); this.playerList.appendChild(item); }); + + // Enable/disable start button based on player count + if (players.length < 2) { + this.startGameBtn.disabled = true; + this.startGameBtn.style.opacity = '0.5'; + this.startGameBtn.title = 'Il faut au moins 2 joueurs'; + } else { + this.startGameBtn.disabled = false; + this.startGameBtn.style.opacity = '1'; + this.startGameBtn.title = ''; + } } async leaveRoom() { @@ -941,6 +1209,11 @@ export class GameRoomWindow extends Window { } showMessage(text, type = 'info') { + // Clear any existing timeout + if (this.messageTimeout) { + clearTimeout(this.messageTimeout); + } + this.message.textContent = text; this.message.className = CSS.MESSAGE; @@ -951,6 +1224,12 @@ export class GameRoomWindow extends Window { } else { this.message.classList.add(CSS.MESSAGE_INFO); } + + // Auto-clear message after 5 seconds + this.messageTimeout = setTimeout(() => { + this.message.textContent = ''; + this.message.className = CSS.MESSAGE; + }, 5000); } // ============================================ @@ -978,6 +1257,23 @@ export class GameRoomWindow extends Window { this.lobbyButtons.style.display = 'none'; this.clearCanvas(); this.guessHistory.innerHTML = ''; + + // If spectating, show indicator and disable interactions + if (this.isSpectating) { + this.currentDrawerInfo.textContent = '👁️ MODE SPECTATEUR - Vous regardez la partie'; + this.currentDrawerInfo.style.backgroundColor = '#2196F3'; + this.currentDrawerInfo.style.color = 'white'; + this.currentDrawerInfo.style.padding = '8px'; + this.currentDrawerInfo.style.borderRadius = '4px'; + this.currentDrawerInfo.style.textAlign = 'center'; + + // Change button text for spectators + this.backToLobbyBtn.textContent = 'Arrêter de regarder'; + this.endRoundBtn.style.display = 'none'; // Hide end game button for spectators + } else { + this.backToLobbyBtn.textContent = 'Quitter la partie'; + this.endRoundBtn.style.display = 'inline-block'; + } } resetGameUI() { @@ -987,6 +1283,21 @@ export class GameRoomWindow extends Window { this.gameState.revealedLetters = []; this.gameState.revealedWord = []; this.gameState.drawer = null; + this.isSpectating = false; + + this.gameState.scores = {}; + this.gameState.players = []; + this.gameState.currentPlayerIndex = 0; + this.gameState.guessedLetters = []; + + // Clear scores display + if (this.scoresDisplay) + this.scoresDisplay.textContent = ''; + + if (this.guessHistory) + this.guessHistory.innerHTML = ''; + + this.clearCanvas(); this.gameContainer.style.display = 'none'; this.playerList.style.display = 'flex'; @@ -996,6 +1307,12 @@ export class GameRoomWindow extends Window { this.guessContainer.style.display = 'none'; this.drawTools.style.display = 'none'; + // Reset spectator styling + this.currentDrawerInfo.style.backgroundColor = ''; + this.currentDrawerInfo.style.color = ''; + this.currentDrawerInfo.style.padding = ''; + this.currentDrawerInfo.style.borderRadius = ''; + this.currentDrawerInfo.style.textAlign = ''; this.currentDrawerInfo.classList.remove('gameroom__drawer-info--winner'); } @@ -1010,8 +1327,8 @@ export class GameRoomWindow extends Window { console.log('Players found:', players); - if (players.length < 1) { - this.showMessage('Il faut au moins 1 joueur pour jouer', 'error'); + if (players.length < 2) { + this.showMessage('Il faut au moins 2 joueurs pour commencer', 'error'); return; } @@ -1048,6 +1365,15 @@ export class GameRoomWindow extends Window { this.guessHistory.innerHTML = ''; this.clearCanvas(); + // Spectators cannot interact + if (this.isSpectating) { + this.wordInputContainer.style.display = 'none'; + this.guessContainer.style.display = 'none'; + this.drawTools.style.display = 'none'; + this.currentDrawerInfo.textContent = '👁️ MODE SPECTATEUR - Vous regardez la partie'; + return; + } + if (this.isCurrentUserDrawer()) { // Drawer chooses a word this.wordInputContainer.style.display = 'flex'; @@ -1254,9 +1580,14 @@ export class GameRoomWindow extends Window { } backToLobby() { + if (this.socket?.connected) { + this.socket.emit('leave-room-during-game'); + } + // Return to lobby without ending game for others this.resetGameUI(); - this.loadLobby(); + this.exitLobby(); + this.showMessage('Vous avez quitté la partie', 'info'); } endGame() { diff --git a/Transcendence/srcs/frontend/src/global_chat.js b/Transcendence/srcs/frontend/src/global_chat.js index b2b121b..6e64598 100644 --- a/Transcendence/srcs/frontend/src/global_chat.js +++ b/Transcendence/srcs/frontend/src/global_chat.js @@ -17,6 +17,8 @@ export class GlobalChat extends Window { this.socket = null; this.connected = false; this.friendIds = new Set(); + this.currentUserId = null; + this.currentUsername = null; this.buildUI(); this.bindEvents(); @@ -169,6 +171,19 @@ export class GlobalChat extends Window { await this.connect(); } + decodeToken(token) + { + try + { + const payload = token.split('.')[1]; + return (JSON.parse(atob(payload))); + } + catch + { + return (null); + } + } + /** * Connects to the Socket.IO server */ @@ -180,6 +195,13 @@ export class GlobalChat extends Window { return; } + const tokenData = this.decodeToken(token); + + if (tokenData) { + this.currentUserId = tokenData.id || tokenData.userId || tokenData.user_id || tokenData.sub || null; + this.currentUsername = tokenData.username || tokenData.name || null; + } + if (this.socket?.connected) { this.addSystemMessage('Already connected to global chat'); return; @@ -239,6 +261,7 @@ export class GlobalChat extends Window { this.socket.on('connect', () => { console.log('Socket connected, ID:', this.socket.id); this.connected = true; + this.output.innerHTML = ''; this.addSystemMessage('Connected to global chat', 'success'); eventBus.emit(Events.CHAT_CONNECTED, { socketId: this.socket.id }); }); @@ -262,15 +285,38 @@ export class GlobalChat extends Window { // Display recent messages data.messages.forEach(msg => { - const isFriend = this.friendIds.has(msg.sender_id); - this.addChatMessage(msg.username, msg.content, false, isFriend); + const isOwn = this.isOwnMessage(msg); + const isFriend = !isOwn && this.friendIds.has(msg.sender_id); + const displayUsername = isOwn ? 'Me' : msg.username; + this.addChatMessage(displayUsername, msg.content, isOwn, isFriend); }); }); this.socket.on('chat-message', (msg) => { + const isOwn = this.isOwnMessage(msg); + if (isOwn) + return; + const isFriend = this.friendIds.has(msg.sender_id); this.addChatMessage(msg.username, msg.content, false, isFriend); eventBus.emit(Events.CHAT_MESSAGE_RECEIVED, msg); }); } + + isOwnMessage(msg) + { + if (this.currentUserId !== null && msg.sender_id !== undefined && msg.sender_id !== null) + { + if (String(this.currentUserId) === String(msg.sender_id)) + return (true); + } + + if (this.currentUsername && msg.username) + { + if (this.currentUsername.toLowerCase() === msg.username.toLowerCase()) + return (true); + } + + return (false); + } }