import { useEffect, useState, useCallback, useMemo } from 'react' import { useParams, useNavigate } from 'react-router-dom' import { motion, AnimatePresence } from 'framer-motion' import { useSocket } from '../hooks/useSocket' import { useSound } from '../hooks/useSound' import { useGameStore } from '../stores/gameStore' import { useThemeStyles } from '../themes/ThemeProvider' import EmojiReactions from '../components/chat/EmojiReactions' import ReactionOverlay from '../components/chat/ReactionOverlay' import TeamChat from '../components/chat/TeamChat' import SoundControl from '../components/ui/SoundControl' import type { Question } from '../types' // All available categories with their styling const allCategories: Record = { 1: { name: 'Nintendo', icon: '🍄', color: '#E60012' }, 2: { name: 'Xbox', icon: '🎮', color: '#107C10' }, 3: { name: 'PlayStation', icon: '🎯', color: '#003791' }, 4: { name: 'Anime', icon: '⛩️', color: '#FF6B9D' }, 5: { name: 'Música', icon: '🎵', color: '#1DB954' }, 6: { name: 'Películas', icon: '🎬', color: '#F5C518' }, 7: { name: 'Libros', icon: '📚', color: '#8B4513' }, 8: { name: 'Historia', icon: '🏛️', color: '#6B5B95' }, } export default function Game() { useParams<{ roomCode: string }>() const navigate = useNavigate() const { selectQuestion, submitAnswer, stealDecision, sendTeamMessage } = useSocket() const { play } = useSound() const { room, playerName, currentQuestion, showStealPrompt, setShowStealPrompt, teamMessages } = useGameStore() const { config, styles } = useThemeStyles() const [answer, setAnswer] = useState('') const [timeLeft, setTimeLeft] = useState(0) const [hoveredCell, setHoveredCell] = useState(null) // Redirect if game finished useEffect(() => { if (room?.status === 'finished') { navigate(`/results/${room.code}`) } }, [room?.status, room?.code, navigate]) // Play sound when question is revealed useEffect(() => { if (currentQuestion) { play('question_reveal') } }, [currentQuestion, play]) // Timer logic with sound effects useEffect(() => { if (!currentQuestion) return setTimeLeft(currentQuestion.time_seconds) const interval = setInterval(() => { setTimeLeft((prev) => { if (prev <= 1) { clearInterval(interval) return 0 } if (prev <= 4 && prev > 1) { play('countdown') } else if (prev <= 6 && prev > 4) { play('timer_urgent') } else if (prev > 6) { play('timer_tick') } return prev - 1 }) }, 1000) return () => clearInterval(interval) }, [currentQuestion, play]) // Hover sound handler const handleCellHover = useCallback((cellId: string, canSelect: boolean) => { if (canSelect && hoveredCell !== cellId) { setHoveredCell(cellId) play('hover') } }, [hoveredCell, play]) if (!room) { return (
) } const myTeam = room.teams.A.find(p => p.name === playerName) ? 'A' : 'B' const isMyTurn = room.current_team === myTeam const currentPlayer = isMyTurn ? room.teams[myTeam][room.current_player_index[myTeam]] : null const amICurrentPlayer = currentPlayer?.name === playerName const handleSelectQuestion = (question: Question, categoryId: number) => { if (!amICurrentPlayer || question.answered) return play('select') selectQuestion(question.id, categoryId) } const handleSubmitAnswer = () => { if (!currentQuestion || !answer.trim()) return submitAnswer(answer, currentQuestion as unknown as Record, room.can_steal) setAnswer('') } const handleStealDecision = (attempt: boolean) => { if (!currentQuestion) return if (!attempt) { stealDecision(false, currentQuestion.id) } setShowStealPrompt(false) } const handleSendTeamMessage = (message: string) => { if (room && playerName && myTeam) { sendTeamMessage(message, room.code, myTeam, playerName) } } const isGameActive = room.status === 'playing' const timerProgress = currentQuestion ? (timeLeft / currentQuestion.time_seconds) * 100 : 100 // Get active categories from the board (dynamic based on what backend sends) const activeCategories = useMemo(() => { if (!room.board) return [] return Object.keys(room.board).map(id => ({ id: parseInt(id), ...allCategories[parseInt(id)] || { name: `Cat ${id}`, icon: '❓', color: '#666' } })) }, [room.board]) const numCategories = activeCategories.length || 5 return (
{/* Header with Room Code */}
TRIVY
Sala: {room.code}
{/* Scoreboard */}
{/* Team A Score */} {room.current_team === 'A' && ( )}
EQUIPO A
{room.scores.A}
{room.teams.A.map(p => p.name).join(', ')}
{/* Turn Indicator */}
TURNO
{room.current_team}
{amICurrentPlayer && ( ¡TU TURNO! )}
{/* Team B Score */} {room.current_team === 'B' && ( )}
EQUIPO B
{room.scores.B}
{room.teams.B.map(p => p.name).join(', ')}
{/* Game Board - Jeopardy Style */} {/* Category Headers */}
{activeCategories.map((cat, index) => (
{cat.icon}
{cat.name}
))}
{/* Questions Grid */} {[1, 2, 3, 4, 5].map((difficulty, rowIndex) => (
{activeCategories.map((cat, colIndex) => { const questions = room.board[String(cat.id)] || [] const question = questions.find(q => q.difficulty === difficulty) const isAnswered = question?.answered const cellId = `${cat.id}-${difficulty}` const canSelect = !isAnswered && amICurrentPlayer && question return ( question && handleSelectQuestion(question, cat.id)} onMouseEnter={() => handleCellHover(cellId, !!canSelect)} onMouseLeave={() => setHoveredCell(null)} disabled={isAnswered || !amICurrentPlayer} className={`relative aspect-[4/3] md:aspect-square flex items-center justify-center transition-all duration-200 ${ isAnswered ? 'cursor-default' : canSelect ? 'cursor-pointer' : 'cursor-not-allowed' }`} style={{ background: isAnswered ? `linear-gradient(135deg, ${config.colors.bg} 0%, ${config.colors.bg}90 100%)` : `linear-gradient(135deg, ${cat.color}50 0%, ${cat.color}30 100%)`, borderRight: colIndex < numCategories - 1 ? '1px solid rgba(255,255,255,0.1)' : 'none', borderBottom: '1px solid rgba(255,255,255,0.1)', opacity: isAnswered ? 0.3 : (!amICurrentPlayer ? 0.6 : 1), }} > {/* Glow effect on hover */} {canSelect && hoveredCell === cellId && ( )} {/* Points display */} ${difficulty * 100} {/* Answered checkmark */} {isAnswered && ( )} ) })}
))}
{/* Question Modal */} {currentQuestion && ( {/* Timer Bar */}
30 ? config.colors.primary : timerProgress > 15 ? '#FFA500' : '#FF4444', boxShadow: `0 0 10px ${timerProgress > 30 ? config.colors.primary : '#FF4444'}` }} />
{/* Points & Timer */}
${currentQuestion.points} 5 ? config.colors.primary : undefined, textShadow: timeLeft <= 5 ? '0 0 20px #FF0000' : `0 0 20px ${config.colors.primary}50` }} > {timeLeft}
{/* Question */} {currentQuestion.question_text} {/* Answer Input */} {amICurrentPlayer && ( setAnswer(e.target.value)} onKeyDown={(e) => e.key === 'Enter' && handleSubmitAnswer()} placeholder="Escribe tu respuesta..." autoFocus className="w-full px-6 py-4 rounded-xl bg-black/30 outline-none text-xl text-center transition-all focus:ring-2" style={{ border: `3px solid ${config.colors.primary}50`, color: config.colors.text, '--tw-ring-color': config.colors.primary, } as React.CSSProperties} /> RESPONDER )} {!amICurrentPlayer && (
Esperando respuesta de {currentPlayer?.name}
)}
)}
{/* Steal Prompt */} {showStealPrompt && room.current_team === myTeam && ( 🎯

¡OPORTUNIDAD DE ROBO!

El equipo contrario falló

⚠️ Si fallas, perderás puntos

handleStealDecision(true)} className="px-8 py-4 rounded-xl font-bold text-lg" style={{ background: `linear-gradient(135deg, ${config.colors.accent} 0%, #FF6B6B 100%)`, color: '#FFF', boxShadow: `0 5px 30px ${config.colors.accent}50` }} > ¡ROBAR! handleStealDecision(false)} className="px-8 py-4 rounded-xl font-bold text-lg" style={{ backgroundColor: 'transparent', color: config.colors.text, border: `2px solid ${config.colors.text}50`, }} > Pasar
)}
{/* Emoji Reactions Bar */}
{/* Sound Control */}
{isGameActive && ( )}
) }