final POC
This commit is contained in:
@@ -16,42 +16,48 @@ export const GameWebSocketProvider = ({ children }) => {
|
||||
const socketRef = useRef(null);
|
||||
const gameTokenRef = useRef(null);
|
||||
const [isConnected, setIsConnected] = useState(false);
|
||||
|
||||
// Single game object containing all game data
|
||||
const [gameState, setGameState] = useState(null);
|
||||
const [boardData, setBoardData] = useState(null);
|
||||
// Structure: {
|
||||
// gameCode: string,
|
||||
// boardData: object,
|
||||
// turnInfo: { currentPlayer, currentPlayerName, turnNumber },
|
||||
// players: [{ playerId, playerName, isOnline, isReady }],
|
||||
// playerPositions: { playerName: position },
|
||||
// connectedPlayers: [],
|
||||
// readyPlayers: [],
|
||||
// state: string,
|
||||
// isGamemaster: boolean
|
||||
// }
|
||||
|
||||
const [error, setError] = useState(null);
|
||||
const [isGamemaster, setIsGamemaster] = useState(false);
|
||||
const [gameStarted, setGameStarted] = useState(false);
|
||||
const [pendingPlayers, setPendingPlayers] = useState([]);
|
||||
const [approvalStatus, setApprovalStatus] = useState(null);
|
||||
const [playerIdentifier, setPlayerIdentifier] = useState(null);
|
||||
const [isMyTurnFlag, setIsMyTurnFlag] = useState(false); // Directly controlled by game:your-turn
|
||||
const [playerDiceRolls, setPlayerDiceRolls] = useState({});
|
||||
|
||||
// Memoized derived values
|
||||
// Memoized derived values - extract from single game object
|
||||
const players = useMemo(() => {
|
||||
const connectedPlayers = gameState?.connectedPlayers || [];
|
||||
const currentPlayers = gameState?.currentPlayers || [];
|
||||
|
||||
if (currentPlayers.length > 0) {
|
||||
return currentPlayers;
|
||||
}
|
||||
|
||||
if (connectedPlayers.length > 0) {
|
||||
return connectedPlayers.map((nameOrObj, index) => {
|
||||
const playerName = typeof nameOrObj === 'string'
|
||||
? nameOrObj
|
||||
: (nameOrObj.playerName || nameOrObj.name || `Player ${index + 1}`);
|
||||
|
||||
return {
|
||||
id: `player-${index}`,
|
||||
name: playerName,
|
||||
isOnline: true,
|
||||
isReady: gameState?.readyPlayers?.includes(playerName) || false,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
return [];
|
||||
}, [gameState?.connectedPlayers, gameState?.currentPlayers, gameState?.readyPlayers]);
|
||||
return gameState?.players || [];
|
||||
}, [gameState?.players]);
|
||||
|
||||
const currentTurn = useMemo(() => gameState?.currentPlayer || null, [gameState?.currentPlayer]);
|
||||
const playerPositions = useMemo(() => {
|
||||
return gameState?.playerPositions || {};
|
||||
}, [gameState?.playerPositions]);
|
||||
|
||||
const boardData = useMemo(() => {
|
||||
return gameState?.boardData || null;
|
||||
}, [gameState?.boardData]);
|
||||
|
||||
const currentTurn = useMemo(() => gameState?.turnInfo?.currentPlayer || null, [gameState?.turnInfo?.currentPlayer]);
|
||||
const currentTurnName = useMemo(() => gameState?.turnInfo?.currentPlayerName || null, [gameState?.turnInfo?.currentPlayerName]);
|
||||
|
||||
// isMyTurn is simply the flag set by game:your-turn event
|
||||
const isMyTurn = isMyTurnFlag;
|
||||
|
||||
/**
|
||||
* Connect to game WebSocket with a game token
|
||||
@@ -79,6 +85,16 @@ export const GameWebSocketProvider = ({ children }) => {
|
||||
log('🔌 Connecting to game WebSocket...');
|
||||
gameTokenRef.current = gameToken;
|
||||
|
||||
// Decode token to get player identifier
|
||||
try {
|
||||
const payload = JSON.parse(atob(gameToken.split('.')[1]));
|
||||
const identifier = payload.userId || payload.playerName;
|
||||
setPlayerIdentifier(identifier);
|
||||
log('🎮 Player identifier:', identifier);
|
||||
} catch (err) {
|
||||
logError('Failed to decode game token:', err);
|
||||
}
|
||||
|
||||
// Connect to /game namespace
|
||||
socketRef.current = io(`${API_CONFIG.wsURL}/game`, {
|
||||
transports: ['websocket'],
|
||||
@@ -102,11 +118,22 @@ export const GameWebSocketProvider = ({ children }) => {
|
||||
logError('❌ Connection error:', err);
|
||||
setIsConnected(false);
|
||||
setError(err.message);
|
||||
|
||||
// If reconnection fails completely, navigate to home
|
||||
if (err.message?.includes('timeout') || err.message?.includes('xhr poll error')) {
|
||||
setTimeout(() => {
|
||||
if (!socketRef.current?.connected) {
|
||||
logError('⚠️ Connection failed - redirecting to home');
|
||||
window.location.href = '/';
|
||||
}
|
||||
}, 10000); // Give 10 seconds for reconnection attempts
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('disconnect', (reason) => {
|
||||
log('🔌 Disconnected:', reason);
|
||||
setIsConnected(false);
|
||||
setIsMyTurnFlag(false); // Clear turn flag on disconnect
|
||||
});
|
||||
|
||||
// Game state handlers
|
||||
@@ -115,7 +142,72 @@ export const GameWebSocketProvider = ({ children }) => {
|
||||
if (state?.isGamemaster !== undefined) {
|
||||
setIsGamemaster(state.isGamemaster);
|
||||
}
|
||||
setGameState(state);
|
||||
// Merge state into single game object
|
||||
setGameState(prev => {
|
||||
// Build players array from various sources
|
||||
let playersArray = prev?.players || [];
|
||||
|
||||
// If we have currentPlayers or players from backend, use them (already have proper structure)
|
||||
if (state.currentPlayers && Array.isArray(state.currentPlayers) && state.currentPlayers.length > 0) {
|
||||
playersArray = state.currentPlayers.map(p => ({
|
||||
playerId: p.playerId || p.id,
|
||||
playerName: p.playerName || p.name,
|
||||
name: p.playerName || p.name, // Add name for compatibility
|
||||
isOnline: p.isOnline !== undefined ? p.isOnline : true,
|
||||
isReady: p.isReady || false
|
||||
}));
|
||||
} else if (state.players && Array.isArray(state.players) && state.players.length > 0) {
|
||||
playersArray = state.players.map(p => ({
|
||||
playerId: p.playerId || p.id,
|
||||
playerName: p.playerName || p.name,
|
||||
name: p.playerName || p.name, // Add name for compatibility
|
||||
isOnline: p.isOnline !== undefined ? p.isOnline : true,
|
||||
isReady: p.isReady || false
|
||||
}));
|
||||
}
|
||||
// If players array is still empty but we have connectedPlayers (array of strings), convert them
|
||||
else if (playersArray.length === 0 && state.connectedPlayers && Array.isArray(state.connectedPlayers) && state.connectedPlayers.length > 0) {
|
||||
playersArray = state.connectedPlayers.map((playerName, index) => ({
|
||||
playerId: `player-${index}`, // Temporary ID until we get the real one
|
||||
playerName: playerName,
|
||||
name: playerName, // Add name for compatibility
|
||||
isOnline: true,
|
||||
isReady: false
|
||||
}));
|
||||
}
|
||||
|
||||
// Initialize playerPositions from backend data if joining game in progress
|
||||
let playerPositions = prev?.playerPositions || {};
|
||||
// If we don't have positions yet but backend sent players with positions, initialize from them
|
||||
if (Object.keys(playerPositions).length === 0 && state.players && Array.isArray(state.players)) {
|
||||
const positionsFromBackend = {};
|
||||
state.players.forEach(p => {
|
||||
const playerName = p.playerName || p.name;
|
||||
if (playerName && p.position !== undefined) {
|
||||
positionsFromBackend[playerName] = p.position;
|
||||
}
|
||||
});
|
||||
if (Object.keys(positionsFromBackend).length > 0) {
|
||||
playerPositions = positionsFromBackend;
|
||||
}
|
||||
}
|
||||
|
||||
const merged = {
|
||||
...prev,
|
||||
gameCode: state.gameCode || prev?.gameCode,
|
||||
boardData: state.boardData || prev?.boardData,
|
||||
state: state.state || prev?.state,
|
||||
connectedPlayers: state.connectedPlayers || prev?.connectedPlayers || [],
|
||||
readyPlayers: state.readyPlayers || prev?.readyPlayers || [],
|
||||
// Preserve turn info - only turn-changed updates it
|
||||
turnInfo: prev?.turnInfo || { currentPlayer: null, currentPlayerName: null, turnNumber: 0 },
|
||||
// Use the built players array
|
||||
players: playersArray,
|
||||
// Initialize playerPositions from backend if joining in progress, otherwise preserve
|
||||
playerPositions: playerPositions
|
||||
};
|
||||
return merged;
|
||||
});
|
||||
});
|
||||
|
||||
socket.on('game:state-update', (state) => {
|
||||
@@ -123,7 +215,19 @@ export const GameWebSocketProvider = ({ children }) => {
|
||||
if (state?.isGamemaster !== undefined) {
|
||||
setIsGamemaster(state.isGamemaster);
|
||||
}
|
||||
setGameState(state);
|
||||
// Merge state update into single game object
|
||||
setGameState(prev => ({
|
||||
...prev,
|
||||
gameCode: state.gameCode || prev?.gameCode,
|
||||
boardData: state.boardData || prev?.boardData,
|
||||
state: state.state || prev?.state,
|
||||
connectedPlayers: state.connectedPlayers || prev?.connectedPlayers,
|
||||
readyPlayers: state.readyPlayers || prev?.readyPlayers,
|
||||
// Preserve turn info and positions
|
||||
turnInfo: prev?.turnInfo,
|
||||
players: prev?.players,
|
||||
playerPositions: prev?.playerPositions
|
||||
}));
|
||||
});
|
||||
|
||||
socket.on('game:joined', (data) => {
|
||||
@@ -131,6 +235,26 @@ export const GameWebSocketProvider = ({ children }) => {
|
||||
if (data.isGamemaster !== undefined) {
|
||||
setIsGamemaster(data.isGamemaster);
|
||||
}
|
||||
// Initialize or update gameState with gameCode and gameId from join confirmation
|
||||
setGameState(prev => {
|
||||
if (prev && prev.gameCode) {
|
||||
// If already has gameCode, just add gameId if missing
|
||||
return {
|
||||
...prev,
|
||||
gameId: data.gameId || prev.gameId,
|
||||
gameCode: data.gameCode,
|
||||
playerName: data.playerName,
|
||||
isAuthenticated: data.isAuthenticated
|
||||
};
|
||||
}
|
||||
return {
|
||||
...prev,
|
||||
gameId: data.gameId, // Store gameId from backend
|
||||
gameCode: data.gameCode,
|
||||
playerName: data.playerName,
|
||||
isAuthenticated: data.isAuthenticated
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
socket.on('game:player-joined', (data) => {
|
||||
@@ -138,60 +262,128 @@ export const GameWebSocketProvider = ({ children }) => {
|
||||
setGameState(prev => {
|
||||
if (!prev) return prev;
|
||||
const currentConnected = prev.connectedPlayers || [];
|
||||
if (!currentConnected.includes(data.playerName)) {
|
||||
return {
|
||||
...prev,
|
||||
connectedPlayers: [...currentConnected, data.playerName]
|
||||
};
|
||||
const currentPlayers = prev.players || [];
|
||||
|
||||
// Add to connectedPlayers if not already there
|
||||
const updatedConnected = currentConnected.includes(data.playerName)
|
||||
? currentConnected
|
||||
: [...currentConnected, data.playerName];
|
||||
|
||||
// Add to players array if not already there (without position - that's in playerPositions)
|
||||
const playerExists = currentPlayers.some(p =>
|
||||
p.playerName === data.playerName || p.name === data.playerName
|
||||
);
|
||||
|
||||
const updatedPlayers = playerExists
|
||||
? currentPlayers
|
||||
: [...currentPlayers, {
|
||||
playerId: data.playerId,
|
||||
playerName: data.playerName,
|
||||
name: data.playerName, // Add name for compatibility with Lobby display
|
||||
isOnline: true,
|
||||
isReady: false
|
||||
}];
|
||||
|
||||
return {
|
||||
...prev,
|
||||
connectedPlayers: updatedConnected,
|
||||
players: updatedPlayers
|
||||
};
|
||||
});
|
||||
window.dispatchEvent(new CustomEvent('game:player-joined', { detail: data }));
|
||||
});
|
||||
|
||||
socket.on('game:player-left', (data) => {
|
||||
log('👋 Player left:', data.playerName);
|
||||
setGameState(prev => {
|
||||
if (!prev) return prev;
|
||||
return {
|
||||
...prev,
|
||||
connectedPlayers: (prev.connectedPlayers || []).filter(p => p !== data.playerName)
|
||||
};
|
||||
});
|
||||
window.dispatchEvent(new CustomEvent('game:player-left', { detail: data }));
|
||||
});
|
||||
|
||||
socket.on('game:player-ready', (data) => {
|
||||
log('✅ Player ready:', data.playerName);
|
||||
setGameState(prev => {
|
||||
if (!prev) return prev;
|
||||
const readyPlayers = prev.readyPlayers || [];
|
||||
if (data.ready && !readyPlayers.includes(data.playerName)) {
|
||||
return { ...prev, readyPlayers: [...readyPlayers, data.playerName] };
|
||||
} else if (!data.ready) {
|
||||
return { ...prev, readyPlayers: readyPlayers.filter(p => p !== data.playerName) };
|
||||
}
|
||||
return prev;
|
||||
});
|
||||
window.dispatchEvent(new CustomEvent('game:player-ready', { detail: data }));
|
||||
});
|
||||
|
||||
socket.on('game:all-ready', (data) => {
|
||||
log('✅ All players ready! Game can start.');
|
||||
window.dispatchEvent(new CustomEvent('game:all-ready', { detail: data }));
|
||||
});
|
||||
|
||||
socket.on('game:start', (data) => {
|
||||
log('🎮 Game started:', data);
|
||||
setGameStarted(true);
|
||||
|
||||
// Store board data if provided
|
||||
if (data.boardData) {
|
||||
setBoardData(data.boardData);
|
||||
log('✅ Board data stored from game:start event');
|
||||
}
|
||||
|
||||
// Update game state with turn info
|
||||
if (data.playerOrder) {
|
||||
setGameState(prev => ({
|
||||
...prev,
|
||||
playerOrder: data.playerOrder,
|
||||
currentPlayer: data.currentPlayer,
|
||||
turnSequence: data.playerOrder
|
||||
}));
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('game:started', (data) => {
|
||||
log('🎮 Game started (legacy event):', data);
|
||||
setGameStarted(true);
|
||||
});
|
||||
|
||||
socket.on('game:player-moved', (moveData) => {
|
||||
log('🏃 Player moved:', moveData.playerName);
|
||||
// Update game state with position initialization
|
||||
setGameState(prev => {
|
||||
if (!prev?.currentPlayers) return prev;
|
||||
return {
|
||||
// Initialize all player positions to 0 at game start
|
||||
const initialPositions = {};
|
||||
|
||||
// Use existing players from prev (already set by game:joined/game:state)
|
||||
const existingPlayers = prev?.players || [];
|
||||
|
||||
// Initialize positions for all existing players
|
||||
existingPlayers.forEach((player) => {
|
||||
const playerName = player.playerName || player.name;
|
||||
if (playerName) {
|
||||
// Initialize ALL positions to 0 - only game:player-arrived will change them
|
||||
initialPositions[playerName] = 0;
|
||||
}
|
||||
});
|
||||
|
||||
const updated = {
|
||||
...prev,
|
||||
currentPlayers: prev.currentPlayers.map(p =>
|
||||
p.playerId === moveData.playerId
|
||||
? { ...p, boardPosition: moveData.newPosition }
|
||||
: p
|
||||
),
|
||||
gameCode: data.gameCode || prev?.gameCode,
|
||||
boardData: data.boardData || prev?.boardData,
|
||||
state: data.gamePhase || data.state || 'playing',
|
||||
boardSize: data.boardSize || prev?.boardSize,
|
||||
// Keep existing players list, initialize positions to 0
|
||||
players: existingPlayers,
|
||||
playerPositions: initialPositions,
|
||||
// Preserve turn info and other data
|
||||
turnInfo: prev?.turnInfo || { currentPlayer: null, currentPlayerName: null, turnNumber: 0 },
|
||||
connectedPlayers: prev?.connectedPlayers || [],
|
||||
readyPlayers: prev?.readyPlayers || []
|
||||
};
|
||||
return updated;
|
||||
});
|
||||
});
|
||||
|
||||
socket.on('game:turn-changed', (data) => {
|
||||
log('🔄 Turn changed to:', data.currentPlayerName);
|
||||
setGameState(prev => prev ? { ...prev, currentPlayer: data.currentPlayer } : prev);
|
||||
|
||||
// Turn changed means it's NOT my turn anymore
|
||||
setIsMyTurnFlag(false);
|
||||
|
||||
// This is the ONLY place turnInfo should be set
|
||||
setGameState(prev => ({
|
||||
...prev,
|
||||
turnInfo: {
|
||||
currentPlayer: data.currentPlayer,
|
||||
currentPlayerName: data.currentPlayerName,
|
||||
turnNumber: data.turnNumber || prev?.turnInfo?.turnNumber || 0
|
||||
}
|
||||
}));
|
||||
|
||||
// Force a re-render by logging after state update
|
||||
setTimeout(() => {
|
||||
console.log('🔄 [game:turn-changed] State should be updated now');
|
||||
}, 100);
|
||||
});
|
||||
|
||||
socket.on('game:error', (err) => {
|
||||
@@ -246,6 +438,199 @@ export const GameWebSocketProvider = ({ children }) => {
|
||||
return prev;
|
||||
});
|
||||
});
|
||||
|
||||
socket.on('game:your-turn', (data) => {
|
||||
log('🎯 Your turn!', data);
|
||||
console.log('🎯 [game:your-turn] Received - enabling dice');
|
||||
|
||||
// Set flag to true - dice is now enabled
|
||||
setIsMyTurnFlag(true);
|
||||
|
||||
// Emit custom event for GameScreen to display turn notification
|
||||
window.dispatchEvent(new CustomEvent('game:your-turn', { detail: data }));
|
||||
});
|
||||
|
||||
socket.on('game:dice-rolled', (data) => {
|
||||
log('🎲 Dice rolled:', data.diceValue, 'by', data.playerName);
|
||||
// Track the dice roll for this player
|
||||
setPlayerDiceRolls(prev => ({
|
||||
...prev,
|
||||
[data.playerName]: data.diceValue
|
||||
}));
|
||||
window.dispatchEvent(new CustomEvent('game:dice-rolled', { detail: data }));
|
||||
});
|
||||
|
||||
socket.on('game:player-moving', (data) => {
|
||||
log('🚶 Player moving:', data.playerName, 'from', data.fromPosition, 'to', data.toPosition);
|
||||
window.dispatchEvent(new CustomEvent('game:player-moving', { detail: data }));
|
||||
});
|
||||
|
||||
socket.on('game:player-arrived', (data) => {
|
||||
log('🎯 Player arrived:', data.playerName, 'at position', data.position, '(' + data.fieldType + ')');
|
||||
|
||||
// Update playerPositions in game object - THIS IS THE ONLY PLACE POSITIONS ARE MODIFIED
|
||||
setGameState(prev => {
|
||||
if (!prev) return prev;
|
||||
const updatedPositions = { ...prev.playerPositions, [data.playerName]: data.position };
|
||||
|
||||
return {
|
||||
...prev,
|
||||
playerPositions: updatedPositions
|
||||
};
|
||||
});
|
||||
|
||||
window.dispatchEvent(new CustomEvent('game:player-arrived', { detail: data }));
|
||||
});
|
||||
|
||||
socket.on('game:card-drawn', (data) => {
|
||||
log('🃏 Card drawn:', data.cardType, 'by', data.playerName);
|
||||
window.dispatchEvent(new CustomEvent('game:card-drawn', { detail: data }));
|
||||
});
|
||||
|
||||
socket.on('game:card-result', (data) => {
|
||||
log('🎴 Card result:', data.playerName, data.description);
|
||||
window.dispatchEvent(new CustomEvent('game:card-result', { detail: data }));
|
||||
});
|
||||
|
||||
socket.on('game:answer-timeout', (data) => {
|
||||
log('⏰ Answer timeout:', data.playerName);
|
||||
window.dispatchEvent(new CustomEvent('game:answer-timeout', { detail: data }));
|
||||
});
|
||||
|
||||
socket.on('game:answer-result', (data) => {
|
||||
log('📝 Answer result:', data.correct ? '✅ Correct' : '❌ Wrong', '-', data.playerName);
|
||||
window.dispatchEvent(new CustomEvent('game:answer-result', { detail: data }));
|
||||
});
|
||||
|
||||
socket.on('game:gamemaster-decision-request', (data) => {
|
||||
log('👨⚖️ Gamemaster decision request:', data.playerName);
|
||||
window.dispatchEvent(new CustomEvent('game:gamemaster-decision-request', { detail: data }));
|
||||
});
|
||||
|
||||
socket.on('game:gamemaster-decision-result', (data) => {
|
||||
log('⚖️ Gamemaster decision result:', data.decision, '-', data.playerName);
|
||||
window.dispatchEvent(new CustomEvent('game:gamemaster-decision-result', { detail: data }));
|
||||
});
|
||||
|
||||
socket.on('game:card-drawn-self', (data) => {
|
||||
log('🃏 You drew a card:', data.cardType);
|
||||
window.dispatchEvent(new CustomEvent('game:card-drawn-self', { detail: data }));
|
||||
});
|
||||
|
||||
socket.on('game:joker-activated', (data) => {
|
||||
log('🃏 Joker activated:', data.playerName);
|
||||
window.dispatchEvent(new CustomEvent('game:joker-activated', { detail: data }));
|
||||
});
|
||||
|
||||
socket.on('game:extra-turn', (data) => {
|
||||
log('⭐ Extra turn:', data.playerName);
|
||||
window.dispatchEvent(new CustomEvent('game:extra-turn', { detail: data }));
|
||||
});
|
||||
|
||||
socket.on('game:turn-lost', (data) => {
|
||||
log('😞 Turn lost:', data.playerName);
|
||||
window.dispatchEvent(new CustomEvent('game:turn-lost', { detail: data }));
|
||||
});
|
||||
|
||||
socket.on('game:no-movement', (data) => {
|
||||
log('⛔ No movement:', data.playerName, '-', data.reason);
|
||||
window.dispatchEvent(new CustomEvent('game:no-movement', { detail: data }));
|
||||
});
|
||||
|
||||
socket.on('game:penalty-avoided', (data) => {
|
||||
log('🛡️ Penalty avoided:', data.playerName);
|
||||
window.dispatchEvent(new CustomEvent('game:penalty-avoided', { detail: data }));
|
||||
});
|
||||
|
||||
socket.on('game:guess-timeout', (data) => {
|
||||
log('⏰ Guess timeout:', data.playerName);
|
||||
window.dispatchEvent(new CustomEvent('game:guess-timeout', { detail: data }));
|
||||
});
|
||||
|
||||
socket.on('game:player-guessing', (data) => {
|
||||
log('🤔 Player guessing:', data.playerName);
|
||||
window.dispatchEvent(new CustomEvent('game:player-guessing', { detail: data }));
|
||||
});
|
||||
|
||||
socket.on('game:secondary-landing', (data) => {
|
||||
log('🎯 Secondary landing:', data.playerName, 'on', data.fieldType);
|
||||
window.dispatchEvent(new CustomEvent('game:secondary-landing', { detail: data }));
|
||||
});
|
||||
|
||||
socket.on('game:player-disconnected', (data) => {
|
||||
log('⚠️ Player disconnected:', data.playerName);
|
||||
setGameState(prev => {
|
||||
if (!prev) return prev;
|
||||
return {
|
||||
...prev,
|
||||
connectedPlayers: (prev.connectedPlayers || []).filter(p => p !== data.playerName)
|
||||
};
|
||||
});
|
||||
window.dispatchEvent(new CustomEvent('game:player-disconnected', { detail: data }));
|
||||
});
|
||||
|
||||
socket.on('game:player-disconnected-during-turn', (data) => {
|
||||
log('⚠️ Player disconnected during turn:', data.playerName);
|
||||
window.dispatchEvent(new CustomEvent('game:player-disconnected-during-turn', { detail: data }));
|
||||
});
|
||||
|
||||
socket.on('game:player-disconnected-during-card', (data) => {
|
||||
log('⚠️ Player disconnected during card:', data.playerName);
|
||||
window.dispatchEvent(new CustomEvent('game:player-disconnected-during-card', { detail: data }));
|
||||
});
|
||||
|
||||
socket.on('game:guess-result', (data) => {
|
||||
log('🎯 Guess result:', data);
|
||||
// Position update will come from game:player-arrived event
|
||||
window.dispatchEvent(new CustomEvent('game:guess-result', { detail: data }));
|
||||
});
|
||||
|
||||
socket.on('game:joker-complete', (data) => {
|
||||
log('🃏 Joker complete:', data);
|
||||
// Position update will come from game:player-arrived event
|
||||
window.dispatchEvent(new CustomEvent('game:joker-complete', { detail: data }));
|
||||
});
|
||||
|
||||
socket.on('game:luck-consequence', (data) => {
|
||||
log('🍀 Luck consequence:', data);
|
||||
// Position update will come from game:player-arrived event
|
||||
window.dispatchEvent(new CustomEvent('game:luck-consequence', { detail: data }));
|
||||
});
|
||||
|
||||
socket.on('game:ended', (data) => {
|
||||
log('🏁 Game ended! Winner:', data.winner);
|
||||
setGameState(prev => ({
|
||||
...prev,
|
||||
status: 'finished',
|
||||
winner: data.winner,
|
||||
finalScores: data.scores
|
||||
}));
|
||||
window.dispatchEvent(new CustomEvent('game:ended', { detail: data }));
|
||||
|
||||
// Don't auto-navigate - let the player close the winner modal manually
|
||||
// They can use the "Vissza a főoldalra" button when ready
|
||||
});
|
||||
|
||||
socket.on('game:extra-turn-remaining', (data) => {
|
||||
log('⭐ Extra turn remaining:', data);
|
||||
window.dispatchEvent(new CustomEvent('game:extra-turn-remaining', { detail: data }));
|
||||
});
|
||||
|
||||
socket.on('game:players-skipped', (data) => {
|
||||
log('⏭️ Players skipped:', data.skippedPlayers);
|
||||
window.dispatchEvent(new CustomEvent('game:players-skipped', { detail: data }));
|
||||
});
|
||||
|
||||
socket.on('game:cleanup-complete', (data) => {
|
||||
log('🧹 Cleanup complete:', data);
|
||||
window.dispatchEvent(new CustomEvent('game:cleanup-complete', { detail: data }));
|
||||
|
||||
// Navigate to home after cleanup
|
||||
setTimeout(() => {
|
||||
log('🏠 Navigating to home after cleanup');
|
||||
window.location.href = '/';
|
||||
}, 2000);
|
||||
});
|
||||
}, []);
|
||||
|
||||
/**
|
||||
@@ -259,8 +644,7 @@ export const GameWebSocketProvider = ({ children }) => {
|
||||
socketRef.current = null;
|
||||
gameTokenRef.current = null;
|
||||
setIsConnected(false);
|
||||
setGameState(null);
|
||||
setBoardData(null);
|
||||
setGameState(null); // Clear entire game object
|
||||
setError(null);
|
||||
setIsGamemaster(false);
|
||||
setGameStarted(false);
|
||||
@@ -316,9 +700,79 @@ export const GameWebSocketProvider = ({ children }) => {
|
||||
return true;
|
||||
}, [isConnected, isGamemaster, gameState?.gameCode]);
|
||||
|
||||
const submitAnswer = useCallback((answer, cardId = null) => {
|
||||
const socket = socketRef.current;
|
||||
if (!socket || !isConnected) {
|
||||
warn('⚠️ Cannot submit answer: not connected');
|
||||
return false;
|
||||
}
|
||||
log('📝 Submitting answer:', answer, 'for card:', cardId);
|
||||
socket.emit('game:card-answer', {
|
||||
gameCode: gameState?.gameCode,
|
||||
answer,
|
||||
cardId
|
||||
});
|
||||
return true;
|
||||
}, [isConnected, gameState?.gameCode]);
|
||||
|
||||
const submitPositionGuess = useCallback((guessedPosition) => {
|
||||
const socket = socketRef.current;
|
||||
if (!socket || !isConnected) {
|
||||
warn('⚠️ Cannot submit position guess: not connected');
|
||||
return false;
|
||||
}
|
||||
log('🎯 Submitting position guess:', guessedPosition);
|
||||
socket.emit('game:position-guess', { gameCode: gameState?.gameCode, guessedPosition });
|
||||
return true;
|
||||
}, [isConnected, gameState?.gameCode]);
|
||||
|
||||
const submitJokerPositionGuess = useCallback((guessedPosition) => {
|
||||
const socket = socketRef.current;
|
||||
if (!socket || !isConnected) {
|
||||
warn('⚠️ Cannot submit joker position guess: not connected');
|
||||
return false;
|
||||
}
|
||||
log('🃏🎯 Submitting joker position guess:', guessedPosition);
|
||||
socket.emit('game:joker-position-guess', { gameCode: gameState?.gameCode, guessedPosition });
|
||||
return true;
|
||||
}, [isConnected, gameState?.gameCode]);
|
||||
|
||||
const approveJoker = useCallback((requestId) => {
|
||||
const socket = socketRef.current;
|
||||
if (!socket || !isConnected || !isGamemaster) {
|
||||
warn('⚠️ Cannot approve joker: not gamemaster or not connected');
|
||||
return false;
|
||||
}
|
||||
log('✅ Approving joker request:', requestId);
|
||||
socket.emit('game:gamemaster-decision', {
|
||||
gameCode: gameState?.gameCode,
|
||||
requestId,
|
||||
decision: 'approve'
|
||||
});
|
||||
return true;
|
||||
}, [isConnected, isGamemaster, gameState?.gameCode]);
|
||||
|
||||
const rejectJoker = useCallback((requestId, reason = 'Joker answer rejected') => {
|
||||
const socket = socketRef.current;
|
||||
if (!socket || !isConnected || !isGamemaster) {
|
||||
warn('⚠️ Cannot reject joker: not gamemaster or not connected');
|
||||
return false;
|
||||
}
|
||||
log('❌ Rejecting joker request:', requestId, 'Reason:', reason);
|
||||
socket.emit('game:gamemaster-decision', {
|
||||
gameCode: gameState?.gameCode,
|
||||
requestId,
|
||||
decision: 'reject',
|
||||
reason
|
||||
});
|
||||
return true;
|
||||
}, [isConnected, isGamemaster, gameState?.gameCode]);
|
||||
|
||||
const addEventListener = useCallback((event, handler) => {
|
||||
const socket = socketRef.current;
|
||||
if (socket) socket.on(event, handler);
|
||||
if (socket) {
|
||||
socket.on(event, handler);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const removeEventListener = useCallback((event, handler) => {
|
||||
@@ -329,15 +783,20 @@ export const GameWebSocketProvider = ({ children }) => {
|
||||
const value = {
|
||||
socket: socketRef.current,
|
||||
isConnected,
|
||||
gameState,
|
||||
players,
|
||||
boardData,
|
||||
currentTurn,
|
||||
gameState, // Single game object with all data
|
||||
players, // Memoized from gameState.players
|
||||
playerPositions, // Memoized from gameState.playerPositions (SINGLE SOURCE OF TRUTH for positions)
|
||||
boardData, // Memoized from gameState.boardData
|
||||
currentTurn, // Memoized from gameState.turnInfo.currentPlayer
|
||||
currentTurnName, // Memoized from gameState.turnInfo.currentPlayerName
|
||||
isMyTurn,
|
||||
playerIdentifier,
|
||||
error,
|
||||
isGamemaster,
|
||||
gameStarted,
|
||||
pendingPlayers,
|
||||
approvalStatus,
|
||||
playerDiceRolls,
|
||||
// Connection management
|
||||
connect,
|
||||
disconnect,
|
||||
@@ -348,6 +807,11 @@ export const GameWebSocketProvider = ({ children }) => {
|
||||
leaveGame,
|
||||
approvePlayer,
|
||||
rejectPlayer,
|
||||
submitAnswer,
|
||||
submitPositionGuess,
|
||||
submitJokerPositionGuess,
|
||||
approveJoker,
|
||||
rejectJoker,
|
||||
addEventListener,
|
||||
removeEventListener,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user