final POC

This commit is contained in:
magdo
2025-11-24 23:28:57 +01:00
parent ce02f55a99
commit 6b3446e9b6
49 changed files with 4634 additions and 4620 deletions
+521 -108
View File
@@ -50,14 +50,22 @@ const GameScreen = () => {
isConnected,
gameState,
players: backendPlayers,
playerPositions, // NEW: Get dedicated position tracking state
boardData: websocketBoardData,
currentTurn,
currentTurnName,
isMyTurn,
playerIdentifier,
isGamemaster,
error,
playerDiceRolls,
rollDice,
approveJoker,
rejectJoker,
submitAnswer,
submitPositionGuess,
submitJokerPositionGuess,
leaveGame,
addEventListener,
removeEventListener
} = useGameWebSocketContext()
@@ -89,6 +97,7 @@ const GameScreen = () => {
// Card display modal state
const [isCardModalOpen, setIsCardModalOpen] = useState(false)
const [currentCard, setCurrentCard] = useState(null)
const [isMyCardTurn, setIsMyCardTurn] = useState(false) // Track if I'm the one answering
// Consequence modal state
const [isConsequenceModalOpen, setIsConsequenceModalOpen] = useState(false)
@@ -98,6 +107,14 @@ const GameScreen = () => {
const [isPredictionModalOpen, setIsPredictionModalOpen] = useState(false)
const [currentPredictionData, setCurrentPredictionData] = useState(null)
// End game modal state
const [isEndGameModalOpen, setIsEndGameModalOpen] = useState(false)
const [endGameData, setEndGameData] = useState(null)
// Animation state management
const [animatingPlayers, setAnimatingPlayers] = useState({}) // { playerId: { from, to, startTime, duration } }
const [animatedPositions, setAnimatedPositions] = useState({}) // { playerId: currentAnimatedPosition }
// Memoized board dimensions
const { rows, cols, totalCells, cellSize, cellMargin, rowSpacing, topOffset, width, height } = useMemo(() => {
const { rows, cols, cellSize, cellMargin, rowSpacing } = BOARD_CONFIG
@@ -175,61 +192,210 @@ const GameScreen = () => {
}
}, [boardData, generateWindingPath])
// Update players from backend - memoized mapping
// Update players from backend - memoized mapping (UI properties only, no position)
useEffect(() => {
if (!backendPlayers?.length) return
const mappedPlayers = backendPlayers.map((player, index) => ({
id: player.playerId || player.id || index,
name: player.playerName || player.name || `Player ${index + 1}`,
position: player.boardPosition || 0,
score: player.score || 0,
color: PLAYER_STYLES[index % PLAYER_STYLES.length].color,
emoji: PLAYER_STYLES[index % PLAYER_STYLES.length].emoji,
isOnline: player.isOnline !== undefined ? player.isOnline : true,
isReady: player.isReady || false,
}))
const mappedPlayers = backendPlayers.map((player, index) => {
const playerName = player.playerName || player.name || `Player ${index + 1}`;
return {
id: player.playerId || player.id || index,
name: playerName,
// NO position stored here - always read from context playerPositions
score: player.score || 0,
color: PLAYER_STYLES[index % PLAYER_STYLES.length].color,
emoji: PLAYER_STYLES[index % PLAYER_STYLES.length].emoji,
isOnline: player.isOnline !== undefined ? player.isOnline : true,
isReady: player.isReady || false,
};
})
setPlayers(mappedPlayers)
}, [backendPlayers])
// Listen to player movement - optimized to update only moved player
// Debug: Log playerPositions changes
useEffect(() => {
console.log('🔍 [GameScreen] playerPositions changed:', JSON.stringify(playerPositions));
players.forEach(p => {
const pos = playerPositions?.[p.name];
console.log(`🔍 [GameScreen] Player ${p.name} position from context: ${pos}`);
});
}, [playerPositions, players]);
// Animation loop using requestAnimationFrame
useEffect(() => {
let animationFrameId
const animate = () => {
const now = Date.now()
const updates = {}
let hasActiveAnimations = false
Object.entries(animatingPlayers).forEach(([playerId, animation]) => {
const elapsed = now - animation.startTime
const progress = Math.min(elapsed / animation.duration, 1)
// Easing function (ease-in-out)
const eased = progress < 0.5
? 2 * progress * progress
: 1 - Math.pow(-2 * progress + 2, 2) / 2
// Interpolate position
const currentPos = Math.round(
animation.from + (animation.to - animation.from) * eased
)
updates[playerId] = currentPos
if (progress < 1) {
hasActiveAnimations = true
}
// Animation complete - no local position update needed
// Position always comes from context playerPositions
})
if (Object.keys(updates).length > 0) {
setAnimatedPositions(updates)
}
// Clean up completed animations
if (!hasActiveAnimations && Object.keys(animatingPlayers).length > 0) {
setAnimatingPlayers({})
setAnimatedPositions({})
} else if (hasActiveAnimations) {
animationFrameId = requestAnimationFrame(animate)
}
}
if (Object.keys(animatingPlayers).length > 0) {
animationFrameId = requestAnimationFrame(animate)
}
return () => {
if (animationFrameId) {
cancelAnimationFrame(animationFrameId)
}
}
}, [animatingPlayers])
// Listen to player-moving event to start animation
useEffect(() => {
if (!addEventListener) return
const handlePlayerMoved = (moveData) => {
setPlayers(prev =>
prev.map(p =>
p.id === moveData.playerId
? { ...p, position: moveData.newPosition }
: p
)
)
const handlePlayerMoving = (moveData) => {
const duration = Math.abs(moveData.toPosition - moveData.fromPosition) * 50 // 50ms per position
const clampedDuration = Math.max(500, Math.min(duration, 2000)) // Between 0.5s and 2s
// Backend sends 1-based positions (1-100), use directly
setAnimatingPlayers(prev => ({
...prev,
[moveData.playerId]: {
from: moveData.fromPosition,
to: moveData.toPosition,
startTime: Date.now(),
duration: clampedDuration
}
}))
}
addEventListener('game:player-moved', handlePlayerMoved)
return () => removeEventListener('game:player-moved')
addEventListener('game:player-moving', handlePlayerMoving)
return () => removeEventListener('game:player-moving')
}, [addEventListener, removeEventListener])
// Listen to Joker card events (csak Gamemaster számára)
// Listen to errors and close modals
useEffect(() => {
if (!addEventListener) return
const handleGameError = (errorData) => {
console.error('❌ Game error:', errorData)
// Close any open modals on error
setIsCardModalOpen(false)
setIsPredictionModalOpen(false)
setIsJokerModalOpen(false)
// Show error in consequence modal if severe
if (errorData.message && errorData.message.includes('card')) {
setCurrentConsequence({
isCorrect: false,
explanation: errorData.message || 'An error occurred',
consequenceType: null,
consequenceValue: 0
})
setIsConsequenceModalOpen(true)
}
}
addEventListener('game:error', handleGameError)
return () => removeEventListener('game:error')
}, [addEventListener, removeEventListener])
// Listen to player-arrived event (trigger animation for position changes)
useEffect(() => {
const handlePlayerArrived = (event) => {
const arrivalData = event.detail
// Context manager already updated playerPositions
// Just set animation flag for visual animation
const player = players.find(p => p.id === arrivalData.playerId || p.name === arrivalData.playerName)
if (player) {
setAnimatingPlayers(prevAnimating => ({
...prevAnimating,
[player.id]: true
}))
// Clear animation flag after animation completes (2 seconds)
setTimeout(() => {
setAnimatingPlayers(prevAnimating => {
const newAnimating = { ...prevAnimating }
delete newAnimating[player.id]
return newAnimating
})
}, 2000)
}
}
// Listen to window CustomEvent instead of socket event (context already handles socket)
window.addEventListener('game:player-arrived', handlePlayerArrived)
return () => window.removeEventListener('game:player-arrived', handlePlayerArrived)
}, [players])
// Listen to Joker card events
useEffect(() => {
if (!addEventListener) return
const handleJokerDrawn = (jokerData) => {
console.log('🃏 Joker kártya húzva:', jokerData)
// Joker approval modal megjelenítése
setCurrentJokerRequest({
playerId: jokerData.playerId,
playerName: jokerData.playerName,
playerEmoji: jokerData.playerEmoji || "🎭",
cardTitle: jokerData.cardTitle || jokerData.jokerCard?.question,
cardDescription: jokerData.cardDescription || jokerData.jokerCard?.consequence?.description,
points: jokerData.points || jokerData.jokerCard?.consequence?.value,
cardId: jokerData.cardId || jokerData.jokerCard?.id,
requestId: jokerData.requestId, // Important: requestId from backend
timestamp: Date.now()
})
setIsJokerModalOpen(true)
if (isGamemaster) {
// Gamemaster sees approval modal with approve/deny buttons
console.log('👑 Gamemaster látja a jóváhagyási modal-t')
setCurrentJokerRequest({
playerId: jokerData.playerId,
playerName: jokerData.playerName,
playerEmoji: jokerData.playerEmoji || "🎭",
cardTitle: jokerData.cardTitle || jokerData.jokerCard?.question,
cardDescription: jokerData.cardDescription || jokerData.jokerCard?.consequence?.description,
points: jokerData.points || jokerData.jokerCard?.consequence?.value,
cardId: jokerData.cardId || jokerData.jokerCard?.id,
requestId: jokerData.requestId, // Important: requestId from backend
timestamp: Date.now()
})
setIsJokerModalOpen(true)
} else {
// Other players see the joker card as a read-only card display
console.log('👥 Játékosok látják a joker kártyát (csak olvasás)')
setCurrentCard({
type: 'JOKER',
question: jokerData.jokerCard?.question || jokerData.cardTitle || 'Joker Kártya Feladat',
consequence: jokerData.jokerCard?.consequence?.description || jokerData.cardDescription,
playerName: jokerData.playerName,
playerEmoji: jokerData.playerEmoji || "🎭",
isReadOnly: true,
waitingForGamemaster: true
})
setIsCardModalOpen(true)
setIsMyCardTurn(false) // Not my turn to answer
}
}
// Listen for gamemaster decision request (correct event name per docs)
@@ -240,33 +406,57 @@ const GameScreen = () => {
removeEventListener('game:joker-drawn')
removeEventListener('game:gamemaster-decision-request')
}
}, [addEventListener, removeEventListener])
}, [addEventListener, removeEventListener, isGamemaster])
// Listen to card drawn events (kártya megjelenítés)
useEffect(() => {
if (!addEventListener) return
const handleCardDrawn = (cardData) => {
console.log('🎴 Kártya húzva:', cardData)
// Handle card drawn FOR ME (I need to answer)
const handleCardDrawnSelf = (data) => {
console.log('🎴 Kártya húzva NEKEM:', data)
const cardData = data.cardData || data;
console.log('📦 Card data structure:', cardData)
setCurrentCard({
id: cardData.cardId || cardData.id,
type: cardData.cardType || cardData.type,
question: cardData.question || cardData.text,
answerOptions: cardData.answerOptions || cardData.options || [],
id: cardData.cardid || cardData.id,
type: cardData.type,
question: cardData.question || cardData.text || cardData.statement,
answerOptions: cardData.answerOptions || [],
sentencePairs: cardData.sentencePairs || [],
words: cardData.words || [],
acceptableAnswers: cardData.acceptableAnswers || [],
correctAnswer: cardData.correctAnswer,
hint: cardData.hint,
points: cardData.points || 0,
timeLimit: cardData.timeLimit || 60
timeLimit: data.timeLimit || cardData.timeLimit || 60
})
setIsMyCardTurn(true) // I need to answer
setIsCardModalOpen(true)
}
// Listen for both generic and self-specific events
// Handle card drawn by ANOTHER PLAYER (spectator mode)
const handleCardDrawn = (data) => {
console.log('👀 Kártya húzva más játékos által:', data)
const cardData = data.cardData || data;
setCurrentCard({
id: cardData.cardid || cardData.id,
type: cardData.type,
question: cardData.question || cardData.text || cardData.statement,
answerOptions: cardData.answerOptions || [],
sentencePairs: cardData.sentencePairs || [],
words: cardData.words || [],
timeLimit: data.timeLimit || cardData.timeLimit || 60
})
setIsMyCardTurn(false) // Spectator mode
setIsCardModalOpen(true)
}
addEventListener('game:card-drawn-self', handleCardDrawnSelf)
addEventListener('game:card-drawn', handleCardDrawn)
addEventListener('game:card-drawn-self', handleCardDrawn)
return () => {
removeEventListener('game:card-drawn')
removeEventListener('game:card-drawn-self')
removeEventListener('game:card-drawn')
}
}, [addEventListener, removeEventListener])
@@ -297,11 +487,30 @@ const GameScreen = () => {
const handleLuckConsequence = (luckData) => {
console.log('🍀 Szerencse kártya következménye:', luckData)
// Close card modal if it's open (shouldn't be for luck, but just in case)
setIsCardModalOpen(false)
setCurrentConsequence({
isCorrect: true, // Luck cards don't have right/wrong answers
consequenceType: luckData.consequenceType,
consequenceValue: luckData.value || luckData.consequenceValue || 0,
explanation: luckData.message || 'Szerencse kártya!',
explanation: luckData.description || luckData.message || 'Szerencse kártya!',
playerAnswer: null,
correctAnswer: null
})
setIsConsequenceModalOpen(true)
}
const handleCardResult = (resultData) => {
console.log('🎴 Card result (luck):', resultData)
// This is for luck cards that use game:card-result event
setIsCardModalOpen(false)
setCurrentConsequence({
isCorrect: true,
consequenceType: resultData.consequence?.type,
consequenceValue: resultData.consequence?.value || 0,
explanation: resultData.description || 'Szerencse kártya!',
playerAnswer: null,
correctAnswer: null
})
@@ -310,10 +519,12 @@ const GameScreen = () => {
addEventListener('game:answer-validated', handleAnswerValidated)
addEventListener('game:luck-consequence', handleLuckConsequence)
addEventListener('game:card-result', handleCardResult)
return () => {
removeEventListener('game:answer-validated')
removeEventListener('game:luck-consequence')
removeEventListener('game:card-result')
}
}, [addEventListener, removeEventListener])
@@ -334,16 +545,105 @@ const GameScreen = () => {
setIsPredictionModalOpen(true)
}
const handleJokerPositionGuessRequest = (predictionData) => {
console.log('🃏🎯 Joker után pozíció tippelés kérés:', predictionData)
setCurrentPredictionData({
currentPosition: predictionData.currentPosition,
diceRoll: predictionData.diceRoll || predictionData.dice,
fieldStepValue: predictionData.fieldStepValue || predictionData.fieldStep || 0,
patternModifier: predictionData.patternModifier || predictionData.zoneModifier || 0,
cardText: predictionData.message || 'Tippeld meg a végső pozíciódat joker után!',
timeLimit: predictionData.timeLimit || 30,
isJoker: true // Mark this as joker guess
})
setIsPredictionModalOpen(true)
}
const handleGuessResult = (resultData) => {
console.log('✅ Tippelés eredménye:', resultData)
// Close prediction modal
setIsPredictionModalOpen(false)
// Backend already emits game:player-arrived before this event
// Position is handled by context manager, no need for pendingPositionUpdate
setCurrentConsequence({
isCorrect: resultData.guessCorrect,
playerAnswer: resultData.guessedPosition,
correctAnswer: resultData.actualPosition,
explanation: resultData.message,
consequenceType: resultData.penaltyApplied ? 'penalty' : 'success',
consequenceValue: resultData.penaltyApplied ? -2 : 0
})
setIsConsequenceModalOpen(true)
}
const handleJokerComplete = (resultData) => {
console.log('🃏✅ Joker tippelés eredménye:', resultData)
// Close prediction modal
setIsPredictionModalOpen(false)
// Backend already emits game:player-arrived before this event (if moved)
// Position is handled by context manager, no need for pendingPositionUpdate
setCurrentConsequence({
isCorrect: resultData.guessCorrect,
playerAnswer: resultData.guessedPosition,
correctAnswer: resultData.actualPosition,
explanation: resultData.message,
consequenceType: resultData.penaltyApplied ? 'penalty' : (resultData.moved ? 'success' : 'neutral'),
consequenceValue: resultData.penaltyApplied ? -2 : 0
})
setIsConsequenceModalOpen(true)
}
addEventListener('game:position-guess-request', handlePositionGuessRequest)
return () => removeEventListener('game:position-guess-request')
addEventListener('game:joker-position-guess-request', handleJokerPositionGuessRequest)
addEventListener('game:guess-result', handleGuessResult)
addEventListener('game:joker-complete', handleJokerComplete)
return () => {
removeEventListener('game:position-guess-request')
removeEventListener('game:joker-position-guess-request')
removeEventListener('game:guess-result')
removeEventListener('game:joker-complete')
}
}, [addEventListener, removeEventListener])
// Listen to game end event
useEffect(() => {
if (!addEventListener) return
const handleGameEnded = (endData) => {
console.log('🏆 Játék vége:', endData)
setEndGameData({
winnerName: endData.winnerName,
winnerId: endData.winner,
finalPositions: endData.finalPositions || [],
message: endData.message
})
setIsEndGameModalOpen(true)
}
const handleGamemasterDecision = (decisionData) => {
console.log('👑 Gamemaster döntés:', decisionData)
// Close joker card modal for non-gamemaster players when decision is made
if (!isGamemaster) {
setIsCardModalOpen(false)
}
}
addEventListener('game:ended', handleGameEnded)
addEventListener('game:gamemaster-decision-result', handleGamemasterDecision)
return () => {
removeEventListener('game:ended')
removeEventListener('game:gamemaster-decision-result')
}
}, [addEventListener, removeEventListener, isGamemaster])
// Joker jóváhagyás
const handleApproveJoker = useCallback(async (jokerRequest) => {
console.log('✅ Joker feladat jóváhagyva:', jokerRequest)
// WebSocket üzenet a backend felé
approveJoker(jokerRequest.playerId, jokerRequest.cardId, jokerRequest.requestId)
// WebSocket üzenet a backend felé - csak requestId kell
approveJoker(jokerRequest.requestId)
// Modal bezárása
setIsJokerModalOpen(false)
@@ -353,8 +653,8 @@ const GameScreen = () => {
const handleRejectJoker = useCallback(async (jokerRequest) => {
console.log('❌ Joker feladat elutasítva:', jokerRequest)
// WebSocket üzenet a backend felé
rejectJoker(jokerRequest.playerId, jokerRequest.cardId, jokerRequest.requestId)
// WebSocket üzenet a backend felé - csak requestId kell
rejectJoker(jokerRequest.requestId, 'Joker rejected by gamemaster')
// Modal bezárása
setIsJokerModalOpen(false)
@@ -362,31 +662,45 @@ const GameScreen = () => {
// Kártya válasz beküldése
const handleSubmitAnswer = useCallback((answer) => {
console.log('📝 Válasz beküldve:', answer)
console.log('📝 Válasz beküldve:', answer, 'Card ID:', currentCard?.id)
// WebSocket emit a backend felé
if (currentCard?.id) {
submitAnswer(currentCard.id, answer)
}
// WebSocket emit a backend felé - uses context method with card ID
submitAnswer(answer, currentCard?.id)
// A consequence modal automatikusan megnyílik a 'game:answer-validated' event hatására
}, [currentCard?.id, submitAnswer])
}, [submitAnswer, currentCard])
// Consequence modal bezárása
const handleConsequenceClose = useCallback(() => {
// Position updates are handled by game:player-arrived event in context
// No need to manually update positions here
setIsConsequenceModalOpen(false)
}, [])
// Pozíció tippelés beküldése
const handleSubmitPrediction = useCallback((predictedPosition) => {
console.log('🎯 Pozíció tippelés beküldve:', predictedPosition)
// WebSocket emit a backend felé
submitPositionGuess(predictedPosition)
// WebSocket emit a backend felé (különböző event joker-nél)
if (currentPredictionData?.isJoker) {
console.log('🃏 Joker pozíció tipp beküldése')
submitJokerPositionGuess(predictedPosition)
} else {
submitPositionGuess(predictedPosition)
}
// Modal bezárása
setIsPredictionModalOpen(false)
}, [submitPositionGuess])
}, [submitPositionGuess, submitJokerPositionGuess, currentPredictionData])
// Sorted players - memoized
// Sorted players - memoized (sort by context position)
const sortedPlayers = useMemo(
() => [...players].sort((a, b) => b.position - a.position),
[players]
() => [...players].sort((a, b) => {
const posA = playerPositions?.[a.name] || 0
const posB = playerPositions?.[b.name] || 0
return posB - posA
}),
[players, playerPositions]
)
// Handle dice roll
@@ -437,35 +751,45 @@ const GameScreen = () => {
return (
<div className="p-4 bg-gradient-to-br from-gray-900 via-gray-800 to-teal-900 min-h-screen flex items-center justify-center">
<div className="w-full">
{/* Connection Status Indicator */}
<div className="fixed top-4 right-4 z-50">
<div className={`px-4 py-2 rounded-lg shadow-lg flex items-center gap-2 ${
isConnected
? 'bg-green-600 text-white'
: 'bg-red-600 text-white'
}`}>
<div className={`w-3 h-3 rounded-full ${
isConnected ? 'bg-green-300 animate-pulse' : 'bg-red-300'
}`}></div>
<span className="text-sm font-medium">
{isConnected ? '🟢 Csatlakozva' : '🔴 Kapcsolódás...'}
</span>
</div>
{error && !error.includes('Game not found') && !error.includes('token invalid') && (
<div className="mt-2 px-4 py-2 rounded-lg shadow-lg bg-red-600 text-white text-xs">
{error}
</div>
)}
</div> {/* Game Info Bar */}
{/* Exit Game Button - Top Right Corner */}
<div className="fixed top-4 right-4 z-50">
<button
onClick={() => {
if (window.confirm('Biztosan ki szeretnél lépni a játékból?')) {
leaveGame()
window.location.href = '/'
}
}}
className="bg-red-600 hover:bg-red-700 text-white font-semibold py-2 px-4 rounded-lg shadow-lg transition-colors duration-200 flex items-center gap-2 cursor-pointer"
title="Kilépés a játékból"
>
🚪 Kilépés
</button>
</div>
{/* Game Info Bar */}
{gameState && (
<div className="fixed top-4 left-4 z-50">
<div className="bg-gray-800 border border-teal-700 px-4 py-2 rounded-lg shadow-lg">
<div className="text-teal-300 text-sm font-medium">
🎮 Játék kód: <span className="font-bold text-white">{gameState.gameCode || 'N/A'}</span>
</div>
<div className="flex items-center gap-2 mt-1">
<div className={`w-2 h-2 rounded-full ${
isConnected ? 'bg-green-400 animate-pulse' : 'bg-red-400'
}`}></div>
<span className={`text-xs ${
isConnected ? 'text-green-400' : 'text-red-400'
}`}>
{isConnected ? 'Csatlakozva' : 'Kapcsolódás...'}
</span>
</div>
{currentTurn && (
<div className="text-gray-400 text-xs mt-1">
🎯 Köron: <span className="text-white">{players.find(p => p.id === currentTurn)?.name || 'Betöltés...'}</span>
🎯 Köron: <span className={`font-bold ${isMyTurn ? 'text-green-400' : 'text-white'}`}>
{currentTurnName || players.find(p => p.id === currentTurn || p.playerName === currentTurn || p.name === currentTurn)?.name || currentTurn || 'Betöltés...'}
</span>
{isMyTurn && <span className="ml-2 text-green-400 animate-pulse"> Te vagy!</span>}
</div>
)}
</div>
@@ -513,18 +837,28 @@ const GameScreen = () => {
))}
{/* Player tokens */}
{players.map((player) => (
<div
key={player.id}
className={`absolute w-6 h-6 ${player.color} rounded-full border-2 border-white shadow-lg flex items-center justify-center text-white text-xs font-bold z-10 animate-bounce`}
style={{
...getPlayerPosition(player.position),
transform: "translate(17px, 17px)",
}}
>
{player.emoji}
</div>
))}
{players.map((player) => {
// ALWAYS read position from context playerPositions, not local state
// Backend uses 1-based indexing (1-100)
const contextPosition = playerPositions?.[player.name] ?? 1
// Use animated position if player is currently animating, otherwise use context position
const displayPosition = animatedPositions[player.id] ?? contextPosition
const isAnimating = animatingPlayers[player.id] !== undefined
return (
<div
key={player.id}
className={`absolute w-6 h-6 ${player.color} rounded-full border-2 border-white shadow-lg flex items-center justify-center text-white text-xs font-bold z-10 transition-transform ${isAnimating ? 'scale-110' : ''}`}
style={{
...getPlayerPosition(displayPosition),
transform: "translate(17px, 17px)",
transition: isAnimating ? 'none' : 'all 0.3s ease'
}}
>
{player.emoji}
</div>
)
})}
</div>
</div>
@@ -591,7 +925,7 @@ const GameScreen = () => {
</span>
</div>
<div className="text-xs text-gray-500">
Pozíció: {player.position} Pontszám: {player.score}
Pozíció: {playerPositions?.[player.name] ?? 1} Pontszám: {player.score}
</div>
</div>
</div>
@@ -601,11 +935,23 @@ const GameScreen = () => {
{/* Dice Container */}
<div className="bg-gray-800 rounded-xl p-4 shadow-lg border border-teal-700 text-center">
<h2 className="text-xl font-semibold mb-3 text-teal-300">Dobókocka</h2>
<p className="text-gray-300 text-sm mb-4">
Kattints a kockára dobáshoz!
</p>
<Dice onRoll={handleDiceRoll} />
{isMyTurn ? (
<>
<p className="text-green-400 text-sm mb-4 font-bold animate-pulse">
🎯 A te köröd! Kattints a kockára dobáshoz!
</p>
<Dice onRoll={handleDiceRoll} />
</>
) : (
<>
<p className="text-gray-500 text-sm mb-4">
Várd meg a köröd...
</p>
<div className="opacity-50 pointer-events-none">
<Dice onRoll={handleDiceRoll} />
</div>
</>
)}
{/* Connection warning */}
{!isConnected && (
@@ -624,7 +970,9 @@ const GameScreen = () => {
<div>🎮 Game Code: {gameState?.gameCode || 'N/A'}</div>
<div>👥 Players: {backendPlayers?.length || 0}</div>
<div>🎲 Board Fields: {boardData?.fields?.length || 0}</div>
<div>🏁 Current Turn: {currentTurn || 'N/A'}</div>
<div>🏁 Current Turn: {currentTurnName || currentTurn || 'N/A'}</div>
<div>🆔 My ID: {playerIdentifier || 'N/A'}</div>
<div> Is My Turn: {isMyTurn ? 'YES' : 'NO'}</div>
{/* <div>🔑 Token: {gameToken ? '✅' : '❌'}</div> */}
</div>
</div>
@@ -650,20 +998,85 @@ const GameScreen = () => {
onClose={() => setIsCardModalOpen(false)}
card={currentCard}
onSubmitAnswer={handleSubmitAnswer}
isMyTurn={isMyCardTurn}
/>
{/* Consequence Modal - következmények megjelenítése */}
<ConsequenceModal
isOpen={isConsequenceModalOpen}
onClose={() => setIsConsequenceModalOpen(false)}
consequence={currentConsequence}
onClose={handleConsequenceClose}
isCorrect={currentConsequence?.isCorrect}
consequenceType={currentConsequence?.consequenceType}
consequenceValue={currentConsequence?.consequenceValue}
playerAnswer={currentConsequence?.playerAnswer}
correctAnswer={currentConsequence?.correctAnswer}
explanation={currentConsequence?.explanation}
/>
{/* End Game Modal */}
{isEndGameModalOpen && endGameData && (
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/90">
<div className="relative bg-gradient-to-br from-yellow-900 via-yellow-800 to-yellow-900 rounded-2xl shadow-2xl border-4 border-yellow-500 max-w-2xl w-full p-8 text-center">
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-white/10 to-transparent animate-pulse" />
<div className="relative">
<div className="text-8xl mb-4 animate-bounce">🏆</div>
<h1 className="text-5xl font-bold text-white mb-4">
Játék vége!
</h1>
<div className="bg-black/30 rounded-xl p-6 mb-6">
<p className="text-6xl font-bold text-yellow-300 mb-2">
{endGameData.winnerName}
</p>
<p className="text-2xl text-yellow-100">
🎉 Nyert! 🎉
</p>
</div>
{endGameData.finalPositions && endGameData.finalPositions.length > 0 && (
<div className="bg-black/30 rounded-xl p-4 mb-6">
<h3 className="text-xl font-semibold text-yellow-300 mb-3">Végső állás:</h3>
<div className="space-y-2">
{endGameData.finalPositions
.sort((a, b) => b.boardPosition - a.boardPosition)
.map((player, index) => (
<div key={player.playerId} className="flex items-center justify-between bg-yellow-900/30 rounded-lg p-3">
<div className="flex items-center gap-3">
<span className="text-2xl">
{index === 0 ? '🥇' : index === 1 ? '🥈' : index === 2 ? '🥉' : '🎮'}
</span>
<span className="text-white font-semibold">{player.playerName}</span>
</div>
<span className="text-yellow-300 font-bold">Pozíció: {player.boardPosition}</span>
</div>
))}
</div>
</div>
)}
<button
onClick={() => window.location.href = '/'}
className="bg-gradient-to-r from-yellow-600 to-orange-600 hover:from-yellow-500 hover:to-orange-500 text-white font-bold py-4 px-8 rounded-xl shadow-lg transform transition-all duration-200 hover:scale-105 text-xl"
>
Vissza a főoldalra
</button>
</div>
</div>
</div>
)}
{/* Step Prediction Modal - pozíció tippelés */}
<StepPredictionModal
isOpen={isPredictionModalOpen}
onClose={() => setIsPredictionModalOpen(false)}
predictionData={currentPredictionData}
currentPosition={currentPredictionData?.currentPosition || 0}
diceRoll={currentPredictionData?.diceRoll || 0}
fieldStepValue={currentPredictionData?.fieldStepValue || 0}
patternModifier={currentPredictionData?.patternModifier || 0}
cardText={currentPredictionData?.cardText || "Tippeld meg, melyik pozícióra fogsz lépni!"}
hints={currentPredictionData?.hints || []}
timeLimit={currentPredictionData?.timeLimit || 30}
isJoker={currentPredictionData?.isJoker || false}
onSubmitPrediction={handleSubmitPrediction}
/>
</div>