final POC
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user