fix: ghost players on back - leaveRoom, store.reset, dynamic lobby slots

This commit is contained in:
kevin.lhereec committed 2026-08-17 14:18:27 +02:00
1 parent 6e384d5985
commit c7672b1793
5 files changed
+86 -20

No files matched your search

+15 -2
View File
@@ -4,8 +4,6 @@ let socket = null
export function useSocket() { export function useSocket() {
if (!socket) { if (!socket) {
// En dev : pointe vers localhost:3000
// En prod Docker : connexion sur la même origine (nginx proxifie /socket.io)
const url = import.meta.env.VITE_SERVER_URL || ( const url = import.meta.env.VITE_SERVER_URL || (
import.meta.env.PROD ? window.location.origin : 'http://localhost:3000' import.meta.env.PROD ? window.location.origin : 'http://localhost:3000'
) )
@@ -13,3 +11,18 @@ export function useSocket() {
} }
return socket return socket
} }
/**
* Quitte proprement une salle et nettoie tous les listeners de jeu.
* À appeler dans onUnmounted de LobbyView et GameView.
*/
export function leaveRoom(roomId) {
if (!socket) return
socket.emit('leave-room', { roomId })
// Retirer tous les listeners liés au jeu
const gameEvents = [
'room-update', 'game-start', 'game-state',
'players-ready', 'round-result', 'game-over'
]
gameEvents.forEach(ev => socket.off(ev))
}
+18 -7
View File
@@ -3,7 +3,7 @@ import { ref } from 'vue'
export const useGameStore = defineStore('game', () => { export const useGameStore = defineStore('game', () => {
const playerName = ref('') const playerName = ref('')
const players = ref([]) // { name, teamIndex, gauges:{food,war,health,diplo}, score, handSize } const players = ref([])
const currentTurn = ref(0) const currentTurn = ref(0)
const maxTurns = ref(12) const maxTurns = ref(12)
const phase = ref('idle') const phase = ref('idle')
@@ -16,20 +16,31 @@ export const useGameStore = defineStore('game', () => {
} }
function updateState(state) { function updateState(state) {
players.value = state.players players.value = state.players ?? []
currentTurn.value = state.currentTurn currentTurn.value = state.currentTurn ?? 0
maxTurns.value = state.maxTurns maxTurns.value = state.maxTurns ?? 12
phase.value = state.phase phase.value = state.phase ?? 'idle'
myTeamIndex.value = state.myTeamIndex ?? 0 myTeamIndex.value = state.myTeamIndex ?? 0
if (state.hand !== undefined) hand.value = state.hand if (state.hand !== undefined) hand.value = state.hand
} }
function selectCard(card) { selectedCard.value = card } function selectCard(card) { selectedCard.value = card }
function clearSelection() { selectedCard.value = null } function clearSelection() { selectedCard.value = null }
/** Réinitialise tout l'état de jeu (au retour au menu) */
function reset() {
players.value = []
currentTurn.value = 0
maxTurns.value = 12
phase.value = 'idle'
hand.value = []
myTeamIndex.value = 0
selectedCard.value = null
}
return { return {
playerName, players, currentTurn, maxTurns, playerName, players, currentTurn, maxTurns,
phase, hand, myTeamIndex, selectedCard, phase, hand, myTeamIndex, selectedCard,
setPlayer, updateState, selectCard, clearSelection setPlayer, updateState, selectCard, clearSelection, reset
} }
}) })
+3 -2
View File
@@ -48,7 +48,7 @@
import { ref, onMounted, onUnmounted } from 'vue' import { ref, onMounted, onUnmounted } from 'vue'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
import { useGameStore } from '@/stores/game' import { useGameStore } from '@/stores/game'
import { useSocket } from '@/services/socket' import { useSocket, leaveRoom } from '@/services/socket'
import { createGame } from '@/game/GameFactory' import { createGame } from '@/game/GameFactory'
const route = useRoute() const route = useRoute()
@@ -94,7 +94,8 @@ onMounted(() => {
}) })
onUnmounted(() => { onUnmounted(() => {
socket.off('game-state') leaveRoom(route.params.roomId)
gameStore.reset()
if (phaserGame) phaserGame.destroy(true) if (phaserGame) phaserGame.destroy(true)
}) })
</script> </script>
+37 -9
View File
@@ -1,16 +1,19 @@
<template> <template>
<div class="lobby"> <div class="lobby">
<button class="back-btn" @click="goBack">← Retour</button>
<h2>Salle : <span class="code">{{ roomId }}</span></h2> <h2>Salle : <span class="code">{{ roomId }}</span></h2>
<p class="hint">Partage ce code à tes adversaires (4 joueurs max)</p> <p class="hint">Partage ce code à tes adversaires (4 joueurs max)</p>
<div class="players"> <div class="players">
<!-- Slots dynamiques : 1 par joueur présent + slots vides jusqu'à 4 -->
<div <div
v-for="(player, i) in players" v-for="i in 4"
:key="i" :key="i"
class="player-slot" class="player-slot"
:class="{ filled: player }" :class="{ filled: players[i - 1] }"
> >
<span v-if="player">{{ player.name }}</span> <span v-if="players[i - 1]">{{ players[i - 1].name }}</span>
<span v-else class="empty">En attente...</span> <span v-else class="empty">En attente...</span>
</div> </div>
</div> </div>
@@ -18,7 +21,7 @@
<button <button
class="start-btn" class="start-btn"
@click="startGame" @click="startGame"
:disabled="players.filter(Boolean).length < 2" :disabled="players.length < 2"
> >
Lancer la partie Lancer la partie
</button> </button>
@@ -30,21 +33,26 @@
import { ref, onMounted, onUnmounted } from 'vue' import { ref, onMounted, onUnmounted } from 'vue'
import { useRoute, useRouter } from 'vue-router' import { useRoute, useRouter } from 'vue-router'
import { useGameStore } from '@/stores/game' import { useGameStore } from '@/stores/game'
import { useSocket } from '@/services/socket' import { useSocket, leaveRoom } from '@/services/socket'
const route = useRoute() const route = useRoute()
const router = useRouter() const router = useRouter()
const store = useGameStore() const store = useGameStore()
const socket = useSocket() const socket = useSocket()
const roomId = route.params.roomId const roomId = route.params.roomId
const players = ref([null, null, null, null]) // Tableau des joueurs réels (pas de null, taille variable)
const players = ref([])
onMounted(() => { onMounted(() => {
// Réinitialiser le store au cas où on revient d'une partie
store.reset()
socket.emit('join-room', { roomId, playerName: store.playerName }) socket.emit('join-room', { roomId, playerName: store.playerName })
socket.on('room-update', (data) => { socket.on('room-update', (data) => {
players.value = [...data.players, null, null, null, null].slice(0, 4) // Ne garder que les joueurs réels (pas de null)
players.value = (data.players ?? []).filter(Boolean)
}) })
socket.on('game-start', () => { socket.on('game-start', () => {
@@ -60,6 +68,12 @@ onUnmounted(() => {
function startGame() { function startGame() {
socket.emit('start-game', { roomId }) socket.emit('start-game', { roomId })
} }
function goBack() {
leaveRoom(roomId)
store.reset()
router.push('/')
}
</script> </script>
<style scoped> <style scoped>
@@ -70,7 +84,21 @@ function startGame() {
justify-content: center; justify-content: center;
min-height: 100vh; min-height: 100vh;
gap: 1.5rem; gap: 1.5rem;
position: relative;
} }
.back-btn {
position: absolute;
top: 1.5rem;
left: 1.5rem;
padding: 0.4rem 1rem;
background: transparent;
border: 1px solid #4a3020;
color: #a08060;
font-size: 0.9rem;
border-radius: 4px;
cursor: pointer;
}
.back-btn:hover { border-color: #d4a732; color: #d4a732; }
h2 { font-size: 2rem; } h2 { font-size: 2rem; }
.code { color: #d4a732; letter-spacing: 0.3rem; } .code { color: #d4a732; letter-spacing: 0.3rem; }
.hint { color: #a08060; } .hint { color: #a08060; }
+13
View File
@@ -119,6 +119,19 @@ io.on('connection', (socket) => {
} }
}) })
// ─── Quitter une salle explicitement (bouton retour) ────────────────────────
socket.on('leave-room', ({ roomId }) => {
const room = rooms.get(roomId)
if (room) {
room.removePlayer(socket.id)
socket.leave(roomId)
io.to(roomId).emit('room-update', { players: room.getPlayersInfo() })
if (room.playerCount === 0) rooms.delete(roomId)
console.log(`[${roomId}] ${socket.data.playerName ?? socket.id} a quitté`)
}
socket.data.roomId = null
})
// ─── Déconnexion ───────────────────────────────────────────────────────────── // ─── Déconnexion ─────────────────────────────────────────────────────────────
socket.on('disconnect', () => { socket.on('disconnect', () => {
const roomId = socket.data.roomId const roomId = socket.data.roomId