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() {
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 || (
import.meta.env.PROD ? window.location.origin : 'http://localhost:3000'
)
@@ -13,3 +11,18 @@ export function useSocket() {
}
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', () => {
const playerName = ref('')
const players = ref([]) // { name, teamIndex, gauges:{food,war,health,diplo}, score, handSize }
const players = ref([])
const currentTurn = ref(0)
const maxTurns = ref(12)
const phase = ref('idle')
@@ -16,20 +16,31 @@ export const useGameStore = defineStore('game', () => {
}
function updateState(state) {
players.value = state.players
currentTurn.value = state.currentTurn
maxTurns.value = state.maxTurns
phase.value = state.phase
players.value = state.players ?? []
currentTurn.value = state.currentTurn ?? 0
maxTurns.value = state.maxTurns ?? 12
phase.value = state.phase ?? 'idle'
myTeamIndex.value = state.myTeamIndex ?? 0
if (state.hand !== undefined) hand.value = state.hand
}
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 {
playerName, players, currentTurn, maxTurns,
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 { useRoute } from 'vue-router'
import { useGameStore } from '@/stores/game'
import { useSocket } from '@/services/socket'
import { useSocket, leaveRoom } from '@/services/socket'
import { createGame } from '@/game/GameFactory'
const route = useRoute()
@@ -94,7 +94,8 @@ onMounted(() => {
})
onUnmounted(() => {
socket.off('game-state')
leaveRoom(route.params.roomId)
gameStore.reset()
if (phaserGame) phaserGame.destroy(true)
})
</script>
+37 -9
View File
@@ -1,16 +1,19 @@
<template>
<div class="lobby">
<button class="back-btn" @click="goBack">← Retour</button>
<h2>Salle : <span class="code">{{ roomId }}</span></h2>
<p class="hint">Partage ce code à tes adversaires (4 joueurs max)</p>
<div class="players">
<!-- Slots dynamiques : 1 par joueur présent + slots vides jusqu'à 4 -->
<div
v-for="(player, i) in players"
v-for="i in 4"
:key="i"
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>
</div>
</div>
@@ -18,7 +21,7 @@
<button
class="start-btn"
@click="startGame"
:disabled="players.filter(Boolean).length < 2"
:disabled="players.length < 2"
>
Lancer la partie
</button>
@@ -30,21 +33,26 @@
import { ref, onMounted, onUnmounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
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 store = useGameStore()
const store = useGameStore()
const socket = useSocket()
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(() => {
// Réinitialiser le store au cas où on revient d'une partie
store.reset()
socket.emit('join-room', { roomId, playerName: store.playerName })
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', () => {
@@ -60,6 +68,12 @@ onUnmounted(() => {
function startGame() {
socket.emit('start-game', { roomId })
}
function goBack() {
leaveRoom(roomId)
store.reset()
router.push('/')
}
</script>
<style scoped>
@@ -70,7 +84,21 @@ function startGame() {
justify-content: center;
min-height: 100vh;
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; }
.code { color: #d4a732; letter-spacing: 0.3rem; }
.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 ─────────────────────────────────────────────────────────────
socket.on('disconnect', () => {
const roomId = socket.data.roomId