sto-k-odnomu/src/components/Game.jsx
2025-12-31 20:57:11 +03:00

344 lines
11 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useState, useImperativeHandle, forwardRef, useEffect } from 'react'
import Question from './Question'
import Players from './Players'
import PlayersModal from './PlayersModal'
import QuestionsModal from './QuestionsModal'
import { getCookie, setCookie, deleteCookie } from '../utils/cookies'
import './Game.css'
const Game = forwardRef(({
questions = [],
currentQuestionIndex = 0,
onQuestionIndexChange,
onQuestionsChange,
}, ref) => {
const [players, setPlayers] = useState(() => {
const savedPlayers = getCookie('gamePlayers')
return savedPlayers || []
})
const [currentPlayerId, setCurrentPlayerId] = useState(() => {
const savedId = getCookie('gameCurrentPlayerId')
return savedId !== null ? savedId : null
})
const [playerScores, setPlayerScores] = useState(() => {
const savedScores = getCookie('gamePlayerScores')
return savedScores || {}
})
const [gameOver, setGameOver] = useState(() => {
const savedGameOver = getCookie('gameOver')
return savedGameOver === true
})
const [revealedAnswers, setRevealedAnswers] = useState(() => {
const savedAnswers = getCookie('gameRevealedAnswers')
return savedAnswers || {}
})
// Получаем открытые ответы для текущего вопроса
const getCurrentRevealedAnswers = () => {
return revealedAnswers[currentQuestionIndex] || []
}
// Обновляем открытые ответы для текущего вопроса
const updateRevealedAnswers = (newAnswers) => {
setRevealedAnswers({
...revealedAnswers,
[currentQuestionIndex]: newAnswers,
})
}
const [isPlayersModalOpen, setIsPlayersModalOpen] = useState(false)
const [isQuestionsModalOpen, setIsQuestionsModalOpen] = useState(false)
// Сохраняем состояние в cookies при изменении
useEffect(() => {
if (players.length > 0) {
setCookie('gamePlayers', players)
} else {
deleteCookie('gamePlayers')
}
}, [players])
useEffect(() => {
if (currentPlayerId !== null) {
setCookie('gameCurrentPlayerId', currentPlayerId)
} else {
deleteCookie('gameCurrentPlayerId')
}
}, [currentPlayerId])
useEffect(() => {
if (Object.keys(playerScores).length > 0) {
setCookie('gamePlayerScores', playerScores)
} else {
deleteCookie('gamePlayerScores')
}
}, [playerScores])
useEffect(() => {
setCookie('gameRevealedAnswers', revealedAnswers)
}, [revealedAnswers])
useEffect(() => {
setCookie('gameOver', gameOver)
}, [gameOver])
const currentQuestion = questions[currentQuestionIndex]
const isLastQuestion = currentQuestionIndex === questions.length - 1
const handleShowAllAnswers = () => {
if (!currentQuestion) return
const allAnswerIndices = currentQuestion.answers.map((_, index) => index)
updateRevealedAnswers(allAnswerIndices)
}
useImperativeHandle(ref, () => ({
openPlayersModal: () => setIsPlayersModalOpen(true),
openQuestionsModal: () => setIsQuestionsModalOpen(true),
newGame: () => {
setPlayers([])
setCurrentPlayerId(null)
setPlayerScores({})
setGameOver(false)
setRevealedAnswers({})
},
showAllAnswers: handleShowAllAnswers,
}))
const handleAddPlayer = (name) => {
const newPlayer = {
id: Date.now(),
name: name,
}
const updatedPlayers = [...players, newPlayer]
setPlayers(updatedPlayers)
// Если это первый участник, делаем его текущим
if (updatedPlayers.length === 1) {
setCurrentPlayerId(newPlayer.id)
setPlayerScores({ [newPlayer.id]: 0 })
} else {
setPlayerScores({ ...playerScores, [newPlayer.id]: 0 })
}
}
const handleSelectPlayer = (playerId) => {
setCurrentPlayerId(playerId)
}
const handleRemovePlayer = (playerId) => {
const updatedPlayers = players.filter(p => p.id !== playerId)
setPlayers(updatedPlayers)
const updatedScores = { ...playerScores }
delete updatedScores[playerId]
setPlayerScores(updatedScores)
// Если удалили текущего участника, выбираем другого
if (currentPlayerId === playerId) {
if (updatedPlayers.length > 0) {
setCurrentPlayerId(updatedPlayers[0].id)
} else {
setCurrentPlayerId(null)
}
}
}
const getNextPlayerId = () => {
if (players.length === 0) return null
if (players.length === 1) return currentPlayerId
const currentIndex = players.findIndex(p => p.id === currentPlayerId)
const nextIndex = (currentIndex + 1) % players.length
return players[nextIndex].id
}
const handleAnswerClick = (answerIndex, points) => {
const currentRevealed = getCurrentRevealedAnswers()
if (currentRevealed.includes(answerIndex)) return
if (!currentPlayerId) return
if (!currentQuestion) return
const isLastAnswer = currentRevealed.length === currentQuestion.answers.length - 1
updateRevealedAnswers([...currentRevealed, answerIndex])
// Добавляем очки текущему участнику
setPlayerScores({
...playerScores,
[currentPlayerId]: (playerScores[currentPlayerId] || 0) + points,
})
// Переходим к следующему участнику только если это не последний ответ
if (!isLastAnswer) {
const nextPlayerId = getNextPlayerId()
if (nextPlayerId) {
setTimeout(() => {
setCurrentPlayerId(nextPlayerId)
}, 500)
}
} else {
// Если это последний ответ, переходим к следующему участнику перед следующим вопросом
setTimeout(() => {
const nextPlayerId = getNextPlayerId()
if (nextPlayerId) {
setCurrentPlayerId(nextPlayerId)
}
if (isLastQuestion) {
setGameOver(true)
} else {
setTimeout(() => {
nextQuestion()
}, 500)
}
}, 2000)
}
}
const nextQuestion = () => {
if (onQuestionIndexChange) {
onQuestionIndexChange(currentQuestionIndex + 1)
}
// Не сбрасываем открытые ответы - они сохраняются для каждого вопроса отдельно
}
const restartGame = () => {
if (onQuestionIndexChange) {
onQuestionIndexChange(0)
}
setGameOver(false)
setRevealedAnswers({})
const initialScores = {}
players.forEach(player => {
initialScores[player.id] = 0
})
setPlayerScores(initialScores)
if (players.length > 0) {
setCurrentPlayerId(players[0].id)
}
}
const newGame = () => {
setPlayers([])
setCurrentPlayerId(null)
setPlayerScores({})
setGameOver(false)
setRevealedAnswers({})
if (onQuestionIndexChange) {
onQuestionIndexChange(0)
}
}
const handlePreviousQuestion = () => {
if (currentQuestionIndex > 0 && onQuestionIndexChange) {
onQuestionIndexChange(currentQuestionIndex - 1)
// Открытые ответы сохраняются для каждого вопроса отдельно
}
}
const handleNextQuestion = () => {
if (currentQuestionIndex < questions.length - 1 && onQuestionIndexChange) {
onQuestionIndexChange(currentQuestionIndex + 1)
// Открытые ответы сохраняются для каждого вопроса отдельно
}
}
if (gameOver) {
// Находим победителя(ей)
const scores = Object.values(playerScores)
const maxScore = scores.length > 0 ? Math.max(...scores) : 0
const winners = players.filter(p => playerScores[p.id] === maxScore)
return (
<div className="game-over">
<div className="game-over-content">
<h2 className="game-over-title">🎉 Игра окончена! 🎉</h2>
<div className="final-scores">
<h3 className="final-scores-title">Итоговые результаты:</h3>
{players
.sort((a, b) => (playerScores[b.id] || 0) - (playerScores[a.id] || 0))
.map((player) => (
<div
key={player.id}
className={`final-score-item ${
winners.includes(player) ? 'final-score-winner' : ''
}`}
>
<span className="final-score-name">{player.name}</span>
<span className="final-score-value">
{playerScores[player.id] || 0} очков
</span>
</div>
))}
</div>
<button className="restart-button" onClick={restartGame}>
Играть снова
</button>
</div>
</div>
)
}
return (
<div className="game">
<PlayersModal
isOpen={isPlayersModalOpen}
onClose={() => setIsPlayersModalOpen(false)}
players={players}
onAddPlayer={handleAddPlayer}
onRemovePlayer={handleRemovePlayer}
/>
<QuestionsModal
isOpen={isQuestionsModalOpen}
onClose={() => setIsQuestionsModalOpen(false)}
questions={questions}
onUpdateQuestions={onQuestionsChange}
/>
<div className="game-header">
{players.length > 0 && (
<Players
players={players}
currentPlayerId={currentPlayerId}
playerScores={playerScores}
onSelectPlayer={handleSelectPlayer}
/>
)}
</div>
{players.length > 0 && currentPlayerId ? (
<div className="game-content">
{questions.length === 0 ? (
<div className="no-players-message">
<p>Добавьте вопросы, чтобы начать игру</p>
</div>
) : currentQuestion ? (
<Question
question={currentQuestion}
questionNumber={currentQuestionIndex + 1}
onAnswerClick={handleAnswerClick}
revealedAnswers={getCurrentRevealedAnswers()}
onPreviousQuestion={handlePreviousQuestion}
onNextQuestion={handleNextQuestion}
canGoPrevious={currentQuestionIndex > 0}
canGoNext={currentQuestionIndex < questions.length - 1}
/>
) : (
<div className="no-players-message">
<p>Ошибка: вопрос не найден</p>
</div>
)}
</div>
) : (
<div className="no-players-message">
<p>Добавьте участников, чтобы начать игру</p>
</div>
)}
</div>
)
})
Game.displayName = 'Game'
export default Game