fix: ghost players on back - leaveRoom, store.reset, dynamic lobby slots
This commit is contained in:
1 parent
6e384d5985
commit
c7672b1793
5 files changed
+86
-20
No files matched your search
@@ -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))
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
})
|
||||
@@ -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>
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in new issue
Block a user